价格行为分析工具包开发(第五部分):波动率导航智能交易系统(Volatility Navigator EA)·进阶篇
(2/3)·不少交易者方向看对却败在点位粗糙,本篇拆解EA如何靠布林带RSI与ATR自动算入场与风控
三指标叠合才放单的入场判定
波动率导航类 EA 的核心不在指标本身,而在把 RSI、布林带、ATR 三套数据放在同一根 tick 上做交叉验证。OnInit 里先把三个句柄建好,之后每次 OnTick 直接拉当前值,避免重复创建带来的开销。 RSI 用 70 / 30 两个阈值切超买超卖,布林带看收盘价是否碰上轨或下轨,ATR 则设了一道 atrThreshold 波动性门槛。单看任何一个都容易假信号,EA 的逻辑是三者同满足才动手:RSI<30 且价格<下轨且 ATR>阈值,倾向触发买入;反向条件齐备才倾向卖出。 图表上用 ObjectCreate 画 OBJ_ARROW 做可视化,买卖箭头带时间戳命名,不会互相覆盖。外汇与贵金属杠杆高、滑点随机,这套组合只提高信号概率,不消除踏空或止损风险,实盘前建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M15 跑一遍看信号密度。 下面这段是 EA 里指标句柄初始化与信号判定的原样代码,逐行拆完你能直接抄进自己的 ea 模板:先声明 rsiHandle、bbHandle、atrHandle 三个 int 句柄;OnInit 中用 iRSI(NULL,0,rsiPeriod,PRICE_CLOSE) 建 RSI 句柄,参数为当前品种、当前周期、rsiPeriod 周期、收盘价;iBands 建布林带句柄,传入 bbPeriod 与上下偏离 bbDevUp/bbDevDown;iATR 建平均真实波幅句柄;返回 INIT_SUCCEEDED 表示初始化通过。 后续用 iCustom 取 RSI 值,>70 注释为超买可能卖,<30 为超卖可能买;iBands 分别取上轨下轨,Close[0] 碰下轨倾向买、碰上轨倾向卖;iATR 取波动值,超阈值才考虑进场;最后 if 把三个条件用 && 锁死,全满足才 Generate buy/sell signal;DrawTradeSignals 里用 ObjectCreate 画箭头,买信号名前缀 BuySignal 加时间,卖信号前缀 SellSignal 加时间。
class=class="str">"cmt">// Declare indicator handles class="type">int rsiHandle; class="type">int bbHandle; class="type">int atrHandle; class=class="str">"cmt">// OnInit function class="type">int OnInit() { class=class="str">"cmt">// Create indicator handles rsiHandle = iRSI(NULL, class="num">0, rsiPeriod, PRICE_CLOSE); bbHandle = iBands(NULL, class="num">0, bbPeriod, bbDevUp, bbDevDown, PRICE_CLOSE); atrHandle = iATR(NULL, class="num">0, atrPeriod); class="kw">return(INIT_SUCCEEDED); } class="type">class="kw">double rsiValue = iCustom(NULL, class="num">0, "RSI", rsiPeriod, class="num">0); if (rsiValue > class="num">70) { class=class="str">"cmt">// Market is overbought - potentially signal to sell } else if (rsiValue < class="num">30) { class=class="str">"cmt">// Market is oversold - potentially signal to buy } class="type">class="kw">double upperBand = iBands(NULL, class="num">0, bbPeriod, bbDevUp, class="num">0, PRICE_CLOSE); class="type">class="kw">double lowerBand = iBands(NULL, class="num">0, bbPeriod, -bbDevDown, class="num">0, PRICE_CLOSE); class="type">class="kw">double price = Close[class="num">0]; if (price < lowerBand) { class=class="str">"cmt">// Price is touching the lower band - potential buy signal } else if (price > upperBand) { class=class="str">"cmt">// Price is touching the upper band - potential sell signal } class="type">class="kw">double atrValue = iATR(NULL, class="num">0, atrPeriod); if (atrValue > atrThreshold) { class=class="str">"cmt">// High volatility - consider entering trades } else { class=class="str">"cmt">// Low volatility - potentially stay out of the market } if (rsiValue < class="num">30 && price < lowerBand && atrValue > atrThreshold) { class=class="str">"cmt">// Generate buy signal } else if (rsiValue > class="num">70 && price > upperBand && atrValue > atrThreshold) { class=class="str">"cmt">// Generate sell signal } class="type">void DrawTradeSignals() { if (buySignal) { class=class="str">"cmt">// Draw buy signal on the chart ObjectCreate(class="num">0, "BuySignal" + IntegerToString(TimeCurrent()), OBJ_ARROW, class="num">0, Time[class="num">0], price); } else if (sellSignal) { class=class="str">"cmt">// Draw sell signal on the chart ObjectCreate(class="num">0, "SellSignal" + IntegerToString(TimeCurrent()), OBJ_ARROW, class="num">0, Time[class="num">0], price); } }
「给波动率EA接上声音与图表双重预警」
把 RSI 的极端值变成可感知的提醒,是波动率导航类 EA 落地时最实用的一步。原文方案用音频播报加图表文字对象,覆盖超买(>70)与超卖(<30)两种状态,交易者不必盯死屏幕也能知道阈值被碰。 在 OnTick 里持续取 RSI(14, 收盘价) 即可,不需要额外开定时器。超过 70 就 PlaySound 放 alert.wav,同时在 High[0] 位置写红色「Overbought Signal!」;跌破 30 则在 Low[0] 写绿色「Oversold Signal!」。这种双通道反馈让反应速度倾向更快,但外汇与贵金属波动剧烈,信号只是概率提示,不代表反转必现。 下面这段是核心警报片段,逐行看逻辑:iRSI 取当前品种、当前周期、周期 14、收盘价;if 判断 >70 进音频与红色文本对象分支;else if <30 走绿色文本对象分支。ObjectCreate 的 OBJ_TEXT 挂在 0 号子窗口,时间轴用 Time[0] 对齐最新 K 线。 阈值别照抄 70/30 就完事。EURUSD 在 M15 上 RSI 刺穿 70 后继续冲 20~30 点的情况不少见,真要降噪可把 input 参数暴露出来,自己调到 75/25 回测几周再定。
class=class="str">"cmt">// Example of defining thresholds class="type">class="kw">double rsiValue = iRSI(NULL, class="num">0, class="num">14, PRICE_CLOSE); class=class="str">"cmt">// Check for overbought and oversold conditions if (rsiValue > class="num">70) { class=class="str">"cmt">// Audio Alert PlaySound("alert.wav"); class=class="str">"cmt">// Visual Alert on Chart ObjectCreate("OverboughtAlert", OBJ_TEXT, class="num">0, Time[class="num">0], High[class="num">0]); ObjectSetText("OverboughtAlert", "Overbought Signal!", class="num">12, "Arial", clrRed); class=class="str">"cmt">// Additional actions like placing an order can be added here } else if (rsiValue < class="num">30) { class=class="str">"cmt">// Audio Alert PlaySound("alert.wav"); class=class="str">"cmt">// Visual Alert on Chart ObjectCreate("OversoldAlert", OBJ_TEXT, class="num">0, Time[class="num">0], Low[class="num">0]); ObjectSetText("OversoldAlert", "Oversold Signal!", class="num">12, "Arial", clrGreen); class=class="str">"cmt">// Additional actions like placing an order can be added here }
◍ 布林带与ATR参数及图表清理绘制逻辑
这套信号脚本把布林带周期钉在 20、偏差 2.0,ATR 取 14 周期并用 1.5 倍乘数算止损止盈, alert.wav 作声音提醒。外汇与贵金属波动剧烈,参数直接照搬可能放大滑点风险,建议先在 MT5 策略测试器里跑一轮再上实盘。 ClearPreviousDrawings 在每次重画前清掉旧对象:用 ObjectFind 判是否存在 EntryPoint、StopLoss、TakeProfit、SignalText、BuyArrow、SellArrow 六个命名图元,非 -1 就 ObjectDelete。这样能避免历史箭头和水平线堆叠干扰读图。 DrawTradeLines 先调清理函数,再 ObjectCreate 画 OBJ_HLINE 类型的 EntryPoint,设绿色宽 2;随后同样方式建 StopLoss 线。若创建失败 Print 出 GetLastError 码,方便你定位是句柄问题还是对象名冲突。开 MT5 把这段贴进 EA 的 include 段,改 clrGreen 为 clrDodgerBlue 就能立刻看出自己画的入场线。
input class="type">int bbPeriod = class="num">20; class=class="str">"cmt">// Period for Bollinger Bands input class="type">class="kw">double bbDeviation = class="num">2.0; class=class="str">"cmt">// Deviation for Bollinger Bands input class="type">int atrPeriod = class="num">14; class=class="str">"cmt">// ATR period for stop loss and take profit calculations input class="type">class="kw">double atrMultiplier = class="num">1.5; class=class="str">"cmt">// Multiplier for ATR in calculating stop loss and take profit input class="type">class="kw">string signalSound = "alert.wav"; class=class="str">"cmt">// Sound file for alert notifications class=class="str">"cmt">// Indicator handles for Bollinger Bands and ATR class="type">int bbHandle = class="num">0; class="type">int atrHandle = class="num">0; class=class="str">"cmt">// Function to clear previous drawings from the chart class="type">void ClearPreviousDrawings() { class=class="str">"cmt">// Delete any previously created trade lines and signal text if (ObjectFind(class="num">0, "EntryPoint") != -class="num">1) ObjectDelete(class="num">0, "EntryPoint"); if (ObjectFind(class="num">0, "StopLoss") != -class="num">1) ObjectDelete(class="num">0, "StopLoss"); if (ObjectFind(class="num">0, "TakeProfit") != -class="num">1) ObjectDelete(class="num">0, "TakeProfit"); if (ObjectFind(class="num">0, "SignalText") != -class="num">1) ObjectDelete(class="num">0, "SignalText"); if (ObjectFind(class="num">0, "BuyArrow") != -class="num">1) ObjectDelete(class="num">0, "BuyArrow"); if (ObjectFind(class="num">0, "SellArrow") != -class="num">1) ObjectDelete(class="num">0, "SellArrow"); } class=class="str">"cmt">// Function to draw entry points, stop loss, and take profit on the chart class="type">void DrawTradeLines(class="type">class="kw">double entryPoint, class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit, class="type">class="kw">string signalText) { class=class="str">"cmt">// Clear previous drawings before drawing new ones ClearPreviousDrawings(); class=class="str">"cmt">// Draw the entry point line if (!ObjectCreate(class="num">0, "EntryPoint", OBJ_HLINE, class="num">0, TimeCurrent(), entryPoint)) Print("Failed to create EntryPoint line. Error: ", GetLastError()); ObjectSetInteger(class="num">0, "EntryPoint", OBJPROP_COLOR, clrGreen); ObjectSetInteger(class="num">0, "EntryPoint", OBJPROP_WIDTH, class="num">2); class=class="str">"cmt">// Draw the stop loss line if (!ObjectCreate(class="num">0, "StopLoss", OBJ_HLINE, class="num">0, TimeCurrent(), stopLoss))
把止损止盈和信号直接画进图表
信号触发后若只靠日志输出,盯盘时很容易漏看。下面这段把止损线、止盈线和文字标签直接绘到 MT5 主图,视觉上一眼可辨。 止损线用红色水平线(OBJ_HLINE),宽度设为 2;止盈线用蓝色同类型线。若 ObjectCreate 返回 false,立即用 GetLastError 打印具体错误码,方便排查对象名冲突或图表权限问题。 信号文字标签用 OBJ_LABEL,锚定在 entryPoint+10 的价格位,XDISTANCE 10 像素、YDISTANCE 30 像素偏移,白字 12 号字体。注意 label 的 Y 坐标在 OBJ_LABEL 里是价格而非像素,所以 entryPoint+10 对 XAUUSD 这类点值大的品种可能偏出视野,实盘前先按品种点差调这个数。 DrawEntryArrow 用 OBJ_ARROW 且 arrowCode=233( Wingdings 上箭头),宽 2。循环建仓对象时若失败直接 return,不阻塞后续逻辑。ManageOpenPositions 从 PositionsTotal()-1 倒序遍历,用 PositionGetTicket + PositionSelectByTicket 锁定当前 symbol 的持仓,避免正序删除导致的索引错位。外汇和贵金属杠杆高,图形辅助不等于风控,对象绘制失败也可能让止损线失踪,开 MT5 跑一遍确认对象树里 StopLoss/TakeProfit 都在才算稳。
Print("Failed to create StopLoss line. Error: ", GetLastError()); ObjectSetInteger(class="num">0, "StopLoss", OBJPROP_COLOR, clrRed); ObjectSetInteger(class="num">0, "StopLoss", OBJPROP_WIDTH, class="num">2); class=class="str">"cmt">// Draw the take profit line if (!ObjectCreate(class="num">0, "TakeProfit", OBJ_HLINE, class="num">0, TimeCurrent(), takeProfit)) Print("Failed to create TakeProfit line. Error: ", GetLastError()); ObjectSetInteger(class="num">0, "TakeProfit", OBJPROP_COLOR, clrBlue); ObjectSetInteger(class="num">0, "TakeProfit", OBJPROP_WIDTH, class="num">2); class=class="str">"cmt">// Draw a label with the signal text to provide information at a glance if (!ObjectCreate(class="num">0, "SignalText", OBJ_LABEL, class="num">0, TimeCurrent(), entryPoint + class="num">10)) Print("Failed to create SignalText label. Error: ", GetLastError()); ObjectSetInteger(class="num">0, "SignalText", OBJPROP_XDISTANCE, class="num">10); ObjectSetInteger(class="num">0, "SignalText", OBJPROP_YDISTANCE, class="num">30); ObjectSetInteger(class="num">0, "SignalText", OBJPROP_COLOR, clrWhite); ObjectSetInteger(class="num">0, "SignalText", OBJPROP_FONTSIZE, class="num">12); ObjectSetString(class="num">0, "SignalText", OBJPROP_TEXT, signalText); } class=class="str">"cmt">// Function to draw arrows on the chart at entry points class="type">void DrawEntryArrow(class="type">class="kw">double price, class="type">class="kw">string label, class="type">class="kw">color arrowColor) { if (!ObjectCreate(class="num">0, label, OBJ_ARROW, class="num">0, TimeCurrent(), price)) { Print("Failed to create arrow object. Error: ", GetLastError()); class="kw">return; } ObjectSetInteger(class="num">0, label, OBJPROP_ARROWCODE, class="num">233); class=class="str">"cmt">// Arrow code for upward direction ObjectSetInteger(class="num">0, label, OBJPROP_COLOR, arrowColor); ObjectSetInteger(class="num">0, label, OBJPROP_WIDTH, class="num">2); class=class="str">"cmt">// Set the width of the arrow } class=class="str">"cmt">// Function to manage open positions for efficient trade execution class="type">void ManageOpenPositions(class="type">class="kw">string symbol) { class=class="str">"cmt">// Loop through all open positions for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { class="type">ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { class=class="str">"cmt">// Check if the position is for the current symbol