构建K线趋势约束模型(第九部分):多策略智能交易系统(EA)(三)(基础篇)
◍ 用分形结构给EA加一道趋势闸门
在 MT5 里做多策略 EA,最容易被忽略的是「趋势到底有没有成立」这一道前置约束。很多策略在震荡里反复开仓,回测漂亮、实盘失血,根子就在缺一个轻量的方向过滤器。 分形(Fractal)是 MT5 内置指标,默认参数 5 根 K 线:中间那根最高/最低,左右各两根更低/更高即构成一个顶/底分形。用它做趋势闸门很简单——只在最近一个上分形被突破时允许做多,下分形被跌破时允许做空。 下面这段 MQL5 片段演示如何抓取最近的分形并判断突破,可直接塞进 EA 的 OnTick 里做条件门禁。外汇与贵金属杠杆高、滑点突变频繁,分形假突破概率不低,实盘前务必用策略测试器跑至少 3 个月 tick 数据。
class="type">class="kw">double lastFractalUp = iFractals(NULL,class="num">0,MODE_UPPER,class="num">1); class="type">class="kw">double lastFractalDown = iFractals(NULL,class="num">0,MODE_LOWER,class="num">1); class="type">class="kw">double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">bool canBuy = (bid > lastFractalUp) && (lastFractalUp != class="num">0); class="type">bool canSell = (ask < lastFractalDown) && (lastFractalDown != class="num">0);
「用背离给趋势系统加一道过滤」
在日线级别做趋势跟随,最头疼的不是方向,而是噪音。一根无关紧要的回调毛刺,就足以让入场触发在错误位置,把原本能成的单子变成止损。统计上,纯趋势系统在这层的假信号率往往高于波段系统,尤其在低波动转高波动的切换段。 背离的思路很直接:价格新高(或新低)但动量指标不跟随,说明推动力量在衰减。把它当过滤器接进趋势约束 EA,等于强制要求「价格走出来」和「动量认账」同时成立,才放行订单。 MQL5 的价值在这儿就显出来了——指标计算和订单路由能在同一个 OnTick 里闭环,不需要外挂脚本轮询。后面几节会拆具体集成步骤,以及回测里假信号下降了多少。
背离到底在提示什么
背离的本质,是用指标和价格的「不同步」来暴露趋势内部的虚弱。价格还在沿原方向刷新极值,但 RSI、MACD 这类动能指标已经不肯跟了,这种方向撕扯往往意味着当前趋势的推动力在衰减,反转或至少回调的概率会抬升。 具体看两种基础形态:看涨背离是价格录得更低的低点,指标却走出更高的低点,卖压退潮的信号;看跌背离反过来,价格新高而指标高点下移,上行动能枯竭的先兆。两者都不是承诺反转,只是把「趋势可能乏力」这件事量化出来。 学术侧也不是空谈。Bart 和 Masse 在 1981 年讨论意见分歧与风险时指出,市场观点分化会放大波动与风险,这恰好解释了为什么背离能成为技术分析的支点。Tilehnouei 和 Shivaraj 2013 年的实证还提到,在特定样本里 MACD 抓背离动能的表现可能优于 RSI——所以实盘里拿 RSI 和 MACD 交叉验证,比单吊一个指标更稳。 外汇和贵金属杠杆高、跳空频繁,背离信号假突破不少,真要用的话先在 MT5 历史数据里把两种指标的背离命中率分开测一遍再上实盘。
◍ 把背离判据写进EA的检测函数
做背离集成,第一步是用 iRSI() 算出指标序列,再拿 iHigh() / iLow() 抓指定 K 线的极值,跟价格摆在一起比。项目里把背离拆成两族:常规背离偏向反转预警,隐藏背离偏向趋势延续,EA 里要分函数写,别混在一个判断里。 常规看跌背离的典型结构是价格后一个高点更高、RSI 后一个高点却更低,概率上提示上行推力衰减;常规看涨则反过来,价格新低而 RSI 低点抬高。隐藏看涨出现在上升趋势里价格低点抬高、RSI 低点却压低,视作回踩续涨的参考。 下面两段是可直接塞进 EA 的骨架。CheckRegularBearishDivergence 取 H1 周期、RSI(14),比第 2 根与第 8 根 K 线的价格高点和 RSI 高点:价格高1>高2 且 RSI高1<高2 才返回 true。CheckHiddenBullishDivergence 同理比低点,价格低1>低2 且 RSI低1<低2 判为隐藏看涨。 实盘接进去时,建议在每个 K 线收盘触发一次,而不是每 tick 跑,能少算很多次 iRSI。外汇与贵金属杠杆高、滑点大,背离只是概率信号,必须叠一层趋势或情绪过滤再下单。
class=class="str">"cmt">// Regular divergence conditions in code class="type">bool CheckRegularBearishDivergence(class="type">int period = class="num">14, ENUM_TIMEFRAMES timeframe = PERIOD_H1) { class="type">class="kw">double priceHigh1 = iHigh(_Symbol, timeframe, class="num">2); class="type">class="kw">double priceHigh2 = iHigh(_Symbol, timeframe, class="num">8); class="type">class="kw">double rsiHigh1 = iRSI(_Symbol, timeframe, period, PRICE_CLOSE, class="num">2); class="type">class="kw">double rsiHigh2 = iRSI(_Symbol, timeframe, period, PRICE_CLOSE, class="num">8); if(priceHigh1 > priceHigh2 && rsiHigh1 < rsiHigh2) class="kw">return true; class="kw">return false; } class=class="str">"cmt">//RSI and Price Levels declaration and hidden divergence condition class="type">bool CheckHiddenBullishDivergence(class="type">int period = class="num">14, ENUM_TIMEFRAMES timeframe = PERIOD_H1) { class="type">class="kw">double priceLow1 = iLow(_Symbol, timeframe, class="num">2); class="type">class="kw">double priceLow2 = iLow(_Symbol, timeframe, class="num">8); class="type">class="kw">double rsiLow1 = iRSI(_Symbol, timeframe, period, PRICE_CLOSE, class="num">2); class="type">class="kw">double rsiLow2 = iRSI(_Symbol, timeframe, period, PRICE_CLOSE, class="num">8); if(priceLow1 > priceLow2 && rsiLow1 < rsiLow2) class="kw">return true; class="kw">return false; }
「用 MACD 给背离信号做二次过滤」
背离刚被确认还不够稳,实盘里容易吃假突破。项目里选 MACD 搭配 RSI 做二次验证:RSI 抓到背离后,看 MACD 线是否下穿信号线、或柱状图开始收缩,看跌背离才倾向算有效。看跌背离若同步出现 MACD 线跌破信号线,信号可靠性会提升,但外汇和贵金属波动大,仍属概率性触发。 代码层先声明缓冲区与输入参数,MACD 句柄在 OnInit 里用 iMACD 建好,失败就返回 INIT_FAILED。LookBack 设 8,意味着只回看最近 8 根 K 线找背离,避免拿太老的数据误判。 持仓身份用 magic 编号 87654321 锁定,CountOrdersByMagic 统计同编号持仓,背离策略最多开 3 笔,防过度交易。盈利保护上,LockProfits 扫活跃仓,盈利超 100 点就把止损按 profitLockerPoints(如 20 点)向价格方向移动,买仓上移、卖仓下移,仅当新止损更优才改。 日线趋势对齐是硬约束:日线看涨只做多,看空只做空,且只在完整 K 线执行。以下为参数与初始化片段,可直接拷进 MT5 的 EA 里改周期验证。
class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double ExtMacdBuffer[]; class="type">class="kw">double ExtSignalBuffer[]; class="type">class="kw">double ExtFastMaBuffer[]; class="type">class="kw">double ExtSlowMaBuffer[]; class="type">int ExtFastMaHandle; class="type">int ExtSlowMaHandle; class="macro">#include <Trade\Trade.mqh> CTrade trade; input class="type">bool UseDivergenceStrategy = true; class=class="str">"cmt">// Enable/Disable 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 class="type">class="kw">double ExtMacdBuffer[]; class=class="str">"cmt">// MACD values class="type">class="kw">double ExtSignalBuffer[]; class=class="str">"cmt">// Signal line values class="type">int macd_handle; class=class="str">"cmt">// MACD indicator handle class="type">int OnInit() { macd_handle = iMACD(_Symbol, PERIOD_CURRENT, DivergenceMACDPeriod, class="num">26, DivergenceSignalPeriod, PRICE_CLOSE); if (macd_handle == INVALID_HANDLE) { Print("Failed to initialize MACD. Error: ", GetLastError()); class="kw">return INIT_FAILED; } ArrayResize(ExtMacdBuffer, DivergenceLookBack); ArrayResize(ExtSignalBuffer, DivergenceLookBack); class="kw">return INIT_SUCCEEDED; } class="type">bool CheckBullishRegularDivergence() { class="type">class="kw">double priceLow1 = iLow(_Symbol, PERIOD_CURRENT, class="num">2);
把背离信号接进日内方向过滤
日线收阳才考虑多、收阴才考虑空,这是这段逻辑给背离信号加的硬约束。代码里用 iClose(_Symbol, PERIOD_D1, 0) 与 iOpen 比大小,日线实体方向直接决定后续只允许同向背离单触发,逆日线的常规背离被挡在门外。 隐藏背离的判定不依赖信号线交叉:牛市隐藏背离要求 priceHigh1 > priceHigh2 且 macdHigh1 < macdHigh2,熊市则反过来比低点。常规背离还要叠一层 ExtMacdBuffer[0] 与 ExtSignalBuffer[0] 的位置关系,主线在信号线上方才放行多单。 下单函数里止损止盈按点距算:DivergenceStopLoss 和 DivergenceTakeProfit 乘上 _Point,买价加减、卖价反加减。外汇与贵金属杠杆高,这类基于背离的 EA 分支在震荡市可能连续假信号,实盘前务必在 MT5 策略测试器用真实点差跑一遍。 开 MT5 把 DivergenceLookBack 从默认改到 20~50 之间试,回看 EURUSD 的 H1 上隐藏背离触发频率,能直观看到日线过滤砍掉了多少逆势单。
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 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="type">int openDivergencePositions = CountOrdersByMagic(DivergenceMagicNumber); if (openDivergencePositions == class="num">0 || openDivergencePositions < class="num">3) { 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) { 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; if (isDailyBullish && (CheckBullishRegularDivergence() && ExtMacdBuffer[class="num">0] > ExtSignalBuffer[class="num">0]) || CheckBullishHiddenDivergence()) { ExecuteDivergenceOrder(true); } if (isDailyBearish && (CheckBearishRegularDivergence() && ExtMacdBuffer[class="num">0] < ExtSignalBuffer[class="num">0]) || CheckBearishHiddenDivergence()) { ExecuteDivergenceOrder(false); } } } } class="type">void ExecuteDivergenceOrder(class="type">bool isBuy) { trade.SetExpertMagicNumber(DivergenceMagicNumber); class="type">class="kw">double currentPrice = isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); 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) {