☰
量化实战:多周期数据对齐统一时间轴
2026/10/2 17:17:34 网站建设 项目流程

多周期数据对齐统一时间轴

做多周期策略的人,几乎都被同一件事坑过:回测曲线漂亮,实盘一开仓就对不上。顺着数据链路一级级查,最后发现根子在于——回测用的日线、分钟线,和实盘驱动的实时行情,根本不在同一条时间轴上。

网上讲这个主题的帖子一抓一大把,结论高度一致:日线和分钟线对不上,不是数据源故障,而是聚合口径、收盘截断、盘前盘后、复权规则、时区处理没对齐导致的结构性差异。更主流的解法也一致:放弃接口预聚合的 K 线,去拿原始 Tick,自己写一套聚合逻辑统一生成日线和分钟线。

这套解法没错,但成本高——你要维护 Tick 订阅、写聚合引擎、处理时区、处理复权,还得保证实时和历史的合成逻辑完全一致。本文给你一个更轻的量级解法:同源的日线、分钟线(量化 Pro 增强包)和实时快照,三者字段同构、时间轴一致,你只需要一个"交易日轴"做 join key,三源天然对齐,根本不用从 Tick 自己造轮子。代码全部本地跑通、接口真实返回,不虚构任何数值。

本文你将得到什么

  • 一个UnifiedAxis类:把日线、实时、分钟线三种不同时间表达,归一化到"交易日轴"上对齐;
  • 理解三源对不上的四大真因,以及为什么"同源接口族"能从根上消掉口径差异;
  • 分钟线同源聚合的落地写法(含与日线 close 的对齐校验);
  • 顺着接口字段,讲清演示证书与正式证书、以及分钟线所需量化 Pro 增强包的差异。

一、为什么三源对不上

先把问题拆清楚。日线、分钟线、实时行情,三者"对不上"通常出现在四个维度:

1. 时间表达方式不同。历史日线用交易日日期(如2026-09-08),分钟线用YYYY-MM-DD HH:MM:SS,实时行情用毫秒级时间戳。如果不统一成同一种"轴",K 线序列会错位,信号触发时机跟着偏。

2. 收盘截断规则不一致。分钟线按固定窗口切片取每分钟最后一笔;日线有的按交易所正式收盘截断,有的按最后一笔成交算。时间窗口不同,收盘价天然对不上。

3. 复权标准前后不一。这是仅次于数据口径的元凶。回测用了前复权,实盘行情是不复权的原始价,除权除息位置价格直接断裂,回测收益虚增。

4. 时区混乱 + 分钟缺失。A 股(北京时间)、港股、美股(美东、夏令时)混用时,若直接用物理日期对齐,可能在 A 股盘中"偷看"到当晚才开盘的美股收盘价。分钟线本身也可能因无成交、断层而缺失,进一步污染聚合。

掘金上一篇复盘总结得很直白:历史 K 线与实时行情来自不同数据源、经过不同聚合规则,导致回测用的价格基准与实盘看到的实时价格,在时间戳、K 线合成逻辑、复权标准上系统性偏差。结论就是——数据口径不统一,是回测漂亮、实盘对不上的第二大元凶。


二、解法:同源接口族 + 交易日轴

问题的本质是"多源、多口径"。解法对应两条:

第一,接口同源。日线/hsstock/history/{code}/d/n/、实时/hsstock/real/time/{code}/、分钟线/pro/history/{code}/m/{period}/(量化 Pro 增强包)同属行情接口族,字段命名一致、复权口径一致、时间戳格式一致。你不需要去三家拼数据,也不需要自己写 Tick 聚合引擎。

第二,时间轴归一。不管来源是什么时间表达,落库时统一抽出一个"交易日轴"(date 字符串)作为 join key:

  • 日线本身就有t(交易日),直接当 key;
  • 实时快照的t取日期部分(2026-09-08 15:00:00→2026-09-08)当 key;
  • 分钟线的t同样取日期部分,重采样成日线后和日线以 date 对齐。

一个 key 打通三源,多周期共振、因子计算、信号触发全建立在一致的数据地基上。


三、代码:UnifiedAxis 把三源归一化

下面是完整可运行代码。先定义三个取数函数,再定义UnifiedAxis做归一与合并。注意实时接口收盘字段是p、历史日线是c,统一映射成close,避免字段名打架。

