第 10 课 行情数据从哪里来¶
行情表看起来像一排已经准备好的数字。这一课沿着数字的来路反向追踪,直到市场、时间和清洗过程都能说清。

市场先发生订单与成交,数据系统随后收集、整理并发布记录。一行日线只是这段过程留下的摘要,不能替代被省略的盘中顺序和可成交数量。
下载成功只是数据工作的起点¶
小岚从一个网站下载了十年股票日线。文件有日期、开盘、最高、最低、收盘和成交量,打开以后整整齐齐,也没有明显空白。她很快算出一条均线策略,却发现另一个数据平台给出的结果不同。两份文件都叫 close,价格却在分红日附近出现差异;一份保留了后来退市的公司,另一份只有当前仍在交易的股票;成交量的单位也没有写在列名里。
这个例子说明,数据不是从市场直接落到研究者电脑里的客观成品。交易场所先产生原始事件,供应方再按照自己的口径汇总、校正和调整,接口或文件把结果交给用户,用户还会继续去重、补缺和合并。任何一层改变,都可能让同一段代码得到不同答案。
研究者因此要为数据建立一条可追查的来路。至少要知道数字来自哪里、字段怎样定义、何时能够看见、经历过哪些调整,以及当前文件是哪一个版本。字段名称相同,只能说明它们看起来相似,不能证明含义完全一致。
先分清行、列、字段与记录¶
在行情表中,一列通常表示一种属性,例如收盘价或成交量,这种有固定含义的项目叫作字段。一行保存某只资产在某个时点的一组字段,这一行叫作记录。许多记录按照相同字段排列,就形成一张表。数据字典是解释每个字段名称、类型、单位、来源和时间含义的说明文档。
日线表常见下面这些字段。开盘、最高、最低与收盘合称 OHLC,它们是四个英文单词首字母的组合。缩写只用于方便沟通,理解时仍要回到每个价格怎样形成。
| 字段 | 它记录的内容 | 还必须确认的细节 |
|---|---|---|
date |
这条记录属于哪个交易日 | 时区、交易日历和日期边界 |
ticker |
资产在数据中的代码 | 代码是否变更,能否唯一指向同一资产 |
open |
开盘阶段形成的价格 | 具体市场怎样定义开盘,是否经过复权 |
high |
期间出现过的最高成交价 | 只记录边界,不记录该价位可成交数量 |
low |
期间出现过的最低成交价 | 无法证明你的订单能在最低点成交 |
close |
收盘阶段形成的价格 | 何时完整,使用原始价还是调整价 |
volume |
期间完成交易的数量 | 单位是股、手、张还是其他口径 |
amount |
期间成交金额的汇总 | 币种、税费是否包含在内 |
同一字段在不同市场可能具有不同规则。股票成交量可能按股或手记录,期货还会同时出现成交量与未平仓数量,跨时区市场的“同一天”也未必覆盖同一段自然时间。下载数据以后先阅读供应方文档,比根据熟悉的列名自行猜测更可靠。
K 线保存了边界,也丢掉了路径¶
第 9 课已经看到,一根日 K 线把一天的许多成交压缩为开盘、最高、最低和收盘。它能告诉我们价格活动的范围,却不能还原最高价与最低价出现的先后,也看不见某个价位停留多久、排队多少订单。数据粒度表示记录时间的细致程度,日线、分钟线和逐笔数据属于不同粒度。
研究问题决定所需粒度。每晚收盘后计算一次信号、第二天调整一次持仓,日线可能已经够用;若规则声称同一天先止损再反手买入,日线通常无法证明盘中顺序;依赖盘口队列或极低延迟的策略,需要更细的市场事件。更细数据会增加存储、清洗与时间同步难度,选择时应以策略需要辨认的动作为标准,额外细节若没有回答新问题,只会增加处理负担。

