多周期信号融合:用 K-1 后移消灭未来函数,让周线给日线当过滤器
2026/9/24 6:14:32 网站建设 项目流程

【量化工程实战进阶 】多周期信号融合:用 K-1 后移消灭未来函数,让周线给日线当过滤器

上一篇我们把成本算到了基点级,结论是「回测里固定佣金会排错策略名次」。这一篇聊另一个更隐蔽、却更致命的回测杀手:多周期信号融合里的未来函数

先看一个用真实行情(魔码纯 HTTP 接口、演示证书拉取)跑出来的事实:

日线金叉明细 + 周线闸门判定: 日线=2025-10-22 收盘=11.520 MA5=11.462 MA20=11.444 -> 引用已完成周=2025-10-19 周MA20=11.960 价>周MA=False 周MA上行=False => 融合=NO(被过滤) 日线=2025-11-06 收盘=11.510 MA5=11.474 MA20=11.472 -> 引用已完成周=2025-11-02 周MA20=11.941 价>周MA=False 周MA上行=False => 融合=NO(被过滤)

同一段样本里,日线出现了 2 次 MA5/MA20 金叉,但两次都被周线过滤器挡掉了——因为价格(11.52、11.51)还压在周线 MA20(约 11.96)下面,周线本身也没抬头。这不是策略「没信号」,是过滤器在帮你挡噪音。

而如果你用错了引用方式,后果更严重。下面这行是同一个日线金叉点(2025-10-22)下,「偷懒写法」和「正确写法」引用的周线:

[未来函数反例] 日线金叉处:completed(K-1后移) vs naive(最新周线) 日线=2025-10-22 completed周=2025-10-19 naive周=2025-11-30 判定=未来函数! completed周MA20=11.960 naive周MA20=11.793

注意naive周=2025-11-30:在 2025-10-22 这一天,你的代码「看到了」11 月 30 日那一周的周线——那一周在 10 月 22 日根本还没发生。这就是多周期策略里最经典的「开天眼」作弊,回测虚高、实盘归零。

本文把这件事一次说透:什么是多周期融合、为什么它会引入未来函数、怎么用一行K-1 后移彻底消灭它、以及怎么用魔码统一数据源让「日线 + 周线」来自同一条管线(从根上杜绝跨源拼接偏差)。

本文你将得到什么

  1. 一个对「多周期共振」的正经定义:它不是玄学,是多尺度信号滤波;共振失效的本质是信号延迟累积 + 同一数据源重复采样导致的伪阳性。
  2. 一套零 SDK、纯 HTTP的取数方式:日线和周线用同一个端点、只换一个周期参数就拿到了,数据天然同源同口径。
  3. 一个可运行的K-1 后移实现,把「未完成周线」从信号计算里剔除,附真实输出证明它和偷懒写法差出整整 6 周。
  4. 一个「日线触发器 + 周线过滤器」融合框架,给出完整代码和过滤结果。
  5. 四个高频坑:跨源拼接、naive 引用最新周线、小周期过度投票、频率限制误算。

全部零 SDK、纯 HTTP GET,代码先跑通再成文。

一、为什么「单周期信号」是隐形杀手

做择时的人几乎都从单周期起步:日线金叉买、死叉卖,回测一跑夏普诱人,挂上模拟盘就被「虚假信号」淹没。问题不在因子,在周期的颗粒度

  • 小周期(分钟/日线)高噪声、低信息量。日线上一根放量长阳就触发买入,但那可能只是日内情绪,几小时后就阴跌回去。让分钟级形态参与投票,策略稳定性会断崖式下跌。
  • 大周期(周/月线)反应慢。周线 MA20 的平滑特性意味着它对价格变动极不敏感——等周线发出「金叉」,趋势往往已经走完大半,此时的「共振」常常不是入场点,而是趋势末端的确认。
  • 自相关性伪阳性。在日线和周线同时找相似形态,本质是对同一段价格序列做重复采样。震荡市里这会制造极高的伪阳性率——「买入即套」。

所以工程上正确的姿势不是「在不同周期各写一个策略」,而是分工:大周期定方向(趋势过滤器),小周期定时机(触发器)。只有两者同向才出信号。这一篇聚焦的就是这套分工里最容易被写错的那一环——时间对齐

二、多周期融合的本质:信号滤波,不是玄学

