使用MQL5开发基于震荡区间突破策略的EA·综合运用
(3/3)·从策略拆解到MT5回测落地,一篇走完震荡突破EA的完整开发闭环
区间突破的挂单触发与实时重绘
在 MT5 的 EA 逻辑里,先把当前买价做精度归一化是基础动作:用 SymbolInfoDouble 取 SYMBOL_BID,再套 NormalizeDouble 按 _Digits 截断,避免后续比较时出现浮点尾巴导致误触发。 当震荡区间已确认(isRangeExist 且 isInRange 为真),突破价并非区间边缘本身,而是向外扩 rangeSizePoints*_Point。例如区间上沿 PRICE2_Y2 加 N 个点得到 R_HighBreak_Prc,下沿 PRICE1_Y1 减 N 个点得到 R_LowBreak_Prc,这种缓冲能过滤毛刺。 若 Ask 直接大于 R_HighBreak_Prc,代码会打印 BUY NOW 并重置两个区间标志;若已有持仓(PositionsTotal>0)则直接 return 不重复开仓,否则用 obj_Trade.Buy(0.01,...) 以 Ask 入场、Bid 加减 sl/tp 点数为止损止盈。外汇与贵金属杠杆高,实盘这类突破单可能连续假突破,仓位务必小。 区间内若 Ask 刷新了 PRICE1_Y1 或 Bid 跌破了 PRICE2_Y2,说明箱体被实时拓宽,这时调 iTime 取当前柱时间并调 plotConsolidationRange 重绘图形,让视觉区间跟着价格走而不是死框。
class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">// 取当前买价并按品种精度归一化 if (isRangeExist && isInRange){ class=class="str">"cmt">// 区间存在且价格处于区间内 class="type">class="kw">double R_HighBreak_Prc = (PRICE2_Y2+rangeSizePoints*_Point); class=class="str">"cmt">// 计算向上突破触发价(上沿+缓冲点) class="type">class="kw">double R_LowBreak_Prc = (PRICE1_Y1-rangeSizePoints*_Point); class=class="str">"cmt">// 计算向下突破触发价(下沿-缓冲点) if (Ask > R_HighBreak_Prc){ class=class="str">"cmt">// 卖价突破上破价 Print("BUY NOW, ASK = ",Ask,", L = ",PRICE2_Y2,", H BREAK = ",R_HighBreak_Prc); class=class="str">"cmt">// 输出买入提示 isInRange = class="kw">false; isRangeExist = class="kw">false; class=class="str">"cmt">// 清除区间状态 if (PositionsTotal() > class="num">0){class="kw">return;} class=class="str">"cmt">// 有持仓则退出 obj_Trade.Buy(class="num">0.01,_Symbol,Ask,Bid-sl_points*_Point,Bid+tp_points*_Point); class="kw">return; class=class="str">"cmt">// 开多后退出 } else if (Bid < R_LowBreak_Prc){ class=class="str">"cmt">// 买价跌破下破价 Print("SELL NOW"); class=class="str">"cmt">// 输出卖出提示 isInRange = class="kw">false; isRangeExist = class="kw">false; class=class="str">"cmt">// 清除区间状态 if (PositionsTotal() > class="num">0){class="kw">return;} class=class="str">"cmt">// 有持仓则退出 obj_Trade.Sell(class="num">0.01,_Symbol,Bid,Ask+sl_points*_Point,Ask-tp_points*_Point); class="kw">return; class=class="str">"cmt">// 开空后退出 } if (Ask > PRICE1_Y1){ class=class="str">"cmt">// 卖价高于原区间高点 PRICE1_Y1 = Ask; class=class="str">"cmt">// 高点更新为卖价 TIME2_Y2 = iTime(_Symbol,_Period,class="num">0); class=class="str">"cmt">// 结束时间更新为当前柱时间 Print("UPDATED RANGE PRICE1_Y1 TO ASK, NEEDS REPLOT"); class=class="str">"cmt">// 提示需重绘 plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); class=class="str">"cmt">// 重绘区间 } else if (Bid < PRICE2_Y2){ class=class="str">"cmt">// 买价低于原区间低点 PRICE2_Y2 = Bid; class=class="str">"cmt">// 低点更新为买价 TIME2_Y2 = iTime(_Symbol,_Period,class="num">0); class=class="str">"cmt">// 结束时间更新为当前柱时间 Print("UPDATED RANGE PRICE2_Y2 TO BID, NEEDS REPLOT"); class=class="str">"cmt">// 提示需重绘 plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); class=class="str">"cmt">// 重绘区间 } else{ if (isNewBar){ class=class="str">"cmt">// 出现新K线
◍ 把整理区间画到前一根K线收盘
当判定当前区间需要延展时,代码先把区间右边界的时间戳拉回前一根bar的收盘时刻:iTime(_Symbol,_Period,1) 取的就是当前品种、当前周期下序号1(即上一根)的K线时间。随后 Print 一句日志方便在专家日志里确认区间被重绘,再调用 plotConsolidationRange 用新的起止时间和高低价重画矩形。 plotConsolidationRange 这个函数负责在图表上落地整理区间。若 ObjectFind 返回小于0,说明同名矩形还不存在,就用 ObjectCreate 以 OBJ_RECTANGLE 类型建一个,并顺手设蓝色、填充开、线宽5;若已存在,则走 else 分支用 ObjectSetInteger / ObjectSetDouble 按锚点0和1更新起止时间与高低价,最后 ChartRedraw 强制重绘。 在策略测试器里跑完这套逻辑后,余额/净值曲线和按周期入场的回测明细可以直观看到:区间随前一根bar时间动态延伸,避免了在实时tick上频繁改写右边界带来的视觉抖动。外汇与贵金属品种杠杆高、滑点随机,这类绘制逻辑只解决“看得见”,入场胜率仍受行情结构影响,可能随波动率突变而失效。
TIME2_Y2 = iTime(_Symbol,_Period,class="num">1); class=class="str">"cmt">// Update the end time to the previous bar time Print("EXTEND THE RANGE TO PREV BAR TIME"); class=class="str">"cmt">// Print a message indicating the range is extended plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); class=class="str">"cmt">// Replot the consolidation range class="type">void plotConsolidationRange(class="type">class="kw">string rangeName,class="type">class="kw">datetime time1_x1,class="type">class="kw">double price1_y1, class="type">class="kw">datetime time2_x2,class="type">class="kw">double price2_y2){ if (ObjectFind(class="num">0,rangeName) < class="num">0){ class=class="str">"cmt">// If the range object does not exist ObjectCreate(class="num">0,rangeName,OBJ_RECTANGLE,class="num">0,time1_x1,price1_y1,time2_x2,price2_y2); class=class="str">"cmt">// Create the range object ObjectSetInteger(class="num">0,rangeName,OBJPROP_COLOR,clrBlue); class=class="str">"cmt">// Set the class="type">color of the range ObjectSetInteger(class="num">0,rangeName,OBJPROP_FILL,true); class=class="str">"cmt">// Enable fill for the range ObjectSetInteger(class="num">0,rangeName,OBJPROP_WIDTH,class="num">5); class=class="str">"cmt">// Set the width of the range } else { class=class="str">"cmt">// If the range object exists ObjectSetInteger(class="num">0,rangeName,OBJPROP_TIME,class="num">0,time1_x1); class=class="str">"cmt">// Update the start time of the range ObjectSetDouble(class="num">0,rangeName,OBJPROP_PRICE,class="num">0,price1_y1); class=class="str">"cmt">// Update the high price of the range ObjectSetInteger(class="num">0,rangeName,OBJPROP_TIME,class="num">1,time2_x2); class=class="str">"cmt">// Update the end time of the range ObjectSetDouble(class="num">0,rangeName,OBJPROP_PRICE,class="num">1,price2_y2); class=class="str">"cmt">// Update the low price of the range } ChartRedraw(class="num">0); class=class="str">"cmt">// Redraw the chart to reflect changes }
「自动化落地前的最后提醒」
把震荡区间突破做成 EA,核心不在写多少行代码,而在先想清楚低波动期怎么定义。原文给出的参考工程 CONSOLIDATION_RANGE_BREAKOUT.mq5 体积仅 8.31 KB,编译后 ex5 为 33.64 KB,说明逻辑并不臃肿:蜡烛分析、支撑阻力捕捉、突破跟踪、信号开仓都已模块化。 你在 MT5 里直接加载这套样例,重点看它如何标记盘整箱体和触发突破,而不是指望它直接跑出正收益。外汇与贵金属杠杆高、滑点跳空频繁,任何区间突破信号都只是概率倾向,样本外失效很常见。 真要自己改,建议先调低波动识别窗口这个参数,用历史数据观察箱体厚度变化,再决定突破过滤阈值。能跑通一套可验证的逻辑,比背熟理论更有用。