图中蜡烛实体连接开盘与收盘,上下影线标出最高与最低。右侧时间轴提醒读者,四个数字只保存结果边界,蜡烛本身没有记录盘中先后。
每一列都要回答何时能够看见¶
小岚在周三收盘以后下载日线,能够看到完整的周三收盘价;若她在周三上午运行程序,最终收盘价还没有形成。两次使用的是同一个字段名,可用信息却不同。数据的时间含义至少要拆成“它描述哪段时期”和“市场最早何时知道”两部分。
| 信息 | 它描述的时期 | 最早完整可见的时点 | 可以影响的教学订单 |
|---|---|---|---|
| 周三开盘价 | 周三开盘阶段 | 周三开盘形成以后 | 周三开盘之后的订单 |
| 周三最高价 | 周三整个交易日 | 周三交易结束以后 | 通常只能影响更晚的订单 |
| 周三收盘价 | 周三收盘阶段 | 周三收盘以后 | 最早影响周四订单 |
| 第一季度利润 | 第一季度经营活动 | 公司正式披露以后 | 披露之后的订单 |
| 修订后的宏观数字 | 某个历史月份 | 修订版本公布以后 | 不能替换公布前看到的初值 |
保存历史每一时点当时可见版本的数据,常被称为时点数据,英文写作 point-in-time data。它要求研究者回到过去时,只能使用当时已经发布的内容。今天下载到的完整历史表可能已经补齐缺失、修正错误并采用最终修订值,适合了解历史全貌,却未必能原样模拟过去决策。
财务数据尤其容易混淆两个日期。报表可能描述第一季度,却在四月末才正式披露。若程序按照季度结束日把利润放入三月三十一日的选股表,就提前使用了尚未公开的信息。稳妥的表会同时保留报告期结束日和实际披露时间,回测连接行情时依据披露时间决定何时可用。
原始价格与复权价格服务于不同任务¶
公司分红、拆股和配股会让名义价格发生跳变。假设一股 20 元的股票执行一拆二,投资者原有一股变成两股,拆分后的每股价格大约变为 10 元。若只比较原始收盘价,曲线会显示价格下跌约一半;投资者持有的总数量同时翻倍,账户价值并没有因此损失一半。
数据供应方会用调整因子处理这些公司行动,让历史价格在同一口径下更容易比较,这个过程叫复权。前复权常把较早价格调整到接近当前口径,后复权则把后来价格延伸到较早口径。不同供应方的具体定义可能不同,因此报告必须写明方法,不能只留下“已复权”三个字。
复权序列适合计算连续收益和技术指标,模拟真实订单仍要考虑当时市场中实际出现的名义价格、最小价格单位和交易限制。研究表可以同时保留 raw_close 与 adjusted_close,分别承担成交参考与连续计算。一个 close 列同时用于所有任务,会让后来的人无法判断它在每一步代表什么。
历史股票池不能使用今天的幸存名单¶
假设研究者在 2026 年下载当前上市公司清单,再用这些公司回测 2016 年到 2025 年。过去十年退市、被合并或失去指数资格的公司已经消失,程序等于提前知道哪些对象能活到今天。表现较差的对象更容易被排除,结果往往因此显得更平稳。
只观察后来留下的对象所产生的系统偏差叫作幸存者偏差。股票清单、基金数据库、指数成分和商品合约都会出现这类问题。历史研究应在每一个日期重建当时有资格被选择的范围。这个范围叫作当日股票池或候选池。
flowchart TB
accTitle: 历史股票池的形成过程
accDescr: 每个历史交易日先读取当时已知的上市、退市、停牌和指数成员记录,再形成当日候选池并计算信号,随后才移动到下一个日期
records["历史上市、退市与成分记录"] --> date["回到某个交易日"]
date --> known["只保留当时已经知道的状态"]
known --> eligible["形成当日候选池"]
eligible --> signal["在候选池内计算信号"]
signal --> next["移动到下一交易日"]
next --> date
classDef source fill:#f6f0ff,stroke:#7653c7,stroke-width:2px,color:#34205f
classDef action fill:#eef3ff,stroke:#3157d5,stroke-width:2px,color:#172b65
class records source
class date,known,eligible,signal,next action
拿不到完整历史成员记录时,研究者需要缩小结论。例如只研究一组能够确认存续历史的资产,并明确说明样本限制。用今天的名单替代历史名单,再把结果写成整个市场都适用,会把数据缺口隐藏成研究发现。
空白位置先问原因,再决定处理¶
缺失值表示某个位置没有可用数据。它可能来自真实市场状态,也可能来自下载故障。程序看到的都是空白,人却要先分辨空白背后的事件,才能决定是否删除、保留或补充。
| 缺失原因 | 当时可能发生的情况 | 合理的第一步 |
|---|---|---|
| 尚未上市 | 市场中还没有这项资产 | 保持不可选,不能补出上市前价格 |
| 停牌 | 资产存在,当日却没有正常成交机会 | 保留停牌状态,禁止虚构成交 |
| 接口失败 | 市场可能有记录,当前文件没有取到 | 重新获取并标记数据错误 |
| 财务信息尚未披露 | 经济期间已经结束,数字仍未公开 | 等到实际披露以后再允许使用 |
| 市场休市 | 某一市场当天没有交易 | 使用各自交易日历,不把休市混成异常 |
把所有价格向前填充,会把“无法观察新价格”变成“价格没有变化”。如果策略随后允许在这个填充值上自由成交,停牌资产就获得了现实中不存在的交易机会。删除所有带空白的日期也有风险,因为市场压力时期的停牌与数据问题可能被一起删掉,样本因此只剩较顺利的日子。
财务数据有时可以从披露日开始沿用上一份已知报告,因为投资者在新报告发布前确实只能使用旧信息。价格是否能沿用、沿用后能否交易,则是另一个问题。相同的填充函数放在不同字段上,含义可能完全不同。
原始文件怎样变成研究表¶
数据处理通常会经过原始文件、清洗表和研究表三层。原始文件保存供应方交付的内容,不在上面直接覆盖修改。清洗表统一日期、类型、重复记录和明确的数据错误。研究表再按照当前问题添加复权价格、候选池状态或策略需要的指标。三层分开以后,发现异常时可以沿着变换路径回到最初输入。
flowchart LR
accTitle: 数据从来源进入研究的证据链
accDescr: 交易所与公告产生记录,供应方发布原始文件,清洗过程统一格式并保留异常标记,研究表再生成调整价格和可用状态,每一步保存版本与说明
source["市场成交与正式公告"] --> raw["原始文件<br/>保持原样"]
raw --> clean["清洗表<br/>类型、去重、缺失标记"]
clean --> study["研究表<br/>复权、候选池、特征"]
study --> report["回测与报告"]
version["来源、下载时间、版本与校验值"] -.-> raw
version -.-> clean
version -.-> study
校验值可以理解为文件指纹。内容完全相同的文件会得到同一串校验字符,只要一个数字变化,指纹通常也会改变。它不能判断数据是否正确,却能帮助研究者确认两次运行使用的输入是否相同。毕业项目不需要搭建庞大数据库,保留原始文件、下载日期、字段说明和校验值已经能解决许多追查问题。
用 pandas 做第一次表格体检¶
下面的代码只检查一张已经读入的日线表,不计算策略。输入是一张名为 prices 的 pandas 表格,其中应有 date、ticker、open 和 close 四列。任务是查看列类型、重复的资产日期以及每列缺失数量。运行结果会根据你的数据变化,因此先关注输出说明了哪一类问题。
required = {"date", "ticker", "open", "close"}
missing_columns = required - set(prices.columns)
print("缺少的必要列", missing_columns)
duplicate_rows = prices.duplicated(subset=["date", "ticker"]).sum()
print("重复的资产日期", duplicate_rows)
print("每列缺失数量")
print(prices.isna().sum())
第一行建立必要字段集合,第二行用集合差找出表中不存在的必要列。如果输出 set(),表示这四个列名都存在;它仍不能证明单位和时间含义正确。duplicated 检查同一资产在同一天是否出现多行,sum 把真假结果加起来得到重复数量。isna 会标出空白位置,再按列求和。
代码输出零并不等于数据合格。日期是否覆盖正确交易日、收盘价是否复权、停牌怎样表示、退市资产是否保留,都需要结合数据字典和来源说明检查。程序擅长寻找格式异常,语义要由研究者回到文档与市场规则确认。
数据来源要留下证据链¶
学习阶段可以从交易所、指数公司、公开数据平台或证券数据服务获取样本。来源选择需要同时查看覆盖市场、更新频率、字段定义、复权口径、历史成员、使用许可和稳定性。第三方 Python 库只是访问入口,接口成功返回不代表字段已经适合当前研究。
财务与公司行动应优先核对正式披露。美国上市公司可以通过 SEC EDGAR 查找提交记录,A 股研究应根据交易所和公司公告确认实际披露时间。使用商业平台时也要保存该平台的数据字典和下载日期,因为供应方可能修复历史错误或更新调整因子。
一份可复查的数据说明至少记录供应方、下载日期、覆盖范围、字段定义、时区、复权方法、缺失处理、历史候选池与文件版本。后来结果改变时,先比较输入版本,再检查代码与参数,可以避免在错误方向上寻找原因。
让 AI 帮忙暴露未知项¶
AI 适合把数据说明变成检查问题,也能根据五行样例指出类型、重复和时间字段可能存在的风险。它无法知道供应商没有公开的内部规则,因此任务要明确要求它标出未知项,不能用常识补写字段含义。
我会提供数据字典和五行样例。请为每一列列出单位、描述时期、最早可见时间和缺失含义。无法从材料确认的内容标为“待确认”,不要猜测。随后检查未来信息、幸存者偏差、复权混用和错误填充风险,并把问题整理为核对清单。
拿到回答以后,把“待确认”逐项带回供应方文档、交易所规则和原始文件。AI 在这里承担的是提问助手,它不能替数据来源作出保证。
一张可以直接使用的数据体检表¶
| 检查项 | 需要写下的答案 |
|---|---|
| 市场与资产 | 哪个交易场所、什么品种、代码怎样识别、使用什么币种 |
| 时间 | 时区、交易日历、字段描述时期与最早可见时点 |
| 价格 | 原始还是复权,采用哪种调整方法,成交使用哪一列 |
| 候选池 | 是否保存历史上市、退市、停牌与指数成员状态 |
| 缺失 | 每类空白表示什么,怎样处理,处理后是否允许交易 |
| 公司行动 | 分红、拆股、配股与代码变更怎样进入数据 |
| 交易限制 | 停牌、价格限制、最小数量和可成交容量怎样处理 |
| 数据版本 | 来源、下载时间、覆盖范围、许可与文件校验值 |
一天一天揭开数据¶
价格侦探不会一次显示完整历史。每次点击只会增加一天,你需要先依据当前可见内容写下判断,再揭开下一天。请把注意力放在信息边界上,观察完整历史表会怎样诱使我们越过当时能够看见的范围。是否猜中下一天不影响练习完成。
容易弄错的地方¶
| 常见做法 | 隐藏的问题 | 检查方法 |
|---|---|---|
看到 close 就直接计算 |
不知道是否复权,也不知道何时完整 | 查数据字典并保存价格口径 |
| 用当前股票清单回测十年前 | 提前排除了后来退市的对象 | 按历史日期重建候选池 |
| 对所有空白统一前向填充 | 停牌、尚未上市和接口失败被混为一类 | 先记录缺失原因,再决定处理 |
| 把季度末当成财务数据可用日 | 报表可能数周后才披露 | 保存实际披露时间并按它连接行情 |
| 只保留清洗后的最终表 | 结果改变后无法回到原始输入 | 原始文件只读保存,记录每步变换 |
一分钟自测¶
一家公司周五晚上发布财报。周五白天运行的策略能否使用这份财报?
查看答案
不能。财报在周五交易结束以后才公开,周五白天的市场参与者还看不到。它最早只能影响发布之后的可交易时点。本课术语¶
| 词语 | 先这样理解 |
|---|---|
| 字段 | 数据表中具有固定含义的一列内容 |
| 数据字典 | 解释字段名称、类型、单位、来源和时间的文档 |
| 数据粒度 | 记录时间的细致程度,例如日线、分钟线或逐笔 |
| 时点数据 | 保存历史每个时点当时可见版本的数据 |
| 复权 | 为处理分红、拆股等公司行动而调整价格序列 |
| 幸存者偏差 | 只观察后来留下的对象而产生的系统偏差 |
| 缺失值 | 某个位置暂时没有可用数据 |
| 校验值 | 用来辨认文件内容是否变化的一串字符 |
课后练习¶
1 理解字段¶
成交量和成交额有什么差别,为什么还要确认它们的单位?
提示
一个描述数量,一个描述金额,不同市场可能使用股、手、张或不同币种。答案
成交量记录完成交易的资产数量,成交额汇总相应金额。单位不明时,同一个数字可能相差成百上千倍,也无法跨市场比较。2 判断可见时间¶
三月份利润在四月二十日披露,它能否用于四月一日的选股?
提示
区分数据描述的时期与市场最早知道的日期。答案
不能。利润属于三月份或第一季度,不代表三月底已经公开,四月一日仍无法看到四月二十日才披露的数字。3 寻找清洗错误¶
一只股票停牌三天,研究者向前填充价格,并允许策略在三天中按填充值自由买卖。错在哪里?
提示
价格沿用与市场是否提供成交机会是两个问题。答案
填充值可以让表格连续,却不能创造真实成交。停牌期间应保留不可交易状态,订单不能假设按旧价格完成。4 动手检查¶
为一份教学 CSV 填写本课的数据体检表,并运行 pandas 检查必要列、重复记录与缺失数量。
提示
程序只能检查格式,字段单位、复权口径和可见时间还要从说明文档填写。答案
结果取决于所选文件。完整答案应同时包含程序输出和来源说明,不能只贴三行数字。5 开放思考¶
若拿不到完整的历史指数成分,你会怎样缩小研究结论?
评价要点
可以改用能够确认历史资格的固定资产集合,缩短研究区间,寻找替代官方记录,或明确声明幸存者限制。不能继续使用当前成分,却把结论写成适用于完整历史指数。看到任何行情列时,请先问四件事。它记录什么,使用什么单位,何时完整可见,经历过哪些调整。再确认缺失原因、历史候选池和原始文件版本。下一课会在口径清楚的数据上计算单日收益、复利和净值曲线。