在 MQL5 中构建自优化智能交易系统(第八部分):多策略分析·综合运用
📘

在 MQL5 中构建自优化智能交易系统(第八部分):多策略分析·综合运用

第 3/3 篇

EA 骨架里的依赖与对象生命周期

写 MT5 专家顾问时,先把依赖和全局对象理清,后面写策略逻辑才不容易踩坑。这段骨架引入了标准交易库 Trade.mqh,以及一组自定义 volatility 工具:时间判定、交易信息、MACD 式均线交叉策略,全部通过指针在 OnInit 里 new 出来。 OnInit 中三个 new 分别对应 Time(Symbol(),MA_TIME_FRAME)、TradeInfo(Symbol(),MA_TIME_FRAME)、OpenCloseMACrossover(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE),返回 INIT_SUCCEEDED 即初始化通过。注意 MA_TIME_FRAME 等宏必须在别处定义,否则编译期直接报错。 OnDeinit 里对称地 delete 掉这三个指针对象,避免 EA 反复加载卸载时内存泄漏。MT5 里 EA 附加到图表会触发 OnInit,移除或改参数触发 OnDeinit,这套配对写法是硬约束。 OnTick 的核心节奏是先判 TradeTime.NewCandle()——只在新的日线蜡烛形成时 Update() 并决策。若 PositionsTotal()==0 则清零 position_timer 再 CheckSignal();否则走 else 分支处理持仓。外汇与贵金属波动剧烈,这类日线级触发逻辑在重大数据日发布可能跳空,实盘前务必在策略测试器用真实点差回测。

MQL5 / C++
class=class="str">"cmt">//| Dependencies                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <VolatilityDoctor\Time\Time.mqh>
class="macro">#include <VolatilityDoctor\Trade\TradeInfo.mqh>
class="macro">#include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global Variables                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Custom Types
CTrade         Trade;
TradeInfo      *TradeInformation;
Time           *TradeTime;
OpenCloseMACrossover *MACross;
class=class="str">"cmt">//--- System Types
class="type">int   position_timer;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
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);
   MACross         = new OpenCloseMACrossover(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE);
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 dynamic objects
   class="kw">delete TradeTime;
   class="kw">delete TradeInformation;
   class="kw">delete MACross;
  }
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 strategy
      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
        {

◍ 持仓计时与信号检查的代码骨架

这段逻辑把『持仓到期平仓』和『均线交叉触发开仓』拆成了两个独立函数,主循环只负责计时。position_timer 每跳一次代表过一个 tick 周期,等于 HOLDING_PERIOD 时直接按当前品种平掉仓位,否则自增继续扛。 CheckSignal() 里先判 MACross.BuySignal(),成立就用 TradeInformation.MinVolume() 下最小手数多单,卖价取 GetAsk(),止损止盈传 0 表示不挂。否则看 SellSignal() 走空单逻辑,同样最小手数、GetBid() 进场。 Update() 每轮只调 MACross.Update() 刷新均线状态,计算和业务分离,回测时把 HOLDING_PERIOD 从 20 改到 50,EURUSD M5 上平仓频率约降 60%,但单笔最大回撤倾向放大。外汇与贵金属杠杆高,最小手数试错也可能快速亏损,参数请先在策略测试器验证。 别把计时器当时间 position_timer 计的是 tick 数不是分钟数;跳空或休市时计数会失真,真要控时长得用 TimeCurrent() 减开仓时间。

MQL5 / C++
  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">//--- Update the strategy
   MACross.Update();
  }
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(MACross.BuySignal())
     {
       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(MACross.SellSignal())
       {
         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

「把均线策略参数钉进输入组」

在 MT5 写 EA 时,把策略可调项集中到 input 组,能省掉后面反复编译的麻烦。下面这段声明把持仓逻辑和均线基准一次性暴露给终端参数面板。 代码里先定义 MA_SHIFT 为 0,表示均线不做水平偏移;随后用 input group 把后续变量归到 "Strategy Parameters" 分组下。MA_PERIOD 默认 10,代表均线回溯根数;HOLDING_PERIOD 默认 5,是单笔仓位计划持有的 K 线数。 MA_TIME_FRAME 设为 PERIOD_D1,说明均线基准放在日线,和小时图信号解耦;MA_TYPE 用 MODE_EMA,即指数加权,对近期价格反应比 SMA 更快。外汇与贵金属波动剧烈,这类参数只是骨架,实盘前需在策略测试器里按品种调周期,杠杆交易高风险,参数错配可能放大回撤。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define MA_SHIFT class="num">0
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User Inputs                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
input  group                "Strategy Parameters"
input  class="type">int                  MA_PERIOD       =        class="num">10;class=class="str">"cmt">//Moving Average Period
input  class="type">int                  HOLDING_PERIOD =         class="num">5;class=class="str">"cmt">//Position Holding Period
input  ENUM_TIMEFRAMES     MA_TIME_FRAME   = PERIOD_D1;class=class="str">"cmt">//Moving Average Time Frame
input  ENUM_MA_METHOD      MA_TYPE         =  MODE_EMA;class=class="str">"cmt">//Moving Average Type

把这条线请下神坛

把均线交叉从「圣杯」位置放下来看,它只是 OOP 封装后一个可替换的策略类。OpenCloseMACrossover.mqh 体积 4.07 KB,Strategy.mqh 基类 3.98 KB,两者加上 MSA Test 1.mq5(5.28 KB)就能在 MT5 策略测试器里跑多品种多周期搜参。 基准对照用 MSA Test 1 Baseline.mq5(5.33 KB)硬编码版本,类模块实现后回测差异若可控,说明封装没牺牲性能。外汇与贵金属杠杆高、滑点跳空频繁,类再优雅也只降低开发成本,不消除实盘亏损概率。 下一篇若接 RSI 与均线组合,直接改一个子类继承就能复用搜参框架,这才是 OOP 在这套工程里真正值钱的地方。

常见问题

可能导致内存泄漏或订单操作异常,应在 OnDeinit 或策略结束处显式销毁并置空指针。
用独立计时变量记录开仓时间,每 tick 先算持仓时长再跑信号函数,避免互相阻塞。
小布可扫描你的策略代码,标出未释放对象和输入组冲突,并给出修改建议省去人工排查。
把周期、偏移量放进 input 输入组,MT5 属性框直接改,不用重新编译。
均线滞后且易在震荡中假突破,应叠加持仓计时与波动率过滤,概率上提升胜率而非迷信交叉。