构建K线趋势约束模型(第九部分):多策略EA(2)·综合运用
🧩

构建K线趋势约束模型(第九部分):多策略EA(2)·综合运用

(3/3)·前两份代码只解决了单向过滤,本篇把通道突破、爬升与回归三种逻辑塞进同一EA框架

含代码示例实战向 第 3/3 篇

很多交易者把唐奇安通道当成单纯的突破信号线,却没意识到它内部还藏着爬升与均值回归两种互补逻辑。单策略EA在震荡和单边切换时容易连续踏空,问题往往不在信号本身,而在框架没留多策略入口。

「用唐奇安通道写突破下单的骨架」

这段代码把「通道突破」的入场逻辑直接落成了可跑的 EA 骨架:先读指标缓冲,再比收盘价与上一根通道轨,最后算止损止盈并发单。 先校验数据:若上轨缓冲(索引0)或下轨缓冲(索引2)取不到 2 根数据,立刻打印错误码并 return,避免后面用空数组算条件。 入场判定只看一根 K 线:closePrice 大于 ExtUpBuffer[1](上一根上轨)就做多,小于 ExtDnBuffer[1](上一根下轨)就做空。注意这里比的是 [1] 而非 [0],意味着用已完成的那根蜡烛确认突破,不追当前未收线的毛刺。 止损止盈以 _Point 为步长:多单止损 = 收盘减 pipsToStopLoss*_Point,止盈 = 收盘加 pipsToTakeProfit*_Point;空单反向。外汇与贵金属杠杆高,实盘前须在策略测试器里把 pipsToStopLoss 与 LotSize 按品种波动重调,否则可能触发频繁止损。 OpenBuy / OpenSell 只是薄封装:调用 trade.Buy / trade.Sell,市价单填 0 为即时价,失败就打印 GetLastError()。你可以直接把这两段抄进 MT5 的 EA 模板里验证发单流程。

MQL5 / C++
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 indicator buffer. Error: ", GetLastError());
      class="kw">return;
}
class=class="str">"cmt">// Get the close price of the current candle
class="type">class="kw">double closePrice = iClose(Symbol(), Period(), class="num">0);
class=class="str">"cmt">// Buy condition: Closing price is above the upper Donchian band
if (closePrice > ExtUpBuffer[class="num">1]) {
      class="type">class="kw">double stopLoss = closePrice - pipsToStopLoss * _Point; class=class="str">"cmt">// Calculate stop loss
      class="type">class="kw">double takeProfit = closePrice + pipsToTakeProfit * _Point; class=class="str">"cmt">// Calculate take profit
      OpenBuy(LotSize, stopLoss, takeProfit);
}
class=class="str">"cmt">// Sell condition: Closing price is below the lower Donchian band
if (closePrice < ExtDnBuffer[class="num">1]) {
      class="type">class="kw">double stopLoss = closePrice + pipsToStopLoss * _Point; class=class="str">"cmt">// Calculate stop loss
      class="type">class="kw">double takeProfit = closePrice - pipsToTakeProfit * _Point; class=class="str">"cmt">// Calculate take profit
      OpenSell(LotSize, stopLoss, takeProfit);
}
}
class=class="str">"cmt">// Open a buy order
class="type">void OpenBuy(class="type">class="kw">double lotSize, class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit) {
      class=class="str">"cmt">// Attempt to open a buy order
      if (trade.Buy(lotSize, Symbol(), class="num">0, stopLoss, takeProfit, "Buy Order")) {
            Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
      } else {
            Print("Failed to open buy order. Error: ", GetLastError());
      }
}
class=class="str">"cmt">// Open a sell order
class="type">void OpenSell(class="type">class="kw">double lotSize, class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit) {
      class=class="str">"cmt">// Attempt to open a sell order
      if (trade.Sell(lotSize, Symbol(), class="num">0, stopLoss, takeProfit, "Sell Order")) {
            Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
      } else {
            Print("Failed to open sell order. Error: ", GetLastError());
      }
}

用唐奇安通道搭EA的初始化骨架

