您应当知道的 MQL5 向导技术(第 17 部分):多币种交易·进阶篇
🪙

您应当知道的 MQL5 向导技术(第 17 部分):多币种交易·进阶篇

(2/3)·单一品种 EA 遇多币震荡就裸奔?向导类改两处即可并行拆杠杆

实战向进阶 第 2/3 篇
多数由向导直接生成的 EA 只会盯一个品种,遇到美元齐涨齐跌就被动满仓同方向。把保证金按币种拆开、用负相关的配对对冲,是账户不爆在单条宏观线上的基本功。本篇给你两套可改的向导类模板,省去从零造轮子的工时。

「EA 初始化时的周期与品种校验逻辑」

在 MT5 的 EA 基类里,Init 函数第一道关卡就是周期一致性:若传入的 period 与当前图表 Period() 不符,直接 PrintFormat 报错并返回 false,EA 不会在错误周期上跑。 如果启用了定时器(m_on_timer_process 为真),还会调用 EventSetTimer(PeriodSeconds(period));设失败同样返回 false。这两步决定了 EA 能否在预期时间粒度上运转。 品种对象 m_symbol 为 NULL 时才 new 一个 CSymbolInfo,随后用 Name(symbol) 绑定品种,绑定失败即退出。外汇与贵金属波动剧烈、杠杆高风险大,周期或品种绑错可能让策略信号完全失真。 派生类 CExfolio 覆写 Init 时,先调 CExpert::Init 做基类校验,再用 symbol!=_Symbol 做二次判断——这种双层检查能避免挂错品种还悄无声息地开仓。

MQL5 / C++
class="type">bool CExpert::Init(class="type">class="kw">string symbol,ENUM_TIMEFRAMES period,class="type">bool every_tick,class="type">class="kw">ulong magic)
  {
class=class="str">"cmt">//--- returns false if the EA is initialized on a timeframe different from the current one
   if(period!=::Period())
    {
      PrintFormat(__FUNCTION__+": wrong timeframe(must be: %s)",EnumToString(period));
      class="kw">return(false);
    }
    
   if(m_on_timer_process && !EventSetTimer(PeriodSeconds(period)))
    {
      PrintFormat(__FUNCTION__+": cannot set timer at: ",EnumToString(period));
      class="kw">return(false);
    }
class=class="str">"cmt">//--- initialize common information
   if(m_symbol==NULL)
    {
      if((m_symbol=new CSymbolInfo)==NULL)
        class="kw">return(false);
    }
   if(!m_symbol.Name(symbol))
      class="kw">return(false);

class=class="str">"cmt">//--- ok
   class="kw">return(true);
  }

定时器初始化失败的排查点

在 EA 的 OnInit 流程里,若想靠 OnTimer 做周期轮询,必须先确认 EventSetTimer 真的挂上了。上面这段把 Reinit 判断和定时器设置串在一起:只有 Reinit 返回 true 才把 _init 置真,随后调用 CExpert::OnTimerProcess(true) 准备计时逻辑。 紧接着那行 if(CExpert::m_on_timer_process && !EventSetTimer(PeriodSeconds(_Period))) 是硬关卡——如果内部计时开关已开,但 EventSetTimer 用当前图表周期秒数去注册却返回 false,就会打印「cannot set timer at: 周期枚举」并强制 _init=false,EA 直接起不来。 实盘里遇到 OnInit 返回 INIT_FAILED 且日志出现这句,优先怀疑 MT5 终端在该图表上定时器资源被占用,或 PeriodSeconds(_Period) 取到的数值因离线图表为 0。开 MT5 用 __FUNCTION__ 配合 EnumToString(period) 打印,能直接看到是哪个周期枚举注册失败。外汇与贵金属品种波动剧烈,EA 初始化异常可能错过建仓窗口,属高风险操作环境。

MQL5 / C++
    if(Reinit(symbol,period,every_tick,magic)){ _init=true; }
     }
   }
   
   CExpert::OnTimerProcess(true);
   
   if(CExpert::m_on_timer_process && !EventSetTimer(PeriodSeconds(_Period)))
   {
     printf(__FUNCTION__+": cannot set timer at: ",EnumToString(period));
     _init=false;
   }
   
class=class="str">"cmt">//--- ok
   class="kw">return(_init);
  }

◍ 用相关性过滤多品种信号

在向导生成的 EA 里做多货种交易,核心不是重写信号,而是加一个 Select 函数当过滤器。它依赖组合内品种收盘价的相关系数,只在存在跨行情机会时放行,本质是把第一篇的 RSI 主信号再筛一层。 先得手动开定时交易——默认居然是关的,在 OnInit 里补一行 EventSetTimer 即可。否则后面 OnTimer 里的逻辑根本不会跑。 Select 分两块:无持仓时,遍历组合算各品种两段 30 根 K 线的 CorrCoef,取最大正相关系数者;再看其最新收盘相对 29 根前是涨是跌,定多空。我们只要正相关性最大的,因为目标是组合里趋势最强的标的。 有单之后逻辑反转:若初始仓缩水,就在剩余品种里找与当前持仓相关性绝对值最高者,顺着它的趋势开“对冲”仓。下面这段是无持仓分支的代码骨架,能直接抄进 EA 类里改。 向导类得把 Refresh 和 Process 从 protected 挪到 public,否则 OnTimer 里没法手调。另外自定义品种测试时,组合数组里的名字必须带后缀,不然策略测试器同步不了数据——这是实测坑点。 最后把 OnTick 里的 OnProcess 禁掉,计时器里单独调 OnTimerProcess(true);撰写时 ExpertFolio 的 OnTimer 内 Process 不会自执行,只能手工调且自己声明/注销定时器。

