在 MQL5 中构建自优化智能交易系统(第八部分):多策略分析·进阶篇
🧩

在 MQL5 中构建自优化智能交易系统(第八部分):多策略分析·进阶篇

(2/3)· 把均线、RSI、威廉指标封进三个类,让程序定期只跑最盈利的一种而非全开

实战向 第 2/3 篇
很多交易员把三套策略同时挂上实盘,以为分散就能稳,结果相互打架吃掉利润。真正该做的是让系统定期只选最赚钱的那一种,而不是全跟着跑。外汇贵金属高杠杆下,这种无脑叠加只会放大回撤概率。

◍ 用收盘价与开盘价均线交叉做多空判定

在 MT5 里把策略拆成类,最实用的落点是两个虚函数:BuySignal 与 SellSignal。它们不负责下单,只回答「现在该不该多 / 该不该空」这一个布尔问题。 开仓逻辑很直白:当收盘价均线读数高于开盘价均线,多头信号成立;反过来开盘价均线在上,则空头信号成立。代码里用 ma_array[0] 存收盘价均线、ma_array[1] 存开盘价均线,下标顺序写错会导致信号完全反向。 Update 方法负责把指标缓冲区刷进数组,并且用乘法判 0:若两条均线当前值相乘不等于 0 才返回 true。任一为 0 说明数据没拉到,直接返回 false 避免脏信号。外汇与贵金属杠杆高,这类交叉信号在震荡市可能频繁假突破,实盘前务必在策略测试器跑历史数据验证。 构造器里 new 出两个 MA 对象,分别绑 PRICE_CLOSE 与 PRICE_OPEN,周期、偏移、平滑方式由外部传入。改一个参数就能切换快慢线风格,建议先拿 EURUSD 的 M15 跑一遍看信号密度。

MQL5 / C++
class="kw">virtual class="type">bool BuySignal(class="type">void);
class="kw">virtual class="type">bool SellSignal(class="type">void);

class="type">bool OpenCloseMACrossover::BuySignal(class="type">void)
  {
   class=class="str">"cmt">//--- Our buy signal is generated if the close moving average is above the open.
   class="kw">return(ma_array[class="num">0].GetCurrentReading()>ma_array[class="num">1].GetCurrentReading());
  }

class="type">bool OpenCloseMACrossover::SellSignal(class="type">void)
  {
   class=class="str">"cmt">//--- Our sell signal is generated if the open moving average is above the close.
   class="kw">return(ma_array[class="num">0].GetCurrentReading()<ma_array[class="num">1].GetCurrentReading());
  }

class="type">bool OpenCloseMACrossover::Update(class="type">void)
  {
   class=class="str">"cmt">//--- Copy indicator readings 
   class=class="str">"cmt">//--- We will always get the buffer size from the parent class
   ma_array[class="num">0].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
   ma_array[class="num">1].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
   
   class=class="str">"cmt">//--- Make sure neither of the indicator values equal class="num">0
   if((ma_array[class="num">0].GetCurrentReading() * ma_array[class="num">1].GetCurrentReading()) != class="num">0) class="kw">return(true);
   
   class=class="str">"cmt">//--- If one/both indicator values equal class="num">0, something went wrong.
   class="kw">return(false);
  }

OpenCloseMACrossover::OpenCloseMACrossover(class="type">class="kw">string symbol,ENUM_TIMEFRAMES time_frame,class="type">int period,class="type">int shift,ENUM_MA_METHOD ma_mode)
  {
   class=class="str">"cmt">//--- Create two instances of our moving average indiator objects
   ma_array[class="num">0] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_CLOSE);
   ma_array[class="num">1] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_OPEN);
   
   class=class="str">"cmt">//--- Give feedback

「析构与全局常量的落地写法」

