如何利用 MQL5 创建简单的多币种智能交易系统(第 7 部分):依据动量振荡器指标的之字折线(基础篇)
◍ 用动量振荡器画之字折线做多币种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 决定容忍半截止损还是自定义距离。
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 策略测试器用历史极端缺口数据跑一遍验证平仓触发与日志输出。
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 回测,会看到平仓频率和净收益曲线明显位移,参数敏感得不像教科书说的那样钝感。
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;