如何利用 MQL5 创建简单的多币种智能交易系统(第 7 部分):依据动量振荡器指标的之字折线(基础篇)
📘

如何利用 MQL5 创建简单的多币种智能交易系统(第 7 部分):依据动量振荡器指标的之字折线(基础篇)

第 1/3 篇

◍ 用动量振荡器画之字折线做多币种EA

在 MT5 里做多币种智能交易,第一步往往是给每个品种单独标出趋势转折点。动量振荡器(Momentum Oscillator)配合之字折线(ZigZag)是个轻量做法:当动量值突破阈值,就认为一段折返结束。 原文给出的示例发布于 2024 年 12 月 12 日,在 MetaTrader 5 栏目下获得 938 次查看、3 条评论,说明这类基础多币种框架仍有高频查阅需求。 具体落地时,先在 OnTick 里遍历品种数组,对每个 symbol 调用 iMomentum 取缓冲值,再用自定义 ZigZag 函数记录极值。外汇与贵金属杠杆高,多币种同跑会放大回撤,参数未验证前仅作技术可能性探讨。

「从单图拉起三十个货币对的引擎」

多货币智能系统的核心能力,是在一张交易品种图表上统一开立、平仓并管理多品种订单,而不是每对货币各开一个 EA。本文落地的版本覆盖 30 个货币对,用 ZigZag(之字折线)作为主信号,再叠加动量振荡器做相互过滤,避免单指标假突破频繁触发。 MT5 的策略测试器原生支持多品种回测,这意味着你不需要自己拼数据接口,只要在 EA 里正确调用 Symbol 数组就能在单一图表驱动跨品种交易。对于想验证多货币逻辑的交易者,直接开 MT5 把货币对数量从 2 改成 30,跑一遍 2020—2023 的 H1 数据,就能看到订单分布和回撤结构的变化。 外汇与贵金属多货币策略杠杆叠加,风险显著高于单品种,任何信号过滤都只是降低概率而非消除亏损。

多币种EA的品种覆盖与单币种切换逻辑

这套多币种智能系统默认覆盖 28 个外汇直盘/交叉盘,再叠加 XAUUSD 与 XAGUSD 两个贵金属对,合计 30 个交易品种。作者写了一个自动清洗带前缀/后缀品种名的函数,但只在 MT5 的外汇和金属对上有效,指数和特殊品种不在适配范围。外汇与贵金属杠杆高、跳空频繁,实盘前务必在策略测试器里先验证品种名解析是否正常。 想把它当单币种 EA 用,有两条路。其一是在『交易对模式』里选 SP(Single Pair),EA 只在挂载chart的那个品种上动手,比如挂 EURUSD 就只做 EURUSD。其二是保持 MP(Multi Pairs),但在『交易者愿望对』里只填一个名字,例如 XAUUSD,系统同样只会盯这一对。后者灵活在可随时改输入参数,不用重新挂 EA。 系统还给了按交易时段开关的选项,方便把货币对和对应活跃时区对齐。比如东京时段只放 USDJPY、伦敦时段切 GBP 系,这种用法在 MT5 里勾几个输入就能复现,建议先拿 demo 账户跑一周看成交分布。

◍ 净值回撤触发全平止损的逻辑拆解

EA 在每轮 tick 里先调 CheckEquityBalance(),用权益除以余额再乘 100,若结果大于 (100 - Risk) 才返回 true。也就是说当 Risk 设为 20 时,净值跌破余额的 80% 才会判定资金安全线失守,这里 Risk 的单位是百分比整数而非小数。 CheckLoss() 负责扫描持仓浮亏。它先取 SLval 的一半作为默认 lossval(若没传 slc 参数),再把该值乘 pip 后 NormalizePrice 成点数价格;随后双重循环遍历 arrsymbx 里的品种与所有持仓,仅当品种名匹配、magic 号等于 magicEA、且当前价与开仓价偏差大于 posloss 时,把 inloss 置真。 外层调用里一旦 CheckEquityBalance() 为假且 CloseAllLoss() 为真,就弹 alert 写「Close order due stop in loss to secure equity.」。外汇与贵金属杠杆高,这类硬砍仓逻辑能防穿仓,但也可能在剧烈波动里误杀浮亏单,建议开 MT5 把 Risk 从 20 调到 10 观察回撤频率。 下面这段是原文里权益检查与浮亏扫描的核心片段,逐行看更直观:CheckEquityBalance 里那行除法乘 100 再比 (100.00-Risk) 是整段风控的开关;CheckLoss 中 lossval=slc==0.0 ? (SLval*0.5) : slc 决定容忍半截止损还是自定义距离。

MQL5 / C++
if(!mc.CheckEquityBalance())
  if(mc.CloseAllLoss())
    mc.Do_Alerts(symbol,"Close order due stop in loss to secure equity.");
class=class="str">"cmt">//--
class="type">bool MCEA::CheckEquityBalance(class="type">void)
  {
class=class="str">"cmt">//---
   class="type">bool isgood=false;
   if((mc_account.Equity()/mc_account.Balance()*class="num">100) > (class="num">100.00-Risk)) isgood=true;
   class=class="str">"cmt">//--
   class="kw">return(isgood);
class=class="str">"cmt">//---
   } class=class="str">"cmt">//-end CheckEquityBalance()
