构建K线趋势约束模型(第十部分):战略均线金叉与死叉(智能交易系统EA)·进阶篇
(2/3)·手动盯均线交叉总慢半拍?这篇把金叉死叉写进MQL5 EA,让反转信号自己跑
◍ 双EMA交叉的开仓与平仓骨架
这段逻辑用快慢两条 EMA 的数组末两根值做交叉判定:当快线最新值上穿慢线、且前一根还在慢线下方或等于时,判定为金叉倾向做多;反之快线最新值下穿慢线、前一根在慢线上方或等于时,判定为死叉倾向做空。外汇与贵金属杠杆高,这类信号在历史行情中胜率随周期漂移,实盘前必须在 MT5 策略测试器跑多周期验证。 代码先通过 CopyBuffer 各取 2 根缓冲值,再用 SymbolInfoDouble 拿 point、ask、bid,避免硬编码点值。止损用 StopLossPips * point 再 NormalizeDouble 到 _Digits 精度,保证 XAUUSD 这类小数位多的品种不会报无效价位。 持仓退出条件写得很直白:持多单后若快线低于慢线,或持空单后快线高于慢线,就 PositionClose 平掉。注意原文在死叉分支后还写了一句独立的 if(!trade.Sell(...)) 打印错误,这段脱离交叉判断会无条件尝试卖单,复制时建议删掉或并入死叉块,否则非死叉时段也会发卖单。 输入参数只露了 LotSize=1.0 与 Slippage=20 点,前者直接决定爆仓速度,后者在伦敦盘开盘滑点扩大时可能让成交价偏离 ask 超过预期。建议把 LotSize 先设 0.01 跑通逻辑再放大。
class="type">class="kw">double fastEMAArray[], slowEMAArray[]; CopyBuffer(fastEMAHandle, class="num">0, class="num">0, class="num">2, fastEMAArray); CopyBuffer(slowEMAHandle, class="num">0, class="num">0, class="num">2, slowEMAArray); 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(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 + StopLossPips * point, _Digits); trade.Sell(LotSize, _Symbol, ask, sl); } 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 - StopLossPips * point, _Digits); trade.Buy(LotSize, _Symbol, bid, sl); } if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && fastEMAArray[class="num">0] < slowEMAArray[class="num">0]) || (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && fastEMAArray[class="num">0] > slowEMAArray[class="num">0])) { trade.PositionClose(PositionGetInteger(POSITION_TICKET)); } if(!trade.Sell(LotSize, _Symbol, ask, sl)) { Print("Sell order error: ", GetLastError()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Golden & Death Cross Strategy.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 "Clemence Benjamin" class="macro">#class="kw">property description "GOLDEN AND DEATH CROSS" class="macro">#class="kw">property version "class="num">1.0" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Includes | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include<Trade\Trade.mqh>; CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Input parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ input class="type">class="kw">double LotSize = class="num">1.0; class=class="str">"cmt">// Trade volume(lots) input class="type">int Slippage = class="num">20; class=class="str">"cmt">// Slippage in points
双均线 EA 的定时器与句柄初始化骨架
这段 MT5 代码展示了一个基于快慢 EMA 交叉思路的 EA 初始化与定时轮询骨架。外部参数把定时器间隔设为 1000 秒、止损 1500 点、快线周期 50、慢线周期 200,黄金类品种若用 1500 点止损需确认点值是否超出账户承受范围,外汇与贵金属均属高风险品类。 OnInit 里先 EventSetTimer(TimerInterval) 拉起定时器,随后用 Bars(_Symbol,PERIOD_CURRENT)<SlowEMAPeriod 做护栏:当前图表 K 线数不足 200 根时直接 INIT_FAILED,避免 EMA 算出空值。OnDeinit 对称地 EventKillTimer() 释放资源,防止退出后定时器空转。 OnTimer 是核心节奏点:每次触发先 PositionSelect(_Symbol) 看有无持仓,再用 iMA 拿快/慢 EMA 句柄,CopyBuffer 取最近 2 根收盘价平滑值做交叉判定预备。句柄或拷贝失败就 Print(GetLastError()) 并 return,这种失败早退能避免脏数据下单。打开 MT5 把周期切到 H1 跑这段,可能倾向在慢线 200 根成型后才有稳定信号。
input class="type">int TimerInterval = class="num">1000; class=class="str">"cmt">// Timer interval in seconds input class="type">class="kw">double StopLossPips = class="num">1500; class=class="str">"cmt">// Stop Loss in pips input class="type">int FastEMAPeriod = class="num">50; class=class="str">"cmt">// Fast EMA period(class="kw">default class="num">50) input class="type">int SlowEMAPeriod = class="num">200; class=class="str">"cmt">// Slow EMA period(class="kw">default class="num">200) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- create timer EventSetTimer(TimerInterval); class=class="str">"cmt">//--- Check if there are enough bars to calculate the EMA if(Bars(_Symbol,PERIOD_CURRENT)<SlowEMAPeriod) { Print("Not enough bars for EMA calculation"); 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">//--- class="kw">delete timer EventKillTimer(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert timer function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { class="type">bool hasPosition = PositionSelect(_Symbol); class="type">int fastEMAHandle = iMA(_Symbol, PERIOD_CURRENT, FastEMAPeriod, class="num">0, MODE_EMA, PRICE_CLOSE); class="type">int slowEMAHandle = iMA(_Symbol, PERIOD_CURRENT, SlowEMAPeriod, class="num">0, MODE_EMA, PRICE_CLOSE); if(fastEMAHandle < class="num">0 || slowEMAHandle < class="num">0) { Print("Failed to create EMA handles. Error: ", GetLastError()); class="kw">return; } class="type">class="kw">double fastEMAArray[], slowEMAArray[]; if(CopyBuffer(fastEMAHandle, class="num">0, class="num">0, class="num">2, fastEMAArray) <= class="num">0 || CopyBuffer(slowEMAHandle, class="num">0, class="num">0, class="num">2, slowEMAArray) <= class="num">0) { Print("Failed to copy EMA data. Error: ", GetLastError()); class="kw">return; } class="type">class="kw">double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
「EMA 交叉触发下的开平仓逻辑」
这段逻辑把双均线死叉 / 金叉直接映射成无持仓时的下单动作,以及持仓时的反手平仓。外汇与贵金属杠杆高,这类信号在震荡市可能连续假突破,实盘前务必用 MT5 策略测试器跑一遍。 无仓位时,若快线当前值大于慢线、且上一根快线小于等于慢线,判定为死叉,按 ask 价做空并将止损设在 ask 加 StopLossPips 个点;反向条件则判定为金叉,按 bid 价做多、止损置于 bid 减对应点数。两种情形都用 NormalizeDouble 对齐品种小数位,避免报价精度导致拒单。 已有仓位时,逻辑只做退出:多单遇到快线下穿慢线、或空单遇到快线上穿慢线,就提取持仓 ticket 调用 PositionClose 平掉。此处没有反向开仓,意味着同一时刻账户至多持有一单,回测中可明显降低多空对锁的手续费损耗。 开仓与平仓都打印了错误码或成交信息,把 StopLossPips 设成 15 这类具体数值,就能在日志里直接核对每笔 SL 距离是否合理,不用猜。
class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); 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 + StopLossPips * point, _Digits); if(!trade.Sell(LotSize, _Symbol, ask, sl )) Print("Buy order error ", GetLastError()); else Print("Buy order opened with TP ", " and SL ", 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 - StopLossPips * point, _Digits); if(!trade.Buy(LotSize, _Symbol, bid, sl )) Print("Sell order error ", GetLastError()); else Print("Sell order opened with TP ", " and SL ", StopLossPips, " pips"); } } else { if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && fastEMAArray[class="num">0] < slowEMAArray[class="num">0]) || (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(Ticket: ", ticket, "). Error: ", GetLastError()); else Print("Position closed : ", ticket); } } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 回测先跑通,离场先别急着定
编译通过后,先丢进 MT5 策略测试器跑一轮,不急着接趋势过滤。测试标的选 Boom 500 指数、周期 M5,用最朴素的金叉做多 / 死叉做空验证逻辑是否跑得起来。 从这一轮初步结果看,订单开平的执行链路是顺的,没有卡单或重复发单。但离场明显拖后腿:不少本该吃到大段的头寸,等到反向交叉才平,回撤已经吃掉大半利润,最终只剩微利甚至翻亏。 盘整段更尴尬,假交叉密集触发,来回止损。Boom 500 这类高波动指数品种本身风险就高,裸叉策略在窄幅来回里基本是送钱。下一步该把离场条件从「等反向交叉」改成跟踪止损或幅度止盈,而不是指望交叉信号自己兜底。
给 EA 加金叉死叉开关而不碰原有变量
把此前三套策略(趋势跟踪、唐奇安突破、背离)装进同一个 EA 后,下一步是塞进第四套——金叉死叉。它按设计独立跑、不受趋势约束,能抓各类反转,所以改动重点不是加逻辑限制,而是开布尔开关并隔离命名。 为防和主程序里已有的 LotSize、Slippage 等撞名,金叉死叉相关变量统一加 GDC_ 前缀。例如原 LotSize 改成 GDC_LotSize = 1.0,这样 MT5 编译时不会报重复定义,也方便你一眼认出哪块是新增的。 代码里 UseGoldenDeathCrossStrategy 默认设 true,另外三个策略开关默认 false。GDC_FastEMAPeriod 是 50、GDC_SlowEMAPeriod 是 200,止损给到 1500 点——外汇和贵金属波动大、杠杆高,这套参数在实盘前务必用策略测试器过一遍历史数据。 想验证就直接把下面代码贴进 MT5 的 MQ5 文件,编译后到输入项里翻 GDC 开头的参数,改周期或手数看回测曲线是否漂移。
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 Benjamini" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.03" 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 = false; class=class="str">"cmt">// Enable/Disable Divergence Strategy input class="type">bool UseGoldenDeathCrossStrategy = true; class=class="str">"cmt">// Enable/Disable Golden/Death Cross Strategy class=class="str">"cmt">// Input parameters for Golden/Death Cross Strategy input class="type">class="kw">double GDC_LotSize = class="num">1.0; class=class="str">"cmt">// Trade volume(lots) for Golden Death Cross input class="type">int GDC_Slippage = class="num">20; class=class="str">"cmt">// Slippage in points for Golden Death Cross input class="type">int GDC_TimerInterval = class="num">1000; class=class="str">"cmt">// Timer interval in seconds for Golden Death Cross input class="type">class="kw">double GDC_StopLossPips = class="num">1500; class=class="str">"cmt">// Stop Loss in pips for Golden Death Cross input class="type">int GDC_FastEMAPeriod = class="num">50; class=class="str">"cmt">// Fast EMA period for Golden Death Cross input class="type">int GDC_SlowEMAPeriod = class="num">200; class=class="str">"cmt">// Slow EMA period for Golden Death Cross class="type">int GDC_fastEMAHandle, GDC_slowEMAHandle; class=class="str">"cmt">// Handles for EMA indicators in Golden Death Cross class=class="str">"cmt">// Global variables class="type">class="kw">double prevShortMA, prevLongMA; 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
「把三套策略参数塞进同一个EA」
这段输入区把趋势约束、突破和背离三套逻辑的参数全摊在一个EA里,开MT5新建mq5文件直接粘贴就能看到变量结构。外汇和贵金属杠杆高,参数错了可能瞬间放大回撤,先读懂再跑。 TakeProfit=200点、TrailingStop=50点、MagicNumber=12345678、OrderLifetime=43200秒(即12小时)管的是趋势约束单;突破部分用InpDonchianPeriod=20通道周期,RiskRewardRatio=1.5,固定0.1手,止损15点止盈30点。 背离块独立用DivergenceMagicNumber=87654321区分订单,MACD快线12、信号9,回看8根,止损300点止盈500点,profitLockerPoints=20表示浮盈超20点后启动锁利。下面代码是输入与句柄声明的原样,逐行对应上面说的归属。 别把MagicNumber当摆设:两套策略若共用一个号,平仓函数会误杀对方单子,实盘前改掉默认值。
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 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() { prevShortMA = class="num">0.0; prevLongMA = class="num">0.0; class=class="str">"cmt">// Initialize RSI handle