把「周线与日线共振」还原成信号处理问题,它等价于:用一个低频滤波器(周线)去给一个高频触发器(日线)做门控。

周线趋势过滤器(低频) ──┐ ├─→ AND ──→ 融合信号(只在两者同向时出) 日线触发器(高频) ──┘

关键点有三个,务必刻进脑子里:

  1. 大周期只做「允许/禁止」的门控,不做精确点位。周线的职责是回答「现在能不能做多」,不是「现在精确买在哪」。
  2. 触发器必须引用「已收盘」的大周期值。这是整篇文章的命门,下一节展开。
  3. 融合是「与」关系,不是「或」。任何一周期说 NO,信号就废。这天然降低了交易次数、优化了盈亏比和最大回撤(胜率不一定升,但不会被冲动交易拖死)。

三、取数据:同一端点切周期,杜绝跨源拼接

多周期策略最隐蔽的坑,不在算法,在数据。很多人的日线来自 Tushare、周线来自 AKShare、分钟线来自爬虫,三个源复权基准不同、缺失补齐规则不同、字段口径不同(比如成交量一个是「手」一个是「股×100」),拼到一起直接污染信号。

魔码的做法简单得多:日线、周线、60 分钟线都是同一个端点,只换一个周期参数。数据源完全一致,复权口径一致,字段名一致,从根上消灭跨源偏差。

importrequests BASE="https://api.momaapi.com"LICENCE="你的魔码证书"# 换成你自己的正式证书CODE="600519"# 日线daily=requests.get(f"{BASE}/hsstock/history/{CODE}/d/n/{LICENCE}?st=20250101&et=20260830",timeout=20).json()# 周线:只把周期参数 d 改成 wweekly=requests.get(f"{BASE}/hsstock/history/{CODE}/w/n/{LICENCE}?st=20250101&et=20260830",timeout=20).json()# 60 分钟线:改成 60min60=requests.get(f"{BASE}/hsstock/history/{CODE}/60/n/{LICENCE}?st=20250101&et=20260830",timeout=20).json()print("日线条数 =",len(daily)," 窗口 =",daily[0]["t"][:10],"->",daily[-1]["t"][:10])print("周线条数 =",len(weekly)," 窗口 =",weekly[0]["t"][:10],"->",weekly[-1]["t"][:10])print("60分条数 =",len(min60)," 窗口 =",min60[0]["t"][:10],"->",min60[-1]["t"][:10])

真实输出(演示证书):

日线条数 = 50 窗口 = 2025-09-15 -> 2025-12-01 周线条数 = 48 窗口 = 2025-01-05 -> 2025-11-30 60分条数 = 50 窗口 = 2025-11-13 14:00:00 -> 2025-12-01 15:00:00

字段坑:历史 K 线的收盘价字段是c、成交量字段是v(单位「手」,1 手 = 100 股)。成交额字段是a,不是实时接口的cje。写历史计算时务必用b["c"]/b["v"]/b["a"],照抄实时字段名会直接KeyError

魔码行情接口的一个约定:周线 bar 的t固定为该周周日(交易所周期结束日标签),即使那天不是交易日。这意味着「周线最后一条的日期在未来」是设计行为,不是 bug——下一节的 K-1 后移正是利用这个约定来判定的。

三个周期同一份数据、同一套字段,后面无论怎么切,都不会出现「日线复权前、周线复权后」之类的错位。

四、K-1 后移:消灭未来函数(本文核心)

现在进入最关键的部分。

问题:当策略跑到历史某一天(比如周二),需要读「周线指标」来决定能不能交易。如果代码直接取「当前最新的周线 bar」,而这一周还没走完,你就等于提前知道了这周周五的收盘——这就是未来函数,回测虚高。

标准解法:K-1 后移。在第 T 日,只允许引用「周日 <= T」的最后一个已完成周线 bar。换句话说,周一到大周五的任何时刻,周线因子最高只用到「上周日收盘」那一根。

实现上就是一行查找:在周线日期列表里,找最后一个周日 <= 当日的位置。

