跳转至

第 4 课 怎样保存一周行情,并准确取回某一天

一个变量只能说明一个当前值。一段行情还需要顺序、字段名称和缺失记录。本课把五天虚构价格逐步整理成一张程序能够读取的小表。

适合谁会使用变量和基本运算的读者
前置知识数值、文字、变量和赋值
建议用时100 分钟,可以分两次完成
本课成果保存并读取五天虚构行情,手算一段相邻收益

五天价格为什么不适合写成五个变量

假设你记录周一到周五的收盘价。最直接的办法是写出 mondaytuesday 等五个变量。它能够运行,却把“这五个值属于同一组并且有先后顺序”这件事藏在变量名里。数据增加到一年以后,程序需要两百多个名字,逐日计算也很难统一进行。

monday = 10.0
tuesday = 10.2
wednesday = 10.1
thursday = 10.4
friday = 10.6

我们需要一个能够按顺序保存多个值的容器。Python 的列表正好解决这个问题。方括号表示列表的边界,逗号把每一项隔开,项目顺序就是我们写入时的顺序。

close_prices = [10.0, 10.2, 10.1, 10.4, 10.6]

变量 close_prices 现在保存整组价格。列表本身不知道第一项属于周一,顺序含义来自研究者的约定,所以还要确保数据在写入以前已经按日期排列。若原始记录是周三、周一、周二,列表会忠实保留这个错误顺序,后面的相邻收益也会跟着算错。

索引为什么从零开始

从列表中取某一项,需要在方括号里写它的位置,这个位置编号叫索引。Python 把第一项编号为 0,第二项编号为 1,第五项编号为 4。许多编程语言都采用这种规则,因为索引可以理解为从列表起点向后移动多少格,第一项与起点之间的偏移量正好是零。

人习惯说的位置 Python 索引 本例取到的价格
第一项 0 10.0
第二项 1 10.2
第三项 2 10.1
第五项 4 10.6

下面代码分别读取第一项、第五项和末尾第一项。正数索引从左向右数,负数索引从右向左数,所以 -1 总是指向当前列表的最后一项。

print(close_prices[0])
print(close_prices[4])
print(close_prices[-1])

运行后会看到 10.0、10.6 和 10.6。使用 -1 的好处是列表长度变化以后仍能取最后一项,代价是读者需要知道负索引规则。研究代码中两种写法都可以使用,关键是日期与位置必须对应。

若读取一个不存在的位置,Python 会给出 IndexError。五项列表只有 0 到 4,写 close_prices[5] 已经越界。可以用 len(close_prices) 得到项目数量,本例结果为 5;最后一个有效正索引则是长度减一,也就是 4。

切片怎样取出连续一段

逐项索引只取一个值,切片可以取出一段连续项目。写法 列表[开始位置:停止位置] 会包含左边的开始位置,并在到达右边位置以前停止。右边界不进入结果,这项规则能让切片长度直接等于“停止位置减开始位置”。

下面要取前三天价格。开始位置是 0,停止位置写 3,程序会读取索引 0、1、2,走到索引 3 以前停下。

first_three = close_prices[0:3]
print(first_three)
print(len(first_three))

输出列表为 [10.0, 10.2, 10.1],长度为 3。开头为 0 时可以省略,close_prices[:3] 得到相同结果;想从索引 2 一直取到末尾,可以写 close_prices[2:]。省略的一侧表示沿该方向取到边界。

切片最常见的错误是把停止位置当成最后一项。想取索引 1 到 3 时,应把停止位置写成 4。初学者可以先把索引写在纸上,再打印切片与长度。三个手算数字足以暴露边界错误,不必等到几千行行情算完才发现。

列表为什么仍然无法说明每个数字是什么

列表很适合保存同一字段随时间变化的顺序,例如一列收盘价。一天行情通常包含日期、开盘、最高、最低、收盘和成交量。若继续用列表保存单日记录,["周一", 10.0, 10.3, 9.9, 10.2, 1200] 能够容纳数值,却要求读者背住每个位置的含义。

Python 的字典使用名字查找内容。每一项由一个键和一个值组成,键负责说明字段名称,值保存具体内容。下面的 bar 描述一根虚构日 K 线,花括号表示字典边界,冒号出现在代码中,用来连接键和值。

