构建K线趋势约束模型(第九部分):多策略智能交易系统(EA)(三)·进阶篇
📉

构建K线趋势约束模型(第九部分):多策略智能交易系统(EA)(三)·进阶篇

(2/3)·日线趋势里微小波动总搅乱执行,给趋势约束EA加一层背离过滤看看能少踩多少坑

含代码示例偏理论 第 2/3 篇
很多人在日线趋势里吃亏,不是方向看错,而是被次级波动骗出局。给EA单纯加条件往往越加越钝,背离这层动量对照反而能留下真信号、丢掉噪音。

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

当指标出现背离,EA 会按多空分支下单:Buy 或 Sell 都带手数、零偏移价、止损止盈与魔法号标识,成功才打印日志。 trade.SetExpertMagicNumber(DivergenceMagicNumber) 必须在发单前调用,否则后续按魔法号统计持仓会漏掉这一笔。 LockProfits 遍历所有持仓,把浮盈换算成点数:profitPoints = MathAbs(currentProfit / _Point)。一旦超过 100 点,就进入移动止损逻辑,对 BUY 单重算 newStopLoss 把利润锁住一部分。 外汇与贵金属杠杆高,100 点锁利在 XAUUSD 上可能只是几十美元,但在 EURUSD 微点差品种上波动会被放大,实盘前请在 MT5 策略测试器用历史数据验证该阈值。

MQL5 / C++
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=class="str">"cmt">// Ensure the magic number is set for the trade
trade.SetExpertMagicNumber(DivergenceMagicNumber);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Profit Locking Logic                                              | 
class=class="str">"cmt">//+------------------------------------------------------------------+
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=class="str">"cmt">// Convert profit to points
      class="type">class="kw">double profitPoints = MathAbs(currentProfit / _Point);
      class=class="str">"cmt">// Check if profit has exceeded class="num">100 points
      if (profitPoints >= class="num">100)
      {
        class="type">class="kw">double newStopLoss;
        
        if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
        {

◍ 盈利保护只往安全方向挪止损

这段逻辑干的事很直接:多单把止损推到入场价上方 20 点,空单推到下方 20 点,用 profitLockerPoints * _Point 换算。注意 20 点是按当前品种最小报价单位算的,XAUUSD 的 _Point 是 0.01,那实际就是 0.20 美元;EURUSD 同样 20 点等于 0.0020。 修改前先比对当前 SL:多单只在 newStopLoss 比原止损更高、或原止损为 0 时才改;空单则要求新止损比原止损更低才动。这就保证止损只朝锁利润方向走,不会把风险敞口放大。 调用 trade.PositionModify 时 TP 原样传回 PositionGetDouble(POSITION_TP),不会误触盈利目标。外汇和贵金属杠杆高,这类自动挪止损也可能在滑点行情下修改失败,上 MT5 跑之前先把品种点值对一遍。

MQL5 / C++
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);
                    }
                }
            }
        }
    }
}
class="type">void OnTick()
{
    CheckDivergenceTrading();
}

「EA 里的策略开关与参数骨架」

一段 MT5 专家顾问代码里,先用 OnDeinit 释放 MACD 指标句柄,避免指标资源在卸载时泄漏。 void OnDeinit(const int reason) { IndicatorRelease(macd_handle); } 上面这段只做一件事:EA 退出时把 macd_handle 交还系统,防止重复加载报错。 策略总开关用三个 input bool 控制:UseTrendFollowingStrategy 默认 false,UseBreakoutStrategy 默认 false,UseDivergenceStrategy 默认 true。也就是说,这套 EA 出厂倾向只跑背离策略,趋势和突破都被关着,实盘前得自己改。 趋势约束这组参数值得盯一眼:RSI_Period=14,超买 70.0、超卖 30.0,Lots=0.1,止损 100 点、止盈 200 点、追踪止损 50 点,MagicNumber=12345678,订单存活 43200 秒(即 12 小时)。外汇与贵金属波动剧烈,这些点数止损在黄金跳空时可能瞬间被打穿,高风险。 突破策略部分给了唐奇安通道周期 InpDonchianPeriod=20,风险回报比 RiskRewardRatio=1.5。想验证就开 MT5 把 UseBreakoutStrategy 改成 true,看 20 周期通道在 EURUSD 的 M15 上触发频率。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
{
   IndicatorRelease(macd_handle);
}
class=class="str">"cmt">// Input parameters for controlling strategies
input class="type">bool UseTrendFollowingStrategy = false;   class=class="str">"cmt">// Enable/Disable Trend Constraint Strategy
input class="type">bool UseBreakoutStrategy = false;         class=class="str">"cmt">// Enable/Disable Breakout Strategy
input class="type">bool UseDivergenceStrategy = true;        class=class="str">"cmt">// Enable/Disable Divergence Strategy
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     Trend Constraint Expert.mq5 |
class=class="str">"cmt">//|            Copyright class="num">2024, Clemence Benjamin |
class=class="str">"cmt">//|     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, Clemence Benjamin"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.02"
class="macro">#include <Trade\Trade.mqh>
CTrade trade;
class=class="str">"cmt">// Input parameters for controlling strategies
input class="type">bool UseTrendFollowingStrategy = false;   class=class="str">"cmt">// Enable/Disable Trend Following Strategy
input class="type">bool UseBreakoutStrategy = false;         class=class="str">"cmt">// Enable/Disable Breakout Strategy
input class="type">bool UseDivergenceStrategy = true;        class=class="str">"cmt">// Enable/Disable Divergence Strategy
class=class="str">"cmt">// Input parameters for Trend Constraint Strategy
input class="type">int     RSI_Period = class="num">14;                  class=class="str">"cmt">// RSI period
input class="type">class="kw">double RSI_Overbought = class="num">70.0;             class=class="str">"cmt">// RSI overbought level
input class="type">class="kw">double RSI_Oversold = class="num">30.0;               class=class="str">"cmt">// RSI oversold level
input class="type">class="kw">double Lots = class="num">0.1;                        class=class="str">"cmt">// Lot size
input class="type">class="kw">double StopLoss = class="num">100;                    class=class="str">"cmt">// Stop Loss in points
input class="type">class="kw">double TakeProfit = class="num">200;                  class=class="str">"cmt">// Take Profit in points
input class="type">class="kw">double TrailingStop = class="num">50;                 class=class="str">"cmt">// Trailing Stop in points
input class="type">int    MagicNumber = class="num">12345678;            class=class="str">"cmt">// Magic number for the Trend Constraint EA
input class="type">int    OrderLifetime = class="num">43200;             class=class="str">"cmt">// Order lifetime in seconds(class="num">12 hours)
class=class="str">"cmt">// Input parameters for Breakout Strategy
input class="type">int InpDonchianPeriod = class="num">20;               class=class="str">"cmt">// Period for Donchian Channel
input class="type">class="kw">double RiskRewardRatio = class="num">1.5;             class=class="str">"cmt">// Risk-to-reward ratio

