价格行为分析工具包开发(第二十九部分):暴涨与暴跌拦截EA(基础篇)
◍ 这套EA要解决什么盘口问题
原文给出的系列第29部分定位很直接:做一个在 MT5 上拦截暴涨与暴跌的 EA。作者 Christian Benjamin 在 2026 年 4 月 14 日发布示例,帖子交互数据为 466 次查看、9 条评论,说明这类价格行为工具在实盘社群里有人盯。 外汇与贵金属波动常出现短时单向极端推进,手动拦截容易滑点甚至错过。EA 的价值是把「识别暴涨/暴跌 → 进场拦截」固化成可回测逻辑,而不是靠肉眼盯盘。 本小节只交代定位与章节结构:策略解析、代码分解、回测与结果、结论。后续三节才会展开具体信号与 MQL5 实现,当前先明确这是一套面向极端波动的自动化拦截思路,贵金属与外汇品种均适用但高风险显著。
EA怎么像防空系统一样筛信号
暴涨与暴跌拦截EA把市场数据当成雷达回波来处理,每一层筛选不过关就直接丢弃,不像很多指标那样见异动就报警。它先用一个滚动速度窗口量当前价格脉冲是否跑赢近期表现,再用基于ATR的激增乘数确认波动率确实放大,最后用MA趋势筛选验证方向偏差。 原文给出的三层基础过滤之外,还留了枢轴区域和交易时段两个可选开关,专门压住流动性稀薄时段的假信号。外汇与贵金属本身高风险,这种多层拦截只能提高信号质量,不保证胜率。 当所有层都确认,EA在图表画“暴涨/暴跌”箭头,颜色、偏移、CSV日志都能自定义。接下来要做的,是去MT5把每层参数校准一遍,看哪些组合在你的品种上误报最少。
「把暴涨暴跌当来袭弹头处理」
把价格瞬间的猛烈单边看成军事上的导弹威胁,EA 的拦截思路就清晰了:先用雷达网持续盯盘,再分多层确认,最后才扣扳机。速度阈值、ATR 突增、均线对齐是三道独立确认层,可选的枢轴区域过滤则像诱饵识别,四层全过才在图上标箭头。 速度雷达看的是过去 velocityHistoryBars 根 K 线内的价格位移,并与历史分位阈值比,只有落在异常大区间的变动才放行。ATR 突增探测器拿最新 ATR 跟前一根 ATR 乘 ATRMultiplier 比,捕捉波动率跳升;趋势一致性检查要求价涨时 SMA 也涨、价跌时 SMA 也跌。 枢轴区域过滤器会屏蔽离前一根 K 线枢轴点过近的信号,强制留 ZoneBufferPoints 个点的缓冲。最终逻辑是:以上条件全满足,才触发看涨或看跌箭头,把交易资金和注意力留给真正危险的弹头。外汇与贵金属波动剧烈,这套过滤只能降低噪音,无法消除方向误判的高风险。 下面这段是核心判定代码的拆解,复制进 MT5 能直接验证各层逻辑: // Record current vs. old price 记录当前卖价与 velocityHistoryBars 根前的旧价,算差值 delta // Build and sort past deltas 把历史相邻价差填入数组 d 并排序,用来找分位阈值 // Pick the (100–VelocityPctile)% threshold 按 VelocityPctile 算出历史大变动的临界值 velTh // Pass if current move exceeds threshold delta 绝对值超 velTh 才通过速度层 // Fetch two most recent ATR values 取最新两根 ATR 值 // Pass if ATR_now > ATR_prev × ATRMultiplier 当前 ATR 大于前根乘倍数才通过波动率层 // Fetch two most recent SMA values 取最新两根 SMA 值 // If delta>0 require SMA_now>SMA_prev… 价涨需 SMA 上行、价跌需 SMA 下行,过趋势层 // Compute prior bar’s pivot 用前根高低收算经典枢轴 pivot // Pass if priceNow is beyond pivot ± buffer 当前价超出枢轴加减缓冲点才过区域层 // Determine direction 依 delta 正负定暴涨或暴跌 // Fire only if … all passed 四层全过才调用 GenerateSignal 发信号
<span class="comment">class=class="str">"cmt">// Record current vs. old price</span> <span class="keyword">class="type">class="kw">double</span> priceNow = <span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>, <span class="macro">SYMBOL_ASK</span>); <span class="keyword">class="type">class="kw">double</span> priceOld = velHistory[VelocityHistoryBars - <span class="number">class="num">1</span>]; <span class="keyword">class="type">class="kw">double</span> delta = priceNow - priceOld; <span class="comment">class=class="str">"cmt">// Build and sort past deltas</span> <span class="keyword">class="type">class="kw">double</span> d[]; <span class="functions">ArrayResize</span>(d, VelocityHistoryBars - <span class="number">class="num">1</span>); <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">1</span>; i < VelocityHistoryBars; i++) d[i - <span class="number">class="num">1</span>] = velHistory[<span class="number">class="num">0</span>] - velHistory[i]; <span class="functions">ArraySort</span>(d); <span class="comment">class=class="str">"cmt">// Pick the(class="num">100–VelocityPctile)% threshold</span> <span class="keyword">class="type">int</span> idx = (<span class="keyword">class="type">int</span>)<span class="functions">MathRound</span>((VelocityPctile / <span class="number">class="num">100.0</span>) * (<span class="functions">ArraySize</span>(d) - <span class="number">class="num">1</span>)); <span class="keyword">class="type">class="kw">double</span> velTh = d[<span class="functions">ArraySize</span>(d) - <span class="number">class="num">1</span> - idx]; <span class="comment">class=class="str">"cmt">// Pass if current move exceeds threshold</span> <span class="keyword">class="type">bool</span> okVel = (delta > velTh || delta < -velTh); <span class="comment">class=class="str">"cmt">// Fetch two most recent ATR values</span> <span class="keyword">class="type">class="kw">double</span> atrArr[<span class="number">class="num">2</span>]; <span class="functions">CopyBuffer</span>(atrHandle, <span class="number">class="num">0</span>, <span class="number">class="num">0</span>, <span class="number">class="num">2</span>, atrArr); <span class="comment">class=class="str">"cmt">// Pass if ATR_now > ATR_prev × ATRMultiplier</span> <span class="keyword">class="type">bool</span> okATR = (atrArr[<span class="number">class="num">0</span>] > atrArr[<span class="number">class="num">1</span>] * ATRMultiplier); <span class="comment">class=class="str">"cmt">// Fetch two most recent SMA values</span> <span class="keyword">class="type">class="kw">double</span> maArr[<span class="number">class="num">2</span>]; <span class="functions">CopyBuffer</span>(maHandle, <span class="number">class="num">0</span>, <span class="number">class="num">0</span>, <span class="number">class="num">2</span>, maArr); <span class="comment">class=class="str">"cmt">// If delta>class="num">0 require SMA_now>SMA_prev; if delta<class="num">0 require SMA_now<SMA_prev</span> <span class="keyword">class="type">bool</span> okTrend = (delta > <span class="number">class="num">0</span> ? maArr[<span class="number">class="num">0</span>] > maArr[<span class="number">class="num">1</span>] : maArr[<span class="number">class="num">0</span>] < maArr[<span class="number">class="num">1</span>]); <span class="comment">class=class="str">"cmt">// Compute prior bar’s pivot</span> <span class="keyword">class="type">class="kw">double</span> h1 = <span class="functions">iHigh</span>(<span class="predefines">_Symbol</span>, MainTF, <span class="number">class="num">1</span>), l1 = <span class="functions">iLow</span> (<span class="predefines">_Symbol</span>, MainTF, <span class="number">class="num">1</span>), c1 = <span class="functions">iClose</span>(<span class="predefines">_Symbol</span>, MainTF, <span class="number">class="num">1</span>); <span class="keyword">class="type">class="kw">double</span> pivot = (h1 + l1 + c1) / <span class="number">class="num">3.0</span>; <span class="comment">class=class="str">"cmt">// Pass if priceNow is beyond pivot ± buffer</span> <span class="keyword">class="type">bool</span> okZone = (delta > <span class="number">class="num">0</span> ? priceNow < pivot - ZoneBufferPoints * <span class="predefines">_Point</span> : priceNow > pivot + ZoneBufferPoints * <span class="predefines">_Point</span>); <span class="comment">class=class="str">"cmt">// Determine direction</span> <span class="keyword">class="type">bool</span> isBoom = (delta > velTh); <span class="keyword">class="type">bool</span> isCrash = (delta < -velTh); <span class="comment">class=class="str">"cmt">// Fire only if velocity, ATR, trend, and zone all passed</span> <span class="keyword">class="type">bool</span> fire = ((isBoom || isCrash) && okVel && okATR && okTrend && okZone); <span class="keyword">if</span>(fire) GenerateSignal(isBoom, priceNow, delta, atrArr[<span class="number">class="num">0</span>], MainTF);
◍ 暴涨暴跌拦截EA的代码骨架与运行逻辑
这套EA用 #property strict 强制MT5做最严类型检查,编译期就能拦掉不少隐错;全局实例化 CTrade 对象后,即便当前版本只画箭头和响警报,后续接 trade.Buy() 就能无缝切到实盘单。 参数面板全用 input 声明,分四组:信号类(周期、速度回溯、ATR、MA)、过滤器(枢轴区+交易时段)、显示(仪表盘位置/颜色/线宽)、CSV日志名。值得盯一眼的是 VelocityPctile 默认 120.0——百分位算法在极端尾部封顶,设超100等于逼价格必须击穿回溯窗内最猛的那一下,触发条件天然极挑剔。 OnInit 里先给 velHistory[96] 开窗、抓ATR和MA句柄,任一失败直接返 INIT_FAILED;随后开或建CSV、写表头、定位末尾,再画半透明仪表盘。OnDeinit 对称释放句柄和对象,避免图表留孤儿。 OnTick 只在主周期新K线收盘后干活:把卖价插数组头部、旧数据下移,算窗口价差并取速度百分位阈值;ATR和MA在更低周期判波动放大与方向;若开枢轴过滤,用前根 (H+L+C)/3 算枢轴,要求信号仍站在枢轴错误侧加缓冲。四条件全真才亮信号。 辅助函数里 ComputeVelocityPercentile 把价格转带符号差、排序取极值,>100时直接拿最大值当历史极端阈;SetHLine 改属性不重建,防闪烁;GenerateSignal 在K线低点/高点偏移处画Wingdings箭头、写CSV、响铃。 线性数组移位对默认96根还行,若你拉长回溯窗,建议改环形缓冲区省CPU。外汇与贵金属波动剧烈、杠杆高风险大,实盘前务必在策略测试器用不同 VelocityPctile 回测确认选择性。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Boom and Crash Interceptor EA| class=class="str">"cmt">//| Copyright class="num">2025, MetaQuotes Ltd.| class=class="str">"cmt">//+------------------------------------------------------------------+ 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> CTrade trade; class=class="str">"cmt">// object-oriented trading wrapper input ENUM_TIMEFRAMES MainTF = PERIOD_CURRENT; class=class="str">"cmt">// Signal TF input class="type">int VelocityHistoryBars = class="num">96; input class="type">class="kw">double VelocityPctile = class="num">120.0; class=class="str">"cmt">// >class="num">100 ⇒ extreme input class="type">int ATRPeriod = class="num">14; input class="type">class="kw">double ATRMultiplier = class="num">1.5; input class="type">bool UseZoneFilter = true;