importrequestsimportnumpyasnp BASE="https://api.momaapi.com"LICENCE="你的魔码证书"CODE="600519"defget_bars(period):url=f"{BASE}/hsstock/history/{CODE}/{period}/n/{LICENCE}?st=20250101&et=20260830"returnrequests.get(url,timeout=20).json()defsma(arr,n):out=np.full(len(arr),np.nan)foriinrange(n-1,len(arr)):out[i]=float(np.mean(arr[i-n+1:i+1]))returnout daily=get_bars("d")weekly=get_bars("w")dc=np.array([float(b["c"])forbindaily])dd=[b["t"][:10]forbindaily]wc=np.array([float(b["c"])forbinweekly])wd=[b["t"][:10]forbinweekly]# 周线 t = 该周周日wma20=sma(wc,20)dma5=sma(dc,5)dma20=sma(dc,20)# K-1 后移:找「周日 <= 当日」的最后一个已完成周线位置defcompleted_weekly_idx(daily_date):best=-1fori,tinenumerate(wd):ift<=daily_date:best=ielse:breakreturnbest# 反例:naive 每根日线都引用「最新可用周线」(可能含未完成周)defnaive_weekly_idx(_daily_date):returnlen(weekly)-1print("[未来函数反例] 日线金叉处:completed(K-1后移) vs naive(最新周线)")foriinrange(1,len(dc)):ifany(np.isnan(x)forxin(dma5[i],dma20[i],dma5[i-1],dma20[i-1])):continuecross_up=(dma5[i-1]<=dma20[i-1])and(dma5[i]>dma20[i-1])ifnotcross_up:continuewi=completed_weekly_idx(dd[i])ni=naive_weekly_idx(dd[i])naive_future=wd[ni]>dd[i]print(f" 日线={dd[i]}completed周={wd[wi]}naive周={wd[ni]}"f"判定={'未来函数!'ifnaive_futureelse'一致'}"f"completed周MA20={wma20[wi]:.3f}naive周MA20={wma20[ni]:.3f}")

真实输出(演示证书):

[未来函数反例] 日线金叉处:completed(K-1后移) vs naive(最新周线) 日线=2025-10-22 completed周=2025-10-19 naive周=2025-11-30 判定=未来函数! completed周MA20=11.960 naive周MA20=11.793 日线=2025-11-06 completed周=2025-11-02 naive周=2025-11-30 判定=未来函数! completed周MA20=11.941 naive周MA20=11.793

看出问题了吗?在 2025-10-22 这一天,naive 写法引用的周线t=2025-11-30——那是 6 周之后的周线。如果你在回测里用了这个值,等于在 10 月 22 日就「知道」了 11 月 30 日的周线收盘,策略当然看起来稳如老狗,但实盘里那一天根本不存在这个信息。

一行if t <= daily_date: best = i就把这个坑填死了。这就是 K-1 后移的全部代码量,但它决定了你的回测是「能复现」还是「自欺欺人」。

提示:演示证书的周线窗口是固定样本(2025-01-05 起),换成你的正式证书后返回真实全区间。K-1 后移逻辑与数据内容无关,直接套用即可。

五、融合框架:日线触发器 + 周线过滤器

把上面的构件拼起来,就是一个最小可用的融合信号生成器。

  • 日线触发器:MA5 上穿 MA20(金叉)。
  • 周线过滤器:引用「已完成周线」的 MA20,要求(a)价格在周线 MA20 之上(多头排列)、(b)周线 MA20 上行(趋势向上)。
  • 融合:触发器 AND 过滤器,两者同向才出信号。
importrequestsimportnumpyasnp BASE="https://api.momaapi.com"LICENCE="你的魔码证书"CODE="600519"defget_bars(period):url=f"{BASE}/hsstock/history/{CODE}/{period}/n/{LICENCE}?st=20250101&et=20260830"returnrequests.get(url,timeout=20).json()defsma(arr,n):out=np.full(len(arr),np.nan)foriinrange(n-1,len(arr)):out[i]=float(np.mean(arr[i-n+1:i+1]))returnout daily=get_bars("d")weekly=get_bars("w")dc=np.array([float(b["c"])forbindaily])dd=[b["t"][:10]forbindaily]wc=np.array([float(b["c"])forbinweekly])wd=[b["t"][:10]forbinweekly]wma20=sma(wc,20)dma5=sma(dc,5)dma20=sma(dc,20)defcompleted_weekly_idx(daily_date):best=-1fori,tinenumerate(wd):ift<=daily_date:best=ielse:breakreturnbest fused=[]foriinrange(1,len(dc)):ifany(np.isnan(x)forxin(dma5[i],dma20[i],dma5[i-1],dma20[i-1])):continuecross_up=(dma5[i-1]<=dma20[i-1])and(dma5[i]>dma20[i-1])ifnotcross_up:continuewi=completed_weekly_idx(dd[i])ifwi<1:continuepos_up=dc[i]>wma20[wi]# 价格在周线MA20之上slope_up=wma20[wi]>wma20[wi-1]# 周线MA20上行ifpos_upandslope_up:fused.append((dd[i],float(dma5[i]),float(dma20[i]),wd[wi]))else:print(f" 日线={dd[i]}收盘={dc[i]:.3f}-> 周线闸门: 价>周MA={pos_up}周MA上行={slope_up}=> 融合=NO(被过滤)")print(f"\n融合信号(日线金叉 ∩ 周线向上)={len(fused)}个")forsinfused:print(f" 日期={s[0]}MA5={s[1]:.3f}MA20={s[2]:.3f}引用周线@{s[3]}")

