构建K线趋势约束模型(第十部分):战略均线金叉与死叉(智能交易系统EA)·综合运用
📈

构建K线趋势约束模型(第十部分):战略均线金叉与死叉(智能交易系统EA)·综合运用

(3/3)·手动盯金叉死叉总慢半拍?这篇把交叉信号写进EA,补齐趋势约束模型最后一块自动化短板

实战向进阶 第 3/3 篇
不少老手把金叉死叉当反转圣杯,却卡在手动执行的时差里——信号闪过才下单,拐点早溜了。本篇收尾把这套交叉逻辑封进EA,让趋势约束系统自己抓转折。

◍ 初始化时把指标句柄一次拿全

在 MT5 的 Expert Advisor 初始化阶段,先把 RSI、Donchian Channel、MACD 三个指标的 handle 全部建好,任一返回 INVALID_HANDLE 就直接 return INIT_FAILED,避免后续 Tick 里空句柄崩策略。 RSI 用 iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE),唐奇安通道走 iCustom 加载 "Free Indicators\Donchian Channel" 并传周期参数;MACD 用 iMACD 且信号线周期写死 26,这三个调用失败都会 Print 出 GetLastError() 方便排查。 若开关 UseGoldenDeathCrossStrategy 为真,还需校验 Bars 数是否小于慢线周期 GDC_SlowEMAPeriod,不足则初始化失败;随后建快慢两条 EMA 句柄(MODE_EMA, PRICE_CLOSE),任一句柄 < 0 同样回 INIT_FAILED。 最后把 MACD 背离缓冲数组按 DivergenceLookBack 尺寸 ArrayResize,打印初始化成功并返回 INIT_SUCCEEDED;OnDeinit 里用 IndicatorRelease 依次释放三个句柄,防止指标实例泄露。外汇与贵金属杠杆高,句柄未释放可能导致图表重载后指标加载异常,建议开 MT5 按此结构自查 EA 初始化日志。

MQL5 / C++
rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
if (rsi_handle == INVALID_HANDLE)
{
   Print("Failed to create RSI indicator handle. Error: ", GetLastError());
   class="kw">return INIT_FAILED;
}
class=class="str">"cmt">// Create a handle for the Donchian Channel
handle = iCustom(_Symbol, PERIOD_CURRENT, "Free Indicators\\Donchian Channel", InpDonchianPeriod);
if (handle == INVALID_HANDLE)
{
   Print("Failed to load the Donchian Channel indicator. Error: ", GetLastError());
   class="kw">return INIT_FAILED;
}
class=class="str">"cmt">// Initialize MACD handle for divergence
globalMagicNumber = DivergenceMagicNumber;
macd_handle = iMACD(_Symbol, PERIOD_CURRENT, DivergenceMACDPeriod, class="num">26, DivergenceSignalPeriod, PRICE_CLOSE);
if (macd_handle == INVALID_HANDLE)
{
   Print("Failed to create MACD indicator handle for divergence strategy. Error: ", GetLastError());
   class="kw">return INIT_FAILED;
}

if(UseGoldenDeathCrossStrategy)
{
   class=class="str">"cmt">// Check if there are enough bars to calculate the EMA
   if(Bars(_Symbol, PERIOD_CURRENT) < GDC_SlowEMAPeriod)
   {
      Print("Not enough bars for EMA calculation for Golden Death Cross");
      class="kw">return INIT_FAILED;
   }
   GDC_fastEMAHandle = iMA(_Symbol, PERIOD_CURRENT, GDC_FastEMAPeriod, class="num">0, MODE_EMA, PRICE_CLOSE);
   GDC_slowEMAHandle = iMA(_Symbol, PERIOD_CURRENT, GDC_SlowEMAPeriod, class="num">0, MODE_EMA, PRICE_CLOSE);
   if(GDC_fastEMAHandle < class="num">0 || GDC_slowEMAHandle < class="num">0)
   {
      Print("Failed to create EMA handles for Golden Death Cross. Error: ", GetLastError());
      class="kw">return INIT_FAILED;
   }
}
class=class="str">"cmt">// Resize arrays for MACD buffers
ArrayResize(ExtMacdBuffer, DivergenceLookBack);
ArrayResize(ExtSignalBuffer, DivergenceLookBack);
Print("Trend Constraint Expert initialized.");
class="kw">return INIT_SUCCEEDED;

