市场模拟跨期订单:用历史数据打通期货合约连续图表(基础篇)
(1/2)·每月重置的期货序列让20周期均线刚画完就作废,长期策略如何不断线
◍ 用跨期订单给行情做压力测试
MT5 的策略测试器不止能跑 EA,还能用脚本手动挂跨期订单来模拟真实盘口的滑点与流动性断层。很多人在回测里看不出问题,一上实盘就被点差吃掉利润,根源就在于没在测试环境里复现跨合约切换的延迟。 具体做法是在测试器里同时下不同到期月的同品种订单,观察它们在同一根蜡烛上的成交价偏差。2025 年 11 月 3 日有交易者实测,在黄金跨期订单模拟中,近月与远月合约在波动率突增时价差一度拉到 0.8 美元/盎司,这种断层在普通单合约回测里完全看不见。 外汇和贵金属杠杆高、跳空频繁,跨期模拟能提前暴露风控漏洞。开 MT5 测试器选「手动」模式,自己挂两笔不同月份的单子,比看十篇理论文都管用。
「期货合约连续性问题卡在哪」
Chart Trade 把用户点买/卖/平的指令发到图表,EA 负责拦截解码执行,这套机制本身稳。但做期货资产时会撞上一个绕不开的不便:合约有到期日,且常同时存在完整合约与迷你合约。 以 B3 美元期货为例,它每月到期,每月约 20 个交易日(4 周×每周 5 日)。当你挂一条 20 周期均线,刚算满画好,合约就到期重置成新序列,指标从头再来;更长周期指标受影响更夸张。跨系列做长线的人,移动平均和各类指标每次都要重算,这是实打实的大问题。 程序员要管的不是交易者看不看得见,而是两件事:用历史数据接续绘图,以及把图表里的交易请求正确路由到服务器。迷你合约策略(如对冲)需要小交易量,但图表若显示完整合约,就得在后台把指令映射到迷你合约执行。 回放系统里这摊事更麻烦,但能拆开打:先死磕期货指标连续化。美元期货是最极端的样本,拿它打通了,同结构的其他合约照方抓药即可。打开 MT5 搜 B3 USD 连续合约,切 20 周期 MA 看每月重置断点,就能复现这个坑。
把历史图表路由到活合约
在 B3 这类交易所,期货合约有六种命名变体,但实质只归为三种主类型、各带完整与迷你两个子变体。图表上跑的历史数据不能直接下单,必须有一层把历史合约名映射到交易者当前想做的活合约。原方案只能同类型映射,比如完整合约历史只能发到完整活合约,迷你历史只能发到迷你活合约。 核心技巧在第 44 行枚举:迷你合约赋偶数值(WDO=2),完整合约赋奇数值(DOL=3)。最低有效位决定奇偶,用按位与就能隔离该位判断。修改后函数多一个参数,让交易者选要完整还是迷你;第 56 行用 EnumToString 把枚举转字符串后,通过两层三元运算符把 eTS 加减 1,实现 WDO↔DOL 的互映射。 举例:图表是 WDO 迷你美元历史,交易者要完整合约,eTS 从 2 变 3,EnumToString 出 DOL,订单就发到完整美元。反过来图表是 DOL 历史要迷你,eTS 从 3 变 2 得 WDO。若某合约无迷你版,枚举里不造奇偶副本(编译器会拒),sz0 保持原值即可,代码里多一道判断就行。 外汇与贵金属期货杠杆高、滑点大,这种映射错误可能直接发错合约类型,实盘前务必在 MT5 策略测试器用历史符号跑一遍确认。
class="num">38. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">39. class="type">void CurrentSymbol(class="type">void) class="num">40. { class="num">41. class="type">MqlDateTime mdt1; class="num">42. class="type">class="kw">string sz0, sz1; class="num">43. class="type">class="kw">datetime dt = macroGetDate(TimeCurrent(mdt1)); class="num">44. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; class="num">45. class="num">46. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, class="num">0, class="num">3); class="num">47. for (eTypeSymbol c0 = class="num">0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); class="num">48. class="kw">switch (eTS) class="num">49. { class="num">50. case DOL : class="num">51. case WDO : sz1 = "FGHJKMNQUVXZ"; class="kw">break; class="num">52. case IND :
◍ 用到期月份代码反推连续合约标的
这段逻辑解决一个实操痛点:拿到当前图表品种后,如何自动拼出下一交割月的真实合约名。DOL/WDO 用月份代码串 "FGHJKMNQUVXZ",IND/WIN 用 "GJMQVZ",年份取自系统时间减 2000,例如 2024 年对应 i1=24。 循环里 i0 在代码串长度内递增,越界归零的同时 i1 加 1,相当于跨年翻月。每一轮用 StringFormat("%s%s%d", sz0, StringSubstr(sz1,i0,1), i1) 拼出候选符号,再取 SYMBOL_EXPIRATION_TIME 与当前时间比大小,第一次到期日大于当前时间就 break——这就是尚未到期的最近合约。 外汇与贵金属品种走 default 分支直接 return,不会误判。把这段塞进 EA 的合约切换模块,MT5 里切到 WIN 图表跑一下,大概率能看到它自动锁定下一张 GJMQVZ 组合出的合约。
class="type">void CurrentSymbol(class="type">bool bUsingFull) { class="type">MqlDateTime mdt1; class="type">class="kw">string sz0, sz1; class="type">class="kw">datetime dt = macroGetDate(TimeCurrent(mdt1)); enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, class="num">0, class="num">3); for (eTypeSymbol c0 = class="num">0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); class="kw">switch (eTS) { case DOL : case WDO : sz1 = "FGHJKMNQUVXZ"; class="kw">break; case IND : case WIN : sz1 = "GJMQVZ"; class="kw">break; class="kw">default : class="kw">return; } sz0 = EnumToString((eTypeSymbol)(((eTS & class="num">1) == class="num">1) ? (bUsingFull ? eTS : eTS - class="num">1) : (bUsingFull ? eTS + class="num">1: eTS))); for (class="type">int i0 = class="num">0, i1 = mdt1.year - class="num">2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : class="num">0), i1 += (i0 == class="num">0 ? class="num">1 : class="num">0))