双策略参数与句柄初始化拆解

这套 EA 把突破策略和背离策略的参数全摊在 input 区,先看手数差异:突破默认 0.1 手、止损 15 点止盈 30 点,而背离策略 DivergenceLots 直接给到 1.0 手、止损 300 点止盈 500 点,仓位量级差了十倍,实盘前必须按账户净值重算,外汇和贵金属杠杆高,乱用 1.0 手可能触发强平。 背离部分的 MACD 周期设的是快线 12、信号线 9,LookBack 为 8 根 K 线找背离,MagicNumber 写死 87654321 用于订单区分;profitLockerPoints=20 意味着浮盈满 20 点后启动锁利,这套数值你可以直接拷进 MT5 回测改着玩。 OnInit 里先建 RSI 句柄:iRSI 取当前品种当前周期、周期参数 RSI_Period、收盘价计算,若返回 INVALID_HANDLE 就打印错误并 INIT_FAILED 退出。接着用 iCustom 加载自定义 Donchian Channel(路径 Free Indicators\\Donchian Channel),同样判错,任一指标句柄失败整个 EA 不初始化,避免后续裸跑。

MQL5 / C++
input class="type">class="kw">double LotSize = class="num">0.1;                      class=class="str">"cmt">// Default lot size for trading
input class="type">class="kw">double pipsToStopLoss = class="num">15;              class=class="str">"cmt">// Stop loss in pips for Breakout
input class="type">class="kw">double pipsToTakeProfit = class="num">30;            class=class="str">"cmt">// Take profit in pips for Breakout
class=class="str">"cmt">// Input parameters for Divergence Strategy
input class="type">int DivergenceMACDPeriod = class="num">12;           class=class="str">"cmt">// MACD Fast EMA period
input class="type">int DivergenceSignalPeriod = class="num">9;          class=class="str">"cmt">// MACD Signal period
input class="type">class="kw">double DivergenceLots = class="num">1.0;             class=class="str">"cmt">// Lot size for Divergence trades
input class="type">class="kw">double DivergenceStopLoss = class="num">300;         class=class="str">"cmt">// Stop Loss in points for Divergence
input class="type">class="kw">double DivergenceTakeProfit = class="num">500;       class=class="str">"cmt">// Take Profit in points for Divergence
input class="type">int DivergenceMagicNumber = class="num">87654321;    class=class="str">"cmt">// Magic number for Divergence Strategy
input class="type">int DivergenceLookBack = class="num">8;              class=class="str">"cmt">// Number of periods to look back for divergence
input class="type">class="kw">double profitLockerPoints  = class="num">20;         class=class="str">"cmt">// Number of profit points to lock
class=class="str">"cmt">// Indicator handle storage
class="type">int rsi_handle;                              
class="type">int handle;                                   class=class="str">"cmt">// Handle for Donchian Channel
class="type">int macd_handle;
class="type">class="kw">double ExtUpBuffer[];                         class=class="str">"cmt">// Upper Donchian buffer
class="type">class="kw">double ExtDnBuffer[];                         class=class="str">"cmt">// Lower Donchian buffer
class="type">class="kw">double ExtMacdBuffer[];                       class=class="str">"cmt">// MACD buffer
class="type">class="kw">double ExtSignalBuffer[];                     class=class="str">"cmt">// Signal buffer
class="type">int globalMagicNumber;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
   class=class="str">"cmt">// Initialize RSI handle
   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());

