MQL5自优化智能交易系统(第九部分):双移动平均线交叉(基础篇)
◍ 用高低周期双交叉压住信号滞后
传统均线交叉大多在同一个周期上跑两条不同周期均线,毛病很直白:行情已经发动了,交叉信号才慢吞吞冒出来,入场要么滞后要么直接踏空。这篇换了个老交易员熟路——大周期定方向,小周期找顺势入口。 具体做法是把策略用两次:先在日线(D1)看均线交叉确立当日偏向,再切到15分钟(M15)等一个同方向的交叉才动手。思路上回归了“顺着大周期方向做”的口头禅,但用算法把两层过滤固化下来,期望靠小周期入场抵消大周期信号本身的滞后。 入场有两种理念可切:趋势跟踪模式下,大周期看涨就去小周期等看涨交叉;均值回归模式下,大周期看涨反而等小周期看跌交叉反向做。出场也更麻烦,可选在小周期偏向破裂时平,或者死扛到日线偏向翻转才平。组合太多,只能丢给遗传优化器在真实数据里筛。 代码里先把时间框架锁死成常量:D1 为高周期、M15 为低周期、H4 算止损,后续才可能放开给优化器调。 [CODE] 段里第一行就定义了高周期常量 TF_1 = PERIOD_D1,这是整套双交叉的锚点。 回测用 EURUSD、1分钟帧跑2020年1月至今共五年,一半数据做前瞻。结果挺打脸:回测里均值回归模式大多盈利,前瞻里却换成趋势跟踪扛盈;更糟的是回测好的一批,前瞻里几乎全翻车,只有极少数参数组合两段都赚钱。外汇与贵金属属高风险品种,这种不稳定现象说明实盘前必须自测验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Double Crossover.mq5 | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd." 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=class="str">"cmt">//--- System time frames class="macro">#define TF_1 PERIOD_D1
先把多周期参数和开关定义清楚
在 MT5 写 EA 时,先把周期与手数倍数固化成宏,能减少后续逻辑里的魔法数字。下面这段把 M15 与 H4 定义成 TF_2、TF_3,LOT_MULTILPLE 设为 1,意味着每笔下单暂不放大仓位,外汇与贵金属波动剧烈,杠杆风险高,实盘前建议在策略测试器里先按 1 倍验证。 策略本身留了两条路:趋势跟随(TREND=0)与均值回归(MEAN_REVERTING=1),用枚举 STRATEGY_MODES 切换;平仓参考周期也分更高周期(HIGHER_TIME_CLOSE=0)与更低周期(LOWER_TIME_CLOSE=1),由 CLOSING_TIME_FRAME 控制。这两个 input 枚举就是 EA 的『模式拨杆』,改一个值就能让同一套均线逻辑跑出完全不同的风格。 指标端用了两组均线:高周期 ma_1_period=10、ma_1_gap=20,低周期 ma_2_period=10、ma_2_gap=20。gap 参数代表取价偏移根数,若你发现信号总是慢半拍,优先调的是这个 20 而不是周期本身。全局区声明了四个均线句柄与数组、最小成交量 volume_min、买卖价 bid/ask 及 state 状态机变量,并挂了 CTrade 交易库——这些都是后续 OnTick 里直接调用的底层素材。
class="macro">#define TF_2 PERIOD_M15 class="macro">#define TF_3 PERIOD_H4 class="macro">#define LOT_MULTILPLE class="num">1 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom enumerations | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- What trading style should we follow when opening our positions, trend following or mean reverting? enum STRATEGY_MODES { TREND = class="num">0, class=class="str">"cmt">//Trend Following Mode MEAN_REVERTING = class="num">1 class=class="str">"cmt">//Mean Reverting Mode }; class=class="str">"cmt">//--- Which time frame should we consult, when determining if we should close our position? enum CLOSING_TIME_FRAME { HIGHER_TIME_CLOSE = class="num">0, class=class="str">"cmt">//Close on higher time frames LOWER_TIME_CLOSE = class="num">1 class=class="str">"cmt">//Close on lower time frames }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Inputs | class=class="str">"cmt">//+------------------------------------------------------------------+ input group "Technical Indicators" input class="type">int ma_1_period = class="num">10; class=class="str">"cmt">//Higher Time Frame Period input class="type">int ma_1_gap = class="num">20; class=class="str">"cmt">//Higher Time Frame Period Gap input class="type">int ma_2_period = class="num">10; class=class="str">"cmt">//Lower Time Frame Period input class="type">int ma_2_gap = class="num">20; class=class="str">"cmt">//Lower Time Frame Period Gap input group "Strategy Settings" input STRATEGY_MODES strategy_mode = class="num">0; class=class="str">"cmt">//Strategy Operation Mode input CLOSING_TIME_FRAME closing_tf = class="num">0; class=class="str">"cmt">//Strategy Closing Timeframe class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int ma_c_1_handle,ma_c_2_handle,ma_c_3_handle,ma_c_4_handle; class="type">class="kw">double ma_c_1[],ma_c_2[],ma_c_3[],ma_c_4[]; class="type">class="kw">double volume_min; class="type">class="kw">double bid,ask; class="type">int state; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Libraries | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Trade\Trade.mqh> CTrade Trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() {
「双时间框架均线与信号触发骨架」
这段初始化与回调代码搭起了一个双时间框架(TF_1 / TF_2)均线系统的底层骨架。先在 OnInit 里抓取品种最小交易量 volume_min,再分别用 iMA 在 TF_1 建两条周期差为 ma_1_gap 的收盘价 SMA,在 TF_2 建两条周期差为 ma_2_gap 的 SMA,state 初值设为 -1 表示无持仓无方向。 OnDeinit 里用 IndicatorRelease 逐个释放四个均线句柄,避免 EA 卸载后指标句柄泄漏。OnTick 只做一件事:取 TF_2 的 0 号 K 线时间,若与静态变量 time_stamp 不同才调用 update(),相当于用高周期新柱来节流,避免每 tick 重算。 find_setup 的入口判断 PositionsTotal()==0,有仓就跳过,无仓才把 state 复位为 -1 并寻找信号。高周期多头条件是 ma_c_1[0] > ma_c_2[0](快线在慢线上方);若 strategy_mode==0 且低周期也满足 ma_c_3[0] > ma_c_4[0],则 Trade.Buy 以 volume_min 手数市价买入,止损放在 bid - padding,state 置 1。外汇与贵金属杠杆高,这类顺势单触发后回撤打掉止损的概率不低,实盘前务必在 MT5 策略测试器用真实点差回测。 别把句柄当自动回收 MQL5 的 iMA 返回的是句柄不是数值,OnInit 里建了四个就必须配对 IndicatorRelease,否则 EA 反复加载会堆积无效句柄。手动加载卸载 EA 两次以上,若没释放就会在终端日志看到 handle leak 警告。
volume_min = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); ma_c_2_handle = iMA(Symbol(),TF_1,ma_1_period,class="num">0,MODE_SMA,PRICE_CLOSE); ma_c_1_handle = iMA(Symbol(),TF_1,(ma_1_period + ma_1_gap),class="num">0,MODE_SMA,PRICE_CLOSE); ma_c_4_handle = iMA(Symbol(),TF_2,ma_2_period,class="num">0,MODE_SMA,PRICE_CLOSE); ma_c_3_handle = iMA(Symbol(),TF_2,(ma_2_period + ma_2_gap),class="num">0,MODE_SMA,PRICE_CLOSE); state = -class="num">1; class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- IndicatorRelease(ma_c_1_handle); IndicatorRelease(ma_c_2_handle); IndicatorRelease(ma_c_3_handle); IndicatorRelease(ma_c_4_handle); } class="type">void OnTick() { class=class="str">"cmt">//--- class="type">class="kw">datetime current_time = iTime(Symbol(),TF_2,class="num">0); class="kw">static class="type">class="kw">datetime time_stamp; if(time_stamp != current_time) { time_stamp = current_time; update(); } } class="type">void find_setup(class="type">class="kw">double padding) { if(PositionsTotal() == class="num">0) { state = -class="num">1; if(ma_c_1[class="num">0] > ma_c_2[class="num">0]) { if((ma_c_3[class="num">0] > ma_c_4[class="num">0]) && (strategy_mode == class="num">0)) { Trade.Buy(volume_min,Symbol(),ask,(bid - padding),class="num">0,""); state = class="num">1; } } } }
◍ 双时间框架下的开平仓与指标刷新
高周期方向由 ma_c_1 与 ma_c_2 两根均线决定:ma_c_1[0] 在 ma_c_2[0] 之上偏多,之下偏空。低周期 ma_c_3 与 ma_c_4 则用于触发动作,strategy_mode 切换趋势跟随或均值回归两种逻辑。 趋势跟随模式下(strategy_mode==0),高周期多头且低周期 ma_c_3[0]>ma_c_4[0] 才做多;高周期空头且 ma_c_3[0]<ma_c_4[0] 才做空。均值回归模式(strategy_mode==1)则反着来:高周期多头却出现低周期死叉时买,高周期空头却金叉时卖,属于抓回摆的思路。外汇与贵金属杠杆高,这类逆趋势单亏损概率不低,实盘前先用策略测试器跑一遍。 平仓由 manage_setup 接管。closing_tf==0 表示看高周期:持多单后若 ma_c_1[0]<ma_c_2[0] 就平,持空单后若 ma_c_1[0]>ma_c_2[0] 就平;closing_tf==1 则盯低周期 ma_c_3 与 ma_c_4 的交叉来收尾。 update 函数里只用 CopyBuffer 把两个均线句柄的最新值刷进数组,每次 tick 或定时器触发时重读,保证上面的判断用的是实时数据。开 MT5 把这段接进 EA 的 OnTick,就能看信号是否如预期触发。
if((ma_c_3[class="num">0] < ma_c_4[class="num">0]) && (strategy_mode == class="num">1)) { Trade.Buy(volume_min,Symbol(),ask,(bid - padding),class="num">0,""); state = class="num">1; } } class=class="str">"cmt">//--- Bearish on the higher time frame if(ma_c_1[class="num">0] < ma_c_2[class="num">0]) { class=class="str">"cmt">//--- Trend following mode if((ma_c_3[class="num">0] < ma_c_4[class="num">0]) && (strategy_mode == class="num">0)) { Trade.Sell(volume_min,Symbol(),bid,(ask + padding),class="num">0,""); state = class="num">0; } class=class="str">"cmt">//--- Mean reverting mode if((ma_c_3[class="num">0] > ma_c_4[class="num">0]) && (strategy_mode == class="num">1)) { Trade.Sell(volume_min,Symbol(),bid,(ask + padding),class="num">0,""); state = class="num">0; } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Manage our open positions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void manage_setup(class="type">void) { if(closing_tf == class="num">0) { if((state ==class="num">0) && (ma_c_1[class="num">0] > ma_c_2[class="num">0])) Trade.PositionClose(Symbol()); if((state ==class="num">1) && (ma_c_1[class="num">0] < ma_c_2[class="num">0])) Trade.PositionClose(Symbol()); } else if(closing_tf == class="num">1) { if((state ==class="num">0) && (ma_c_3[class="num">0] > ma_c_4[class="num">0])) Trade.PositionClose(Symbol()); if((state ==class="num">1) && (ma_c_3[class="num">0] < ma_c_4[class="num">0])) Trade.PositionClose(Symbol()); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update our technical indicators and positions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void update(class="type">void) { class=class="str">"cmt">//Update technical indicators and market readings CopyBuffer(ma_c_2_handle,class="num">0,class="num">0,class="num">1,ma_c_2); CopyBuffer(ma_c_1_handle,class="num">0,class="num">0,class="num">1,ma_c_1);