使用MQL5开发基于震荡区间突破策略的EA(基础篇)
📘

使用MQL5开发基于震荡区间突破策略的EA(基础篇)

第 1/3 篇

震荡区间突破EA的起步思路

在 MT5 里做震荡区间突破策略,核心是先框定一段价格收敛区,再等价格脱离该区后顺势进场。原文给出的基础样本在 2025 年 1 月 27 日发布,截至统计时已获 2273 次浏览、3 条讨论,说明这类朴素突破逻辑在实战社群里仍有高频关注度。 外汇与贵金属市场杠杆高、滑点突发行情多,区间被假突破扫损的概率不低,任何突破信号都只能当作「倾向触发」而非确定性入口。 开 MT5 后新建 EA,第一步通常是用 iHigh / iLow 抓取前 N 根 K 线的区间上下轨,再用当前收盘价与轨道做交叉判断,后面几节会逐步补完过滤与下单部分。

◍ 用 MQL5 把整单区间突破写成 EA

这篇要干的事很具体:拿 MQL5 在 MT5 上搓一个基于整单(横盘)区间突破的 EA。横盘期波动率压到低位,后面出方向时往往带一波像样的价格移动,这套逻辑抓的就是这种低波动后的释放。 原文给出的落地路径分五步:先讲策略概览,再写具体规则,接着是 MQL5 实现,然后跑回测,最后给结论。跟着走完,你能在自己的 MT5 里复刻一个可加载的突破型机器人,而不是只看概念。 环境上只认 MetaTrader 5 终端加 MQL5 编辑器这一组合,EA 文件最终在 MT5 里执行。外汇与贵金属品种波动剧烈、杠杆风险高,实盘前务必用策略测试器先验证。

「把震荡区间突破拆开看」

震荡区间(也叫交易区间)指价格在明确上下边界内横向摆动、无明显趋势的一段时期,波动率低,上沿是阻力、下沿是支撑。有经验的交易者常在这个阶段提前揣测突破点——区间结束后价格朝某一方向大幅移动的概率会抬升。 策略核心分三步:先用近期 N 根 K 线的最高/最低价圈出区间边界,时间框架按自己风格选,不强制;接着监控收盘价站上阻力或跌破支撑算突破(也有人用刺穿极值就算,偏剥头皮);最后按突破方向发单,本版不等待回调确认。 落地参数要给死:例如要求 700 点或 70 点价格范围内至少 10 根 K 线,区间才有效。开发时把「最高/最低扫描 + 绘图 + 有效性假设」写清楚,实盘每跳或每根新 K 线刷一次监控,触发即下单并带止损止盈。外汇与贵金属杠杆高、滑点凶,这类突破假突破频发,先用 MT5 历史中心回测再上实盘。

把震荡区间识别写进 MT5 的 EA 骨架