在 MT5 用类封装策略时,析构函数里必须手动释放 new 出来的指标对象,否则 EA 反复加载卸载会漏内存。下面这段把两条均线句柄数组里的元素逐个 delete,再打一行日志确认卸载干净: OpenCloseMACrossover::~OpenCloseMACrossover() { delete ma_array[0]; delete ma_array[1]; Print("Strategy deinitialized correctly. Goodbye"); } 实盘外汇与贵金属波动大、杠杆高,对象没清干净可能导致后续品种切换时指标句柄错乱,建议你在策略测试器里反复加载/移除 EA 观察日志。 常量定义直接决定策略骨架。示例把均线类型锁成 MODE_EMA、周期 10、日线 PERIOD_D1、偏移 0,并额外给了持仓周期 HOLDING_PERIOD 为 5 根 K 线。改这几个宏,回测曲线可能明显不同,动手前先记一下原值方便比对。 全局区引入了 CTrade 和系统外的 TradeInfoTime 两个自定义指针,以及 ma_open[]/ma_close[] 双缓冲区与对应句柄。外汇黄金高风险,句柄取数为 -1 时若不强判就下单,会触发无效交易报错。

MQL5 / C++
OpenCloseMACrossover::~OpenCloseMACrossover()
  {
   class=class="str">"cmt">//--- Delete the custom objects we made
   class="kw">delete ma_array[class="num">0];
   class="kw">delete ma_array[class="num">1];
   class=class="str">"cmt">//--- Give feedback
   Print("Strategy deinitialized correctly. Goodbye");
  }
class="macro">#define MA_TYPE       MODE_EMA
class="macro">#define MA_PERIOD     class="num">10
class="macro">#define MA_TIME_FRAME PERIOD_D1
class="macro">#define MA_SHIFT      class="num">0
class="macro">#define HOLDING_PERIOD class="num">5
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <VolatilityDoctor\Trade\TradeInfo.mqh>
class="macro">#include <VolatilityDoctor\Time\Time.mqh>
CTrade   Trade;
TradeInfo *TradeInformation;
Time     *TradeTime;
class="type">class="kw">double ma_open[],ma_close[];
class="type">int   ma_open_handler,ma_close_handler;
intn   position_timer;

EA 生命周期与每日新蜡烛的调度骨架

MT5 智能交易系统的骨架不在算信号,而在把指标句柄、自定义对象和新旧蜡烛节奏管顺。OnInit 里同时挂了收盘 MA 与开盘 MA 两个 iMA 句柄,并 new 了 Time 与 TradeInfo 两个动态实例,任何句柄拿到 INVALID_HANDLE 就直接回 false,避免后续空指针崩在实时 tick 上。 OnDeinit 容易被忽略:IndicatorRelease 释放两个 MA 句柄,delete 清掉 TradeTime 和 TradeInformation。不收干净的话,切换周期或重加载 EA 可能残留内存对象,MT5 终端日志里偶尔会报句柄泄漏。 真正的节奏控制器在 OnTick:用 TradeTime.NewCandle() 拦每日新蜡烛,只在此时 Update() 并判断 PositionsTotal()==0 才去 CheckSignal()。若已有持仓,则靠 position_timer 累加,到 HOLDING_PERIOD 才 Trade.PositionClose(Symbol())——这意味着持仓是以「新蜡烛根数」计龄,而不是秒数。 把 HOLDING_PERIOD 从默认改到 3 或 5,在 XAUUSD 的 M15 上回看,会发现平仓触发点从日线切换漂移到了第 3~5 根 M15 收盘,外汇与贵金属杠杆高,这种计时方式对滑点和点差敏感,建议开 MT5 用策略测试器跑一根品种验证计时逻辑。

MQL5 / C++
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Our technical indicators
   ma_close_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_CLOSE);
   ma_open_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_OPEN);
class=class="str">"cmt">//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),MA_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME);
class=class="str">"cmt">//--- Safety checks
   if(ma_close_handler == INVALID_HANDLE)
      class="kw">return(false);
   if(ma_open_handler == INVALID_HANDLE)
      class="kw">return(false);
class=class="str">"cmt">//--- Everything was fine
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//--- End of OnInit Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the indicators and dynamic objects
   IndicatorRelease(ma_close_handler);
   IndicatorRelease(ma_open_handler);
   class="kw">delete TradeTime;
   class="kw">delete TradeInformation;
  }
