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

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

(3/3)·日线趋势里微小波动总搅乱执行?给趋势约束EA加一层背离过滤,看回测怎么变

含代码示例偏理论 第 3/3 篇
在日线趋势里跑EA,最烦的不是没趋势,是价格微微一抖就触发入场。背离检测把价格走势和动量指标方向对着看,能提前筛掉一批要反转的假突破。本篇把这套过滤直接焊进趋势约束系统里。

◍ 用 MACD 缓冲区抓四类背离信号

背离策略的核心不是看指标面板,而是直接读 MACD 缓冲数组与价格序列的对应关系。下面这组函数把常规/隐藏、多头/空头背离全拆成可编译的布尔判断,你复制到 MT5 脚本里就能跑。 常规看涨背离要求 2 号 K 线低点低于回看窗口低点,但同位置 MACD 值反而更高,概率上暗示下行动能衰竭。隐藏看涨背离则反过来:价格低点抬高而 MACD 低点下移,倾向视为趋势中的回调买点。 空头侧逻辑镜像对称。常规看跌背离是价格高点创新高、MACD 高点却走低;隐藏看跌背离是价格高点走低、MACD 高点走高,可能用于趋势续跌中的反抽卖单。 实盘调用前注意 DivergenceLookBack 的取值。代码里用 barsAvailable < DivergenceLookBack * 2 做护栏,若当前品种 K 线数不足回看窗口两倍,直接打印缺数据并跳过,避免数组越界报错。

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.");

MACD缓冲读取与日线过滤的实战写法

在 MT5 的 EA 主逻辑里,MACD 的两条缓冲(主线与信号线)经常因为行情刚启动而暂时取不到。上面这段用 while 循环最多重试 6 次、每次 Sleep(1000) 再试,能把 CopyBuffer 返回小于等于 0 的瞬时失败吃掉,避免直接误杀整轮信号。 若 6 次都没拿到数据,Print 打出失败次数后 return,这一根 K 线就不再参与后续判断。这种「宁可错过、不要脏数据」的写法,在外汇与贵金属这种高波动品种里尤其重要,滑点与断流概率都不低。 接着用 TimeCurrent() == iTime(_Symbol, PERIOD_CURRENT, 0) 判断当前是不是还在走未闭合的 bar,是的话直接跳过——防止用半截价格去算背离。 方向过滤只看 D1:dailyClose > dailyOpen 才允许买侧背离逻辑,dailyClose < dailyOpen 才允许卖侧。多头里要常规看涨背离且主线在信号线上方,或隐藏看涨背离;空头镜像处理。这样把小周期背离锁在大周期趋势里,信号噪声会少一截。 开 MT5 把 DivergenceLookBack 设成 100 左右,挂上这段后观察日志:若频繁出现 'Failed to copy MACD buffer, retrying...' 超过每日 20 次,说明你的品种报价质量或 VPS 延迟可能拖了后腿。

MQL5 / C++
         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)
               class="kw">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

「背离信号的下单与利润锁定实现」

背离策略真正落地时,下单函数必须先绑定独立的 magic number,否则和手动单或其他 EA 单混在一起,后续统计和风控都会乱。下面这段 ExecuteDivergenceOrder 就是按买卖方向分别吃价、算止损止盈并发单的。 当前价用 SymbolInfoDouble 取 ASK 或 BID,止损止盈直接以 _Point 为最小单位乘以参数距离。比如 DivergenceStopLoss 设 500、DivergenceTakeProfit 设 1000,在 XAUUSD 这种 _Point=0.01 的品种上,止损就是 5 美元、盈利目标 10 美元距离——外汇和贵金属杠杆高,这种点数设定请先在策略测试器里跑一遍再上实盘。 CountOrdersByMagic 遍历 PositionsTotal 并按 POSITION_MAGIC 过滤,返回该魔法数下的持仓数。配合前面『最大持仓数』的 Print 提示,就能限制背离策略同方向加仓的泛滥。 LockProfits 从持仓列表尾部倒序遍历,取出开仓价、浮盈和当前价。它给每单独立判断锁定条件留下了接口——你可以在 currentProfit 超过某阈值时把止损挪到 entryPrice 上方(买)或下方(卖),把纸面利润变成不可回吐的部分。

MQL5 / C++
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);
   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);

◍ 盈利超百点后把止损挪到成本附近

