价格行为分析工具包开发(第二十九部分):暴涨与暴跌拦截EA
内容
引言
在现代防空网络中,一系列分层部署的传感器会以极其精准的方式对每个雷达回波和红外特征进行评估。系统会对每个探测目标的速度、轨迹和特征进行测量,然后与一个庞大的威胁数据库进行交叉比对。只有当多个独立的筛选条件达成一致时,系统才会授权发射拦截弹,这样既能节省资源,又能确保真正的威胁得到及时消除。
暴涨与暴跌拦截EA对市场数据采用同样严谨的处理方式。一个滚动速度窗口用于判断当前的价格脉冲是否超越了近期表现;一个基于平均真实波幅(ATR)的激增乘数用于确认波动率是否已显著扩大;移动平均线(MA)趋势筛选条件则用于验证方向性偏差。此外,可选的枢轴区域和交易时段限制条件可进一步抑制在流动性稀薄时期本会出现的信号。
当每一层筛选条件都得到确认时,该EA会在图表上绘制一个明确的“暴涨”或“暴跌”箭头,并允许用户自定义颜色、偏移量和CSV日志记录。这样确保交易者只关注高概率的交易机会。接下来的页面将解释如何校准每个检测层,以及如何将这款MQL5工具作为强大、信号驱动的组件,部署到更广泛的交易框架中。
策略解析
受导弹拦截防御系统的启发,暴涨与暴跌拦截EA将市场价格的急剧变动视为来袭威胁加以应对。在军事领域,导弹指的是任何一种有制导或无制导的抛射物,发射目的是为了击中目标。现代导弹是配备有弹头的自推进火箭,由惯性、雷达、卫星或光学系统引导;其种类繁多,从洲际弹道导弹(ICBMs)到巡航导弹,再到地对空防御弹药等。从历史角度来看,即使是投掷的长矛或射出的箭,也可被视为导弹,因为其飞行轨迹是故意指向目标的。
在交易领域,价格的突然大幅飙升同样危险:它出现迅速,往往出人意料,且可能对毫无准备的账户造成重创。这里,用导弹拦截作为类比颇具价值。导弹拦截系统是一种防御系统,可在来袭弹头命中前将其探测、跟踪并摧毁。其运行过程紧密衔接,共分为四个阶段:
探测与跟踪
- 地面、舰载或天基传感器探测到来袭抛射物的发射。
- 火控软件计算其轨迹、速度和预计撞击点。
发射与制导
- 一旦确认,拦截弹便从发射井、车辆、舰艇或飞机上发射升空。
- 通过惯性制导、中段数据链以及末段雷达或红外寻来实现航向修正。
- 直接碰撞杀伤:拦截弹以超过10马赫的相对速度与弹头直接相撞,依靠动能将其摧毁。
- 近炸引爆:在目标附近实施可控引爆,利用弹片或定向冲击波将其击碎或偏转。
- 短程系统(如“爱国者”PAC-3)在末段拦截威胁。
- 中段系统(如SM-3、“陆基中段防御”系统)在太空实施拦截。
- 助推段概念仍处于试验阶段,目标是在导弹发射后瞬间进行拦截。
设置这些防御层次是为了在最大程度提供保护的同时,尽量减少误发射,这一平衡是通过快速数据融合和严格的确认逻辑来实现的。
暴涨与暴跌拦截EA借鉴了这一架构。持续的市场监测相当于雷达网;速度阈值、ATR突增过滤器以及移动平均线对齐作为独立的确认层;可选的枢轴区域和交易时段过滤器模仿诱饵识别;只有当所有条件都满足时,该EA才会“发射”,即在图表上标注“暴涨”或“暴跌”箭头并记录事件。如此一来,它就像导弹防御系统一样,为真正值得应对的威胁保留交易者的注意力和交易资金,只将拦截弹留给构成真正危险的弹头。

