数据看起来完整,就真的代表当时可以用吗?

金融数据工程:行情、财务与口径

约 9 分钟6 道自测更新于 2026-10-28

一句话记住量化研究先问数据在什么时候可得、代表什么口径,再问模型有多复杂。

01学完你能做到

你能区分不复权、前复权和后复权,识别停牌、涨跌停和退市造成的回测偏差;理解 point-in-time(PIT,时点可得)财务数据;会从年初至今累计数计算单季度和 TTM,并用时间轴检查未来函数。

02这课解决什么问题

一条价格或财务数据看起来只是一个数字,但它可能经过复权、修订、补录,甚至在决策时还没有公布。数据的时间和口径错了,回测即使代码完全正确,也会得到无法实现的结果。

03行情数据:公司行为与可交易性

复权的目的

分红、送股、转增和配股会让价格机械跳变。为了计算持有期收益,需要把公司行为纳入总回报。数据库应尽量保留不复权价格和累计复权因子,使用时再按需求计算。

除息且没有送转时,除权除息参考价约为:

P_ex = P_(t−1) − D

更一般地,含送转和配股时,常见参考价形式为:

P_ex = [(P_(t−1) − D) + K × r] ÷ (1 + r)

D 是每股现金红利,K 是配股价,r 是股份变动比例。具体计算应以对应交易所规则和公司公告为准。

累计复权因子记为 F_t:

  • 后复权价 = 不复权价 × F_t。
  • 前复权价 = 不复权价 × F_t ÷ F_T。

在同一段历史内,两种复权价格计算收益率通常一致,但前复权的价格水平会随着未来分红更新而改变。因此,“价格低于某个金额”这类特征应使用不复权价格。

停牌、涨跌停和退市

停牌期间没有成交,把价格填成前一天会制造一串 0 收益;复牌时的累计变化又集中出现。回测若在停牌期间按停牌价卖出,就假设了现实中不存在的成交。

涨跌停也不等于一定能成交。一字涨停时可能买不到,一字跌停时可能卖不掉。回测应给每天建立可交易标记,例如:

tradable_i,t = 未停牌 × 可按目标方向成交 × 满足上市时间等筛选条件

每一项可以是 0 或 1。上市满多少天、是否排除风险警示股票等,应在策略规则中提前写清楚。

退市股票不能只在当前股票列表里研究。样本还需要包含退市前的价格、退市整理阶段和最后可实现的处理规则,否则会产生幸存者偏差。示意:100 只股票中 3 只退市,退市者平均收益 −70%,幸存者平均收益 8%。只看幸存者得到 8%,全体平均收益为 0.97 × 8% + 0.03 × (−70%) = 5.66%,偏差为 2.34 个百分点。

04财务数据:时点可得与口径

一条数据的多个时间

财务数据至少要区分报告期末、首次公告日、修订日和入库时间。PIT 原则是:

在决策时刻 τ 能使用的值 = 所有可得时间不晚于 τ 的版本中,最新的一个。

不能把报告期末当成可得日,也不能默认数据库中今天看到的修订值当时已经存在。较保守的规则是公告后下一个交易日才能使用,实际研究应把这条规则写入回测说明。

固定滞后与公告日对齐

固定滞后法简单稳健,例如把上一个财年的账面数据延迟到下半年再使用;代价是信息较旧。逐条公告日对齐更及时,但要求数据源保留准确公告日和每个历史版本。

无论选择哪种方法,都不能用报告期一结束就可得的朴素对齐。例如一季报通常不会在 3 月 31 日当天出现,模型若在 3 月末直接使用,就发生前视偏差。

单季度和 TTM

A 股利润表常见的是年初至今累计值(YTD)。单季度利润为:

单季度_Qk = YTD_Qk − YTD_Q(k−1)

一季度直接使用一季度 YTD。最近十二个月(TTM)为:

TTM = 最新 YTD + 上一年年报 − 上一年同期 YTD

示意:最新一季 YTD 为 268.47 亿元,上年年报为 862.28 亿元,上年同期 YTD 为 240.65 亿元,则 TTM = 268.47 + 862.28 − 240.65 = 890.10 亿元。资产负债表是时点数,不能照搬 TTM 公式。

05面板与未来函数检查

时间轴和标签

如果 t 日收盘后计算特征,最早在 t+1 日开盘交易,持有 h 天的开盘到开盘标签可写成:

y_i,t = P_open(i,t+1+h) ÷ P_open(i,t+1) − 1

不能用 t 日收盘到 t+h 日收盘的收益替代,否则会把信号生成后无法成交的那一段收益计入结果。

三个工程检查

  1. 时间戳检查:每个字段都有可得时间,按“截至时刻取最新值”拼接。
  2. 截断检查:把数据截断在某一天,重新运行流程;截断日及以前的特征必须与全量运行一致。
  3. 扰动检查:随机改变截断日之后的数据,截断日及以前的特征和回测结果不应变化。

代码可以保留关键逻辑:

# t 日收盘生成信号,t+1 日开盘起算标签
label = open_price.shift(-(h + 1)) / open_price.shift(-1) - 1

# 只取决策时刻之前可得的最近版本
available = reports[reports.avail_time <= decision_time]
latest = available.sort_values("period_end").iloc[-1]

06延伸阅读

  • Stefan Jansen《Machine Learning for Algorithmic Trading》
  • Shumway《The Delisting Bias in CRSP Data》
  • Fama & French 因子数据说明
想一想

6 道自测

先在心里答一遍,再展开对照。能说清理由,比答对数字更重要。

  1. 01为什么前复权价格不适合直接构造“低价股”特征?

  2. 02停牌期间把价格保持不变,会怎样影响波动率和交易模拟?

  3. 03100 只股票中 3 只退市,幸存者平均收益 8%,退市者平均收益 −70%,只看幸存者会高估多少?

  4. 04YTD 为 300 亿元,上年年报为 1000 亿元,上年同期 YTD 为 240 亿元,TTM 是多少?

  5. 05t 日收盘生成信号,为什么标签不能从 t 日收盘价开始?

  6. 06截断测试和扰动测试分别在检查什么?

已看 0/6 题

练动手练习

任务:选一只股票,建立一张小型数据字典,记录价格口径、复权因子、分红日期、财务报告期、首次公告日和可交易标记;再做一次截断测试,确认截断日以前的特征不会因未来数据变化。

完成这一课

完成这一课

读完讲义、看完自测答案,这一课就算学完了。

  • 读完讲义
  • 看完全部自测

本课为投资者教育内容,案例数字均为示意,不构成任何投资建议,也不承诺收益。规则和费率会变,实际操作前请以交易所、监管机构和产品的最新公告为准。