价格行为分析工具包开发(第二十六部分):针形线、吞没形态与RSI背离(多模式)工具·进阶篇
「EA 初始化与每根 K 线的数据抓取逻辑」
在 MT5 里写背离策略 EA,第一步是把 RSI 句柄抓到手:用 iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE) 绑定当前品种当前周期,若返回 INVALID_HANDLE 直接 INIT_FAILED,避免后面空指针崩。 紧接着把所有价格与时间的数组用 ArraySetAsSeries(..., true) 设成时间序列,这样 rsiBuffer[0]、closeBuffer[0] 就是「最新一根」,写判断时不用倒着数下标,少出 bug。 OnTick 里先卡 Bars(_Symbol, _Period) < 20 就退出——少于 20 根Bar 时统计量不可信,外汇与贵金属波动大,样本不够容易误触发,属高风险环境。 数据拷贝用 CopyTime / CopyOpen / CopyHigh / CopyLow / CopyClose 一次性拉 20 根,任意一项 <=0 就 return;再用 timeBuffer[1]==lastBarTime 拦掉同一根 candle 的重复信号,只认「刚走完的那根」开仓逻辑。 最后 CopyBuffer 刷 RSI,调 FindSignalBar 拿方向,多空入场价与止损分别挂在 highBuffer[1]+EntryBuffer*_Point 与 lowBuffer[1]-SL_Pips*_Point 这类位置,并 DrawSignal 画出来——你打开 MT5 把这段粘进 EA,改 RSI_Period 就能看不同周期背离频率。
rsiHandle = iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE); if(rsiHandle == INVALID_HANDLE) class="kw">return INIT_FAILED; class=class="str">"cmt">// Set arrays as series for easier access to recent data ArraySetAsSeries(rsiBuffer, true); ArraySetAsSeries(timeBuffer, true); ArraySetAsSeries(openBuffer, true); ArraySetAsSeries(highBuffer, true); ArraySetAsSeries(lowBuffer, true); ArraySetAsSeries(closeBuffer, true); class="kw">return INIT_SUCCEEDED; } class="type">void OnTick() { class=class="str">"cmt">// Ensure enough data is available if(Bars(_Symbol, _Period) < class="num">20) class="kw">return; class=class="str">"cmt">// Copy recent market data if(CopyTime(_Symbol, _Period, class="num">0, class="num">20, timeBuffer) <= class="num">0 || CopyOpen(_Symbol, _Period, class="num">0, class="num">20, openBuffer) <= class="num">0 || CopyHigh(_Symbol, _Period, class="num">0, class="num">20, highBuffer) <= class="num">0 || CopyLow(_Symbol, _Period, class="num">0, class="num">20, lowBuffer) <= class="num">0 || CopyClose(_Symbol, _Period, class="num">0, class="num">20, closeBuffer) <= class="num">0) class="kw">return; class=class="str">"cmt">// Prevent multiple signals in the same candle if(timeBuffer[class="num">1] == lastBarTime) class="kw">return; lastBarTime = timeBuffer[class="num">1]; class=class="str">"cmt">// Update RSI data if(CopyBuffer(rsiHandle, class="num">0, class="num">0, class="num">20, rsiBuffer) <= class="num">0) class="kw">return; class=class="str">"cmt">// Detect potential divergence and pattern class="type">int dir = FindSignalBar(); if(dir == class="num">0) class="kw">return; class=class="str">"cmt">// Determine direction and compute entry/stop levels class="type">bool isBullish = (dir > class="num">0); class="type">int idx = class="num">1; class=class="str">"cmt">// most recent completed bar class="type">class="kw">double entry = isBullish ? highBuffer[idx] + EntryBuffer * _Point : lowBuffer[idx] - EntryBuffer * _Point; class="type">class="kw">double stopL = isBullish ? lowBuffer[idx] - SL_Pips * _Point : highBuffer[idx] + SL_Pips * _Point; class=class="str">"cmt">// Visualize and notify DrawSignal(idx, isBullish, entry, stopL); }
◍ 背离叠加蜡烛形态的信号判定函数
下面这段逻辑把「RSI 背离」和「价格行为形态」绑在一起才肯发信号,单独出现背离一律不认。函数扫描最近 5~15 根 K 线,只要价格创新低而 RSI 未创新低且处于超卖区,就标记 bullDiv;反向同理标记 bearDiv。 若两个背离标志都为假,直接 return 0 不浪费算力。只有背离成立、且当前根(索引 1)出现看涨 Pin Bar 或看涨吞没,才返回 +1;看跌组合则返回 -1。外汇与贵金属波动剧烈,这种双重过滤只能提高信号质量,无法消除假突破风险。 Pin Bar 的几何约束很硬:下影线必须大于实体的 2 倍,上影线小于实体 0.5 倍,实体自身还得超过整根振幅的 10%。熊版只是把方向和下影/上影角色对调。 吞没形态这边只写了半截——函数先排除当前根收阴或开盘高于前一根收盘的情况,剩下部分通常要继续比对实体完全包裹前一根才返回 true。开 MT5 把这段补完,回测 EURUSD 15 分钟,超卖阈值设 30 时信号频率会明显下降。
class="type">int FindSignalBar() { class="type">bool bullDiv = class="kw">false, bearDiv = class="kw">false; for(class="type">int i = class="num">5; i <= class="num">15; i++) { class=class="str">"cmt">// Bullish divergence condition if(lowBuffer[i] > lowBuffer[class="num">1] && rsiBuffer[i] < rsiBuffer[class="num">1] && rsiBuffer[class="num">1] < RSI_Oversold) bullDiv = true; class=class="str">"cmt">// Bearish divergence condition if(highBuffer[i] < highBuffer[class="num">1] && rsiBuffer[i] > rsiBuffer[class="num">1] && rsiBuffer[class="num">1] > RSI_Overbought) bearDiv = true; } class=class="str">"cmt">// No divergence detected if(!bullDiv && !bearDiv) class="kw">return class="num">0; class=class="str">"cmt">// Check for candlestick patterns supporting divergence class="type">bool bullPat = IsBullishPinBar(class="num">1) || IsBullishEngulfing(class="num">1); class="type">bool bearPat = IsBearishPinBar(class="num">1) || IsBearishEngulfing(class="num">1); class=class="str">"cmt">// Confirmed signals if(bullDiv && bullPat) class="kw">return +class="num">1; if(bearDiv && bearPat) class="kw">return -class="num">1; class="kw">return class="num">0; class=class="str">"cmt">// No valid signal } class=class="str">"cmt">// Bullish Pin Bar Pattern class="type">bool IsBullishPinBar(class="type">int i) { class="type">class="kw">double body = MathAbs(openBuffer[i] - closeBuffer[i]); class="type">class="kw">double rng = highBuffer[i] - lowBuffer[i]; class="type">class="kw">double lw = MathMin(openBuffer[i], closeBuffer[i]) - lowBuffer[i]; class="type">class="kw">double uw = highBuffer[i] - MathMax(openBuffer[i], closeBuffer[i]); class="kw">return closeBuffer[i] > openBuffer[i] && lw > class="num">2.0 * body && uw < class="num">0.5 * body && body > class="num">0.1 * rng; } class=class="str">"cmt">// Bearish Pin Bar Pattern class="type">bool IsBearishPinBar(class="type">int i) { class="type">class="kw">double body = MathAbs(openBuffer[i] - closeBuffer[i]); class="type">class="kw">double rng = highBuffer[i] - lowBuffer[i]; class="type">class="kw">double uw = highBuffer[i] - MathMax(openBuffer[i], closeBuffer[i]); class="type">class="kw">double lw = MathMin(openBuffer[i], closeBuffer[i]) - lowBuffer[i]; class="kw">return closeBuffer[i] < openBuffer[i] && uw > class="num">2.0 * body && lw < class="num">0.5 * body && body > class="num">0.1 * rng; } class=class="str">"cmt">// Bullish Engulfing Pattern class="type">bool IsBullishEngulfing(class="type">int i) { if(closeBuffer[i] <= openBuffer[i]) class="kw">return class="kw">false; if(openBuffer[i] > closeBuffer[i+class="num">1]) class="kw">return class="kw">false;
吞没形态判定与信号可视化落点
看吞没形态不能只看两根 K 线颜色,核心在实体对前一根的包裹关系。下方函数是 Bullish 与 Bearish Engulfing 的判定逻辑,直接在 MT5 自定义指标里复用即可。
if(closeBuffer[i] < openBuffer[i+class="num">1]) class="kw">return class="kw">false; class="kw">return true; } class=class="str">"cmt">// Bearish Engulfing Pattern class="type">bool IsBearishEngulfing(class="type">int i) { if(closeBuffer[i] >= openBuffer[i]) class="kw">return class="kw">false; if(openBuffer[i] < closeBuffer[i+class="num">1]) class="kw">return class="kw">false; if(closeBuffer[i] > openBuffer[i+class="num">1]) class="kw">return class="kw">false; class="kw">return true; }
closeBuffer[i] < openBuffer[i+1] 表示当前收低于前一根开,不成立才返回 true,即当前为阳线且吞没前阴。Bearish 函数三重过滤——当前非阴线直接否;当前开不低于前收(没形成向下跳空吞没)否;当前收不高于前开(没包住前实体)否;全过才是有效看跌吞没。
DrawSignal 负责把信号画出来:箭头 code 233/234 分别对应多空,entry 画 aqua 横线、stopL 画橙红虚线,再用未来 5~14 根内更低的低点(多)或更高的高点(空)拉一条趋势线做背离确认。外汇与贵金属杠杆高,信号仅提高概率,实盘须自设止损。
开 MT5 把这两段塞进你的指标 OnCalculate,用 EURUSD 15M 回看最近 300 根,统计吞没后 10 根内反向运行≥20 点的比例,大概率在 55%~65% 区间,可作为调参基线。
if(closeBuffer[i] < openBuffer[i+class="num">1]) class="kw">return class="kw">false; class="kw">return true; } class=class="str">"cmt">// Bearish Engulfing Pattern class="type">bool IsBearishEngulfing(class="type">int i) { if(closeBuffer[i] >= openBuffer[i]) class="kw">return class="kw">false; if(openBuffer[i] < closeBuffer[i+class="num">1]) class="kw">return class="kw">false; if(closeBuffer[i] > openBuffer[i+class="num">1]) class="kw">return class="kw">false; class="kw">return true; }
「把形态信号推送到多端」
在画出橙色趋势线标记结构后,EA 会根据多空分支构造告警文本。pattern 变量先判断当前是看涨吞没、看涨 PinBar,还是对应的空头形态,side 则直接给出 Buy / Sell 倾向,整套逻辑依赖前面 IsBullishEngulfing / IsBearishEngulfing 的布尔返回。 StringFormat 把形态名、方向、品种、K线时间、入场与止损价拼成一段多行文本,其中 entry 与 stopL 为前面计算出的浮点值,外汇品种默认保留 5 位小数(如 1.08452)。这一步是离屏提醒的关键,交易者不用盯盘也能拿到精确价位。 真正发出通知时,Alert 弹窗必然触发;若 EnableSound 为真则播放 alert.wav,EnablePush 走 SendNotification 推手机,EnableEmail 发邮件,最后 Print 进日志。外汇与贵金属波动剧烈、杠杆风险高,这类信号仅提示概率倾向,不代表必然反转。 下方输入参数决定了策略边界:RSI 周期 14、超买 70 / 超卖 30,止损止盈都默认 20 点。开 MT5 把 SL_Pips 调到 30 以上,可能更适配黄金的日内毛刺,但需自行回测验证。
ObjectCreate(class="num">0, nameL, OBJ_TREND, class="num">0, timeBuffer[j], highBuffer[j], timeBuffer[i], highBuffer[i]); ObjectSetInteger(class="num">0, nameL, OBJPROP_COLOR, clrOrange); class="kw">break; } class=class="str">"cmt">// Construct alert message class="type">class="kw">string pattern = isBullish ? (IsBullishEngulfing(i) ? "Engulfing" : "PinBar") : (IsBearishEngulfing(i) ? "Engulfing" : "PinBar"); class="type">class="kw">string side = isBullish ? "Buy" : "Sell"; class="type">class="kw">string txt = StringFormat( "%s + RSI Divergence %s Signal\nSymbol: %s\nTime: %s\nEntry: %.5f\nSL: %.5f", pattern, side, _Symbol, TimeToString(timeBuffer[i], TIME_MINUTES), entry, stopL ); class=class="str">"cmt">// Notify trader Alert(txt); if(EnableSound) PlaySound("alert.wav"); if(EnablePush) SendNotification(txt); if(EnableEmail) SendMail("Signal EA Alert", txt); Print(txt); class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.0" class="macro">#class="kw">property strict class="macro">#include <Trade/Trade.mqh> input class="type">int RSI_Period = class="num">14; input class="type">class="kw">double RSI_Overbought = class="num">70.0; input class="type">class="kw">double RSI_Oversold = class="num">30.0; input class="type">class="kw">double SL_Pips = class="num">20.0; class=class="str">"cmt">// in pips input class="type">class="kw">double TP_Pips = class="num">20.0; class=class="str">"cmt">// in pips
◍ 信号触发后的挂单与缓冲管理
信号柱识别完后,真正的动作在 OnTick 收尾段:以上一根信号柱高低点加减 5 点缓冲(EntryBuffer 默认 5.0 点)挂突破单。多头取 highBuffer[1] + EntryBuffer*_Point,空头取 lowBuffer[1] - EntryBuffer*_Point,这个缓冲是为了过滤毛刺,外汇与贵金属点差波动大,直接穿价可能频繁假触发。 止损放在信号柱另一侧:isBullish 时 stopL 取 lowBuffer[idx],空头反之取 highBuffer[idx],距离入场通常落在 10~30 点区间(取决于该柱振幅)。MT5 里把 EntryBuffer 调到 2~3 点,回测欧美 M15 会看到触发次数上升但止损率也同步走高,属于典型参数权衡。 通知开关分开控:EnableSound / EnablePush 默认开,EnableEmail 默认关。实盘跑黄金时建议至少开 Push,否则跳空行情里挂单成交了你还在盯别的品种。 lastBarTime 这个变量是关键节流阀——只在每根新柱第一次 OnTick 时跑信号逻辑,避免同根柱内重复下单。复制这段代码到 MT5 策略测试器,用 2023 年 XAUUSD 数据跑一遍,重点看 lastBarTime 过滤后订单数是否减少 60% 以上。
input class="type">class="kw">double EntryBuffer = class="num">5.0; class=class="str">"cmt">// in points input class="type">bool EnableSound = true; input class="type">bool EnablePush = true; input class="type">bool EnableEmail = class="kw">false; CTrade trade; class=class="str">"cmt">// internal buffers class="type">class="kw">double rsiBuffer[]; class="type">class="kw">datetime timeBuffer[]; class="type">class="kw">double openBuffer[], highBuffer[], lowBuffer[], closeBuffer[]; class="type">int rsiHandle; class="type">class="kw">datetime lastBarTime = class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { rsiHandle = iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE); if(rsiHandle == INVALID_HANDLE) class="kw">return INIT_FAILED; ArraySetAsSeries(rsiBuffer, true); ArraySetAsSeries(timeBuffer, true); ArraySetAsSeries(openBuffer, true); ArraySetAsSeries(highBuffer, true); ArraySetAsSeries(lowBuffer, true); ArraySetAsSeries(closeBuffer, true); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(Bars(_Symbol,_Period) < class="num">20) class="kw">return; if(CopyTime(_Symbol,_Period,class="num">0,class="num">20,timeBuffer) <= class="num">0 || CopyOpen(_Symbol,_Period,class="num">0,class="num">20,openBuffer) <= class="num">0 || CopyHigh(_Symbol,_Period,class="num">0,class="num">20,highBuffer) <= class="num">0 || CopyLow(_Symbol,_Period,class="num">0,class="num">20,lowBuffer) <= class="num">0 || CopyClose(_Symbol,_Period,class="num">0,class="num">20,closeBuffer) <= class="num">0) class="kw">return; if(timeBuffer[class="num">1] == lastBarTime) class="kw">return; lastBarTime = timeBuffer[class="num">1]; if(CopyBuffer(rsiHandle,class="num">0,class="num">0,class="num">20,rsiBuffer) <= class="num">0) class="kw">return; class="type">int dir = FindSignalBar(); if(dir == class="num">0) class="kw">return; class="type">int idx = class="num">1; class="type">bool isBullish = (dir > class="num">0); class="type">class="kw">double entry = isBullish ? highBuffer[idx] + EntryBuffer * _Point : lowBuffer[idx] - EntryBuffer * _Point; class="type">class="kw">double stopL = isBullish