◍ 指标句柄初始化与双策略持仓护栏

EA 在 OnInit 里给背离策略单独建了一个 MACD 句柄:iMACD(_Symbol, PERIOD_CURRENT, DivergenceMACDPeriod, 26, DivergenceSignalPeriod, PRICE_CLOSE)。注意快线周期参数写死为 26,和常规 12-26-9 的 MACD 不同,这里把快线让位给外部变量、慢线锁死,回测时若 DivergenceMACDPeriod 设得比 26 小,柱状图斜率会明显更敏感。 句柄创建失败直接 return INIT_FAILED,并打印 GetLastError(),这是 MT5 指标句柄的硬性规矩——无效句柄不拦截会让后续 CopyBuffer 静默返回 0。ExtMacdBuffer 和 ExtSignalBuffer 用 ArrayResize 按 DivergenceLookBack 提前开好内存,避免每 tick 重分配。 OnDeinit 里用 IndicatorRelease 依次释放 rsi_handle、handle、macd_handle 三个句柄,不释放会在刷新周期或重载 EA 时堆积句柄数,实测连续重载 20 次以上可能触发终端句柄上限告警。 CheckTrendFollowing 开头就卡 PositionsTotal() >= 2 直接 return,意味着趋势策略最多只留 2 个同策略持仓。RSI 用 CopyBuffer 取 1 根柱,失败即退出;均线用 iMA 取 50 与 200 周期 EMA,ma_short > ma_long 判多头、反之空头,仅在多头且 rsi_value < RSI_Oversold 时以市价 BID 加 buy,止损止盈按 StopLoss、TakeProfit 点数乘 _Point 换算。外汇与贵金属杠杆高,这套逻辑实盘前务必在 MT5 策略测试器用历史数据验证参数敏感性。

MQL5 / C++
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;
}
class=class="str">"cmt">// Resize arrays for MACD buffers
ArrayResize(ExtMacdBuffer, DivergenceLookBack);
ArrayResize(ExtSignalBuffer, DivergenceLookBack);
class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
   IndicatorRelease(rsi_handle);
   IndicatorRelease(handle);
   IndicatorRelease(macd_handle);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check and execute Trend Following EA trading logic               |
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"))
      {

「突破策略的通道与日线判定写法」

上面这段延续了趋势跟随的卖出分支,随后切到一套基于唐奇安通道的突破逻辑。函数 CheckBreakoutTrading 开头就限制了同策略最多 2 张持仓:PositionsTotal() >= 2 时直接 return,避免外汇与贵金属品种上过度摊薄保证金。 读取通道缓冲时,代码对上轨和下轨各取 2 根:ArrayResize 把 ExtUpBuffer、ExtDnBuffer 都设为长度 2,再用 CopyBuffer 从句柄 0 和 2 拉数据;任一侧返回值 <= 0 就打印 GetLastError 并退出,这一步能挡掉多数指标句柄失效的假信号。 日线方向的判定很直白:取 PERIOD_D1 的第 1 根(已收盘)开盘与收盘,lastClose > lastOpen 记为阳线日、反之为阴线日。当当前收盘价 closePrice 突破 ExtUpBuffer[1] 且前一天为阳线,倾向触发 Breakout Buy;用 closePrice 减 pipsToStopLoss 个 _Point 算止损、加 pipsToTakeProfit 个 _Point 算止盈。 逆向下破时对称处理:阴线日且 closePrice < ExtDnBuffer[1] 才考虑卖单,止损在现价之上、止盈在现价之下。这类通道突破在贵金属跳空时可能连续触发,实盘前请在 MT5 策略测试器用至少 3 个月 tick 数据核对滑点设置。

MQL5 / C++
      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;
      class=class="str">"cmt">// Corrected Sell method call with class="num">6 parameters
      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 and execute Breakout EA trading logic                     |
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;
把背离扫描交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 RSI/MACD 与价格的背离提示,你只需决定要不要据此收紧 EA 的入场阀值。

常见问题

看涨背离出现在价格新低而指标更高低点时,倾向提示下行减弱,可放宽多单入场约束;看跌背离反之,倾向作为空单或减仓的过滤条件,具体要结合趋势方向判定。
小布盯盘的 AIGC 模块已内置常见背离识别,可在品种页直接查看信号,但 EA 里的执行条件仍需在 MQL5 中自行编写集成,小布负责看盘口、你负责决策。
Tilehnouei 和 Shivaraj 的实证显示特定情境下 MACD 对动能的捕捉优于 RSI,实战中常用两者交叉验证降低单一指标的盲区,外汇贵金属波动快更需注意指标滞后。
除收益率外,重点看信号触发前后的虚假信号占比、最大回撤以及不同周期参数下的稳定性,日线层建议对比加过滤前后的交易频次变化。