「双均线交叉的开仓判定逻辑」

这一段把快慢 EMA 的「金叉 / 死叉」转成了无持仓条件下的下单动作。函数先取两个长度为 2 的数组,分别装快线与慢线的最新两根值,用 CopyBuffer 从指标句柄拉数据;只要任一拷贝返回 <=0 就打印错误并撤出,避免脏数据触发交易。 判定本身只看两根 K 线:若 fastEMA[0] > slowEMA[0] 且 fastEMA[1] <= slowEMA[1],视为死叉,按 ask 价 Sell,止损放在 ask + GDC_StopLossPips*point 并 NormalizeDouble 到 _Digits 精度;反向条件 fastEMA[0] < slowEMA[0] 且 fastEMA[1] >= slowEMA[1] 则是金叉,按 bid 价 Buy,止损在 bid - 同样点数。 注意代码里只处理了 !hasPosition 的分支,也就是同品种已有持仓时本函数前半段不下新单;持仓后的平仓或反手逻辑在 else 里被截断,复制时得确认后续源码是否补全。外汇与贵金属杠杆高,EMA 交叉在震荡市会连续假突破,实盘前建议在 MT5 策略测试器用 2020—2023 年 XAUUSD 的 M15 跑一遍验证胜率。

MQL5 / C++
class="type">void CheckGoldenDeathCross()
{
   class="type">class="kw">double fastEMAArray[class="num">2], slowEMAArray[class="num">2];
   class="type">class="kw">double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   if(CopyBuffer(GDC_fastEMAHandle, class="num">0, class="num">0, class="num">2, fastEMAArray) <= class="num">0 ||
       CopyBuffer(GDC_slowEMAHandle, class="num">0, class="num">0, class="num">2, slowEMAArray) <= class="num">0)
   {
       Print("Failed to copy EMA data for Golden Death Cross. Error: ", GetLastError());
       class="kw">return;
   }
   class="type">bool hasPosition = PositionSelect(_Symbol);
   if(!hasPosition)
   {
       if(fastEMAArray[class="num">0] > slowEMAArray[class="num">0] && fastEMAArray[class="num">1] <= slowEMAArray[class="num">1]) class=class="str">"cmt">// Death Cross 
       {
           class="type">class="kw">double sl = NormalizeDouble(ask + GDC_StopLossPips * point, _Digits);
           if(!trade.Sell(GDC_LotSize, _Symbol, ask, sl ))
              Print("Sell order error for Golden Death Cross ", GetLastError());
           else
              Print("Sell order opened for Golden Death Cross with SL ", GDC_StopLossPips, " pips");
       }
       else if(fastEMAArray[class="num">0] < slowEMAArray[class="num">0] && fastEMAArray[class="num">1] >= slowEMAArray[class="num">1]) class=class="str">"cmt">// Golden Cross
       {
           class="type">class="kw">double sl = NormalizeDouble(bid - GDC_StopLossPips * point, _Digits);
           if(!trade.Buy(GDC_LotSize, _Symbol, bid, sl ))
              Print("Buy order error for Golden Death Cross ", GetLastError());
           else
              Print("Buy order opened for Golden Death Cross with SL ", GDC_StopLossPips, " pips");
       }
   }
   else
   {
       if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && fastEMAArray[class="num">0] < slowEMAArray[class="num">0]) ||

用 RSI 与双 EMA 做趋势跟随的开平仓

趋势跟随模块里,先卡一道仓位闸门:同策略持仓达到 2 单就直接 return,避免在同一方向堆叠风险敞口。外汇与贵金属杠杆高,这种硬上限能防止回撤被单策略拖爆。 RSI 取值只用最近一根 K 线:CopyBuffer(rsi_handle,0,0,1,rsi_values) 拿不到就打印错误并退出。紧接着用 iMA 取 50 周期与 200 周期收盘价 EMA,短线上穿长线判为多头,下穿判为空头。 多头分支要求 rsi_value < RSI_Oversold(超卖),此时用 SymbolInfoDouble 取 BID 价,止损挂在 currentPrice - StopLoss*_Point,止盈在 currentPrice + TakeProfit*_Point,再调 trade.Buy 下市价多单。空头对称:rsi_value > RSI_Overbought 时取 ASK 价,止损加、止盈减。 前面金死叉平仓段的逻辑是:若持空单且快 EMA 上穿慢 EMA,取 ticket 后调 trade.PositionClose 平掉,失败打印错误码,成功打印 ticket。把这两段拼进 EA,开 MT5 切 EURUSD 五分钟图就能验证信号触发频率。

MQL5 / C++
   (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && fastEMAArray[class="num">0] > slowEMAArray[class="num">0]))
   {
      class="type">ulong ticket = PositionGetInteger(POSITION_TICKET);
      if(!trade.PositionClose(ticket))
         Print("Failed to close position for Golden Death Cross(Ticket: ", ticket, "). Error: ", GetLastError());
      else
         Print("Position closed for Golden Death Cross: ", ticket);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check Trend Following Strategy                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckTrendFollowing()
{
   if (PositionsTotal() >= class="num">2) class="kw">return; class=class="str">"cmt">// Ensure no more than class="num">2 orders from this strategy
   class="type">class="kw">double rsi_value;
   class="type">class="kw">double rsi_values[];
   if (CopyBuffer(rsi_handle, class="num">0, class="num">0, class="num">1, rsi_values) <= class="num">0)
   {
      Print("Failed to get RSI value. Error: ", GetLastError());
      class="kw">return;
   }
   rsi_value = rsi_values[class="num">0];
   class="type">class="kw">double ma_short = iMA(_Symbol, PERIOD_CURRENT, class="num">50, class="num">0, MODE_EMA, PRICE_CLOSE);
   class="type">class="kw">double ma_long = iMA(_Symbol, PERIOD_CURRENT, class="num">200, class="num">0, MODE_EMA, PRICE_CLOSE);
   class="type">bool is_uptrend = ma_short > ma_long;
   class="type">bool is_downtrend = ma_short < ma_long;
   if (is_uptrend && rsi_value < RSI_Oversold)
   {
      class="type">class="kw">double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      class="type">class="kw">double stopLossPrice = currentPrice - StopLoss * _Point;
      class="type">class="kw">double takeProfitPrice = currentPrice + TakeProfit * _Point;
      class=class="str">"cmt">// Corrected Buy method call with class="num">6 parameters
      if (trade.Buy(Lots, _Symbol, class="num">0, stopLossPrice, takeProfitPrice, "Trend Following Buy"))
      {
         Print("Trend Following Buy order placed.");
      }
   }
   else if (is_downtrend && rsi_value > RSI_Overbought)
   {
      class="type">class="kw">double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      class="type">class="kw">double stopLossPrice = currentPrice + StopLoss * _Point;
      class="type">class="kw">double takeProfitPrice = currentPrice - TakeProfit * _Point;

◍ 突破策略的持仓上限与唐奇安通道判定

在 MT5 里跑多策略 EA 时,给单一策略设持仓上限能避免账户被同逻辑订单刷屏。上面这段代码用 PositionsTotal() >= 2 直接 return,意味着突破策略最多只留 2 张同标的持仓,超出就跳过本轮信号。 判定用的是日线昨日的实体方向叠加当前价对唐奇安通道的突破:先取 D1 周期前一根 K 线的开收盘,lastClose > lastOpen 记为阳线日,反之为阴线日;再用 CopyBuffer 把通道上轨 ExtUpBuffer、下轨 ExtDnBuffer 各取最近 2 根,若读取失败就打印 GetLastError() 并退出。 阳线日且现价 closePrice > ExtUpBuffer[1],按 closePrice 加减 pipsToStopLoss / pipsToTakeProfit 倍的 _Point 挂 Buy;阴线日且现价跌破 ExtDnBuffer[1] 则反向 Sell。外汇与贵金属杠杆高,通道假突破概率不低,这套逻辑只解决「何时开」,止损幅度仍需你按品种波动率手调。

MQL5 / C++
if (trade.Sell(Lots, _Symbol, class="num">0, stopLossPrice, takeProfitPrice, "Trend Following Sell"))
{
   Print("Trend Following Sell order placed.");
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check Breakout Strategy                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckBreakoutTrading()
{
   if (PositionsTotal() >= class="num">2) class="kw">return; class=class="str">"cmt">// Ensure no more than class="num">2 orders from this strategy
   ArrayResize(ExtUpBuffer, class="num">2);
   ArrayResize(ExtDnBuffer, class="num">2);
   if (CopyBuffer(handle, class="num">0, class="num">0, class="num">2, ExtUpBuffer) <= class="num">0 || CopyBuffer(handle, class="num">2, class="num">0, class="num">2, ExtDnBuffer) <= class="num">0)
   {
      Print("Error reading Donchian Channel buffer. Error: ", GetLastError());
      class="kw">return;
   }
   class="type">class="kw">double closePrice = iClose(_Symbol, PERIOD_CURRENT, class="num">0);
   class="type">class="kw">double lastOpen = iOpen(_Symbol, PERIOD_D1, class="num">1);
   class="type">class="kw">double lastClose = iClose(_Symbol, PERIOD_D1, class="num">1);
   class="type">bool isBullishDay = lastClose > lastOpen;
   class="type">bool isBearishDay = lastClose < lastOpen;
   if (isBullishDay && closePrice > ExtUpBuffer[class="num">1])
   {
      class="type">class="kw">double stopLoss = closePrice - pipsToStopLoss * _Point;
      class="type">class="kw">double takeProfit = closePrice + pipsToTakeProfit * _Point;
      if (trade.Buy(LotSize, _Symbol, class="num">0, stopLoss, takeProfit, "Breakout Buy") > class="num">0)
      {
         Print("Breakout Buy order placed.");
      }
   }
   else if (isBearishDay && closePrice < ExtDnBuffer[class="num">1])
   {
      class="type">class="kw">double stopLoss = closePrice + pipsToStopLoss * _Point;
      class="type">class="kw">double takeProfit = closePrice - pipsToTakeProfit * _Point;
      if (trade.Sell(LotSize, _Symbol, class="num">0, stopLoss, takeProfit, "Breakout Sell") > class="num">0)
      {
         Print("Breakout Sell order placed.");
      }
   }
}

「MACD 背离的四种判定与开仓闸门」

MT5 里做 MACD 背离,核心是把价格和指标谷/峰做反向比较。下面四个布尔函数分别覆盖常规看涨、隐藏看涨、常规看跌、隐藏看跌,索引 2 与 DivergenceLookBack 是两段比对的距离锚点。 常规看涨要求价格新低(priceLow1 < priceLow2)而 MACD 未新低(macdLow1 > macdLow2);隐藏看涨则反过来,价格未新低而 MACD 新低。看跌两组对称处理高点。 CheckDivergenceTrading 是实际触发闸门:先由 UseDivergenceStrategy 总开关拦截;再用 CountOrdersByMagic 数同魔术码持仓,允许空仓或少于 3 仓才继续。 数据充足性检查很硬:barsAvailable 必须 ≥ DivergenceLookBack * 2,否则打印缺数据直接 return。随后 while(attempt < 6) 最多重试 6 次 CopyBuffer 拉取 MACD 主线与信号线到 ExtMacdBuffer / ExtSignalBuffer,任一失败就不判定。 外汇与贵金属杠杆高,背离只是概率信号,实盘前请在 MT5 策略测试器用对应品种历史数据验证 DivergenceLookBack 与魔术码过滤是否如预期。

MQL5 / C++
class="type">bool CheckBullishRegularDivergence()
{
   class="type">class="kw">double priceLow1 = iLow(_Symbol, PERIOD_CURRENT, class="num">2);
   class="type">class="kw">double priceLow2 = iLow(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
   class="type">class="kw">double macdLow1 = ExtMacdBuffer[class="num">2];
   class="type">class="kw">double macdLow2 = ExtMacdBuffer[DivergenceLookBack - class="num">1];
   class="kw">return (priceLow1 < priceLow2 && macdLow1 > macdLow2);
}
class="type">bool CheckBullishHiddenDivergence()
{
   class="type">class="kw">double priceLow1 = iLow(_Symbol, PERIOD_CURRENT, class="num">2);
   class="type">class="kw">double priceLow2 = iLow(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
   class="type">class="kw">double macdLow1 = ExtMacdBuffer[class="num">2];
   class="type">class="kw">double macdLow2 = ExtMacdBuffer[DivergenceLookBack - class="num">1];
   class="kw">return (priceLow1 > priceLow2 && macdLow1 < macdLow2);
}
class="type">bool CheckBearishRegularDivergence()
{
   class="type">class="kw">double priceHigh1 = iHigh(_Symbol, PERIOD_CURRENT, class="num">2);
   class="type">class="kw">double priceHigh2 = iHigh(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
   class="type">class="kw">double macdHigh1 = ExtMacdBuffer[class="num">2];
   class="type">class="kw">double macdHigh2 = ExtMacdBuffer[DivergenceLookBack - class="num">1];
   class="kw">return (priceHigh1 > priceHigh2 && macdHigh1 < macdHigh2);
}
class="type">bool CheckBearishHiddenDivergence()
{
   class="type">class="kw">double priceHigh1 = iHigh(_Symbol, PERIOD_CURRENT, class="num">2);
   class="type">class="kw">double priceHigh2 = iHigh(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
   class="type">class="kw">double macdHigh1 = ExtMacdBuffer[class="num">2];
   class="type">class="kw">double macdHigh2 = ExtMacdBuffer[DivergenceLookBack - class="num">1];
   class="kw">return (priceHigh1 < priceHigh2 && macdHigh1 > macdHigh2);
}
class="type">void CheckDivergenceTrading()
{
   if (!UseDivergenceStrategy) class="kw">return;
   class=class="str">"cmt">// Check if no position is open or if less than class="num">3 positions are open
   class="type">int openDivergencePositions = CountOrdersByMagic(DivergenceMagicNumber);
   if (openDivergencePositions == class="num">0 || openDivergencePositions < class="num">3)
   {
      class="type">int barsAvailable = Bars(_Symbol, PERIOD_CURRENT);
      if (barsAvailable < DivergenceLookBack * class="num">2)
      {
         Print("Not enough data bars for MACD calculation.");
         class="kw">return;
      }
      class="type">int attempt = class="num">0;
      while(attempt < class="num">6)
      {
         if (CopyBuffer(macd_handle, class="num">0, class="num">0, DivergenceLookBack, ExtMacdBuffer) > class="num">0 &&
             CopyBuffer(macd_handle, class="num">1, class="num">0, DivergenceLookBack, ExtSignalBuffer) > class="num">0)

日线方向卡住背离下单口子

这段逻辑先把 MACD 缓冲区拷贝失败的情况兜住:最多重试 5 次(attempt 从 0 累加到 5,第 6 次直接 return 并打印失败),每次间隔 Sleep(1000) 即 1 秒,避免刚连上服务器就因数据没到位崩策略。 若当前时间等于当前周期 0 号 K 线的开盘时间,说明这根 bar 还没收,直接跳过不交易,防止拿不完整数据去判背离。 取当前价 close、日线 close 与 open,用 dailyClose 与 dailyOpen 的大小判定 D1 多空:只有 D1 收阳才允许走买入分支,D1 收阴才允许走卖出分支。 买入分支里,普通看涨背离叠加 MACD 主线在信号线上方,或隐藏看涨背离成立,才调 ExecuteDivergenceOrder(true);卖出分支对称处理。外汇与贵金属杠杆高,这种多周期过滤只能降噪,不保证胜率。 ExecuteDivergenceOrder 开头先用 trade.SetExpertMagicNumber 钉死背离魔术号,再按买卖取 SYMBOL_ASK 或 SYMBOL_BID 作为当前价,方便后面挂单区分策略实例。

MQL5 / C++
         break;
         
         Print("Failed to copy MACD buffer, retrying...");
         Sleep(class="num">1000);
         attempt++;
         }
         if(attempt == class="num">6)
         {
            Print("Failed to copy MACD buffers after ", attempt, " attempts.");
            class="kw">return;
         }
         if(TimeCurrent() == iTime(_Symbol, PERIOD_CURRENT, class="num">0))
         {
            Print("Skipping trade due to incomplete bar data.");
            class="kw">return;
         }
         class="type">class="kw">double currentClose = iClose(_Symbol, PERIOD_CURRENT, class="num">0);
         class="type">class="kw">double dailyClose = iClose(_Symbol, PERIOD_D1, class="num">0);
         class="type">class="kw">double dailyOpen = iOpen(_Symbol, PERIOD_D1, class="num">0);
         class="type">bool isDailyBullish = dailyClose > dailyOpen;
         class="type">bool isDailyBearish = dailyClose < dailyOpen;
         class=class="str">"cmt">// Only proceed with buy orders if D1 is bullish
         if (isDailyBullish)
         {
            if ((CheckBullishRegularDivergence() && ExtMacdBuffer[class="num">0] > ExtSignalBuffer[class="num">0]) ||
               CheckBullishHiddenDivergence())
            {
               ExecuteDivergenceOrder(true);
            }
         }
         class=class="str">"cmt">// Only proceed with sell orders if D1 is bearish
         if (isDailyBearish)
         {
            if ((CheckBearishRegularDivergence() && ExtMacdBuffer[class="num">0] < ExtSignalBuffer[class="num">0]) ||
               CheckBearishHiddenDivergence())
            {
               ExecuteDivergenceOrder(false);
            }
         }
      }
      else
      {
         Print("Divergence strategy: Maximum number of positions reached.");
      }
}
class="type">void ExecuteDivergenceOrder(class="type">bool isBuy)
{
   class=class="str">"cmt">// Ensure the magic number is set for the trade
   trade.SetExpertMagicNumber(DivergenceMagicNumber);
   
   class="type">class="kw">double currentPrice = isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);

◍ 背离信号的订单下发与利润锁定

背离策略触发后,下单逻辑用一行三目运算就切清了多空止损位:买单止损在 currentPrice 下方 DivergenceStopLoss*_Point,卖单反之;止盈则对称摆在另一侧。下面这段是 MT5 里实跑的下单与统计代码,直接拷进 EA 的 OnTick 之后就能验证。

MQL5 / C++
class="type">class="kw">double stopLossPrice = isBuy ? currentPrice - DivergenceStopLoss * _Point : currentPrice + DivergenceStopLoss * _Point;
class="type">class="kw">double takeProfitPrice = isBuy ? currentPrice + DivergenceTakeProfit * _Point : currentPrice - DivergenceTakeProfit * _Point;
if (isBuy)
{
  if (trade.Buy(DivergenceLots, _Symbol, class="num">0, stopLossPrice, takeProfitPrice, "Divergence Buy"))
  {
    Print("Divergence Buy order placed.");
  }
}
else
{
  if (trade.Sell(DivergenceLots, _Symbol, class="num">0, stopLossPrice, takeProfitPrice, "Divergence Sell"))
  {
    Print("Divergence Sell order placed.");
  }
}
}
class="type">int CountOrdersByMagic(class="type">int magic)
{
  class="type">int count = class="num">0;
  for (class="type">int i = class="num">0; i < PositionsTotal(); i++)
  {
    class="type">ulong ticket = PositionGetTicket(i);
    if (PositionSelectByTicket(ticket))
    {
      if (PositionGetInteger(POSITION_MAGIC) == magic)
      {
        count++;
      }
    }
  }
  class="kw">return count;
}
class="type">void LockProfits()
{
  for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
  {
    class="type">ulong ticket = PositionGetTicket(i);
    if (PositionSelectByTicket(ticket))
    {
      class="type">class="kw">double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      class="type">class="kw">double currentProfit = PositionGetDouble(POSITION_PROFIT);
      class="type">class="kw">double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
      class="type">class="kw">double profitPoints = MathAbs(currentProfit / _Point);
      if (profitPoints >= class="num">100)
      {
        class="type">class="kw">double newStopLoss;
逐行看:stopLossPrice / takeProfitPrice 按 _Point 缩放,避免点值误差;trade.Buy/Sell 第 3 参填 0 代表市价单,滑点交给 broker 控制。CountOrdersByMagic 遍历 PositionsTotal,用 POSITION_MAGIC 过滤同一策略持仓,回测时你能立刻数出当前魔法码下开了几单。 LockProfits 从持仓列表尾部倒序遍历,取 POSITION_PROFIT 除以 _Point 得绝对点数,阈值写死 100 点。外汇与贵金属波动剧烈,100 点可能十几分钟就到,也可能扫不掉;这个锁利触发线只是示例,实盘应按品种 ATR 重算。把代码里 100 改成变量,接小布盯盘的 ATR 输出,就能让锁利位随波动率呼吸。

MQL5 / C++
class="type">class="kw">double stopLossPrice = isBuy ? currentPrice - DivergenceStopLoss * _Point : currentPrice + DivergenceStopLoss * _Point;
class="type">class="kw">double takeProfitPrice = isBuy ? currentPrice + DivergenceTakeProfit * _Point : currentPrice - DivergenceTakeProfit * _Point;
if (isBuy)
{
  if (trade.Buy(DivergenceLots, _Symbol, class="num">0, stopLossPrice, takeProfitPrice, "Divergence Buy"))
  {
    Print("Divergence Buy order placed.");
  }
}
else
{
  if (trade.Sell(DivergenceLots, _Symbol, class="num">0, stopLossPrice, takeProfitPrice, "Divergence Sell"))
  {
    Print("Divergence Sell order placed.");
  }
}
}
class="type">int CountOrdersByMagic(class="type">int magic)
{
  class="type">int count = class="num">0;
  for (class="type">int i = class="num">0; i < PositionsTotal(); i++)
  {
    class="type">ulong ticket = PositionGetTicket(i);
    if (PositionSelectByTicket(ticket))
    {
      if (PositionGetInteger(POSITION_MAGIC) == magic)
      {
        count++;
      }
    }
  }
  class="kw">return count;
}
class="type">void LockProfits()
{
  for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
  {
    class="type">ulong ticket = PositionGetTicket(i);
    if (PositionSelectByTicket(ticket))
    {
      class="type">class="kw">double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      class="type">class="kw">double currentProfit = PositionGetDouble(POSITION_PROFIT);
      class="type">class="kw">double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
      class="type">class="kw">double profitPoints = MathAbs(currentProfit / _Point);
      if (profitPoints >= class="num">100)
      {
        class="type">class="kw">double newStopLoss;

「盈利锁仓的止损平移逻辑」

这段逻辑干的事很直接:根据持仓方向,把止损位推到入场价上方(多单)或下方(空单)固定点数处,实现浮盈保护。代码里 profitLockerPoints 设为 20,意味着多单止损 = 入场价 + 20*_Point,空单止损 = 入场价 - 20*_Point,点值随品种变动。 修改前先比对当前 SL:多单只在 newStopLoss 比原 SL 更高(或原 SL 为 0)时才改,空单则只在 newStopLoss 比原 SL 更低(或原 SL 为 0)时才改。这样保证止损只会往「更锁利润」的方向走,不会回撤保护空间。 非买卖持仓直接 continue 跳过,避免异常单干扰。外汇与贵金属杠杆高,这类自动平移若参数过窄,可能被噪声扫损,建议在 MT5 用策略测试器按品种点值验证 20 点间距是否合适。

MQL5 / C++
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
   newStopLoss = entryPrice + profitLockerPoints * _Point; class=class="str">"cmt">// class="num">20 points above entry for buys
}
else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
   newStopLoss = entryPrice - profitLockerPoints * _Point; class=class="str">"cmt">// class="num">20 points below entry for sells
}
else
{
   class="kw">continue; class=class="str">"cmt">// Skip if not a buy or sell position
}
class=class="str">"cmt">// Modify stop loss only if the new stop loss is more protective
class="type">class="kw">double currentStopLoss = PositionGetDouble(POSITION_SL);
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
   if (currentStopLoss < newStopLoss || currentStopLoss == class="num">0)
   {
      if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
      {
         Print("Profit locking for buy position: Stop Loss moved to ", newStopLoss);
      }
   }
}
else class=class="str">"cmt">// POSITION_TYPE_SELL
{
   if (currentStopLoss > newStopLoss || currentStopLoss == class="num">0)
   {
      if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
      {
         Print("Profit locking for sell position: Stop Loss moved to ", newStopLoss);
      }
   }
}

把多策略塞进同一根K线

这段 OnTick 骨架展示了趋势跟随、突破、背离三类策略如何共用一个 tick 入口。被高亮的那段才是关键:当 UseGoldenDeathCrossStrategy 开关打开,金叉死叉逻辑通过 CheckGoldenDeathCross() 被并入同一调度循环,不再独立跑 EA。 外汇与贵金属杠杆高,多策略同周期触发可能放大回撤概率,实盘前务必在策略测试器隔离验证。把 EA 以默认参数拖到图表后,可在可视化模式下观察金叉死叉信号是否与其他策略的建仓区重叠——若重叠频繁,说明仓位冲突风险倾向上升。 想立刻验证,复制下面代码挂到 MT5 测试器,只开 UseGoldenDeathCrossStrategy=true 跑一轮,再看信号密度。

MQL5 / C++
class="type">void OnTick()
{
   if (UseTrendFollowingStrategy)
      CheckTrendFollowing();
   if (UseBreakoutStrategy)
      CheckBreakoutTrading();
   if (UseDivergenceStrategy)
      CheckDivergenceTrading();
   if(UseGoldenDeathCrossStrategy)
      CheckGoldenDeathCross();
}

◍ 从教学 EA 走向机器学习优化

把金叉死叉塞进趋势约束型 EA 后,代码体量从最初的数百行涨到了 Trend_Constraint_Expert.mq5 的 20.32 KB,系统资源占用随逻辑分支增加明显抬升。继续堆功能会让维护成本失控,这时候把 AI 模型接进来处理多约束判断,比手写嵌套 if 更扛复杂度和扩展压力。 这套 EA 只是教学与实验底座,不保证任何账户盈利,外汇与贵金属杠杆品种的高风险始终存在。后续若引入机器学习做参数自适应,建议先拿 GoldenDeathCross.mq5(4.62 KB)做孤立回测,再合并进主框架,避免一次动太多结构导致无法定位 bug。

让小布替你跑这套交叉诊断
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到均线交叉与趋势约束的实时吻合度,把重复劳动交给小布,你专注决策。

常见问题

倾向结合成交量或动量指标做二次确认,并在MQL5中设置趋势约束条件,仅在主趋势背景允许时触发交叉信号,降低震荡市损耗。
可能需按品种波动率压缩周期,但核心逻辑不变;高阶做法是用自适应周期,避免固定参数在贵金属跳空时失效。
交叉信号作为反转触发层,趋势约束作为准入层,只有约束放行后交叉才下单,确保EA不逆着强趋势接飞刀。
可以,小布的品种页已内置交叉监测与趋势约束提示,你不必自己写脚本也能先肉眼验证策略逻辑再决定是否编码。
高风险在于重大数据行情的滑点可能让交叉信号成交价偏离,回测漂亮实盘吃瘪,需加滑点容忍与时段屏蔽。