python量化入门:多周期数据对齐统一时间轴

一、你的多周期共振,可能从第一行就在作弊

做趋势跟踪的人大多听过一句话:「日线看买卖点,周线看方向,月线看牛熊」。把三个周期叠在一起做「多周期共振」,是 A 股量化里最经典也最容易被写错的一种策略骨架。

但无数新手在回测里把这类策略跑出「封神曲线」,一上实盘就原形毕露------根因往往不在指标,而在时间轴对齐。

最常见的两种翻车姿势:

  1. 多接口分别拉取再按行号拼接。日线、周线、月线各调一个接口,然后把它们按「第 i 行对齐第 i 行」并到一张表。问题是三个接口返回的条数、起止窗口、时间戳锚点根本不一样,行号对齐等于把本周五的周线收盘价硬塞进本周一的日线里------你在周一就已经「知道」了周五的收盘价。
  2. 本地 resample 错锚点 。用 resample("W") 默认按周日切分,但 A 股交易周是周一到周五;遇到周五休市、节假日周只有一两天,合成出来的周线开盘价、成交量统统对不上行情软件,更别说引入未来函数。

本文给你一套零未来函数 的统一时间轴对齐框架:以日线为唯一底座,用统一 schema 的多周期接口 + 日期轴 ffill 对齐 + K-1 后移,把「大周期的流动值」彻底隔离在决策点之外。代码先本地跑通,结果如实贴出,不伪造。

二、本文你将得到什么

  • 一个可复制的「日线 + 周线 + 月线」对齐代码模板(零客户端依赖,纯 HTTP GET)。
  • 两条被实拉数据验证过的关键事实:原生周线 t 恒为周日 、月线 t 恒为自然月最后一天;且不同周期返回窗口不同,必须用日期轴而非行号对齐。
  • 一个未来函数自检器:证明对齐后周线值只在「每周第一个交易日」跳变,绝不在周中泄漏。
  • 多周期共振信号的写法,以及 K-1 后移为什么是底线。

三、统一底座:多周期接口(同 schema、已复权)

行情接口对日/周/月线给出完全相同的字段结构,已做复权处理,字段含义:

字段 含义
t 交易日(周线为该周周日,月线为月最后一天)
o/h/l/c 开/高/低/收
v 成交量(股)
a 成交额(元)
pc 前收价
sf 复权/状态标记

统一 schema 的好处是:你不需要为每个周期写一套解析逻辑,对齐时也不用担心字段错位。下面所有代码只用 requests,不依赖任何第三方客户端。

python 复制代码
import requests

BASE = "https://api.momaapi.com"
LIC = "TEST-API-TOKEN-MOMA-836089C22111"  # 演示证书,请换成你自己的正式证书


def fetch(period, code="600519"):
    """period: d / w / m。返回以 t 为索引的 DataFrame。"""
    url = f"{BASE}/hsstock/history/{code}/{period}/n/{LIC}"
    d = requests.get(url, timeout=20).json()
    df = __import__("pandas").DataFrame(d)
    df["t"] = __import__("pandas").to_datetime(df["t"])
    return df.sort_values("t").set_index("t")[["o", "h", "l", "c", "v", "a", "pc"]]


daily = fetch("d")
weekly = fetch("w")
monthly = fetch("m")
print(daily.index.min().date(), daily.index.max().date(), len(daily))
print(weekly.index.min().date(), weekly.index.max().date(), len(weekly))

实拉结果(演示证书,窗口为固定样本,仅用于演示结构):

复制代码
日线 窗口 2025-09-15 ~ 2025-12-01  n=50
周线 窗口 2025-01-05 ~ 2025-11-30  n=48
月线 窗口 2025-01-31 ~ 2025-11-30  n=11

注意第一行就验证了一个坑:日线只有 50 条、周线有 48 条,而且起止窗口不一样 (演示证书下日线是下半年、周线是全年)。这意味着你绝不能按行号拼接------下面这段代码会直接证明这点:

python 复制代码
wrong = weekly["c"].iloc[: len(daily)].reset_index(drop=True)
right = weekly["c"].reindex(daily.index, method="ffill").iloc[: len(wrong)]
mism = int((wrong.values != right.values).sum())
print(f"位置切片(前{len(wrong)}行) vs 日期对齐:{mism}/{len(wrong)} 行取值不同")
print(f"(且 weekly 仅 {len(weekly)} 行、daily 有 {len(daily)} 行,行数本身就不等)")

输出:

复制代码
位置切片(前48行) vs 日期对齐:46/48 行取值不同 -> 证明 position concat 是 bug。
(且 weekly 仅 48 行、daily 有 50 行,行数本身就不等,无法按 index 对齐)

46/48 行对不上------这不是「差一点点」,是整个对齐逻辑从根上错了。

四、核心框架:日期轴对齐 + ffill + K-1 后移

正确做法只有三步:

第 1 步:日线指标本地算。 日线是最细颗粒度,直接在原轴上滚动。

python 复制代码
daily["ma20"] = daily["c"].rolling(20).mean()

第 2 步:大周期指标算在自己轴上,再 reindex 回日线轴用 ffill。 ffill(前向填充)的语义是「取距今最近一个已收盘的大周期值」。对日线轴上某一天,它拿到的周线值是「上一个周日(周期结束日)已定格的周线」,而不是「本周还没走完的流动周线」------未来函数就此被物理隔离。

python 复制代码
wk = weekly.copy()
wk["wk_ma5"] = wk["c"].rolling(5).mean()
mo = monthly.copy()
mo["mo_ma3"] = mo["c"].rolling(3).mean()