图例1. 策略
速度雷达
测量过去velocityHistoryBars(速度历史K线)内的价格变动,并将其与历史百分位阈值进行比较,只有“异常大”的价格变动才能通过筛选。
// Record current vs. old price double priceNow = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double priceOld = velHistory[VelocityHistoryBars - 1]; double delta = priceNow - priceOld; // Build and sort past deltas double d[]; ArrayResize(d, VelocityHistoryBars - 1); for(int i = 1; i < VelocityHistoryBars; i++) d[i - 1] = velHistory[0] - velHistory[i]; ArraySort(d); // Pick the (100–VelocityPctile)% threshold int idx = (int)MathRound((VelocityPctile / 100.0) * (ArraySize(d) - 1)); double velTh = d[ArraySize(d) - 1 - idx]; // Pass if current move exceeds threshold bool okVel = (delta > velTh || delta < -velTh);
ATR突增探测器
通过将最新ATR与前一根K线ATR值的ATR乘数(ATRMultiplier)进行比较,来检测波动率的突然跃升。
// Fetch two most recent ATR values double atrArr[2]; CopyBuffer(atrHandle, 0, 0, 2, atrArr); // Pass if ATR_now > ATR_prev × ATRMultiplier bool okATR = (atrArr[0] > atrArr[1] * ATRMultiplier);
趋势一致性检查
通过验证在价格上涨时简单移动平均线(SMA)是否也在上升(或在价格下跌时SMA是否也在下降),来确保价格走势与趋势保持一致。// Fetch two most recent SMA values double maArr[2]; CopyBuffer(maHandle, 0, 0, 2, maArr); // If delta>0 require SMA_now>SMA_prev; if delta<0 require SMA_now<SMA_prev bool okTrend = (delta > 0 ? maArr[0] > maArr[1] : maArr[0] < maArr[1]);
枢轴区域过滤器(可选)
屏蔽距离前一根K线枢轴点过近的信号,强制设置区域缓冲点数(ZoneBufferPoints)的缓冲距离。
// Compute prior bar’s pivot double h1 = iHigh(_Symbol, MainTF, 1), l1 = iLow (_Symbol, MainTF, 1), c1 = iClose(_Symbol, MainTF, 1); double pivot = (h1 + l1 + c1) / 3.0; // Pass if priceNow is beyond pivot ± buffer bool okZone = (delta > 0 ? priceNow < pivot - ZoneBufferPoints * _Point : priceNow > pivot + ZoneBufferPoints * _Point);
最终“导弹发射”逻辑
仅当所有筛选条件均通过时,触发“看涨”(向上箭头)或“看跌”(向下箭头)信号。
// Determine direction bool isBoom = (delta > velTh); bool isCrash = (delta < -velTh); // Fire only if velocity, ATR, trend, and zone all passed bool fire = ((isBoom || isCrash) && okVel && okATR && okTrend && okZone); if(fire) GenerateSignal(isBoom, priceNow, delta, atrArr[0], MainTF);
代码分解
以下是“暴涨与暴跌拦截EA”各主要部分的系统化详细解析。
头文件与编译指令
开头的注释块及后续的#property指令行相当于该EA的“名片”。它们用于指定版权归属、支持链接、当前版本号以及严格的编译标识。以严格模式编译会强制MetaTrader 5执行最严格的数据类型和范围检查,有助于我们在构建阶段尽早发现潜在的错误。这些信息还便于MetaQuotes市场追踪产品的版本迭代.,并为终端用户提供平滑的版本化更新支持。
//+------------------------------------------------------------------+ //| Boom and Crash Interceptor EA| //| Copyright 2025, MetaQuotes Ltd.| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property strict
库引入与交易对象
通过引入<Trade\Trade.mqh>文件并实例化一个全局的CTrade对象,代码得以访问MetaTrader交易API的面向对象封装层。尽管当前版本仅用于发出警报和绘制图形,但该对象的存在使代码库能够在需要时无缝迁移至实际订单执行功能。
#include <Trade\Trade.mqh> CTrade trade; // object-oriented trading wrapper
外部输入参数 —— EA的控制面板
运行时所有参数均使用input关键字进行声明,这样既支持在策略测试器中进行参数优化,也便于在图表的“输入”选项卡中进行快速修改。这些参数可分为四个逻辑组:
• 信号生成设置:时间周期、速度回溯期数、ATR参数、MA参数。
• 可选过滤器:枢轴区域逻辑和交易时段设置。
• 显示设置:仪表盘位置、颜色、线宽、箭头偏移量。
• 数据记录:CSV文件名。
input ENUM_TIMEFRAMES MainTF = PERIOD_CURRENT; // Signal TF input int VelocityHistoryBars = 96; input double VelocityPctile = 120.0; // >100 ⇒ extreme input int ATRPeriod = 14; input double ATRMultiplier = 1.5; input bool UseZoneFilter = true; input int SessionStartHour = 7; input int SessionEndHour = 17; input string LogFilename = "BoCrashLog.csv";
一个值得注意的细节是,默认的VelocityPctile(速度百分位)值设为120%。由于百分位算法在分布的极端尾部达到上限,将该值设为超过100%,实际上要求价格必须超越回溯窗口内观察到的最剧烈波动,这使得触发条件具有高度的选择性。
全局变量与命名规范
数组velHistory[] 用于存储速度分析所需的滚动窗口数据,而多个字符串常量则集中管理GUI对象的名称。将名称整合到数组中(如dashNames、hVelUp等),可确保对象创建和删除的准确性,避免出现孤立对象,并简化未来的代码重构工作。
double velHistory[]; string dashBG = "DashBG"; string dashNames[] = {"Delta","VelThr","ATR","ATRm","Trend", "Pivot","Zone","Signal"}; string hVelUp = "VelUp"; string hVelDown = "VelDown";
OnInit():资源获取与GUI构建
在初始化阶段,程序会调整速度数组的大小,获取ATR指标和MA指标的句柄。如果任一句柄获取失败,程序将终止并返回INIT_FAILED状态 —— 尽早暴露错误好过运行时的不稳定。随后,该EA会打开或创建CSV日志文件,如果文件为新建则写入表头,并定位到文件末尾以便追加后续记录。最后,该函数会构建一个半透明的仪表盘矩形区域,填充文本标签,并预先构建水平和垂直参考线。所有图形元素均会在首个市场报价到达前在图表上显示。
int OnInit() { ArrayResize(velHistory,VelocityHistoryBars); atrHandle = iATR(_Symbol, ATRTF, ATRPeriod); maHandle = iMA (_Symbol, TrendTF, TrendMAPeriod, 0, MODE_SMA, PRICE_CLOSE); if(atrHandle==INVALID_HANDLE || maHandle==INVALID_HANDLE) return INIT_FAILED; logHandle = FileOpen(LogFilename, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI); if(logHandle>=0 && FileSize(logHandle)==0) FileWrite(logHandle,"DateTime,Type,Velocity,ATR,Price"); // dashboard background ObjectCreate(0,dashBG,OBJ_RECTANGLE_LABEL,0,0,0); ObjectSetInteger(0,dashBG,OBJPROP_XSIZE,200); ObjectSetInteger(0,dashBG,OBJPROP_YSIZE,140); // dashboard labels for(int i=0;i<ArraySize(dashNames);i++) { string name="Dash_"+dashNames[i]; ObjectCreate(0,name,OBJ_LABEL,0,0,0); ObjectSetString(0,name,OBJPROP_TEXT,dashNames[i]+": ?"); } // reference lines ObjectCreate(0,hVelUp ,OBJ_HLINE,0,0,0); ObjectCreate(0,hVelDown,OBJ_HLINE,0,0,0); return INIT_SUCCEEDED; }
OnDeinit():有序关闭
在移除(卸载)时,该EA会释放指标句柄、关闭文件句柄,并删除命名数组中引用的所有图形对象。创建与销毁之间的这种对称性对于保持交易终端对象列表的整洁以及节约操作系统资源至关重要。
void OnDeinit(const int reason) { if(atrHandle!=INVALID_HANDLE) IndicatorRelease(atrHandle); if(maHandle !=INVALID_HANDLE) IndicatorRelease(maHandle); if(logHandle>=0) FileClose(logHandle); string objs[]={dashBG,hVelUp,hVelDown}; for(int i=0;i<ArraySize(objs);i++) ObjectDelete(0,objs[i]); for(int i=0;i<ArraySize(dashNames);i++) ObjectDelete(0,"Dash_"+dashNames[i]); }
OnTick():实时决策引擎
该程序首先确认在MainTF(主时间周期)上是否已形成新的K线收盘。如果未形成,函数将立即返回,避免不必要的计算。一旦检测到K线切换,代码将检查可选的交易时段过滤器,确保仅在交易者设定的时段内执行操作。
void OnTick() { // process only on completed bar int cur=iBars(_Symbol,MainTF)-1; if(cur==lastBar) return; lastBar=cur; // optional session filter MqlDateTime now; TimeToStruct(TimeCurrent(),now); if(UseSessionFilter && (now.hour<SessionStartHour || now.hour>SessionEndHour)) return; // velocity calculation double priceNow = SymbolInfoDouble(_Symbol,SYMBOL_ASK); ArrayMove(velHistory,1,0,VelocityHistoryBars-1); // shift right velHistory[0]=priceNow; double delta = priceNow-velHistory[VelocityHistoryBars-1]; double velTh = ComputeVelocityPercentile(velHistory,VelocityPctile); bool okVel = (delta>velTh || delta<-velTh); // ATR, MA, pivot-zone checks ... /* …remaining signal logic… */ if(fire) GenerateSignal(isBoom,priceNow,delta,atrArr[0],MainTF); }
随后,将最新卖出价插入到velHistory数组的开头,将较旧的数据项依次下移一个索引位置。我们计算窗口内最新价格与最旧价格之间的差值,并使用ComputeVelocityPercentile()函数获取一个百分位阈值。当差值超过上限或下限阈值时,即判定满足速度条件。
ATR和MA过滤器则在可能不同的更低时间周期上进行评估,以捕捉短期波动性扩大并确认方向性偏向。当启用枢轴区域过滤器时,该过滤器会在前一根K线上计算经典的(最高价+最低价+收盘价)/3枢轴点,并要求触发信号的走势必须仍位于该枢轴点的”错误”一侧(加上一个可配置的缓冲区间),以此寻找均值回归的潜力。
仪表盘已更新为原始诊断数值,并立即通过颜色标示出每个条件是否满足。同时,重新定位水平速度线和枢轴线以及垂直时间线,以使图表的视觉上下文与最新K线保持同步。只有当所有四个条件(速度突增、ATR飙升、趋势一致和区域突破)均返回true时,才视为信号有效。
辅助函数:UpdateLabel()
该小型函数集中处理仪表盘文本和颜色的更新。通过将格式设置封装在一个位置,我们确保样式的一致性,并使全局审美调整变得简单直接。
void UpdateLabel(int idx,string txt,bool pass) { string name="Dash_"+dashNames[idx]; ObjectSetString (0,name,OBJPROP_TEXT ,txt); ObjectSetInteger(0,name,OBJPROP_COLOR, pass ? clrLime : clrRed); }
辅助函数: ComputeVelocityPercentile()
该算法将存储的价格序列转换为带符号的差值列表,对该列表进行排序,并根据用户请求的百分位进行索引取值。从排序后数组的极端端点选取值,使得该方法无需额外的逻辑即可顺应价格变动的方向(暴涨或暴跌)。当百分位值大于100%时,函数会取数组中的最大值,从而强制设定一个“窗口期内的历史极端值”阈值。
double ComputeVelocityPercentile(double &hist[],double pct) { int n=ArraySize(hist); if(n<2) return 0; double d[]; ArrayResize(d,n-1); for(int i=1;i<n;i++) d[i-1]=hist[0]-hist[i]; ArraySort(d); // ascending int idx=(int)MathRound((pct/100.0)*(ArraySize(d)-1)); return d[ArraySize(d)-1-idx]; // mirror to get extreme with sign }
辅助函数:SetHLine()
该方法通过直接修改现有图形对象的属性(而非重新创建线条),避免了闪烁和图形延迟问题,即使在配置较低的硬件上也能确保用户流畅的操作体验。
void SetHLine(string name,double price,color clr) { ObjectSetDouble (0,name,OBJPROP_PRICE, price); ObjectSetInteger(0,name,OBJPROP_COLOR, clr); ObjectSetInteger(0,name,OBJPROP_WIDTH, 1); }
辅助函数:GenerateSignal()
当所有过滤条件均达成一致时,该函数会在K线低点(暴涨)或高点(暴跌)的偏移位置绘制一个带颜色的Wingdings箭头符号,同时向CSV日志文件中写入一条详细记录(包含时间戳、信号类型、差值、ATR、价格等信息),并触发声音警报。由于CTrade对象的作用域得以保留,因此只需调用trade.Buy()或trade.Sell(),即可将这些视觉标记转换为实盘交易订单。
void GenerateSignal(bool isBoom,double price,double delta, double atr,ENUM_TIMEFRAMES tf) { int code = isBoom ? 233 : 234; double y = isBoom ? iLow(_Symbol,tf,0)-ArrowOffsetPips*_Point : iHigh(_Symbol,tf,0)+ArrowOffsetPips*_Point; color clr = isBoom ? BoomArrowColor : CrashArrowColor; string tag = (isBoom?"BOOM":"CRASH")+"_"+ TimeToString(TimeCurrent(),TIME_SECONDS); datetime t0 = iTime(_Symbol,tf,0); ObjectCreate(0,tag,OBJ_ARROW,0,t0,y); ObjectSetInteger(0,tag,OBJPROP_ARROWCODE,code); ObjectSetInteger(0,tag,OBJPROP_COLOR,clr); if(logHandle>=0) FileWrite(logHandle,TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS), isBoom?"BOOM":"CRASH",DoubleToString(delta,2), DoubleToString(atr,_Digits),DoubleToString(price,_Digits)); Alert((isBoom?"BOOM":"CRASH")+" signal @"+ DoubleToString(price,_Digits)); }
策略目标与扩展机遇。
// Inside GenerateSignal(), after drawing the arrow: double lot = 0.02; // example risk sizing double sl = isBoom ? price - atr*2 : price + atr*2; double tp = pivot; // mean-reversion target if(AutoTrade) // user-controlled switch { if(isBoom) trade.Buy(lot,_Symbol,price,sl,tp,"Boom-auto"); else trade.Sell(lot,_Symbol,price,sl,tp,"Crash-auto"); }
从概念上讲,该EA旨在寻找在新的波动性扩张过程中出现、且相对于先前枢轴点仍具备均值回归潜力的单根K线剧烈加速行情。这种行情特征在暴涨/暴跌指数中尤为典型,此类指数常出现突发性飙升后迅速回撤的现象。当前代码仅生成提示性警报,如果要实现自动化交易,需增加持仓规模、止损逻辑(例如,设置在相反方向的速度线)和止盈逻辑(例如,设置在枢轴点或缓冲区间的一半位置)。最后,velHistory中采用的线性数组移位方式对于默认的96根K线窗口而言尚可接受,但如果用户选择更长的历史记录,则可改用环形缓冲区以提高效率。
回测与结果
在将“拦截器”投入实盘交易之前,让我们先在模拟账户上进行全面的回测,以优化其参数设置:
1. 编译与加载
在MetaEditor中成功编译EA后,切换至MetaTrader平台。
2. 打开策略测试器
在MetaTrader的工具栏中,点击“策略测试器”图标。从“专家”列表中选择您的“暴涨与暴跌拦截EA“。
3. 配置测试参数
- 交易品种与时间周期:选择与实盘交易完全一致的品种和MainTF。
- 测试周期:选择涵盖不同市场条件(盘整、趋势、剧烈波动)的时间段。
- 参数设置:调整VelocityHistoryBars(速度历史K线数)、 VelocityPctile(速度百分位)、ATRMultiplier(ATR乘数)、TrendMAPeriod(趋势均线周期)、ZoneBufferPoints(区域缓冲点数)等参数,观察它们对信号频率和准确性的影响。
- 执行方式:选择“每tick”模式以获得最精确的测试结果。
4. 运行与复盘
点击“开始”按钮。回测完成后,检查资金曲线、信号列表以及任何失败或延迟的拦截情况。留意信号集中出现或错过真实行情尖峰的位置。
5. 优化与迭代
每次调整一两个参数,例如收紧速度百分位或扩大枢轴缓冲区间,然后重新测试。反复迭代,直到模拟账户的测试结果显示出清晰、可重复的暴涨/暴跌信号,且回撤在可接受范围内。
当您在多次模拟测试中对EA的表现充满信心后,即可在受控风险条件下将其部署至实盘账户。
以下,我将分享“暴涨与暴跌拦截EA”的回测结果,这些结果尤其具有启发性。

图例2. Boom 900回测
在 Boom 900的回测中,我们的EA如预期般在日志中记录了所有暴涨和暴跌信号。枢轴线以蓝色显示,速度阈值线以橙色显示,提供了清晰的视觉指引。您还会看到绿色向上箭头标记每个暴涨信号,红色向下箭头标记每个暴跌信号。下方附上一张静态图片,以便清晰地查看GIF中捕捉到的内容。

图例3. 回测结果
结论
我们已经构建并严格回测了“暴涨与暴跌拦截EA“,其成果有目共睹。该EA通过追踪价格变动速度、ATR的激增、趋势一致性以及枢轴区突破等关键指标,精准捕捉极端价格走势,并仅在信号最清晰时绘制绿色暴涨箭头或红色暴跌箭头。在Boom 900模拟测试中,完整记录了所有信号,枢轴线和速度线分别以蓝色和橙色呈现,静态图像与动态GIF展示效果完全一致。
尽管如此,仍有少数信号可能需要进一步地过滤优化。请将该EA视为战术雷达,而非独立的订单执行工具。在实盘操作前,务必结合您自己的交易时段、形态模式或基本面分析进行综合判断。如果您希望进一步减少误报,可切换至更长时间周期(如H1、H4或日线图),以过滤市场噪音。
通过严谨的回测、智能的过滤策略以及受控的实盘部署,“暴涨与暴跌拦截EA”将成为真正的市场极端行情拦截利器,助您抢占先机,而非被动跟随每一波行情。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18616
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
市场模拟(第 14 部分):套接字(八)
精通日志记录(第九部分):实现构造器模式并添加默认配置
价格行为分析工具包开发(第三十部分):商品通道指数(CCI)零线的EA
MQL5交易管理面板开发(第十二部分):汇率计算器的集成
你好、
我找不到任何链接,能否下载?
请注意
哈利
速度检测器存在算法问题。
ArraySort(d);在买入方向运行良好、
但对于负值移动的排序就不好了。
,您需要单独处理或更改指数公式(在卖出负值排序时!!!)。
if(delta>0) { for(int i = 1; i < VelocityHistoryBars; i++) d[i - 1] = velHistory[0] - velHistory[i]; ArraySort(d); } if(delta<0) { for(int i = 1; i < VelocityHistoryBars; i++) d[i - 1] = velHistory[i] - velHistory[0]; ArraySort(d); }
int idx= (int)MathRound((VelocityPctile / 100.0) * (ArraySize(d) - 1)); double velTh = d[ArraySize(d) - 1 - idx]; bool okVel = MathAbs(delta) > velTh;
我有一些中枢排列的变体。
我不知道这样是否更好。
(从中枢向上的价格开始,我们等待向上移动 .... )
bool okZone = false; if((delta > 0 ) && (priceNow > pivot)) okZone = true; if((delta < 0 ) && (priceNow < pivot)) okZone = true;bool okZone = false; if((delta > 0 ) && (priceNow > pivot) && (priceNow < ( pivot + ZONE_Points * _Point))) okZone = true; if((delta < 0 ) && (priceNow < pivot) && (priceNow > ( pivot - ZONE_Points * _Point))) okZone = true;速度检测器的同方向条形图评级:
我们可以对准绿色/红色条形图进行过滤。
(负方向翻转为正值)
0.8 = 80% 的条形图方向正确。
double goodBars = 1; for(int i=ArraySize(d) - 1; i>=0; i--) if(d[i] < 0) goodBars=1 - (i+1.0)/ArraySize(d);