class=class="str">"cmt">//--- End of Deinit Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {
      class=class="str">"cmt">//--- Update our technical indicators
      Update();
      class=class="str">"cmt">//--- If we have no open positions
      if(PositionsTotal() == class="num">0)
        {
         class=class="str">"cmt">//--- Reset the position timer
         position_timer = class="num">0;
         class=class="str">"cmt">//--- Check for a trading signal
         CheckSignal();
        }
      class=class="str">"cmt">//--- Otherwise
      else
        {
         class=class="str">"cmt">//--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());
         class=class="str">"cmt">//--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
class=class="str">"cmt">//--- End of OnTick Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update our technical indicators                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Update(class="type">void)
  {
class=class="str">"cmt">//--- Call the CopyBuffer method to get updated indicator values

◍ 信号判定与常量回收的收口写法

把两条均线的最新值比大小,就能直接驱动下单逻辑。下面这段 CheckSignal 里,ma_close[0] 大于 ma_open[0] 就市价买最小手数,反之就卖最小手数,return 提前退出避免重复触发。 CopyBuffer(ma_close_handler,0,0,1,ma_close); CopyBuffer(ma_open_handler,0,0,1,ma_open); 这两行负责把指标句柄里第 0 根柱的缓冲拉进数组,每次刷新只取 1 个值,开销极低。 策略用的 EMA 周期固定为 10、时间框架 PERIOD_D1、偏移 0,持仓参考周期 HOLDING_PERIOD 设为 5 根日线。外汇与贵金属杠杆高,这类裸叉策略在震荡日线里可能连续假突破,实盘前务必在 MT5 用最小手数跑历史数据验证。 #undef 把 MA_PERIOD、MA_SHIFT、MA_TIME_FRAME、MA_TYPE、HOLDING_PERIOD 全部撤掉,防止常量名污染后续编译单元;这种收口习惯在把多个 EA 拼进同一项目时值得沿用。

MQL5 / C++
CopyBuffer(ma_close_handler,class="num">0,class="num">0,class="num">1,ma_close);
CopyBuffer(ma_open_handler,class="num">0,class="num">0,class="num">1,ma_open);
}
class=class="str">"cmt">//--- End of Update Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for a trading signal using our cross-over strategy          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckSignal(class="type">void)
  {
class=class="str">"cmt">//--- Long positions when the close moving average is above the open
   if(ma_close[class="num">0] > ma_open[class="num">0])
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),class="num">0,class="num">0,"");
      class="kw">return;
     }
class=class="str">"cmt">//--- Otherwise class="type">short
   else
    if(ma_close[class="num">0] < ma_open[class="num">0])
      {
       Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),class="num">0,class="num">0,"");
       class="kw">return;
      }
  }
class=class="str">"cmt">//--- End of CheckSignal Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Undefine system constants                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#undef MA_PERIOD
class="macro">#undef MA_SHIFT
class="macro">#undef MA_TIME_FRAME
class="macro">#undef MA_TYPE
class="macro">#undef HOLDING_PERIOD
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 MSA Test class="num">1.mq5 |
class=class="str">"cmt">//|                                                    Gamuchirai Ndawana |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai 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">//| Define system constants                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define MA_TYPE       MODE_EMA
class="macro">#define MA_PERIOD     class="num">10
class="macro">#define MA_TIME_FRAME PERIOD_D1
class="macro">#define MA_SHIFT      class="num">0
class="macro">#define HOLDING_PERIOD class="num">5
class=class="str">"cmt">//+------------------------------------------------------------------+
把策略切换交给小布盯盘
小布盯盘的 AIGC 已内置多策略轮换的诊断视图,打开对应品种页就能看到哪套规则当前占优,你只管确认切换时机。

常见问题

回测用固定历史窗口调参,前向测试把样本外数据留给验证,能暴露过拟合。多策略组合参数空间大,靠前向测试才倾向选出稳健设置。
父类通常放信号计算、仓位接口和状态重置,子类只重写开平仓逻辑。这样换策略不影响主循环,副作用概率更低。
趋势强时二者可能共振,震荡市常背离。系统若不会定期重选,就容易在假突破上重复挨打。
目前小布提供策略占优诊断和切换提醒,执行层仍需你在终端部署 EA,但重复监控劳动可以交给它。
快速遗传适合粗筛参数域,慢速在窄域细搜。多策略基准先快后慢,更可能在合理耗时内逼近盈利参数。