重构经典策略(第十三部分):最小化均线交叉的滞后性(基础篇)
「用同周期双均线掐掉交叉滞后」
传统均线交叉要靠长短周期差制造信号,但周期差本身就是滞后的根。我们之前对超过200个交易品种做过测试,机器更擅长预判均线未来值而非价格点位,顺着这个思路可以把滞后再压一层。 做法很直接:两条均线用相同周期,一条算在开盘价上,一条算在收盘价上。开盘价均线压在收盘价均线之上,说明该周期收盘价低于开盘价,价格倾向走弱;反过来收盘价均线在上,价格倾向走强。这样不需要长短周期错位也能出交叉。 这套框架不限于均线。RSI 同样可以分别挂在开盘、收盘、最高、最低价上独立计算;下跌趋势里跟踪开盘价的 RSI 往往会跑到跟踪收盘价的 RSI 上方,和双均线逻辑同构,可平移到别的指标做早信号。 外汇与贵金属波动大、杠杆高,这类同周期结构只降低信号延迟,不消除假交叉风险,上 MT5 用复盘比对一下普通交叉与本法的触发时点差异再决定参数。
◍ EURUSD四年回测的基准设定
先把传统双均线交叉当成基准策略,后面再拿重构后的思路跟它比优劣。今天所有讨论都用 EURUSD 做样本——它是全球成交最活跃的货币对,波动比大多数货币对都猛,恰恰不是简单交叉策略的舒服区。 我们锁定日线图,回测窗口为 2020-01-01 至 2024-12-24,约 4 年历史数据,在 MT5 月线图上能直接看到这段高亮区间。 原始策略就是两条跟踪收盘价的均线交叉:短周期线在长周期线上方,读作上升趋势可能延续;反之视为看跌。图里黄线快线、白线慢线,能看清基本逻辑。 但传统交叉有个硬伤——图里竖线左侧约两个月横盘,信号反复被打脸、大概率亏钱;直到右侧突破走出真趋势才扬眉吐气。它只在趋势市里好使,而后面要讲的策略正是冲着这种迟滞期来的。
先把均线交叉的骨架钉死
做一套可回测的均线交叉 EA,第一步不是调参数,而是把不该变的东西锁住。系统常量负责隔离逻辑改动带来的干扰:两次测试里止损倍数、交易量必须恒定,否则你分不清收益来自策略还是来自设置漂移。 全局变量盯三件事:指标句柄与缓冲、实时买卖价、K线 OHLC。事件驱动是 MT5 的底层机制——用户操作和新报价都会触发事件,每个事件配一个处理程序。OnInit 调 setup 加载指标,OnDeinit 调 release 释放资源,OnTick 调 update 刷最新价后再决定开仓或管仓。 入场规则极简:快线在慢线上方做多,反之下方做空。止损用 ATR 动态拉伸,入场价±ATR_MULTIPLE 倍 ATR 设止损止盈,并引入过去 90 天平均 ATR 衡量近期波动。三元运算符只在「新持仓比旧持仓更有利」时才更新止盈止损,逻辑紧凑且可独立调 TP/SL。 回测设置上,策略无 AI 无曲线拟合、无待调输入,Forward 设 No。用 Real ticks + Random delay 跑最严苛环境,EURUSD 从起测到 2022 年 7 月近 2 年都在盈亏平衡线挣扎,总亏损交易率约 61%,夏普比接近 0——原始形态不可直接托付资金,但稍调逻辑就可能显著改观。外汇与贵金属属高风险,回测不代表实盘概率。 下面这段是基准版常量与全局变量的骨架代码,开 MT5 新建 EA 把这部分贴进去就能跑通指标加载前的准备。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Channel And MA.mq5 | class=class="str">"cmt">//| Gamuchirai Zororo Ndawana | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Gamuchirai Zororo Ndawana" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| System constants | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define TF_1 PERIOD_D1 class=class="str">"cmt">//--- Our main time frame class="macro">#define TF_2 PERIOD_H1 class=class="str">"cmt">//--- Our lower time frame class="macro">#define ATR_PERIOD class="num">14 class=class="str">"cmt">//--- The period for our ATR class="macro">#define ATR_MULTIPLE class="num">3 class=class="str">"cmt">//--- How wide should our stops be? class="macro">#define VOL class="num">0.01 class=class="str">"cmt">//--- Trading volume class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int trade; class="type">int ma_f_handler,ma_s_handler,atr_handler; class="type">class="kw">double ma_f[],ma_s[],atr[]; class="type">class="kw">double bid,ask; class="type">class="kw">double o,h,l,c; class="type">class="kw">double original_sl; class=class="str">"cmt">//+------------------------------------------------------------------+
「把指标句柄挂进生命周期」
EA 跑起来最先碰到的不是行情,而是句柄。把 iATR 与两条 iMA 的创建塞进 setup(),在 OnInit 里调用,MT5 会在加载时就把指标资源绑好,不至于每 tick 重复申请。 setup() 里固定了 10 周期与 60 周期两条 EMA(PRICE_CLOSE),快线慢线跨度 6 倍,在 EURUSD 的 M5 上这种组合对假突破的过滤概率倾向优于单均线。外汇与贵金属杠杆高,句柄泄漏会拖慢终端,必须重视释放。 OnDeinit 调用 release(),用 IndicatorRelease() 逐个交还 atr_handler、ma_f_handler、ma_s_handler。漏掉这一步,策略切换时 MT5 可能堆积几十个隐形指标,肉眼难查。 OnTick 目前只调 update(),真正的信号逻辑留在后续补。你开 MT5 新建 EA,把下面代码贴进文件,编译后挂图表,能在 Experts 日志看到初始化通过即说明句柄链通了。
class="macro">#include <Trade\Trade.mqh> CTrade Trade; class="type">int OnInit() { class=class="str">"cmt">//--- Setup setup(); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Release our indicators release(); } class="type">void OnTick() { class=class="str">"cmt">//--- Update system variables update(); } class="type">void setup(class="type">void) { atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD); ma_f_handler = iMA(Symbol(),TF_1,class="num">10,class="num">0,MODE_EMA,PRICE_CLOSE); ma_s_handler = iMA(Symbol(),TF_1,class="num">60,class="num">0,MODE_EMA,PRICE_CLOSE); } class="type">void release(class="type">void) { IndicatorRelease(atr_handler); IndicatorRelease(ma_f_handler); IndicatorRelease(ma_s_handler); } class="type">void update(class="type">void) {