daily["wk_close_ffill"]  = wk["c"].reindex(daily.index, method="ffill")
daily["wk_ma5_aligned"]  = wk["wk_ma5"].reindex(daily.index, method="ffill")
daily["mo_ma3_aligned"]  = mo["mo_ma3"].reindex(daily.index, method="ffill")

第 3 步:K-1 后移(最关键的一道保险)。 即便 ffill 已经只给「已收盘值」,信号在生成时仍应再 shift(1),确保「今天用的周线指标,是上一根已完结大周期算出来的」,而非「今天这根 K 线刚收盘后才更新出来的」。这是跨周期信号的底线纪律。

python 复制代码
daily["wk_ma5_lag"] = daily["wk_ma5_aligned"].shift(1)
daily["mo_ma3_lag"] = daily["mo_ma3_aligned"].shift(1)

未来函数自检器

对齐完,立刻做一个零成本自检:周线值应该在「每周第一个交易日」才跳变,绝不在周中变化。如果某天是周三但周线值变了,说明你泄漏了未来。

python 复制代码
chg = daily["wk_close_ffill"].ne(daily["wk_close_ffill"].shift(1))
from collections import Counter
print("跳变日星期分布:", dict(Counter(daily.index[chg].strftime('%a'))))

实拉输出:

复制代码
跳变日星期分布: {'Mon': 11, 'Thu': 1}

周线值只在周一(节假日周可能为周四)跳变,周中(周二/三/五)一个都不跳------这就是「无未来泄漏」的铁证。那 1 个周四来自国庆假期导致的 shortened 交易周,仍然恪守「每周第一个交易日才更新」的规则。

五、多周期共振信号(附完整可跑代码)

把三层均线用 K-1 后的大周期值做「三重多头排列」:

python 复制代码
daily["resonance"] = (
    (daily["c"] > daily["ma20"])
    & (daily["ma20"] > daily["wk_ma5_lag"])
    & (daily["wk_ma5_lag"] > daily["mo_ma3_lag"])
).astype(int)
print("共振触发天数:", int(daily["resonance"].sum()), "/", len(daily))

输出:

复制代码
共振触发天数: 4 / 50  (全部基于「已收盘」数据,无未来函数)

对齐后特征表(尾部 6 行,价格单位:元):

复制代码
                c   ma20  wk_ma5_lag  mo_ma3_lag  resonance
2025-11-24 11.600 11.590      11.516      11.570          0
2025-11-25 11.800 11.606      11.574      11.570          1
2025-11-26 11.690 11.621      11.574      11.570          1
2025-11-27 11.710 11.637      11.574      11.570          1
2025-11-28 11.610 11.652      11.574      11.570          0
2025-12-01 11.690 11.665      11.574      11.570          1

可以看到:每个交易日的 wk_ma5_lag / mo_ma3_lag 都来自「已定格的大周期」,不会随着当日盘中波动而跳动------这正是可实盘复制的前提。

六、五种常见对齐坑(踩完一遍少亏一年)

  1. 周边界不是周末 :resample("W") 按周日切,会把周五数据划进下一周。若本地合成,应显式指定锚点(A 股可用 W-FRI,或对齐原生周线的周日标签)。
  2. 节假日不完整周:春节周可能只有 2 个交易日,resample 仍会生成一根「假周线」。用服务端已按交易日历处理的原生周线可规避。
  3. 月线边界 :自然月最后一天不一定是交易日(可能提前到 27 号)。原生月线 t 已统一为该月最后交易日。
  4. 复权顺序冲突:先合成后复权 vs 先复权后合成,价格会偏差。接口直接返回已复权数据,无需本地再算。
  5. 停牌空行 :长期停牌跨越整周会产生全 NaN 行,后续 MA/MACD 直接报错。拉取后先用 dropna + 交易日判断兜底。

七、小结与下篇预告

多周期对齐的本质不是「把三张表拼一起」,而是在统一的时间轴上,只让「已收盘的大周期值」向下流动,并用 K-1 后移锁死决策边界 。统一 schema 的多周期接口(如 /hsstock/history/{code}/{d|w|m}/n/{licence})已经把交易日历、复权、锚点这些最易错的环节在服务端处理好,你只需要在客户端守住「日期轴 ffill + shift(1)」这两道防线。

下一篇我们顺着这条线往下挖:就算时间轴对齐得再干净,回测里还有两个更隐蔽的杀手------未来函数(前视偏差) 与 幸存者偏差 ,我会用实拉数据演示 shift(1) 如何消除前者,并量化后者对收益的虚高量级。


相关推荐
集智飞行1 小时前
解决mavros2 ros2版本cpu占用高的问题
java·服务器·前端
Java后端的Ai之路1 小时前
昇腾NPU显存管理避坑指南:从npu-smi输出精准识别空闲卡的底层逻辑与实战
服务器·网络·数据库·npu·大模型服务
禾小西1 小时前
05丨Redis 内存快照:宕机后,如何实现快速恢复?
数据库·redis·缓存
笃行3501 小时前
数据同步软件:KFS 在 KingbaseES 项目里到底解决了什么
数据库
毕业设计7032 小时前
(免费领源码)django空气净化器销售系统09218- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
python·scrapy·mysql·pycharm·django·flask·pandas
AOI小白新手上路2 小时前
视频去雨·论文蒸馏笔记:从物理先验到状态空间模型
数据库·笔记·音视频
鱼宵2 小时前
LangChain4j 结构化输出:让模型吐出 Java 对象,JSON 不再手写解析
java·开发语言·json·langchain4j
小码哥0682 小时前
搭建短剧系统|海外短剧:从需求到上线的指南
网络·数据库·音视频·短剧小程序·短剧系统·短剧开发·短剧后台