交易日历与多频率对齐
你的月频标签,有三分之一不是交易日
weekly = close.resample("W-FRI").last()
monthly = close.resample("ME").last()
这两行代码,你大概率写过。它们看起来毫无问题——取每周五、每月末的收盘价。
实测:在 2015 到 2026 这段里,月频重采样产生的 139 个标签中,48 个不是交易日——34.5%。
值是对的,标签是错的。而当你把这张表和另一张表 merge 的时候,错的标签会安静地丢掉三分之一的行。
快递单上写"周一送达",但那天是元旦,快递站根本没开门。包裹实际是周二到的。
单子上的日期和实际发生的日期不一致——单独看没问题,一旦要和别的记录对账,就全乱了。
一、别用 bdate_range 生成交易日
fake = pd.bdate_range("2015-01-01", "2026-07-29") # 周一到周五
实测对比真实交易日历(交易所口径,经 baostock 获取):
真实交易日 2811 天
bdate_range 给的 3020 天
❌ 多算(以为开市其实休市) 209 天 = 7.4% 的交易日被凭空造出来
❌ 漏算(实际开市却被跳过) 0 天
每年被凭空造出 13–20 天:
| 年份 | 2015 | 2018 | 2020 | 2022 | 2024 | 2025 |
|---|---|---|---|---|---|---|
| 多算天数 | 17 | 18 | 19 | 18 | 20 | 18 |
最近的几个例子:2026-02-23(春节)、2026-05-01 / 05-04 / 05-05(五一)、2026-06-19(端午)。
后果:用假日历做 reindex,长假里会插入一串"价格不变"的假交易日。它们会稀释波动率、拉平均线、让换手统计失真——和 p1.3 的停牌坑同源:假的 0 收益,一律往下压你的风险指标。
「调休补班的周六,A股开不开市?」
本次实测区间(2015–2026)里,落在周末的真实交易日是 0 天。
但请不要因此在代码里写死"周末一定不开市"。交易日历是交易所公告的结果,不是可以推算的规律——历史上出现过周末交易安排,规则也可能调整。
正确做法:每次从交易所口径取真实日历,不要自己推。这一条看起来啰嗦,但它是 p1.1「凡是能查的就不要猜」的延续。
二、重采样的标签会骗人
周频 W-FRI: 592 个周期,其中 37 个标签不是交易日 (6.2%)
月频 ME: 139 个周期,其中 48 个标签不是交易日 (34.5%)
月频超过三分之一。
原因不难理解:resample 的标签用的是日历日——每月最后一个自然日、每周的周五。而 5 月 31 日可能是周日,10 月 31 日可能在长假里。
值是对的(pandas 取的是区间内最后一个有数据的日子),标签是错的。
危害在于对账:
- 你以为拿到的是 5 月 31 日的收盘,实际是 5 月 30 日的
- 和另一张按真实交易日索引的表 merge → 那一行直接消失
- 如果你 merge 前后没检查行数,丢了三分之一的数据都不会有人告诉你
修法:把标签换成区间内真实的最后一个交易日
g = close.groupby(pd.Grouper(freq="ME"))
last_day = g.apply(lambda s: s.index[-1] if len(s) else pd.NaT).dropna()
aligned = pd.Series(g.last().dropna().to_numpy(), index=pd.DatetimeIndex(last_day))
实测:修正后 139 个月度点,非交易日标签 0 个。
🔧 动手做:你的月频标签,三分之一不是交易日(5 分钟)
跑 python calendar_align.py。它把 pandas 默认的工作日/重采样标签,和真实交易日历比。
你会看到(实测):pd.bdate_range 生成的"工作日"比真实交易日多算 209 天(7.4%)(它不知道国庆春节休市);月频重采样 ME 的 139 个标签里,48 个落在非交易日(34.5%)——你以为在每月末取值,其实三分之一取在了休市日。
想明白:pandas 的日历不懂 A 股休市。用它生成的标签去对齐行情,会静默地取错日子(取到最近可用的、或 NaN)。改用"区间内最后一个真实交易日"作索引,非交易日标签立刻归零。任何按日历重采样的操作,先问:这个日历是真实交易日历吗?
三、跨市场对齐:重叠率只有 93.4%
A股与美股(标普 500)在同一段区间:
A股交易日 2811 | 美股交易日 2908 | 共同开市 2715 天
仅 A股开市 96 天(美股假期:感恩节/圣诞/独立日…)
仅美股开市 193 天(A股长假:春节/国庆/五一…)
重叠率 93.4%
近 300 个交易日,两个市场只开一边。跨市场策略必须先回答三个问题:
- 调仓日怎么定?只在共同开市日调,还是各自日历各自跑?
- 缺失日怎么填?直接 join 会产生 NaN;前值填充等于"拿昨天的美股当今天用"——这是 p1.2 讲过的对齐层泄漏
- 时差怎么算?美股当日收盘发生在北京时间次日凌晨。用美股收盘信号做 A 股决策,至少要再延一天,否则就是未来函数
第三条最容易漏。很多"中美联动"策略的超额收益,拆开看全部来自这一天的时差。
📌 免责:本课为技术教学,不构成投资建议。交易日历以交易所公告为准,课文数据为 2026-07-31 实测快照。
三个实测数字:
- 209 天:
bdate_range在 11 年里凭空造出的假交易日(7.4%),每年 13–20 天 - 34.5%:月频
resample标签里不是交易日的比例(修正后 0) - 93.4%:A股与美股的交易日重叠率——近 300 天只开一边
一条纪律:交易日历必须从交易所口径取,不能自己推算。周末不开市、节假日不开市,这些都是"目前如此"而不是"必然如此"。
下一节把数据存下来:DuckDB / Parquet 怎么选、schema 怎么定、增量更新怎么做到幂等。从这一节起,你不用每次跑回测都重新联网取数——那既慢,又让你的结果依赖当天的网络状况。
🔎 来源与核验· 4 条,点开核对
