构建K线趋势约束模型(第九部分):多策略智能交易系统(EA)(三)·进阶篇
(2/3)·日线趋势里微小波动总搅乱执行,给趋势约束EA加一层背离过滤看看能少踩多少坑
背离信号的订单触发与利润锁定
当指标出现背离,EA 会按多空分支下单:Buy 或 Sell 都带手数、零偏移价、止损止盈与魔法号标识,成功才打印日志。 trade.SetExpertMagicNumber(DivergenceMagicNumber) 必须在发单前调用,否则后续按魔法号统计持仓会漏掉这一笔。 LockProfits 遍历所有持仓,把浮盈换算成点数:profitPoints = MathAbs(currentProfit / _Point)。一旦超过 100 点,就进入移动止损逻辑,对 BUY 单重算 newStopLoss 把利润锁住一部分。 外汇与贵金属杠杆高,100 点锁利在 XAUUSD 上可能只是几十美元,但在 EURUSD 微点差品种上波动会被放大,实盘前请在 MT5 策略测试器用历史数据验证该阈值。
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 跑之前先把品种点值对一遍。
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 上触发频率。
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 不初始化,避免后续裸跑。
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 策略测试器用历史数据验证参数敏感性。
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 数据核对滑点设置。
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;