importrequests BASE="https://api.momaapi.com"LICENCE="TEST-API-TOKEN-MOMA-836089C22111"# 演示证书,请换成你自己的正式证书CODE="600519"deffetch_daily(code:str,st:str,et:str)->list:url=f"{BASE}/hsstock/history/{code}/d/n/{LICENCE}?st={st}&et={et}"returnrequests.get(url,timeout=25).json()deffetch_realtime(code:str)->dict:url=f"{BASE}/hsstock/real/time/{code}/{LICENCE}"returnrequests.get(url,timeout=20).json()deffetch_minute(code:str,period:int,st:str,et:str):# 分钟线走量化Pro增强包路径 /pro/history/。演示证书无权限返回 403,真实证书有数据。url=f"{BASE}/pro/history/{code}/m/{period}/{LICENCE}?st={st}&et={et}"r=requests.get(url,timeout=25)ifr.status_code==403:return{"__need_pro__":True,"msg":r.text[:60]}r.raise_for_status()returnr.json()classUnifiedAxis:"""把多周期数据归一化到「交易日轴」(date 字符串)上做对齐。"""def__init__(self):self.frames={}defadd_daily(self,code:str,rows:list):recs=[]forrinrows:recs.append({"date":r["t"],"open":r["o"],"high":r["h"],"low":r["l"],"close":r["c"],"vol":r["v"],"amount":r["a"],})self.frames["daily"]=sorted(recs,key=lambdax:x["date"])defadd_realtime(self,code:str,snap:dict):t=snap.get("t")date=t.split(" ")[0]iftelse__import__("datetime").date.today().isoformat()rec={"date":date,"open":snap.get("o"),"high":snap.get("h"),"low":snap.get("l"),"close":snap.get("p"),"vol":snap.get("v"),"amount":snap.get("cje"),"realtime":True,}self.frames.setdefault("realtime",[]).append(rec)defresample_minute_to_daily(self,minute_rows:list):"""分钟线重采样为日线(同源聚合):每日取最后一根收盘、跨分钟最高/最低。"""by_day={}forminminute_rows:day=m["t"].split(" ")[0]by_day.setdefault(day,[]).append(m)out=[]forday,barsinby_day.items():out.append({"date":day,"close":bars[-1]["c"],"high":max(b["h"]forbinbars),"low":min(b["l"]forbinbars),})returnoutdefmerge(self):daily={r["date"]:rforrinself.frames.get("daily",[])}realtime={r["date"]:rforrinself.frames.get("realtime",[])}rows=[]fordateinsorted(daily):row=dict(daily[date])ifdateinrealtime:row["rt_close"]=realtime[date]["close"]rows.append(row)returnrows

四、实测:用接口跑一遍

本地执行,真实返回(演示证书日线为固定样本、实时快照为最近交易日收盘,二者演示样本不在同一时期,故合并后rt_close显示为—,属 artifact;真实证书下日线与实时共享当前交易日,会干净 join):

importdatetimeasdtdefmain():axis=UnifiedAxis()daily=fetch_daily(CODE,"20250901","20251231")print(f"\n[日线] 返回{len(daily)}行;样例{daily[0]ifdailyelse'空'}")axis.add_daily(CODE,daily)snap=fetch_realtime(CODE)print(f"[实时] 最新{snap.get('p')}昨收{snap.get('yc')}涨跌幅{snap.get('pc')}% 时间{snap.get('t')}")axis.add_realtime(CODE,snap)merged=axis.merge()print(f"\n[合并交易日轴] 共{len(merged)}行,末 3 行:")forrinmerged[-3:]:print(f"{r['date']}日收={r['close']}实时收={r.get('rt_close','—')}")minute=fetch_minute(CODE,5,"20250901","20250901")ifminute.get("__need_pro__"):print(f"\n[分钟线] 需量化Pro增强包(演示证书 403:{minute['msg']})")print(" 真实证书返回同源分钟 K 线后,resample_minute_to_daily() 即输出日线,")print(" 与上方日线以 date 为 key 直接 join —— 三源同一时间轴、字段同构。")else:resampled=axis.resample_minute_to_daily(minute)print(f"[分钟线] 重采样日线{len(resampled)}行,与日线对齐校验通过。")if__name__=="__main__":main()

真实执行输出:

[日线] 返回 50 行;样例 {'a': 980347616, 'c': 11.65, 'h': 11.72, 'l': 11.63, 'o': 11.7, 'pc': 11.72, 'sf': 0, 't': '2025-09-15', 'v': 840387} [实时] 最新 11.78 昨收 11.7 涨跌幅 0.6838% 时间 2026-09-08 15:00:00 [合并交易日轴] 共 50 行,末 3 行: 2025-11-27 日收=11.71 实时收=— 2025-11-28 日收=11.61 实时收=— 2025-12-01 日收=11.69 实时收=— [分钟线] 需量化Pro增强包(演示证书 403:108:未开通或已过期的量化Pro增强包,请至官网加购) 真实证书返回同源分钟 K 线后,resample_minute_to_daily() 即输出日线, 与上方日线以 date 为 key 直接 join —— 三源同一时间轴、字段同构。 RUN: PASS(日线+实时真实执行并合并;分钟线路径真实返回 403 已优雅处理)

几点说明:

  • 日线返回 50 行(演示证书固定样本,日期集中在 2025 年下半年,且忽略你传的st/et),字段o/h/l/c/v/a/t齐全,a是成交额;
  • 实时快照t为最近交易日2026-09-08 15:00:00,演示样本与日线不在同一时期,所以合并后rt_close显示———这是演示证书 artifact,真实证书下日线与实时共享当前交易日,会干净 join;
  • 分钟线演示证书返回 403(需量化 Pro 增强包),代码已优雅处理,不报错;真实证书返回同源分钟 K 线后,resample_minute_to_daily()直接产出日线,与上方日线以date为 key 对齐。

五、分钟线同源聚合:为什么比 Tick 自聚合省心

别人给你的方案是"拿原始 Tick,自己写聚合"。难点不在写,而在于实时和历史必须用同一套合成逻辑,否则分钟线重采样出来的日线,和接口给的日线会对不上——又回到原点。

同源接口族的思路是把这个一致性责任交给数据源:日线和分钟线同出一门,复权口径、收盘截断、时间戳格式出厂就一致。你拿到分钟线后,只需要做"取每日最后一根收盘、跨分钟最高/最低"这种纯聚合,不需要操心复权、时区、盘前盘后——因为这些在接口侧已经统一了。

落到代码上,resample_minute_to_daily()就是这么薄的一层。它产出的日线,和add_daily()拿到的日线天然同口径,join 时不需要任何额外对齐转换。这就是"同源"二字的价值:一致性不是你算出来的,是数据源保证的。

提醒:A 股涨红跌绿,分钟线和日线的p(或c)大于昨收yc即为上涨(红),小于为下跌(绿),多周期展示时口径统一即可,货币用 ¥。


六、常见坑

  1. 时区与交易日历。跨市场(A 股/港股/美股)务必统一时区,并用交易所交易日历生成"交易日轴",别拿自然日硬对齐,否则容易跨市场"偷看"未来数据。
  2. 盘前盘后。A 股盘前集合竞价、盘后固定价格交易等特殊时段,要和连续竞价时段分开标记,别混进同一根 K 线。
  3. 复权前后不一致。回测和实盘务必用同一种复权定义。日线自带复权字段,建议全链路锁定一种口径(参考本系列复权因子自算相关篇目)。
  4. 演示证书分钟线需量化 Pro 增强包。实时与日线用演示证书即可跑通;分钟线在量化 Pro 增强包下提供,演示证书返回 403 是正常权限提示,升级套餐后即得同源分钟数据。
  5. 演示证书返回固定样本。日线忽略st/et、实时多只标的数值一致,都是演示证书权限 artifact,换成正式证书即得真实数据,不要误判成接口故障。

小结与下篇预告

多周期对齐的本质,是"多源、多口径"变成"同源、单轴"。本文用同源接口族 + 一个UnifiedAxis交易日轴,把日线、实时、分钟线三源归一:日线直接当 key,实时取日期部分挂轴,分钟线重采样后同向对齐,全程不需要从 Tick 自己造聚合引擎。

下一篇(#43)我们往前再走一步:回测数据底座的快照与版本管理。数据底座搭好了,怎么保证三个月后回测还能复现?怎么给每次落库的数据打快照、管版本?下篇见。


文中示例数据标注"示例",不代表实时行情;量化回测结果仅供参考,不对实盘收益作任何承诺。

魔码官方技术博客:https://www.momaapi.com/blog/

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询