bar = {
    "date": "周一",
    "open": 10.0,
    "high": 10.3,
    "low": 9.9,
    "close": 10.2,
    "volume": 1200,
}

读取字典时,在方括号中写键。bar["date"] 得到周一,bar["close"] 得到 10.2。程序无需记住收盘价位于第几个位置,字段名已经承担说明作用。

print(bar["date"])
print(bar["close"])

若键不存在,Python 会给出 KeyError。例如字典使用 "close",代码却写 bar["Close"],大小写不同会被当成两个键。可以先运行 print(bar.keys()) 查看现有字段,再回到数据说明核对标准名称。随手把列名改到“能跑”可能掩盖来源口径错误。

列表与字典怎样组合成多天行情

列表负责日期顺序,字典负责单日字段,把多个字典放进一个列表,就得到一张最基础的多行行情表。外层每个位置是一日记录,内层通过字段名读取当天数值。

rows = [
    {"date": "周一", "open": 10.0, "close": 10.2},
    {"date": "周二", "open": 10.2, "close": 10.1},
    {"date": "周三", "open": 10.1, "close": 10.4},
]

second_day = rows[1]
second_close = second_day["close"]
print(second_close)

读取过程被故意拆成两步。rows[1] 先取索引 1 对应的周二字典,second_day["close"] 再从这张字典中取收盘价,结果为 10.1。熟悉以后可以写成 rows[1]["close"],两个方括号仍然分别承担“按位置取一行”和“按名称取一个字段”。

flowchart TB
    accTitle: 列表和字典怎样组成一张行情表
    accDescr: 外层列表保存日期顺序,每个列表位置是一行字典,字典再用字段名保存当天开盘价和收盘价

    a[列表 rows<br/>保存多天顺序] --> b[索引 0<br/>周一字典]
    a --> c[索引 1<br/>周二字典]
    a --> d[索引 2<br/>周三字典]
    c --> e[键 open<br/>数值 10.2]
    c --> f[键 close<br/>数值 10.1]

这种结构已经接近表格。第 7 课会把它交给 pandas 的 DataFrame,让相同字段自动排列成列。先理解列表和字典,后面看到表格索引、行和列时就知道它们仍然在解决“按什么位置、用什么名称找到一个值”。

一行行情为什么必须保持固定口径

日线表中的一行通常描述某个资产在一个交易日的记录。日期、开盘、最高、最低、收盘和成交量应来自同一资产与同一日。若日期写周一,收盘价却来自周二,单独看每个值都可能合理,组合在一起已经失去金融含义。

字段名相当于一份长期约定。全表中的 close 应使用同一种收盘价口径,不能前半段使用未复权价格,后半段突然换成后复权价格。真实项目会在数据说明中记录字段类型、单位、时区、复权方式和最早可见时间。字典只保存这份约定,无法替研究者验证来源。

row = {
    "date": "2026-01-05",
    "symbol": "TEACH",
    "close": 10.2,
    "volume": 1200,
}

这行教学数据至少可以检查四件事。日期能否解析,资产代码是否为空,收盘价是否大于零,成交量是否为非负数。检查失败时应回到原始记录,不能因为容器允许修改就随手填一个顺眼的值。

容器怎样增加和修改内容

列表与字典都允许修改。append 会把新项目加到列表末尾,给一个新键赋值会在字典中增加字段。下面代码把周六价格追加到收盘列表,并给 bar 增加成交额。

close_prices.append(10.8)
bar["amount"] = 12360.0

执行后,列表长度从 5 变成 6,字典多出 amount 键。修改能力便于逐步构建结果,也可能让原始数据在清洗时被意外覆盖。真实研究通常把读入的原始文件保持只读,在新的变量或副本上处理,并记录每次转换。这样发现异常时还能回到最初输入。

列表项目也可以按索引替换,例如 close_prices[1] = 10.25。这种操作会直接改掉第二项。若修改来自数据供应商更正,应保留版本和原因;若只是想测试一组假设,最好复制一份教学列表,避免试验值混入原记录。

相邻收益为什么最容易差一天

收益率需要同时读取今天和昨天。若变量 i 表示今天的位置,昨天的位置就是 i - 1。下面三项价格对应周一、周二和周三,我们选择 i = 2,所以当前价格为第三项 10.1,前一价格为第二项 10.2。

prices = [10.0, 10.2, 10.1]
i = 2