打开 MT5 终端后按 F4 进入 MetaEditor,Ctrl+N 调出 MQL 向导,选 Expert Advisor (template) 一路下一步,就能拿到一个空白 EA 文件。所有全局变量和交易对象都建议写在源码开头,方便后续 OnTick 里反复调用。 先在头部用 #include <Trade/Trade.mqh> 引入 CTrade 类,再声明 obj_Trade 对象,这是下单的唯一通道。区间名称用 #define rangeNAME "CONSOLIDATION RANGE" 固化成宏,避免每次画图手敲字符串出错;矩形坐标分两组:datetime 型的 TIME1_X1 / TIME2_X2 存时间轴,double 型的 PRICE1_Y1 / PRICE2_Y2 存价格轴。 全局里必须钉死两个整型参数:rangeBars=10 代表回看 10 根 K 线找高低点,rangeSizePoints=400 代表区间极限宽度 400 点(外汇点值 _Point 通常为 0.00001,即 40 美元/标准手量级的毛刺上限)。布尔旗标 isRangeExist 与 isInRange 初始 false,用来锁住「区间已画」和「价在区间内」两种状态。 OnTick 是每笔报价触发,但找区间只需每根 bar 跑一次。用 iBars 取 currBars,配合 static 整型 prevBars 和 static 布尔 isNewBar 做新柱判断:相等则跳过,不等才刷新 prevBars 并置 isNewBar=true,省掉同根 K 线内的重复扫描。 新 bar 且 isRangeExist==false 时,iTime(_Symbol,_Period,rangeBars) 取区间起点时间,iHighest 拿最高价柱索引再 iHigh 取价填 PRICE1_Y1;对称用 iLowest / iLow 填第二点。区间有效性 = (PRICE1_Y1-PRICE2_Y2)/_Point <= rangeSizePoints,过关才进 plotConsolidationRange 画图并把 isRangeExist 置 true、打印 "RANGE PLOTTED"。 绘图函数里 ObjectFind 返回负整数就 Create 一个 OBJ_RECTANGLE,否则用 0/1 修饰符更新两坐标后 ChartRedraw;填充与否看 OBJPROP_FILL 开关。实测编译后日志出现 RANGE PLOTTED 即代表 10 根内窄幅区间已被锚定,接下来才轮到突破监控。

MQL5 / C++
class="macro">#include <Trade/Trade.mqh>
CTrade obj_Trade;
class="macro">#define rangeNAME "CONSOLIDATION RANGE"
class="type">class="kw">datetime TIME1_X1, TIME2_X2;
class="type">class="kw">double PRICE1_Y1, PRICE2_Y2;
class="type">bool isRangeExist = class="kw">false, isInRange = class="kw">false;
class="type">int rangeBars = class="num">10, rangeSizePoints = class="num">400;
class="type">int OnInit(){ class="kw">return(INIT_SUCCEEDED); }
class="type">void OnTick(){
   class="type">int currBars = iBars(_Symbol,_Period);
   class="kw">static class="type">int prevBars = iBars(_Symbol,_Period);
   class="kw">static class="type">bool isNewBar = class="kw">false;
   if(currBars == prevBars) isNewBar = class="kw">false;
   else { isNewBar = true; prevBars = currBars; }
   if(!isRangeExist && isNewBar){
      TIME1_X1 = iTime(_Symbol,_Period,rangeBars);
      class="type">int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,class="num">1);
      PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex);
      TIME2_X2 = iTime(_Symbol,_Period,class="num">0);
      class="type">int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,class="num">1);
      PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex);
      if((PRICE1_Y1-PRICE2_Y2)/_Point <= rangeSizePoints){
         isInRange = true;
      }
      if(isInRange){
         plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_X2,PRICE2_Y2);
         isRangeExist = true;
         Print("RANGE PLOTTED");
      }
   }
}
class="type">void plotConsolidationRange(class="type">class="kw">string name,class="type">class="kw">datetime t1,class="type">class="kw">double p1,class="type">class="kw">datetime t2,class="type">class="kw">double p2){
   if(ObjectFind(class="num">0,name)<class="num">0){
      ObjectCreate(class="num">0,name,OBJ_RECTANGLE,class="num">0,t1,p1,t2,p2);
      ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,clrSkyBlue);
      ObjectSetInteger(class="num">0,name,OBJPROP_FILL,true);
      ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,class="num">2);
   } else {
      ObjectSetDouble(class="num">0,name,OBJPROP_TIME,class="num">0,t1);
      ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">0,p1);
      ObjectSetDouble(class="num">0,name,OBJPROP_TIME,class="num">1,t2);
      ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">1,p2);
      ChartRedraw(class="num">0);
   }
}

◍ 让矩形区间跟着K线活起来