浮盈护锁的常见做法是:当一笔持仓的未平利润换算成点数后达到某个阈值,就把止损位推到入场价附近,让交易至少不亏。下面这段代码用的阈值是 100 点,护锁偏移是 20 点(profitLockerPoints 变量控制),读者在 MT5 里把这两个数字改小改大,能直接看出触发频率和锁仓激进程度的变化。 代码先把 currentProfit 除以 _Point 取绝对值,得到 profitPoints;若 >=100 才进入挪止损逻辑。买仓新止损 = 入场价 + 20 点,卖仓新止损 = 入场价 - 20 点,相当于把止损挂在成本上方/下方 20 点处。 修改前会比对当前 SL:买仓只有当新 SL 比旧 SL 更高(或原 SL 为 0)才允许修改,卖仓则要求新 SL 比旧 SL 更低才改。这一步很关键,避免把止损往亏损方向回拉。外汇与贵金属杠杆高,这类自动护锁仍可能因滑点失效,实际触发概率受点差和流动性影响。 别把 100 点写死在 EA 里 不同品种点值差异大,XAUUSD 的 100 点与 EURUSD 的 100 点风险含义完全不同;按品种单独设阈值,比全局硬编码更稳。

MQL5 / C++
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)
   {
      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)
      {

把盈利锁进卖单的止损移动

上面这段截自一个 EA 的持仓管理分支,核心动作是把空头的止损价推向 newStopLoss,实现浮盈锁定。PositionModify 只改止损、不动止盈,止盈沿用 PositionGetDouble(POSITION_TP) 的当前值,避免把原本的离场线误删。 Print 那行不是必须的,但在 MT5 回测或实盘日志里能看到「Profit locking for sell position: Stop Loss moved to XXX」,你可以据此确认锁利逻辑确实触发过,而不是静默失败。 OnTick 里 LockProfits() 与三种策略函数并列调用,说明锁利是独立于趋势、突破、背离信号的全天候动作。外汇与贵金属杠杆高,移动止损若间距过窄,在常态毛刺中可能被扫掉而错失后续趋势,参数需按品种波动率手调。

MQL5 / C++
if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
            {
               Print("Profit locking for sell position: Stop Loss moved to ", newStopLoss);
            }
         }
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
   if (UseTrendFollowingStrategy) CheckTrendFollowing();
   if (UseBreakoutStrategy) CheckBreakoutTrading();
   if (UseDivergenceStrategy) CheckDivergenceTrading();
   LockProfits(); class=class="str">"cmt">// Call this function to check and lock profits
}

「三单上限的回测实况」

在 MT5 策略测试器里加载 Experts 栏下的趋势约束型智能交易系统,先用模拟账户跑一遍,别碰实盘。默认参数直接回测 EURUSD M15,能看到所有订单都正常成交,没有漏单或卡单。 核心现象是持仓被硬锁在三笔以内:系统每轮最多只留三张带“Divergence Sell”注释的空单,订单注释一致、便于肉眼核对,也说明滤波逻辑确实在起作用。 想验证就自己开测试器,把最大持仓参数从 3 改到 1 或 5,对比成交密度和浮亏曲线。外汇与贵金属杠杆高、回测不代表未来,参数调优仅作概率层面的参考。

◍ 画得少,看得清

这套趋势约束型 EA 把 RSI、MACD 背离和日线级 K 线方向绑在一起,实盘信号只在大周期趋势同向时才放行,避免逆风接刀。三个独立策略模块各带 MAGIC 编号,单策略最大持仓可限,订单不会被别的逻辑误平。 自定义利润锁定是核心:价格碰止盈前反转,止损自动上移保住浮盈,回测里这一机制显著降低了利润回吐幅度。源码 Trend_Constraint_Expert_.mq5 已放出,16.34 KB,直接丢进 MT5 改参数跑模拟盘。 外汇和贵金属杠杆高、滑点狠,任何信号都只是概率倾向,教学代码务必先在模拟账户验证再谈别的。

把背离扫描交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到RSI与价格的背离标记,你只管判断要不要跟EA的触发一致。

常见问题

价格创更低低点而RSI低点抬高即看涨背离,反之为看跌背离;MQL5里用iLow与iRSI取极值比对方向即可。
部分实证显示特定情境下MACD捕捉动能优于RSI,实战中建议双指标交互验证而非单信一个。
主流外汇与贵金属品种页已内置背离扫描,打开即可看系统标记的潜在反转区,省去手动比对的重复劳动。
概率上倾向减少虚假信号,但具体取决于周期与品种波动结构,外汇贵金属属高风险,须自行验证样本外表现。
日线级别单根K线容纳的噪音空间大,短期毛刺可能满足原入场条件却不具趋势意义,背离层对此有过滤作用。