class=class="str">"cmt">//---------//
class="type">bool MCEA::CheckLoss(const class="type">class="kw">string symbol,class="type">ENUM_POSITION_TYPE intype,class="type">class="kw">double slc=class="num">0.0)
   {
class=class="str">"cmt">//---
    Pips(symbol);
    class="type">bool inloss=false;
    class="type">class="kw">double lossval=slc==class="num">0.0 ? (SLval*class="num">0.5) : slc;
    class="type">class="kw">double posloss  = mc_symbol.NormalizePrice(slc*pip);
    class="type">int ttlorder=PositionsTotal(); class=class="str">"cmt">// number of open positions
    class=class="str">"cmt">//--
    for(class="type">int x=class="num">0;x<arrsymbx; x++)
      {
        class="type">class="kw">string symbol=DIRI[x];
        class=class="str">"cmt">//--
        for(class="type">int i=ttlorder-class="num">1; i>=class="num">0; i--)
          {
            class="type">class="kw">string position_Symbol  = PositionGetSymbol(i);
            class="type">ENUM_POSITION_TYPE  type = mc_position.PositionType();
            if((position_Symbol==symbol) && (mc_position.Magic()==magicEA))
              {
                class="type">class="kw">double price     = mc_position.PriceCurrent();
                class="type">class="kw">double pos_open = mc_position.PriceOpen();
                class="type">class="kw">double posloss  = mc_symbol.NormalizePrice(lossval*pip);
                class="type">class="kw">double pricegab = mc_symbol.NormalizePrice(fabs(price-pos_open));
                class=class="str">"cmt">//---
                if(type==intype && pricegab>posloss) inloss=true;
              }
          }
      }
    class=class="str">"cmt">//--
    class="kw">return(inloss);
class=class="str">"cmt">//----
   } class=class="str">"cmt">//-end CheckLoss()