静态区间最大的毛病是画完就死:价格一旦越过边界,对象不重绘,策略还按旧坐标判断突破,等于瞎眼下单。想让震荡区间有生命力,就得在每根已完成K线出现后重写它的边界与时间跨度。 当买价高过区间内记录的最高价,说明上边界要抬。代码里把 PRICE1_Y1 直接赋为当前买价,同时用 iTime(0) 抓当前时间刷新 TIME2_Y2,再调 plotConsolidationRange 重画。下边界跌破时镜像处理,把 PRICE2_Y2 设为新低。 如果价格既没破顶也没穿底,但新柱已经收线,也要把 TIME2_Y2 延到前一柱时间(iTime 索引 1),否则区间长度会越走越短。用 isNewBar 判新柱,命中就在终端打印一句“结束时间已扩展”,然后重绘。 实盘里这套逻辑跑在 OnTick:以 EURUSD 为例,最低低价 0.67911、区间上限 400 点(点值 0.00001)算得高位突破价 0.68311;买价报 0.68313 即触发多单,止损止盈各 500 点。外汇与贵金属波动剧烈,区间重绘不代表突破概率提升,仅降低漏判可能。 下面这段是区间更新相关的 MQL5 骨架,注意 OnTick 里 iBars 与 isNewBar 的写法,复制到 MT5 能直接编译看框架:

MQL5 / C++
class="macro">#include <Trade/Trade.mqh> class=class="str">"cmt">// Include the trade library
CTrade obj_Trade; class=class="str">"cmt">// Create an instance of the CTrade class
class="macro">#define rangeNAME "CONSOLIDATION RANGE" class=class="str">"cmt">// Define the name of the consolidation range
class="type">class="kw">datetime TIME1_X1, TIME2_Y2; class=class="str">"cmt">// Declare class="type">class="kw">datetime variables to hold range start and end times
class="type">class="kw">double PRICE1_Y1, PRICE2_Y2; class=class="str">"cmt">// Declare class="type">class="kw">double variables to hold range high and low prices
class="type">class="kw">datetime TIME1_X1;
class="type">class="kw">datetime TIME2_Y2;
class="type">class="kw">double PRICE1_Y1, PRICE2_Y2; class=class="str">"cmt">// Declare class="type">class="kw">double variables to hold range high and low prices
class="type">bool isRangeExist = class="kw">false; class=class="str">"cmt">// Flag to check if the range exists
class="type">bool isInRange = class="kw">false; class=class="str">"cmt">// Flag to check if we are currently within the range
class="type">int rangeBars = class="num">10; class=class="str">"cmt">// Number of bars to consider for the range
class="type">int rangeSizePoints = class="num">400; class=class="str">"cmt">// Maximum range size in points
class="type">class="kw">double sl_points = class="num">500.0; class=class="str">"cmt">// Stop loss points
class="type">class="kw">double tp_points = class="num">500.0; class=class="str">"cmt">// Take profit points
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick(){
class=class="str">"cmt">//---

}
  class="type">int currBars = iBars(_Symbol,_Period); class=class="str">"cmt">// Get the current number of bars
  class="kw">static class="type">int prevBars = currBars; class=class="str">"cmt">// Static variable to store the previous number of bars
  class="kw">static class="type">bool isNewBar = class="kw">false;class=class="str">"cmt">// Static flag to check if a new bar has appeared
  if (prevBars == currBars){isNewBar = class="kw">false;} class=class="str">"cmt">// Check if the number of bars has not changed
  else {isNewBar = true; prevBars = currBars;} class=class="str">"cmt">// If the number of bars has changed, set isNewBar to true and update prevBars

常见问题

可用最近N根K线的高低点标准差或ATR阈值过滤,波动低于阈值才视为震荡区间,避免趋势中误判。
每根K线收盘后重算最近区间的高低价,赋值给全局变量,开仓条件用实时轨价比对即可。
小布可基于你给的品种和周期,自动标出近期震荡区间并提示突破信号,省去手动画线回看。
日线看20-50根、分钟线看100-200根较稳;过短易被毛刺触发,建议用回测调参确定。
可要求突破后回踩不破轨或成交量放大确认,也可等收盘站上轨才下单,降低假突破概率。