构建K线趋势约束模型(第九部分):多策略EA(2)·综合运用
(3/3)·前两份代码只解决了单向过滤,本篇把通道突破、爬升与回归三种逻辑塞进同一EA框架
很多交易者把唐奇安通道当成单纯的突破信号线,却没意识到它内部还藏着爬升与均值回归两种互补逻辑。单策略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 模板里验证发单流程。
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 根本不会跑。
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 末尾就能接上前面的通道句柄直接验证。
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=保证金不够),比单纯打印数字更利于盘后排查。外汇与贵金属杠杆高,发单失败常发生在波动突发行情,需把重试逻辑和风控上限分开写。
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,观察突破信号触发频率变化——周期拉长后假突破大概率减少,但持仓时间会同步拉长。
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 点时信号频率是否过高。
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 跑一遍验证命中率。
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 秒观察成交频率。
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 用策略测试器跑一遍最直观。
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 实盘前务必先在历史数据多周期回测,风险偏高。
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,再谈整合,别一上来就贪大。