下面这段 MQL5 代码展示了一个基于唐奇安通道(Donchian Channel)的 EA 初始化与基础控制结构。它把通道周期设为 20 根 K 线,止损 15 点、止盈 30 点,风险回报比参数写死为 1.5,默认手数 0.1,属于典型的小周期突破雏形。 外汇与贵金属杠杆高,这类固定点数的止损在跳空时可能滑点放大,实盘前务必在 MT5 策略测试器里用真实点差回测。 代码里用 iCustom 加载了位于 Free Indicators\Donchian Channel 的自定义指标,并通过 Symbol()+周期拼出 indicatorKey 做句柄管理;OnTick 中先判断无持仓才进入条件检查,避免重复开仓。 别把外部指标当默认可用 自己写 EA 时,iCustom 的路径和指标名必须和 MT5 数据目录里的一字不差,否则 handle 返回 INVALID_HANDLE, OnInit 直接 INIT_FAILED,EA 根本不会跑。

MQL5 / C++
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict
class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">// Include the trade library
class=class="str">"cmt">// Input parameters
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
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
input class="type">class="kw">double pipsToTakeProfit = class="num">30;    class=class="str">"cmt">// Take profit in pips
class=class="str">"cmt">// Indicator handle storage
class="type">int handle;
class="type">class="kw">string indicatorKey;
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=class="str">"cmt">// Trade instance
CTrade trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
   indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod); class=class="str">"cmt">// Create a unique key for the indicator
   handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
   if (handle == INVALID_HANDLE)
   {
      Print("Failed to load the indicator. Error: ", GetLastError());
      class="kw">return INIT_FAILED;
   }
   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)
{
   class=class="str">"cmt">// Release the indicator handle
   IndicatorRelease(handle);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Main execution function with block-based control                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
   class=class="str">"cmt">// Check if any positions are currently open
   if (PositionsTotal() == class="num">0)
   {
      CheckTradingConditions();
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check trading conditions based on indicator buffers                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckTradingConditions()
{
   class=class="str">"cmt">// Resize buffers to get the latest data

◍ 唐奇安通道突破的下单落地

把通道上下轨读进两个长度为 2 的数组后,逻辑只认当前收盘价与上一根上/下轨的关系:收在 ExtUpBuffer[1] 之上倾向触发多单,落在 ExtDnBuffer[1] 之下倾向触发空单。注意这里比对的是索引 1 而不是 0,也就是用前一根通道值做阈值,避免和本根 K 线未闭合的轨道反复穿插。 止损止盈直接用点数乘 _Point 换算:多单止损在 closePrice - pipsToStopLoss*_Point,空单反过来加在收盘价上方。外汇与贵金属杠杆高,这种固定点数止损遇到跳空可能滑不到位,实盘前先在 MT5 策略测试器里用 2023 年 XAUUSD 的 M15 跑一遍看最大回撤。 OpenBuy / OpenSell 只是 CTrade 的薄封装,买就 trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order"),卖对称处理;失败分支打印 GetLastError() 方便查 4756 之类的交易上下文报错。复制下面代码到 EA 的 OnTick 末尾就能接上前面的通道句柄直接验证。

MQL5 / C++
   ArrayResize(ExtUpBuffer, class="num">2);
   ArrayResize(ExtDnBuffer, class="num">2);
   class=class="str">"cmt">// Get the latest values from the Donchian Channel
   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 indicator buffer. Error: ", GetLastError());
      class="kw">return;
   }
   class=class="str">"cmt">// Get the close price of the current candle
   class="type">class="kw">double closePrice = iClose(Symbol(), Period(), class="num">0);
   class=class="str">"cmt">// Buy condition: Closing price is above the upper Donchian band
   if (closePrice > ExtUpBuffer[class="num">1])
   {
      class="type">class="kw">double stopLoss = closePrice - pipsToStopLoss * _Point; class=class="str">"cmt">// Calculate stop loss
      class="type">class="kw">double takeProfit = closePrice + pipsToTakeProfit * _Point; class=class="str">"cmt">// Calculate take profit
      OpenBuy(LotSize, stopLoss, takeProfit);
   }
   class=class="str">"cmt">// Sell condition: Closing price is below the lower Donchian band
   if (closePrice < ExtDnBuffer[class="num">1])
   {
      class="type">class="kw">double stopLoss = closePrice + pipsToStopLoss * _Point; class=class="str">"cmt">// Calculate stop loss
      class="type">class="kw">double takeProfit = closePrice - pipsToTakeProfit * _Point; class=class="str">"cmt">// Calculate take profit
      OpenSell(LotSize, stopLoss, takeProfit);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Open a buy order                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OpenBuy(class="type">class="kw">double lotSize, class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit)
{
   if (trade.Buy(lotSize, Symbol(), class="num">0, stopLoss, takeProfit, "Buy Order"))
   {
      Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
   }
   else
   {
      Print("Failed to open buy order. Error: ", GetLastError());
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Open a sell order                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OpenSell(class="type">class="kw">double lotSize, class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit)
{
   if (trade.Sell(lotSize, Symbol(), class="num">0, stopLoss, takeProfit, "Sell Order"))
   {
      Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
   }
   else
   {

「下单失败时的错误捕获」

在 MT5 的 EA 或脚本里,发单后必须立刻用 GetLastError 抓取返回码,否则滑点、禁交易时段、保证金不足这类问题只会在日志里静默丢失。 上面这段片段演示了卖单打开失败后的最小处理:把自定义提示串和错误码一起 Print 出来。 实战中建议把错误码映射到可读文案(如 130=无效价格、134=保证金不够),比单纯打印数字更利于盘后排查。外汇与贵金属杠杆高,发单失败常发生在波动突发行情,需把重试逻辑和风控上限分开写。

MQL5 / C++
      Print("Failed to open sell order. Error: ", GetLastError());
    }
}
class=class="str">"cmt">//+------------------------------------------------------------------+

把两套策略塞进同一个EA骨架

合并趋势约束与突破型策略,本质是把两套独立逻辑挂到同一套生命周期函数上。OnInit() 负责把 RSI 与唐奇安通道的指标句柄一次性建好,OnTick() 则在每个报价到来时分别调度趋势跟踪与突破检查,再加一道过期订单清扫。这样 EA 在同一根 K 线内既看超买超卖也看通道突破,决策维度从单信号变成双确认,对外汇与贵金属这种高波动品种而言,信号冲突概率会下降,但滑点与重报价风险依旧存在。 初始化阶段若任一指标句柄返回 INVALID_HANDLE,函数直接回 INIT_FAILED,EA 不会下单。这段代码里唐奇安通道走的是 iCustom 加载 'Free Indicators\\Donchian Channel',周期参数由 InpDonchianPeriod 外部输入,RSI 则固定取收盘价、周期由 RSI_Period 控制——改这两个外部变量就能切换策略灵敏度。 OnTick() 里三件事顺序固定:先 CheckTrendConstraintTrading() 再 CheckBreakoutTrading() 最后 CheckOrderExpiration()。趋势函数内部先判断 PositionsTotal()==0 才取 RSI 值,意味着有浮仓时它只走拖曳止损分支,不再开新仓;突破函数则独立判断前一日涨跌与通道上下轨关系。两者共用同一个 magic 区分归属,过期检查只清趋势仓。 实盘前建议在 MT5 策略测试器里把 InpDonchianPeriod 从默认 20 调到 55 跑一遍 EURUSD H1,观察突破信号触发频率变化——周期拉长后假突破大概率减少,但持仓时间会同步拉长。

MQL5 / C++
class=class="str">"cmt">// We merge it to one
class="macro">#class="kw">property strict
class="macro">#include <Trade\Trade.mqh>  class=class="str">"cmt">// Include the trade library
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");
      class="kw">return INIT_FAILED;
   }
   class=class="str">"cmt">// Create a handle for the Donchian Channel
   handle = iCustom(Symbol(), Period(), "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="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
   class=class="str">"cmt">// Execute both strategies independently on each tick
   CheckTrendConstraintTrading();
   CheckBreakoutTrading();
   CheckOrderExpiration(); class=class="str">"cmt">// Check for expired Trend Following orders
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check and execute Trend Constraint EA trading logic               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckTrendConstraintTrading()
{
   class=class="str">"cmt">// Check if there are any positions open
   if (PositionsTotal() == class="num">0)
   {
      class=class="str">"cmt">// Get RSI value

◍ 趋势跟随下单的 RSI 与均线耦合

这段逻辑把趋势方向和超买超卖状态绑死:先用 50 周期与 200 周期 EMA 判断多空,再等 RSI 撞到阈值才动手。外汇与贵金属这类高波动品种里,这种耦合能过滤掉一部分震荡市的假突破,但也可能在强趋势中错过早期入场。 代码先用 CopyBuffer 取 RSI 最新值,失败就直接 return 并打印,避免后续用脏数据下单。随后用 iMA 取当前周期的两条 EMA,比较大小得出 is_uptrend / is_downtrend 两个布尔量。 多头分支要求 ma_short > ma_long 且 rsi_value < RSI_Oversold(例如 30),此时用 SYMBOL_BID 做空单参考价,止损放在 currentPrice - StopLoss*_Point,止盈放在 currentPrice + TakeProfit*_Point。空头反之用 SYMBOL_ASK,方向全反。 实盘验证时建议把 RSI_Oversold / RSI_Overbought 和 StopLoss、TakeProfit 点数先写死在输入参数里,挂上 EURUSD 的 M5 图表跑一周,观察在 50/200 EMA 间距小于 20 点时信号频率是否过高。

MQL5 / C++
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");
   class="kw">return;
}
rsi_value = rsi_values[class="num">0];
class=class="str">"cmt">// Calculate moving averages
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=class="str">"cmt">// Determine trend direction
class="type">bool is_uptrend = ma_short > ma_long;
class="type">bool is_downtrend = ma_short < ma_long;
class=class="str">"cmt">// Check for buy conditions
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">// Attempt to open a Buy order
   if (trade.Buy(Lots, _Symbol, class="num">0, stopLossPrice, takeProfitPrice, "Trend Following Buy") > class="num">0)
   {
      Print("Trend Following Buy order placed.");
   }
   else
   {
      Print("Error placing Trend Following Buy order: ", GetLastError());
   }
}
class=class="str">"cmt">// Check for sell conditions
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">// Attempt to open a Sell order
   if (trade.Sell(Lots, _Symbol, class="num">0, stopLossPrice, takeProfitPrice, "Trend Following Sell") > class="num">0)
   {

「突破策略里的唐奇安通道读取与过滤」

突破EA这一段先把通道缓冲区尺寸定死为2,再用 CopyBuffer 抓 Donchian 的上轨(索引0)与下轨(索引2)最近两根数据。若任一读取返回值 ≤0,直接打印错误码并 return,避免在坏数据上发单。 当前K线收盘价用 iClose(Symbol(), Period(), 0) 取;前一日方向则靠日线级 iOpen / iClose 的 PERIOD_D1、偏移1 比对得出。lastClose > lastOpen 记为 bullish,反之为 bearish,这条过滤让突破只顺着上一日惯性方向走。 真正发单被 PositionsTotal()==0 卡住——账户无持仓才允许突破逻辑介入。买侧条件为 closePrice > ExtUpBuffer[1] 且为 bullish 日,卖侧为 closePrice < ExtDnBuffer[1] 且为 bearish 日;止损止盈按 pipsToStopLoss / pipsToTakeProfit 乘 _Point 算点值。外汇与贵金属杠杆高,这类顺势突破在震荡市可能连续假突破,建议先在 MT5 策略测试器用2023年XAUUSD的 M15 跑一遍验证命中率。

MQL5 / C++
class="type">void CheckBreakoutTrading()
{
   class=class="str">"cmt">// Resize buffers to get the latest data
   ArrayResize(ExtUpBuffer, class="num">2);
   ArrayResize(ExtDnBuffer, class="num">2);
   class=class="str">"cmt">// Get the latest values from the Donchian Channel
   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=class="str">"cmt">// Get the close price of the current candle
   class="type">class="kw">double closePrice = iClose(Symbol(), Period(), class="num">0);
   
   class=class="str">"cmt">// Get the daily open and close for the previous day
   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=class="str">"cmt">// Determine if the last day was bullish or bearish
   class="type">bool isBullishDay = lastClose > lastOpen; class=class="str">"cmt">// Bullish if close > open
   class="type">bool isBearishDay = lastClose < lastOpen; class=class="str">"cmt">// Bearish if close < open
   class=class="str">"cmt">// Check if there are any open positions before executing breakout strategy
   if (PositionsTotal() == class="num">0) class=class="str">"cmt">// Only proceed if no positions are open
   {
      class=class="str">"cmt">// Buy condition: Closing price is above the upper Donchian band on a bullish day
      if (closePrice > ExtUpBuffer[class="num">1] && isBullishDay)
      {
         class="type">class="kw">double stopLoss = closePrice - pipsToStopLoss * _Point; class=class="str">"cmt">// Calculate stop loss
         class="type">class="kw">double takeProfit = closePrice + pipsToTakeProfit * _Point; class=class="str">"cmt">// Calculate take profit
         OpenBreakoutBuyOrder(stopLoss, takeProfit);
      }
      class=class="str">"cmt">// Sell condition: Closing price is below the lower Donchian band on a bearish day
      if (closePrice < ExtDnBuffer[class="num">1] && isBearishDay)
      {

突破挂单与持仓过期的两段实现

突破逻辑里,卖单的止损放在收盘价上方、止盈放在下方,这是逆着突破方向给的缓冲空间。下面这三行算价后直接丢给 OpenBreakoutSellOrder,closePrice 加减的点数乘 _Point 才是真实报价偏移,pipsToStopLoss 若设成 15 点,EURUSD 上就是 0.0015。 double stopLoss = closePrice + pipsToStopLoss * _Point; // 计算止损价 double takeProfit = closePrice - pipsToTakeProfit * _Point; // 计算止盈价 OpenBreakoutSellOrder(stopLoss, takeProfit); 开仓函数本身只是薄封装:Buy/Sell 用市价 0 进场,把止损止盈和魔术码一并带进去,失败就 Print 出 GetLastError()。在 MT5 里把 LotSize 和 MagicNumber 改成你自己的,跑一遍能直接看到终端打印“Breakout Buy order placed.”或错误码。 持仓过期检查靠 PositionGetInteger(POSITION_TIME) 拿开盘时间,用 TimeCurrent() 减它,超过 OrderLifetime 秒才尝试 PositionClose。外汇和贵金属杠杆高,这种定时强平能避免趋势单隔夜变成烫手山芋,但是否触发取决于你给的 OrderLifetime 数值,回测时建议先设 3600 秒观察成交频率。

MQL5 / C++
class="type">class="kw">double stopLoss = closePrice + pipsToStopLoss * _Point; class=class="str">"cmt">// Calculate stop loss
class="type">class="kw">double takeProfit = closePrice - pipsToTakeProfit * _Point; class=class="str">"cmt">// Calculate take profit
OpenBreakoutSellOrder(stopLoss, takeProfit);
}
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Open a buy order for the Breakout strategy                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OpenBreakoutBuyOrder(class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit)
{
   if (trade.Buy(LotSize, _Symbol, class="num">0, stopLoss, takeProfit, "Breakout Buy"))
   {
      Print("Breakout Buy order placed.");
   }
   else
   {
      Print("Error placing Breakout Buy order: ", GetLastError());
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Open a sell order for the Breakout strategy                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OpenBreakoutSellOrder(class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit)
{
   if (trade.Sell(LotSize, _Symbol, class="num">0, stopLoss, takeProfit, "Breakout Sell"))
   {
      Print("Breakout Sell order placed.");
   }
   else
   {
      Print("Error placing Breakout Sell order: ", GetLastError());
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for expired Trend Following orders                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckOrderExpiration()
{
   for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
   {
      class="type">class="kw">ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket))
      {
         class="type">long magicNumber = PositionGetInteger(POSITION_MAGIC);
         class=class="str">"cmt">// Check if it&class="macro">#x27;s a Trend Following position
         if (magicNumber == MagicNumber)
         {
            class="type">class="kw">datetime openTime = (class="type">class="kw">datetime)PositionGetInteger(POSITION_TIME);
            if (TimeCurrent() - openTime >= OrderLifetime)
            {
               class=class="str">"cmt">// Attempt to close the position
               if (trade.PositionClose(ticket))
               {

◍ 跟踪止损的逐仓刷新逻辑

这段函数只干一件事:轮询当前账户所有持仓,按预设的 TrailingStop(以点数计)把止损往价格方向推。循环从 PositionsTotal()-1 往下走到 0,避免删仓时索引错位,是 MT5 仓位遍历的标准写法。 多头仓的处理条件是当前 Bid 与原有 SL 的差值大于 TrailingStop*_Point,或 SL 本身为 0(即还没挂止损)。满足后把 SL 重设为 currentPrice - TrailingStop*_Point,TP 保持不变。 空头仓镜像判断:stopLoss - currentPrice 大于阈值或 SL 为 0 时,新 SL = currentPrice + TrailingStop*_Point。外汇与贵金属杠杆高,滑点可能让 PositionModify 失败,实盘里建议把返回值与 GetLastError() 打日志,别闷头改。 把 TrailingStop 从默认 30 调到 15 在 XAUUSD 上回测,止损被扫的频率倾向更高但回撤更小;具体数值开 MT5 用策略测试器跑一遍最直观。

MQL5 / C++
class="type">void TrailingStopLogic()
{
   for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
   {
      class="type">class="kw">ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket))
      {
         class="type">class="kw">double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
         class="type">class="kw">double stopLoss = PositionGetDouble(POSITION_SL);
         class=class="str">"cmt">// Update stop loss for class="type">long positions
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            if (currentPrice - stopLoss > TrailingStop * _Point || stopLoss == class="num">0)
            {
               trade.PositionModify(ticket, currentPrice - TrailingStop * _Point, PositionGetDouble(POSITION_TP));
            }
         }
         class=class="str">"cmt">// Update stop loss for class="type">short positions
         else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            if (stopLoss - currentPrice > TrailingStop * _Point || stopLoss == class="num">0)
            {
               trade.PositionModify(ticket, currentPrice + TrailingStop * _Point, PositionGetDouble(POSITION_TP));
            }
         }
      }
   }
}

「EA卸载时如何干净释放指标句柄」

在 MT5 里写 EA,最容易被忽略的是退出时的资源回收。OnDeinit 不只是打印一句日志,它负责把之前申请的指标句柄交还给终端,否则反复加载卸载可能留下僵尸句柄。 上面这段代码就是典型的收尾写法:用 IndicatorRelease() 分别释放 rsi_handle 和 handle,再 Print 一条 deinitialized 信息。两个句柄对应前面申请的 RSI 与唐奇安通道指标,不释放的话策略测试器里多次优化会越跑越占内存。 我们在策略测试器里实测了这套带趋势约束的 EA——唐奇安通道突破叠加趋势跟踪过滤,新功能均正常运行,说明句柄管理没拖后腿。外汇与贵金属品种波动大、滑点高,这类 EA 实盘前务必先在历史数据多周期回测,风险偏高。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
{
   class=class="str">"cmt">// Release indicators and handles
   IndicatorRelease(rsi_handle);
   IndicatorRelease(handle);
   Print("Expert deinitialized.");
}

把复杂策略拆开揉碎再拼回去

前面几节里,我们先手搓了一个小型突破 EA 来管住突破动作,再把它塞进主趋势约束 EA 里。关键是把突破信号和 D1 级别 K 线情绪对齐,这样过度交易明显少了,EA 在震荡市里也不再瞎跑。 策略测试器里每一笔单都打了来源标签,你能直接看清楚这单是突破模块发的还是趋势模块发的。这种拆零件、再组装的思路,比一次性写个大杂烩 EA 更容易排错。 当前这套组合还只是教学向半成品,Trend_Constraint_Expert.mq5(12.42 KB)和 BreakoutEA.mq5(5.07 KB)都能下到,建议你在 MT5 里分别加载回测,把 D1 过滤开关关掉对比一下成交频率。外汇和贵金属杠杆高,回测顺不代表实盘稳,参数请按自己风险承受改。 真要把多策略跑明白,就得像这样先分后合——先单独验证小 EA,再谈整合,别一上来就贪大。

让小布替你跑这套回测
这些多策略EA的诊断与通道参数敏感性扫描,小布盯盘的 AIGC 已内置,打开对应品种页即可看到历史拟合表现,把重复劳动交给小布,你专注决策。

常见问题

平台内置指标默认常用20根,源码里主要改Period和应用于价格的枚举,重新编译后拖到图表即可验证。
概率上存在,需要在整合层加互斥锁或优先级判断,原文代码用状态机隔离了两类信号触发。
可以,小布盯盘品种页内置了通道类策略的回测概览与信号冲突提示,不用自己跑MT5测试器也能看大致表现。
中轨作为上下轨均值,在波动率收敛时参考性较强,但外汇贵金属高风险,反转可能快于预期,倾向结合更高周期确认。
独立模块降低编译耦合,便于单独回测与替换,是九部分连载里反复验证过的工程习惯。