真实输出(演示证书):

日线=2025-10-22 收盘=11.520 MA5=11.462 MA20=11.444 -> 周线闸门: 价>周MA=False 周MA上行=False => 融合=NO(被过滤) 日线=2025-11-06 收盘=11.510 MA5=11.474 MA20=11.472 -> 周线闸门: 价>周MA=False 周MA上行=False => 融合=NO(被过滤) 融合信号(日线金叉 ∩ 周线向上)= 0 个

注意这里的诚实结论:在这段演示样本里,日线出现 2 次金叉,但周线过滤器把 2 次全挡了——因为价格(11.52 / 11.51)还压在周线 MA20(约 11.96)下面,周线本身也没抬头。这正是过滤器的价值:它宁可少给信号,也不让你逆着大趋势抄底。换成正式证书拉一段真实上行窗口,融合信号就会自然出现;框架本身不挑数据。

六、四个高频坑

坑 1:跨源拼接。日线一个源、周线一个源,复权基准和字段口径不一致。解决:同一端点切周期(见第三节)。

坑 2:naive 引用最新周线(未来函数)。每根日线都取weekly[-1],会把未完成周的信息泄漏给过去。解决:K-1 后移,completed_weekly_idx只用「周日 <= 当日」的周线。

坑 3:小周期过度投票。把分钟线形态也拿去「投票」,噪声会淹没信号。解决:小周期只做执行点(触发器),趋势方向交给大周期;大周期不每根小 K 线重算,用状态变量缓存「最近一次已收盘的大周期方向」即可。

坑 4:频率限制误算。魔码套餐带「次/分钟」频率限制,多周期 + 多标的批量拉取时很容易触发限频。解决:

  • 先拉大周期(周/月),缓存到本地;盘中只增量拉小周期。
  • 批量请求加退避重试,读响应里的限频字段。
  • 历史数据落地后复用,不要把回测每次都打向线上接口。
importtime,requestsdefget_with_retry(url,tries=3):forkinrange(tries):r=requests.get(url,timeout=20)ifr.status_code==429:# 触发限频time.sleep(2**k)# 指数退避continuer.raise_for_status()returnr.json()raiseRuntimeError("限频重试耗尽,请降低拉取频率或升级套餐")

小结

多周期信号融合不是「共振即买」的玄学,而是「大周期过滤 + 小周期触发」的信号滤波工程。它最致命的坑是未来函数——错把未完成的周线泄漏给过去,回测虚高、实盘归零。用一行K-1 后移(只引用「周日 <= 当日」的已完成周线)就能彻底填坑;而用魔码同一端点切周期取数,又从根上消灭了跨源拼接的口径偏差。

下一篇我们接着聊另一个和时间轴有关的坑:交易日历与停牌对齐——多标的回测里,一只股票停牌会让concat直接错位、净值曲线凭空断崖,这篇的 K-1 后移思想在日历对齐上还有更深的应用。

免责声明

本文所有代码与运行结果均来自魔码行情接口实测,仅用于量化工程方法演示,不构成任何投资建议。市场有风险,投资需谨慎。历史回测不代表未来收益,实盘前请务必用正式证书在真实数据上充分验证。

—— 魔码云服

想用真实全量数据跑通上面的融合框架?领取魔码量化 Pro 包证书(含日/周/分钟多周期历史与实时行情),前往www.momaapi.com/certificate-apply.html,免费版也可直接体验。

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

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

立即咨询