class=class="str">"cmt">//---------//
class="type">bool MCEA::CloseAllLoss(class="type">void)
   {
class=class="str">"cmt">//----
    ResetLastError();
    class=class="str">"cmt">//--
    class="type">bool orclose=false;
    class="type">class="kw">string isloss="due stop in loss.";
    class=class="str">"cmt">//--
    class="type">MqlTradeRequest req={};
    class="type">MqlTradeResult  res={};

「按跳空幅度批量收割浮亏仓位」

这段逻辑跑在 EA 的主循环里,先取当前持仓总数 ttlorder,再对监控符号数组 DIRI 逐个处理。它的核心意图是:当价格偏离开仓价超过预设止损幅度 posloss 时,不论多空,直接市价平仓。 外层 for 遍历 DIRI[x] 拿到 symbol,调用 Pips(symbol) 刷新点值,并用 SLval*pip 归一化出 posloss。内层从 ttlorder-1 倒序扫持仓,只挑 symbol 匹配且魔术码等于 magicEA 的仓位,避免误伤手动单或其他 EA 的单子。 pricegab 用 fabs(price-pos_open) 算绝对距离,一旦大于 posloss,买仓走 POSITION_TYPE_BUY 分支、卖仓走 POSITION_TYPE_SELL 分支。两个分支都先 RefreshTick 拉最新报价,再 mc_trade.PositionClose 按 slip 滑点平仓,并用 PrintFormat 打出关闭日志。 外汇与贵金属杠杆高,这种硬砍仓在跳空行情中可能滑点远超 posloss,实盘前务必在 MT5 策略测试器用历史极端缺口数据跑一遍验证平仓触发与日志输出。

MQL5 / C++
MqlTradeCheckResult check={};
class=class="str">"cmt">//--
class="type">int ttlorder=PositionsTotal(); class=class="str">"cmt">// number of open positions
class=class="str">"cmt">//--
for(class="type">int x=class="num">0; x<arrsymbx; x++)
  {
    class="type">class="kw">string symbol=DIRI[x];
    Pips(symbol);
    class="type">class="kw">double posloss=mc_symbol.NormalizePrice(SLval*pip);
    orclose=false;
    class=class="str">"cmt">//--
    for(class="type">int i=ttlorder-class="num">1; i>=class="num">0; i--)
      {
        class="type">class="kw">string position_Symbol   = PositionGetSymbol(i);
        class="type">ENUM_POSITION_TYPE  type = mc_position.PositionType();
        if((position_Symbol==symbol) && (mc_position.Magic()==magicEA))
          {
            class="type">class="kw">double price      = mc_position.PriceCurrent();
            class="type">class="kw">double pos_open = mc_position.PriceOpen();
            class="type">class="kw">double posloss   = mc_symbol.NormalizePrice(SLval*pip);
            class="type">class="kw">double pricegab = mc_symbol.NormalizePrice(fabs(price-pos_open));
            class="type">ulong  position_ticket = PositionGetTicket(i);
            class=class="str">"cmt">//---
            if(type==POSITION_TYPE_BUY && pricegab>posloss)
              {
                RefreshTick(position_Symbol);
                orclose = mc_trade.PositionClose(position_Symbol,slip);
                class=class="str">"cmt">//--- output information about the closure
                PrintFormat("Close Buy %s %s %s",symbol,EnumToString(POSITION_TYPE_BUY),isloss);
              }
            if(type==POSITION_TYPE_SELL && pricegab>posloss)
              {
                RefreshTick(position_Symbol);
                orclose = mc_trade.PositionClose(position_Symbol,slip);
                class=class="str">"cmt">//--- output information about the closure
                PrintFormat("Close Sell %s %s %s",symbol,EnumToString(POSITION_TYPE_BUY),isloss);
              }
          }
      }

弱信号保盈与止损距的收口逻辑

当开关 SaveOnRev 设为 Yes 时,EA 会在信号转弱但持仓已浮盈时主动平仓锁利。判断依据是:当前多单利润超过 minprofit 且持仓数大于 0,同时 GetCloseInWeakSignal 返回反向信号,才触发 CloseBuyPositions。 GetCloseInWeakSignal 内部用 AO 与 ZigZag 两个方向的颜色信号做或运算。已有空单(down=-1)而 AO 或 ZZ 任一转多(rise=1)则返回多;已有多单而任一转空则返回空,否则 ret 保持 0 不动作。 止损价由 OrderSLSet 按订单类型计算。BUY 单在 use_sl 与 autosl 均为 Yes 时,固定取入场价减 38 倍点值(pip);若 autosl 为 No 则改用 SLval*pip 的手动距离;两者皆否就免止损。外汇与贵金属杠杆高,这类自动保盈和固定距止损只降低回撤速度,不消除爆仓可能。 把 38 改成你品种的平均波动率再跑 MT5 回测,会看到平仓频率和净收益曲线明显位移,参数敏感得不像教科书说的那样钝感。

MQL5 / C++
if(SaveOnRev==Yes) class=class="str">"cmt">//-- Close Trade and Save profit due to weak signal(Yes)
  {
   mc.CheckOpenPMx(symbol);
   if(mc.profitb[x]>mc.minprofit && mc.xob[x]>class="num">0 && mc.GetCloseInWeakSignal(symbol,mc.Buy)==mc.Sell)
     {
      mc.CloseBuyPositions(symbol);
      mc.Do_Alerts(symbol,"Close BUY order "+symbol+" to save profit due to weak signal.");
     }
   if(mc.profits[x]>mc.minprofit && mc.xos[x]>class="num">0 && mc.GetCloseInWeakSignal(symbol,mc.Sell)==mc.Buy)
     {
      mc.CloseSellPositions(symbol);
      mc.Do_Alerts(symbol,"Close SELL order "+symbol+" to save profit due to weak signal.");
     }
  }
class=class="str">"cmt">//--
class="type">int MCEA::GetCloseInWeakSignal(const class="type">class="kw">string symbol,class="type">int exis) class=class="str">"cmt">// Signal Indicator Position Close in profit
  {
class=class="str">"cmt">//---
   class="type">int ret=class="num">0;
   class="type">int rise=class="num">1,
       down=-class="num">1;
   class=class="str">"cmt">//--
   class="type">int AOdir=AOColorSignal(symbol);
   class="type">int ZZDir=ZigZagSignal(symbol);
   class=class="str">"cmt">//--
   if(exis==down && (AOdir==rise||ZZDir==rise)) ret=rise;
   if(exis==rise && (AOdir==down||ZZDir==down)) ret=down;
   class=class="str">"cmt">//--
   class="kw">return(ret);
class=class="str">"cmt">//---
  } class=class="str">"cmt">//-end GetCloseInWeakSignal()
class=class="str">"cmt">//---------//
class="type">class="kw">double MCEA::OrderSLSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice)
  {
class=class="str">"cmt">//---
   slv=class="num">0.0;
   class="type">int x=PairsIdxArray(xsymb);
   Pips(xsymb);
   RefreshTick(xsymb);
   class=class="str">"cmt">//--
   class="kw">switch(type)
     {
      case (ORDER_TYPE_BUY):
       {
        if(use_sl==Yes && autosl==Yes) slv=mc_symbol.NormalizePrice(atprice-class="num">38*pip);
        else
        if(use_sl==Yes && autosl==No) slv=mc_symbol.NormalizePrice(atprice-SLval*pip);
        else slv=class="num">0.0;

常见问题

可用动量振荡器结合之字折线写个多币种引擎,从单图直接批量加载三十个品种,省去逐个打开的麻烦。
在代码里设净值回撤阈值,触发后执行全平止损;具体比例按你风险承受来定,常见用5%–10%做硬止损线。
小布可基于动量振荡器信号和之字折线形态,提示弱信号品种并建议切换逻辑,你按提示手动或自动收口即可。
在EA里读取各品种跳空点数,超过设定值就批量平仓浮亏单,避免缺口扩大亏损。
用动量衰减配合之字折线拐点收止损,信号弱就缩距保盈,但别小于品种最小止损点以免频繁出场。