如何利用 MQL5 创建简单的多币种智能交易系统(第 7 部分):依据动量振荡器指标的之字折线·进阶篇
(2/3)· 28外汇对加黄金白银共30品种,如何用ZigZag接动量振荡器彼此过滤信号而不乱套
◍ 止损止盈与跟踪价的自动算价逻辑
这段 MQL5 类方法把挂单/持仓的 SL、TP 和跟踪止损触发价都收敛到几个开关里:use_sl/use_tp 控制是否启用,autosl/autotp 控制用固定点数还是外部参数 SLval/TPval。外汇与贵金属波动大,自动算价前务必在策略测试器跑一遍,确认 NormalizePrice 后的点位符合经纪商止损距离限制,否则可能直接拒单。 自动 SL 对卖单取 atprice+38*pip(autosl=Yes),自动 TP 对买单取 atprice+50*pip、卖单取 atprice-50*pip。38 与 50 都是硬编码点数,想改风偏直接动这两个数字,不用碰整体结构。 跟踪止损 TSPrice() 用 byprice 模式时,多单跟踪价 = Bid()-TSval*pip,空单 = Ask()+TSval*pip,返回前同样走 NormalizePrice。实盘里 TSval 设太小容易被噪音扫掉,EURUSD 这类品种建议先试 15~30 pip 区间观察触发频率。
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; class=class="str">"cmt">//-- break; } case (ORDER_TYPE_SELL): { 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; } } class=class="str">"cmt">//--- class="kw">return(slv); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OrderSLSet() class=class="str">"cmt">//---------// class="type">class="kw">double MCEA::OrderTPSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice) { class=class="str">"cmt">//--- tpv=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_tp==Yes && autotp==Yes) tpv=mc_symbol.NormalizePrice(atprice+class="num">50*pip); else if(use_tp==Yes && autotp==No) tpv=mc_symbol.NormalizePrice(atprice+TPval*pip); else tpv=class="num">0.0; class=class="str">"cmt">//-- break; } case (ORDER_TYPE_SELL): { if(use_tp==Yes && autotp==Yes) tpv=mc_symbol.NormalizePrice(atprice-class="num">50*pip); else if(use_tp==Yes && autotp==No) tpv=mc_symbol.NormalizePrice(atprice-TPval*pip); else tpv=class="num">0.0; } } class=class="str">"cmt">//--- class="kw">return(tpv); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OrderTPSet() class=class="str">"cmt">//---------// class="type">class="kw">double MCEA::TSPrice(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE ptype,class="type">int TS_type) { class=class="str">"cmt">//--- class="type">int br=class="num">2; class="type">class="kw">double pval=class="num">0.0; class="type">int x=PairsIdxArray(xsymb); Pips(xsymb); class=class="str">"cmt">//-- class="kw">switch(TS_type) { case byprice: { RefreshTick(xsymb); if(ptype==POSITION_TYPE_BUY) pval=mc_symbol.NormalizePrice(mc_symbol.Bid()-TSval*pip); if(ptype==POSITION_TYPE_SELL) pval=mc_symbol.NormalizePrice(mc_symbol.Ask()+TSval*pip); break; } case byindi: {
「追踪止损的两种触发逻辑与挂单改SL实现」
这段逻辑展示了追踪止损价位(TSPrice)在两种模式下的取法:一种是引用外部指标缓冲区 VIDyAv,另一种是直接用最近两根K线的高低点。外汇与贵金属杠杆高,回测前先在策略测试器确认品种点值 pip 计算无误,否则 SL 可能偏离预期。 VIDyAv 模式里,多头持仓时若指标当前值低于「买价 - TSval×pip」的归一化价格,就把 pval 设为指标值;空头则反过来,指标值高于「卖价 + TSval×pip」才采纳。这等于把 trailing stop 的锚点交给指标曲线,而不是固定点数。 byHiLo 模式不依赖指标:多头看 HIGH[0]>HIGH[1] 就把前一根低点 LOW[1] 当止损位,空头看 LOW[0]<LOW[1] 取 HIGH[1]。代码只取了 2 根 Bar(UpdatePrice 参数 2),意味着它只用最新两根K线做突破判定。 ModifyOrdersSL 负责真正改单:遍历持仓,筛选同品种且 magic 匹配的单子,对 BUY 单取 TSPrice 算出的 vtrsb 作为新 SL 候选。注意 price=PriceCurrent() 与 vtrsb 是分开取的,实盘里若 tick 跳变快,二者可能有微小时差,建议在 MT5 用真实点差品种跑一遍观察。
class="type">class="kw">double VIDyAv[]; ArrayResize(VIDyAv,br,br); ArraySetAsSeries(VIDyAv,true); CopyBuffer(hVIDyAv[x],class="num">0,class="num">0,br,VIDyAv); RefreshPrice(xsymb,TFt,br); class=class="str">"cmt">//-- if(ptype==POSITION_TYPE_BUY && (VIDyAv[class="num">0]<mc_symbol.NormalizePrice(mc_symbol.Bid()-TSval*pip))) pval=VIDyAv[class="num">0]; if(ptype==POSITION_TYPE_SELL && (VIDyAv[class="num">0]>mc_symbol.NormalizePrice(mc_symbol.Ask()+TSval*pip))) pval=VIDyAv[class="num">0]; break; case byHiLo: { UpdatePrice(xsymb,TFt,class="num">2); class=class="str">"cmt">//-- if(ptype==POSITION_TYPE_BUY && (HIGH[class="num">0]>HIGH[class="num">1])) pval=LOW[class="num">1]; if(ptype==POSITION_TYPE_SELL && (LOW[class="num">0]<LOW[class="num">1])) pval=HIGH[class="num">1]; break; } } class=class="str">"cmt">//-- class="kw">return(pval); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end TSPrice() class=class="str">"cmt">//---------// class="type">bool MCEA::ModifyOrdersSL(const class="type">class="kw">string symbx,class="type">int TS_type) { class=class="str">"cmt">//--- ResetLastError(); class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">int TRSP=TS_type; class="type">bool modist=false; class="type">int x=PairsIdxArray(symbx); Pips(symbx); class=class="str">"cmt">//-- class="type">int total=PositionsTotal(); class=class="str">"cmt">//-- for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class="type">class="kw">string symbol=PositionGetSymbol(i); if(symbol==symbx && mc_position.Magic()==magicEA) { class="type">ENUM_POSITION_TYPE opstype = mc_position.PositionType(); if(opstype==POSITION_TYPE_BUY) { RefreshTick(symbol); class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double vtrsb = mc_symbol.NormalizePrice(TSPrice(symbx,opstype,TRSP)); class="type">class="kw">double pos_open = mc_position.PriceOpen();
多单与空单的止损推进判定
这段逻辑处理持仓止损向有利方向移动的具体条件,分多单和空单两套对称判断。核心是先算出净收益 netp(浮动盈利+隔夜利息+佣金),只有 netp 大于预设 minprofit 才允许改单,避免微利就移动止损徒增摩擦成本。 多单分支里,modstart 以开仓价加 TSmin 个 pip 作为初始参考位,modminsl 用追踪基准 vtrsb 再加(TSmin-1)pip 算出允许修改的下限。只有当当前价大于 modminsl、且目标买止损 modbuysl 高于 modstart、同时原止损为空或新止损更优时,modbuy 才为真。 空单分支镜像处理:modstart 为开仓价减 TSmin*pip,modminsl 用 vtrss 减去(TSmin+1)pip。判定 modsel 要求现价低于 modminsl、新卖止损低于 modstart 且优于原止损。两个条件均满足 netp>minprofit 后,调用 PositionModify 仅改止损、保留原止盈。 外汇与贵金属杠杆高,这类自动推损逻辑在快速反转中可能连续改单失效,建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M15 数据跑一遍,观察 minprofit 设 5 美元与 20 美元时成交次数差异。
class="type">class="kw">double pos_stop = mc_position.StopLoss(); class="type">class="kw">double pos_tp = mc_position.TakeProfit(); class="type">class="kw">double pos_profit = mc_position.Profit(); class="type">class="kw">double pos_swap = mc_position.Swap(); class="type">class="kw">double pos_comm = mc_position.Commission(); class="type">class="kw">double netp=pos_profit+pos_swap+pos_comm; class="type">class="kw">double modstart=mc_symbol.NormalizePrice(pos_open+TSmin*pip); class="type">class="kw">double modminsl=mc_symbol.NormalizePrice(vtrsb+((TSmin-class="num">1.0)*pip)); class="type">class="kw">double modbuysl=vtrsb; class="type">bool modbuy = (price>modminsl && modbuysl>modstart && (pos_stop==class="num">0.0||modbuysl>pos_stop)); class=class="str">"cmt">//-- if(modbuy && netp>minprofit) { modist=mc_trade.PositionModify(symbol,modbuysl,pos_tp); } } if(opstype==POSITION_TYPE_SELL) { RefreshTick(symbol); class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double vtrss = mc_symbol.NormalizePrice(TSPrice(symbx,opstype,TRSP)); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double pos_stop = mc_position.StopLoss(); class="type">class="kw">double pos_tp = mc_position.TakeProfit(); class="type">class="kw">double pos_profit = mc_position.Profit(); class="type">class="kw">double pos_swap = mc_position.Swap(); class="type">class="kw">double pos_comm = mc_position.Commission(); class="type">class="kw">double netp=pos_profit+pos_swap+pos_comm; class="type">class="kw">double modstart=mc_symbol.NormalizePrice(pos_open-TSmin*pip); class="type">class="kw">double modminsl=mc_symbol.NormalizePrice(vtrss-((TSmin+class="num">1.0)*pip)); class="type">class="kw">double modselsl=vtrss; class="type">bool modsel = (price<modminsl && modselsl<modstart && (pos_stop==class="num">0.0||modselsl<pos_stop)); class=class="str">"cmt">//-- if(modsel && netp>minprofit) { modist=mc_trade.PositionModify(symbol,modselsl,pos_tp); } } } } class=class="str">"cmt">//-- class="kw">return(modist);
◍ 盈利后把止盈推到近端
这段逻辑只干一件事:当多单或空单已经浮盈,且原止盈距离当前价不足 TPmin 个 pip 时,把 TakeProfit 改成贴近市价的近端止盈,锁住部分空间。外汇与贵金属波动快,这种动态平移大概率能减少回吐,但仍是高风险操作,不保证胜率。 核心判断在多单分支:price>pos_open 且 (原TP到现价差 < TPmin*pip) 且 原TP < 新近端TP 时才改。空单对称处理,方向反号。 实盘验证建议:把 TPmin 设成 15~30 pip 区间,在 XAUUSD 的 M5 上跑一周,观察改单后是否被扫在近端而非原 TP。若频繁过早离场,说明 TPmin 偏小。
class="type">bool MCEA::ModifyOrdersTP(const class="type">class="kw">string symbx) { class=class="str">"cmt">//--- ResetLastError(); class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">bool modist=false; class="type">int x=PairsIdxArray(symbx); Pips(symbx); class=class="str">"cmt">//-- class="type">int total=PositionsTotal(); class=class="str">"cmt">//-- for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class="type">class="kw">string symbol=PositionGetSymbol(i); if(symbol==symbx && mc_position.Magic()==magicEA) { class="type">ENUM_POSITION_TYPE opstype = mc_position.PositionType(); if(opstype==POSITION_TYPE_BUY) { RefreshTick(symbol); class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double pos_stop = mc_position.StopLoss(); class="type">class="kw">double pos_tp = mc_position.TakeProfit(); class="type">class="kw">double modbuytp = pos_tp==class="num">0.0 ? mc_symbol.NormalizePrice(pos_open+TPmin*pip) : pos_tp; class="type">class="kw">double modpostp = mc_symbol.NormalizePrice(price+TPmin*pip); class="type">bool modtpb = (price>pos_open && modbuytp-price<TPmin*pip && pos_tp<modpostp); class=class="str">"cmt">//-- if(modtpb) { modist=mc_trade.PositionModify(symbol,pos_stop,modpostp); } } if(opstype==POSITION_TYPE_SELL) { RefreshTick(symbol); class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double pos_stop = mc_position.StopLoss(); class="type">class="kw">double pos_tp = mc_position.TakeProfit(); class="type">class="kw">double modseltp = pos_tp==class="num">0.0 ? mc_symbol.NormalizePrice(pos_open-TPmin*pip) : pos_tp; class="type">class="kw">double modpostp = mc_symbol.NormalizePrice(price-TPmin*pip); class="type">bool modtps = (price<pos_open && price-modseltp<TPmin*pip && pos_tp>modpostp); class=class="str">"cmt">//-- if(modtps) {
「反向信号触发后的卖单平仓逻辑」
当面板开关 Close_by_Opps 设为 Yes 且空头机会计数 mc.xos[x] 大于 0 时,EA 会调用 CloseSellPositions 把当前符号下的空单全平掉。这段实现直接遍历 PositionsTotal() 拿到的持仓总数,从尾到头倒序索引,避免平仓后仓位重排导致漏选。 函数里先固化 closetype = POSITION_TYPE_SELL、type_req = ORDER_TYPE_BUY,再用 mc_position.SelectByIndex(i) 逐仓选中。只有 position_Symbol 与传入 symbol 一致、且 Magic 等于 magicEA 的仓位才进入处理,防止误伤手动单或其他 EA 的仓。 命中空单后,RefreshTick 先刷最新报价,再用 mc_trade.PositionClose(position_Symbol, slip) 以允许滑点 slip 市价回购平仓,并通过 PrintFormat 打出 '#ticket 符号 类型' 的日志便于回放。返回值 sellclose 只反映最后一次平仓调用是否成功,多次平仓时前面失败不会被覆盖记录。 对称地,mc.xob[x] > 0 时走 CloseBuyPositions,结构完全一致,只是 closetype 换成 POSITION_TYPE_BUY。外汇与贵金属杠杆高,这类按信号批量反手平仓在跳空行情可能触发大滑点,实盘前建议在 MT5 策略测试器用真实点差回测确认。
if(Close_by_Opps==Yes && mc.xos[x]>class="num">0) mc.CloseSellPositions(symbol); class="type">bool MCEA::CloseSellPositions(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- ResetLastError(); class="type">bool sellclose=false; class="type">int total=PositionsTotal(); class=class="str">"cmt">// number of open positions class="type">ENUM_POSITION_TYPE closetype = POSITION_TYPE_SELL; ENUM_ORDER_TYPE type_req = ORDER_TYPE_BUY; class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">int x=PairsIdxArray(symbol); class=class="str">"cmt">//--- iterate over all open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { if(mc_position.SelectByIndex(i)) { class=class="str">"cmt">//--- Parameters of the order class="type">class="kw">string position_Symbol = PositionGetSymbol(i); class="type">ulong position_ticket = PositionGetTicket(i); class="type">ENUM_POSITION_TYPE type = mc_position.PositionType(); class=class="str">"cmt">//--- if the MagicNumber matches if((position_Symbol==symbol) && (mc_position.Magic()==magicEA)) { class=class="str">"cmt">//-- if(type==closetype) { RefreshTick(position_Symbol); sellclose=mc_trade.PositionClose(position_Symbol,slip); class=class="str">"cmt">//--- output information about the closure PrintFormat("Close Sell #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); } } } } class=class="str">"cmt">//--- class="kw">return(sellclose); } class=class="str">"cmt">//-end CloseSellPositions() if(Close_by_Opps==Yes && mc.xob[x]>class="num">0) mc.CloseBuyPositions(symbol); class="type">bool MCEA::CloseBuyPositions(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- ResetLastError(); class="type">bool buyclose=false;
用持仓遍历把多单平掉
这段逻辑干的事很直接:拿到当前账户所有未平仓位,挨个筛符号与魔法码,命中后把多单按市价平仓。外汇与贵金属杠杆高,批量平仓若滑点设得太小,实盘可能以偏离预期的价格成交,仓位越大偏离越明显。
代码里 total=PositionsTotal() 先取总仓数,循环从 i=total-1 倒序走到 0,避免平仓后索引错位。mc_position.SelectByIndex(i) 选中第 i 仓,再用 PositionGetSymbol(i) 和 PositionGetTicket(i) 取品种与ticket。
只有当 position_Symbol==symbol 且 mc_position.Magic()==magicEA 才继续,这是 EA 自管自仓的基本围栏。类型判断 type==closetype 且 closetype=POSITION_TYPE_BUY 时,调 RefreshTick 刷新报价后执行 mc_trade.PositionClose(position_Symbol,slip),并返回 buyclose 标记。
另有一段补在后面的检查:Close_by_Opps==No 且同品种开仓多空合计 mc.xob[x]+mc.xos[x]>1 时,跑 CheckProfitLoss 看是否触损,真触了就 Do_Alerts 弹“Close order due stop in loss.”。把 slip 调成 2~3 个点,开 MT5 策略测试器用欧美 1 分钟复盘,能看出平仓成交价和请求价的差。
class="type">int total=PositionsTotal(); class=class="str">"cmt">// number of open positions class="type">ENUM_POSITION_TYPE closetype = POSITION_TYPE_BUY; ENUM_ORDER_TYPE type_req = ORDER_TYPE_SELL; class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">int x=PairsIdxArray(symbol); class=class="str">"cmt">//--- iterate over all open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { if(mc_position.SelectByIndex(i)) { class=class="str">"cmt">//--- Parameters of the order class="type">class="kw">string position_Symbol = PositionGetSymbol(i); class="type">ulong position_ticket = PositionGetTicket(i); class="type">ENUM_POSITION_TYPE type = mc_position.PositionType(); class=class="str">"cmt">//--- if the MagicNumber matches if((position_Symbol==symbol) && (mc_position.Magic()==magicEA)) { class=class="str">"cmt">//-- if(type==closetype) { RefreshTick(position_Symbol); buyclose=mc_trade.PositionClose(position_Symbol,slip); class=class="str">"cmt">//--- output information about the closure PrintFormat("Close Buy #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); } } } } class=class="str">"cmt">//--- class="kw">return(buyclose); class=class="str">"cmt">//---- } class=class="str">"cmt">//-end CloseBuyPositions() class=class="str">"cmt">//---------// class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); if(Close_by_Opps==No && (mc.xob[x]+mc.xos[x]>class="num">1)) { if(mc.CheckProfitLoss(symbol)) mc.Do_Alerts(symbol,"Close order due stop in loss."); } class=class="str">"cmt">//-- class="type">bool MCEA::CheckProfitLoss(const class="type">class="kw">string symbol) { class=class="str">"cmt">//---- ResetLastError(); class=class="str">"cmt">//-- class="type">bool closeinloss=false; class="type">class="kw">string isloss="due stop in loss."; class=class="str">"cmt">//-- class="type">int xx=PairsIdxArray(symbol); class=class="str">"cmt">//--