构建K线趋势约束模型(第十部分):战略均线金叉与死叉(智能交易系统EA)·综合运用
(3/3)·手动盯金叉死叉总慢半拍?这篇把交叉信号写进EA,补齐趋势约束模型最后一块自动化短板
◍ 初始化时把指标句柄一次拿全
在 MT5 的 Expert Advisor 初始化阶段,先把 RSI、Donchian Channel、MACD 三个指标的 handle 全部建好,任一返回 INVALID_HANDLE 就直接 return INIT_FAILED,避免后续 Tick 里空句柄崩策略。 RSI 用 iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE),唐奇安通道走 iCustom 加载 "Free Indicators\Donchian Channel" 并传周期参数;MACD 用 iMACD 且信号线周期写死 26,这三个调用失败都会 Print 出 GetLastError() 方便排查。 若开关 UseGoldenDeathCrossStrategy 为真,还需校验 Bars 数是否小于慢线周期 GDC_SlowEMAPeriod,不足则初始化失败;随后建快慢两条 EMA 句柄(MODE_EMA, PRICE_CLOSE),任一句柄 < 0 同样回 INIT_FAILED。 最后把 MACD 背离缓冲数组按 DivergenceLookBack 尺寸 ArrayResize,打印初始化成功并返回 INIT_SUCCEEDED;OnDeinit 里用 IndicatorRelease 依次释放三个句柄,防止指标实例泄露。外汇与贵金属杠杆高,句柄未释放可能导致图表重载后指标加载异常,建议开 MT5 按此结构自查 EA 初始化日志。
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()); 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; } if(UseGoldenDeathCrossStrategy) { class=class="str">"cmt">// Check if there are enough bars to calculate the EMA if(Bars(_Symbol, PERIOD_CURRENT) < GDC_SlowEMAPeriod) { Print("Not enough bars for EMA calculation for Golden Death Cross"); class="kw">return INIT_FAILED; } GDC_fastEMAHandle = iMA(_Symbol, PERIOD_CURRENT, GDC_FastEMAPeriod, class="num">0, MODE_EMA, PRICE_CLOSE); GDC_slowEMAHandle = iMA(_Symbol, PERIOD_CURRENT, GDC_SlowEMAPeriod, class="num">0, MODE_EMA, PRICE_CLOSE); if(GDC_fastEMAHandle < class="num">0 || GDC_slowEMAHandle < class="num">0) { Print("Failed to create EMA handles for Golden Death Cross. Error: ", GetLastError()); class="kw">return INIT_FAILED; } } class=class="str">"cmt">// Resize arrays for MACD buffers ArrayResize(ExtMacdBuffer, DivergenceLookBack); ArrayResize(ExtSignalBuffer, DivergenceLookBack); Print("Trend Constraint Expert initialized."); class="kw">return INIT_SUCCEEDED;
「双均线交叉的开仓判定逻辑」
这一段把快慢 EMA 的「金叉 / 死叉」转成了无持仓条件下的下单动作。函数先取两个长度为 2 的数组,分别装快线与慢线的最新两根值,用 CopyBuffer 从指标句柄拉数据;只要任一拷贝返回 <=0 就打印错误并撤出,避免脏数据触发交易。 判定本身只看两根 K 线:若 fastEMA[0] > slowEMA[0] 且 fastEMA[1] <= slowEMA[1],视为死叉,按 ask 价 Sell,止损放在 ask + GDC_StopLossPips*point 并 NormalizeDouble 到 _Digits 精度;反向条件 fastEMA[0] < slowEMA[0] 且 fastEMA[1] >= slowEMA[1] 则是金叉,按 bid 价 Buy,止损在 bid - 同样点数。 注意代码里只处理了 !hasPosition 的分支,也就是同品种已有持仓时本函数前半段不下新单;持仓后的平仓或反手逻辑在 else 里被截断,复制时得确认后续源码是否补全。外汇与贵金属杠杆高,EMA 交叉在震荡市会连续假突破,实盘前建议在 MT5 策略测试器用 2020—2023 年 XAUUSD 的 M15 跑一遍验证胜率。
class="type">void CheckGoldenDeathCross() { class="type">class="kw">double fastEMAArray[class="num">2], slowEMAArray[class="num">2]; class="type">class="kw">double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(CopyBuffer(GDC_fastEMAHandle, class="num">0, class="num">0, class="num">2, fastEMAArray) <= class="num">0 || CopyBuffer(GDC_slowEMAHandle, class="num">0, class="num">0, class="num">2, slowEMAArray) <= class="num">0) { Print("Failed to copy EMA data for Golden Death Cross. Error: ", GetLastError()); class="kw">return; } class="type">bool hasPosition = PositionSelect(_Symbol); if(!hasPosition) { if(fastEMAArray[class="num">0] > slowEMAArray[class="num">0] && fastEMAArray[class="num">1] <= slowEMAArray[class="num">1]) class=class="str">"cmt">// Death Cross { class="type">class="kw">double sl = NormalizeDouble(ask + GDC_StopLossPips * point, _Digits); if(!trade.Sell(GDC_LotSize, _Symbol, ask, sl )) Print("Sell order error for Golden Death Cross ", GetLastError()); else Print("Sell order opened for Golden Death Cross with SL ", GDC_StopLossPips, " pips"); } else if(fastEMAArray[class="num">0] < slowEMAArray[class="num">0] && fastEMAArray[class="num">1] >= slowEMAArray[class="num">1]) class=class="str">"cmt">// Golden Cross { class="type">class="kw">double sl = NormalizeDouble(bid - GDC_StopLossPips * point, _Digits); if(!trade.Buy(GDC_LotSize, _Symbol, bid, sl )) Print("Buy order error for Golden Death Cross ", GetLastError()); else Print("Buy order opened for Golden Death Cross with SL ", GDC_StopLossPips, " pips"); } } else { if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && fastEMAArray[class="num">0] < slowEMAArray[class="num">0]) ||
用 RSI 与双 EMA 做趋势跟随的开平仓
趋势跟随模块里,先卡一道仓位闸门:同策略持仓达到 2 单就直接 return,避免在同一方向堆叠风险敞口。外汇与贵金属杠杆高,这种硬上限能防止回撤被单策略拖爆。 RSI 取值只用最近一根 K 线:CopyBuffer(rsi_handle,0,0,1,rsi_values) 拿不到就打印错误并退出。紧接着用 iMA 取 50 周期与 200 周期收盘价 EMA,短线上穿长线判为多头,下穿判为空头。 多头分支要求 rsi_value < RSI_Oversold(超卖),此时用 SymbolInfoDouble 取 BID 价,止损挂在 currentPrice - StopLoss*_Point,止盈在 currentPrice + TakeProfit*_Point,再调 trade.Buy 下市价多单。空头对称:rsi_value > RSI_Overbought 时取 ASK 价,止损加、止盈减。 前面金死叉平仓段的逻辑是:若持空单且快 EMA 上穿慢 EMA,取 ticket 后调 trade.PositionClose 平掉,失败打印错误码,成功打印 ticket。把这两段拼进 EA,开 MT5 切 EURUSD 五分钟图就能验证信号触发频率。
(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && fastEMAArray[class="num">0] > slowEMAArray[class="num">0])) { class="type">ulong ticket = PositionGetInteger(POSITION_TICKET); if(!trade.PositionClose(ticket)) Print("Failed to close position for Golden Death Cross(Ticket: ", ticket, "). Error: ", GetLastError()); else Print("Position closed for Golden Death Cross: ", ticket); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check Trend Following Strategy | 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")) { 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;
◍ 突破策略的持仓上限与唐奇安通道判定
在 MT5 里跑多策略 EA 时,给单一策略设持仓上限能避免账户被同逻辑订单刷屏。上面这段代码用 PositionsTotal() >= 2 直接 return,意味着突破策略最多只留 2 张同标的持仓,超出就跳过本轮信号。 判定用的是日线昨日的实体方向叠加当前价对唐奇安通道的突破:先取 D1 周期前一根 K 线的开收盘,lastClose > lastOpen 记为阳线日,反之为阴线日;再用 CopyBuffer 把通道上轨 ExtUpBuffer、下轨 ExtDnBuffer 各取最近 2 根,若读取失败就打印 GetLastError() 并退出。 阳线日且现价 closePrice > ExtUpBuffer[1],按 closePrice 加减 pipsToStopLoss / pipsToTakeProfit 倍的 _Point 挂 Buy;阴线日且现价跌破 ExtDnBuffer[1] 则反向 Sell。外汇与贵金属杠杆高,通道假突破概率不低,这套逻辑只解决「何时开」,止损幅度仍需你按品种波动率手调。
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 Breakout Strategy | 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; if (trade.Sell(LotSize, _Symbol, class="num">0, stopLoss, takeProfit, "Breakout Sell") > class="num">0) { Print("Breakout Sell order placed."); } } }
「MACD 背离的四种判定与开仓闸门」
MT5 里做 MACD 背离,核心是把价格和指标谷/峰做反向比较。下面四个布尔函数分别覆盖常规看涨、隐藏看涨、常规看跌、隐藏看跌,索引 2 与 DivergenceLookBack 是两段比对的距离锚点。 常规看涨要求价格新低(priceLow1 < priceLow2)而 MACD 未新低(macdLow1 > macdLow2);隐藏看涨则反过来,价格未新低而 MACD 新低。看跌两组对称处理高点。 CheckDivergenceTrading 是实际触发闸门:先由 UseDivergenceStrategy 总开关拦截;再用 CountOrdersByMagic 数同魔术码持仓,允许空仓或少于 3 仓才继续。 数据充足性检查很硬:barsAvailable 必须 ≥ DivergenceLookBack * 2,否则打印缺数据直接 return。随后 while(attempt < 6) 最多重试 6 次 CopyBuffer 拉取 MACD 主线与信号线到 ExtMacdBuffer / ExtSignalBuffer,任一失败就不判定。 外汇与贵金属杠杆高,背离只是概率信号,实盘前请在 MT5 策略测试器用对应品种历史数据验证 DivergenceLookBack 与魔术码过滤是否如预期。
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."); 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)
日线方向卡住背离下单口子
这段逻辑先把 MACD 缓冲区拷贝失败的情况兜住:最多重试 5 次(attempt 从 0 累加到 5,第 6 次直接 return 并打印失败),每次间隔 Sleep(1000) 即 1 秒,避免刚连上服务器就因数据没到位崩策略。 若当前时间等于当前周期 0 号 K 线的开盘时间,说明这根 bar 还没收,直接跳过不交易,防止拿不完整数据去判背离。 取当前价 close、日线 close 与 open,用 dailyClose 与 dailyOpen 的大小判定 D1 多空:只有 D1 收阳才允许走买入分支,D1 收阴才允许走卖出分支。 买入分支里,普通看涨背离叠加 MACD 主线在信号线上方,或隐藏看涨背离成立,才调 ExecuteDivergenceOrder(true);卖出分支对称处理。外汇与贵金属杠杆高,这种多周期过滤只能降噪,不保证胜率。 ExecuteDivergenceOrder 开头先用 trade.SetExpertMagicNumber 钉死背离魔术号,再按买卖取 SYMBOL_ASK 或 SYMBOL_BID 作为当前价,方便后面挂单区分策略实例。
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 { Print("Divergence strategy: Maximum number of positions reached."); } } 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);
◍ 背离信号的订单下发与利润锁定
背离策略触发后,下单逻辑用一行三目运算就切清了多空止损位:买单止损在 currentPrice 下方 DivergenceStopLoss*_Point,卖单反之;止盈则对称摆在另一侧。下面这段是 MT5 里实跑的下单与统计代码,直接拷进 EA 的 OnTick 之后就能验证。
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); class="type">class="kw">double profitPoints = MathAbs(currentProfit / _Point); if (profitPoints >= class="num">100) { class="type">class="kw">double newStopLoss;
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); class="type">class="kw">double profitPoints = MathAbs(currentProfit / _Point); if (profitPoints >= class="num">100) { class="type">class="kw">double newStopLoss;
「盈利锁仓的止损平移逻辑」
这段逻辑干的事很直接:根据持仓方向,把止损位推到入场价上方(多单)或下方(空单)固定点数处,实现浮盈保护。代码里 profitLockerPoints 设为 20,意味着多单止损 = 入场价 + 20*_Point,空单止损 = 入场价 - 20*_Point,点值随品种变动。 修改前先比对当前 SL:多单只在 newStopLoss 比原 SL 更高(或原 SL 为 0)时才改,空单则只在 newStopLoss 比原 SL 更低(或原 SL 为 0)时才改。这样保证止损只会往「更锁利润」的方向走,不会回撤保护空间。 非买卖持仓直接 continue 跳过,避免异常单干扰。外汇与贵金属杠杆高,这类自动平移若参数过窄,可能被噪声扫损,建议在 MT5 用策略测试器按品种点值验证 20 点间距是否合适。
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) { if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP))) { Print("Profit locking for sell position: Stop Loss moved to ", newStopLoss); } } }
把多策略塞进同一根K线
这段 OnTick 骨架展示了趋势跟随、突破、背离三类策略如何共用一个 tick 入口。被高亮的那段才是关键:当 UseGoldenDeathCrossStrategy 开关打开,金叉死叉逻辑通过 CheckGoldenDeathCross() 被并入同一调度循环,不再独立跑 EA。 外汇与贵金属杠杆高,多策略同周期触发可能放大回撤概率,实盘前务必在策略测试器隔离验证。把 EA 以默认参数拖到图表后,可在可视化模式下观察金叉死叉信号是否与其他策略的建仓区重叠——若重叠频繁,说明仓位冲突风险倾向上升。 想立刻验证,复制下面代码挂到 MT5 测试器,只开 UseGoldenDeathCrossStrategy=true 跑一轮,再看信号密度。
class="type">void OnTick() { if (UseTrendFollowingStrategy) CheckTrendFollowing(); if (UseBreakoutStrategy) CheckBreakoutTrading(); if (UseDivergenceStrategy) CheckDivergenceTrading(); if(UseGoldenDeathCrossStrategy) CheckGoldenDeathCross(); }
◍ 从教学 EA 走向机器学习优化
把金叉死叉塞进趋势约束型 EA 后,代码体量从最初的数百行涨到了 Trend_Constraint_Expert.mq5 的 20.32 KB,系统资源占用随逻辑分支增加明显抬升。继续堆功能会让维护成本失控,这时候把 AI 模型接进来处理多约束判断,比手写嵌套 if 更扛复杂度和扩展压力。 这套 EA 只是教学与实验底座,不保证任何账户盈利,外汇与贵金属杠杆品种的高风险始终存在。后续若引入机器学习做参数自适应,建议先拿 GoldenDeathCross.mq5(4.62 KB)做孤立回测,再合并进主框架,避免一次动太多结构导致无法定位 bug。