MQL5 / C++
class=class="str">"cmt">//
ExtExpert[f].OnTimerProcess(true);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Symbol Selector via Correlation                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool  Select(class="type">class="kw">double Direction, class="type">int &Index, class="type">ENUM_POSITION_TYPE &Type)
{  if(PositionsTotal() == class="num">0)
   {  class="type">class="kw">double _max = class="num">0.0;
      class="type">int _index = -class="num">1;
      Type = INVALID_HANDLE;
      for(class="type">int f = class="num">0; f < __FOLIO; f++)
      {  vector _v_0, _v_1;
         _v_0.CopyRates(__F[f], Period(), class="num">8, class="num">0, class="num">30);
         _v_1.CopyRates(__F[f], Period(), class="num">8, class="num">30, class="num">30);
         class="type">class="kw">double _corr = _v_0.CorrCoef(_v_1);
         if(_max < _corr && ((Direction > class="num">0.0 && _v_0[class="num">0] > _v_0[class="num">29]) || (Direction < class="num">0.0 && _v_0[class="num">0] < _v_0[class="num">29])))
         {  _max = _corr;
            _index = f;
            if(_v_0[class="num">0] > _v_0[class="num">29])
            {  Type = POSITION_TYPE_BUY;
            }
            else if(_v_0[class="num">0] < _v_0[class="num">29])
            {  Type = POSITION_TYPE_SELL;
            }
         }
      }
      Index = _index;
      class="kw">return(true);
   }
   else if(PositionsTotal() == class="num">1)
   { 
class=class="str">"cmt">//...
class=class="str">"cmt">//...
   }
   class="kw">return(false);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「持仓浮亏时按相关性挑下一个标的」

当账户里只有一笔持仓且当前浮亏(PositionGetDouble(POSITION_PROFIT) < 0.0)时,这段逻辑会去组合里找和它走势最像的另一个品种接单。它先通过 ArrayBsearch 定位当前持仓品种在 __F 数组里的下标,再遍历其余 __FOLIO 个品种逐一算相关系数。 相关系数用 vector 的 CorrCoef 算,两边各取 Period() 周期下最近 30 根 K 线(CopyRates 第 4、5 参数 0 和 30),取绝对值 fabs 后比较。只有比已记录的最大值 _max 更大才更新候选,所以最终选出的是与亏损单相关性最高的那个标的。 方向判定很直接:候选品种 _v_1[0](最新收盘)大于 _v_1[29](30 根前收盘)就给 POSITION_TYPE_BUY,反之下破给 POSITION_TYPE_SELL,并把下标写进 Index、类型写进 Type 返回 true。外汇与贵金属杠杆高,相关性会随流动性骤变而断裂,这种接单逻辑只降低盲选概率,不消除爆仓风险,实盘前务必在 MT5 策略测试器用真实点差回测。

MQL5 / C++
class="type">bool  Select(class="type">class="kw">double Direction, class="type">int &Index, class="type">ENUM_POSITION_TYPE &Type)
{  if(PositionsTotal() == class="num">0)
   {  
class=class="str">"cmt">//...
class=class="str">"cmt">//...
   }
   else if(PositionsTotal() == class="num">1)
   {  class="type">class="kw">ulong _ticket = PositionGetTicket(class="num">0);
      if(PositionSelectByTicket(_ticket))
      {  class="type">class="kw">double _float = PositionGetDouble(POSITION_PROFIT);
         class="type">ENUM_POSITION_TYPE _type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         class="type">int _index = ArrayBsearch(__F, PositionGetString(POSITION_SYMBOL));
         class="type">class="kw">double _max = class="num">0.0;
         Type = INVALID_HANDLE;
         for(class="type">int f = class="num">0; f < __FOLIO; f++)
         {  if(f == _index)
            {  class="kw">continue;
            }
            else
            {  vector _v_0, _v_1;
               _v_0.CopyRates(__F[_index], Period(), class="num">8, class="num">0, class="num">30);
               _v_1.CopyRates(__F[f], Period(), class="num">8, class="num">0, class="num">30);
               class="type">class="kw">double _corr = fabs(_v_0.CorrCoef(_v_1));
               if(_float < class="num">0.0 && _max < _corr)
               {  _max = _corr;
                  Index = f;
                  if(_v_1[class="num">0] > _v_1[class="num">29])
                  {  Type = POSITION_TYPE_BUY;
                  }
                  else if(_v_1[class="num">0] < _v_1[class="num">29])
                  {  Type = POSITION_TYPE_SELL;
                  }
               }
            }
         }
      }
      class="kw">return(true);
   }
   class="kw">return(false);
}
让小布替你跑这套跨市扫描
这些多币相关性诊断与保证金拆分逻辑,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时配对建议,你只管决定改哪段向导类。

常见问题

向导标准模板的主文件只绑定 Chart 所在品种,要在 OnTick 里遍历目标品种数组并分别下单,需要改写 CExpert 派生类的品种容器与订单路由。
并行按权重拆保证金、同帧对冲,倾向降低聚拢风险;顺序则抓跨市场时间差,依赖负相关性逐次补仓,对滑点和延迟更敏感。
纯挂单对冲多数平台允许,但近似套利的高频对敲违反用户协议,外汇贵金属本身高杠杆高风险,过周末前需核对合约细则。
目前小布提供品种相关性与保证金占用诊断,模板代码仍需在 MQL5 编辑器内按本篇结构替换,后续版本可能开放一键片段。
可读取历史分笔算 Pearson,或让向导在初始化时调用 iPearson 系列函数,低于 -0.3 的配对纳入候选池,再结合新闻日历过滤。