previous_price = prices[i - 1]
current_price = prices[i]
daily_return = current_price / previous_price - 1

print(previous_price, current_price, daily_return)

输出中的前值应为 10.2,当前值应为 10.1,收益约为负 0.98%。代码不仅打印最终收益,还保留两个参与计算的价格,目的是让读者核对日期关系。若只看负 0.98%,很难判断程序使用了哪两天。

一个危险错误是从索引 0 开始计算相邻收益。此时 i - 1 等于 -1,Python 不会报错,它会取列表最后一项。第一天因此和未来的最后一天连接,产生未来数据泄漏。程序能够给出数字,却没有使用正确时间。

第 5 课会用循环处理所有日期,并从索引 1 开始,确保每个当前日都有合法的前一日。现在先把三项小列表手算清楚。任何批量计算都应该先通过这种答案已知的小例子。

缺失项目为什么不能自动填零

多个字典放进列表时,每行最好拥有同一组键。若周二字典缺少 close,后面的 pandas 表格会在对应位置放入缺失标记。标记表达“这里没有可用值”,它和真实价格零元是两件事。

新股历史不足、停牌、数据文件损坏和字段本来不适用都会造成缺失,不同原因需要不同处理。随手填零会让价格曲线像突然归零,收益率也会出现巨大跳跃。程序结构允许空值存在,是为了保留不知道的事实,研究者应先查原因,再决定排除、等待、填补或增加缺失说明。

容易弄错的地方

操作 为什么容易错 最小检查方法
用正索引读取列表 第一项编号为 0 在三项列表上打印索引 0、1、2
使用切片 停止位置不进入结果 并排查看原列表、切片和长度
从嵌套结构读取字段 外层按位置,内层按键 rows[1]["close"] 拆成两行
从索引 0 读取前一项 -1 会合法地取到末项 相邻计算从索引 1 开始
缺失字段填成零 未知状态被伪造成真实数值 先记录缺失原因,再选择处理

一分钟自测

prices = [8, 9, 10, 11] 中,prices[1:3] 会得到什么?为什么结果没有 11?

查看答案 结果是 `[9, 10]`。索引 1 被包含,索引 3 是停止位置,不进入结果,所以对应索引 3 的 11 不会出现。

本课术语

词语 现在应该怎样理解
列表 按顺序保存多个项目的容器
索引 项目在列表中的位置编号,第一项从 0 开始
切片 按开始与停止位置取出列表中的连续一段
字典 使用字段名称查找具体值的容器
字典中负责标识和查找内容的名称
某个键实际保存的内容
嵌套 一个容器内部继续保存另一个容器
缺失值 当前记录中没有可用内容的明确标记

课后练习

1 读取价格

prices = [5.1, 5.3, 5.2] 的最后一个价格怎样读取?

提示可以从前向后数,也可以从末尾向前数。
答案`prices[2]` 或 `prices[-1]` 都能得到 5.2。前者依赖当前长度,后者始终表示末项。

2 解释切片

prices[:2] 会取几项?如果想取索引 1 和 2,应怎样写?

提示省略左边表示从开头开始,右边停止位置不进入结果。
答案`prices[:2]` 取索引 0 和 1,共两项。要取索引 1 和 2,应写 `prices[1:3]`。

3 修正读取方式

bar = {"open": 10, "close": 11},有人写 bar[1] 想取收盘价。应怎样修改,并说明原因。

提示字典按键读取,列表才按位置读取。
答案应写 `bar["close"]`。字典中的键是 `"open"` 和 `"close"`,整数 1 并不是现有键。

4 动手修改

创建两天行情,每天包含日期、开盘价和收盘价,再读取第二天收盘价。

提示先写两个字典,把它们放进列表,随后先按索引取第二行。
答案例如 `rows = [{"date": "周一", "open": 10, "close": 10.2}, {"date": "周二", "open": 10.2, "close": 10.1}]`,读取时写 `rows[1]["close"]`。

5 开放思考

为什么日期顺序适合由列表表达,而一日行情字段适合由字典表达?

评价要点回答应区分“第几天”和“这个数字代表什么”两类问题,并提到列表保存顺序、字典保存字段含义、二者组合后仍需检查日期与口径。

下一课会让程序按照同一规则逐日检查这张小表,并解释条件、循环、缩进与相邻日期怎样配合。继续阅读第 5 课 让程序逐日作出判断