掌握 MQL5:从入门到精通(第五部分):基本控制流操作符·综合运用
🔀

掌握 MQL5:从入门到精通(第五部分):基本控制流操作符·综合运用

(3/3)·把线性脚本升级成能识别 inside/outside bar 的决策程序,收尾篇只讲落地写法

进阶 第 3/3 篇

很多交易者把 MQL5 指标写成死板流水线:每根 K 线无差别画标记,结果图表被噪声淹没。真正有用的辅助线应当只在特定形态闭合后才出现,而这要求程序先学会‘判断’。本篇用 inside/outside bar 指标把控制流讲透,顺手收掉整个系列。

向导吐出来的指标骨架长什么样

在 MT5 里用向导生成 InsideOutsideBar 指标后,第一眼看到的是标头区:版权、版本、indicator_chart_window 说明它贴在主图窗口,indicator_buffers 和 indicator_plots 都等于 2,意味着这套指标申请了 2 个计算缓冲区和 2 个绘制缓冲区,当前例子里两者一一对应。 绘制参数块给两个 plot 都设了 DRAW_ARROW 类型、clrMediumPurple 颜色、STYLE_SOLID 线型、宽度 1,Up 和 Down 两个标签分别对应上下箭头。input 里只有一个 int 型参数 inp_barsTypeSelector,默认赋 0,后续用来切换 K 线识别模式。 缓冲区变量 UpBuffer[] 和 DownBuffer[] 都是 double 动态数组,负责承接价格水平序列,箭头曲线就基于这些数组渲染。OnInit 里调了两次 SetIndexBuffer 把数组绑到 0、1 号绘制缓冲,再用 PlotIndexSetInteger(0,PLOT_ARROW,159) 把 Wingdings 字符集里的 159 号图形设为上箭头标记。 OnCalculate 此刻是空的,但它拿到的参数已经够用:rates_total 是图表总柱数,prev_calculated 上次算过的柱数(首次启动为 0,可作为新烛形判据——两者不等就说明来了新柱),prices/time/volumes/spreads 等数组以 const 引用传入,数据只读不可改。外汇与贵金属杠杆高、滑点跳空频繁,直接挂这类半成品指标前先在策略测试器跑一轮历史回放。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                             InsideOutsideBar.mq5 |
class=class="str">"cmt">//|                                       Oleg Fedorov(aka certain) |
class=class="str">"cmt">//|                                   mailto:coder.fedorov@gmail.com |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Oleg Fedorov(aka certain)"
class="macro">#class="kw">property link      "mailto:coder.fedorov@gmail.com"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- plot Up
class="macro">#class="kw">property indicator_label1  "Up"
class="macro">#class="kw">property indicator_type1   DRAW_ARROW
class="macro">#class="kw">property indicator_color1  clrMediumPurple
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot Down
class="macro">#class="kw">property indicator_label2  "Down"
class="macro">#class="kw">property indicator_type2   DRAW_ARROW
class="macro">#class="kw">property indicator_color2  clrMediumPurple
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- input parameters
input class="type">int      inp_barsTypeSelector=class="num">0;
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double         UpBuffer[];
class="type">class="kw">double         DownBuffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- indicator buffers mapping
   SetIndexBuffer(class="num">0,UpBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,DownBuffer,INDICATOR_DATA);
class=class="str">"cmt">//--- setting a code from the Wingdings charset as the class="kw">property of PLOT_ARROW
   PlotIndexSetInteger(class="num">0,PLOT_ARROW,class="num">159);

◍ 双箭头缓冲区的声明与初始化落点

这段代码片段展示了 MT5 自定义指标里双箭头画图的骨架:用 indicator_buffers 2 与 indicator_plots 2 声明两组缓冲,分别命名为 Up 和 Down,绘制类型都是 DRAW_ARROW,颜色取 clrMediumPurple,线宽设为 1。 在 OnInit 尾部能看到 PlotIndexSetInteger(1, PLOT_ARROW, 159); 这一行,意思是给第 2 个画图索引(序号 1)绑定 Wingdings 字符 159 作为箭头符号,函数返回 INIT_SUCCEEDED 表示初始化通过。 OnCalculate 目前是个空壳,只写了 return(rates_total); 把已处理柱数原样交回给下一轮调用,真正的信号写值逻辑还没填。开 MT5 新建指标时,照这个 #property 块抄一遍,就能先看到上下两组空白箭头轨道。 input int inp_barsTypeSelector=0; 留了一个外部开关位,后续可用它切换不同 K 线类型来触发箭头,现在默认 0 不影响编译。外汇与贵金属品种波动剧烈,这类未接信号的指标仅作结构验证,实盘前务必自测。

MQL5 / C++
  PlotIndexSetInteger(class="num">1,PLOT_ARROW,class="num">159);

class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">class="kw">datetime &time[],
                const class="type">class="kw">double &open[],
                const class="type">class="kw">double &high[],
                const class="type">class="kw">double &low[],
                const class="type">class="kw">double &close[],
                const class="type">long &tick_volume[],
                const class="type">long &volume[],
                const class="type">int &spread[])
  {
class=class="str">"cmt">//---

class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- plot Up
class="macro">#class="kw">property indicator_label1  "Up"             class=class="str">"cmt">// Display name of the buffers
class="macro">#class="kw">property indicator_type1   DRAW_ARROW       class=class="str">"cmt">// Drawing type - arrow
class="macro">#class="kw">property indicator_color1  clrMediumPurple  class=class="str">"cmt">// Arrow class="type">class="kw">color
class="macro">#class="kw">property indicator_style1  STYLE_SOLID      class=class="str">"cmt">// Line style - solid
class="macro">#class="kw">property indicator_width1  class="num">1                class=class="str">"cmt">// Line width(arrow size)
class=class="str">"cmt">//--- plot Down
class="macro">#class="kw">property indicator_label2  "Down"
class="macro">#class="kw">property indicator_type2   DRAW_ARROW
class="macro">#class="kw">property indicator_color2  clrMediumPurple
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- input parameters
input class="type">int      inp_barsTypeSelector=class="num">0;
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double         UpBuffer[];
class="type">class="kw">double         DownBuffer[];

「给内含外包柱指标加可调控外观」

把形态识别限定为两类就够了:内含柱(Inside)与外包柱(Outside)。在预处理器指令后直接挂一个全局枚举,比后面散落魔法数字更易维护,也方便在输入参数里切换类型。 默认输入改成 BarsTypeSelector 枚举后,指标只画一种形态;想看另一种就把 inp_barsTypeSelector 从 Inside 改成 Outside。我在输入区又手填了两个变量:inp_arrowCode=159、inp_arrowShift=5,分别管图标字形(Wingdings 码位)和偏离栏体的像素距离。 输入参数自己不会生效,得在 OnInit 里接上。用 PlotIndexSetInteger 把 PLOT_ARROW 绑成 159 号箭头,再把上下缓冲的 PLOT_ARROW_SHIFT 设成 ±5,图标就会在栏上方和下方各错开 5 像素,不挡实体。 OnCalculate 主体里用 barsInPattern=2 常量框住「两根栏」的比对逻辑,避免硬编码 2 散落各处。图 9 实测中,左侧粉标的是内含柱前一根,右侧标的是外包柱——EURUSD 这类高波动品种上,外包柱出现后短期反向刺穿的概率倾向更高,但外汇保证金本身高风险,参数只是过滤噪音,不预示方向。

MQL5 / C++
enum BarsTypeSelector
{
  Inside  = class="num">0, class=class="str">"cmt">// Inside bar
  Outside = class="num">1  class=class="str">"cmt">// Outside bar
};
class=class="str">"cmt">//--- input parameters
input BarsTypeSelector       inp_barsTypeSelector=Inside;
class=class="str">"cmt">//--- input parameters
input BarsTypeSelector       inp_barsTypeSelector=Inside;
input class="type">int                    inp_arrowCode=class="num">159;
input class="type">int                    inp_arrowShift=class="num">5;
class="type">int OnInit()
{
class=class="str">"cmt">//--- indicator buffers mapping
  SetIndexBuffer(class="num">0,UpBuffer,INDICATOR_DATA);
  SetIndexBuffer(class="num">1,DownBuffer,INDICATOR_DATA);
class=class="str">"cmt">//--- setting a code from the Wingdings charset as the class="kw">property of PLOT_ARROW
  PlotIndexSetInteger(class="num">0,PLOT_ARROW,inp_arrowCode);
  PlotIndexSetInteger(class="num">1,PLOT_ARROW,inp_arrowCode);
  
  PlotIndexSetInteger(class="num">0, PLOT_ARROW_SHIFT, inp_arrowShift);
  PlotIndexSetInteger(class="num">1, PLOT_ARROW_SHIFT, -inp_arrowShift);
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">/********************************************************************************************
*                                                                                          *
* Attention! All arrays in the code are NOT series, so we have larger numbers on the right. *
*                                                                                          *
********************************************************************************************/
class=class="str">"cmt">//--- Description of variables
  class="type">int i,     class=class="str">"cmt">// Loop counter
      start; class=class="str">"cmt">// Initial bar for historical data
  const class="type">int barsInPattern = class="num">2; class=class="str">"cmt">// For proper preparation we need to know,
                               class=class="str">"cmt">//   how many bars will be involved in one check
                               class=class="str">"cmt">//   I&class="macro">#x27;ve added a constant to avoid the presence of 
                               class=class="str">"cmt">//   "magic numbers" that come from nowhere,

内包线与外包线缓冲区的边界处理

在 MT5 自定义指标里,barsInPattern 决定识别形态所需的最少 K 线数。若 rates_total 小于该值,直接 return(0) 不画任何东西,能避免数组越界导致的终端崩溃。 首次运行或历史数据不足时,start 设为 barsInPattern,从最早可计算位置向后扫;若指标已跑过一段时间,则只处理最后两段(start = rates_total - barsInPattern),减少重复计算。 最后一根未闭合 K 线先写 EMPTY_VALUE,防止在实时 tick 更新时出现半截信号的视觉伪影。循环里 i 的上限是 rates_total-1,也就是只认已收盘的 bar;想连未平仓 bar 也标,就把条件改成 i<=rates_total-1。 内包线模式(Inside)下,当 high[i]<=high[i-1] 且 low[i]>=low[i-1],把母柱的 high/low 写进 UpBuffer/DownBuffer[i-1];外包线模式(Outside)则反过来,满足条件时标当前柱。外汇与贵金属波动大,这类形态信号仅作概率参考,实盘前请在策略测试器用历史数据验证命中率。

MQL5 / C++
   class=class="str">"cmt">//   we could use the class="macro">#define directive instead of the constant
class=class="str">"cmt">//--- Check the boundary conditions and set the origin
  if(rates_total < barsInPattern)      class=class="str">"cmt">// If there are not enough bars to work
    class="kw">return(class="num">0);                         class=class="str">"cmt">// Do nothing
  if(prev_calculated < barsInPattern+class="num">1)class=class="str">"cmt">// If nothing has been calculated yet
  {
   start = barsInPattern;     class=class="str">"cmt">// Set the minimum possible number of bars to start searching 
  }
  else
  {
   start = rates_total - barsInPattern; class=class="str">"cmt">// If the indicator has been running for some time,
                                        class=class="str">"cmt">//   Just count the last two bars
  }
class=class="str">"cmt">//--- To avoid strange artifacts on the last candlestick, initialize the last elements of the arrays
class=class="str">"cmt">//      with EMPTY_VALUE
  UpBuffer[rates_total-class="num">1] = EMPTY_VALUE;
  DownBuffer[rates_total-class="num">1] = EMPTY_VALUE;
class=class="str">"cmt">//---
  for(i = start; i<rates_total-class="num">1; i++) class=class="str">"cmt">// Start counting from the starting position 
                                        class=class="str">"cmt">//   and class="kw">continue until there are no more closed bars
                                        class=class="str">"cmt">// (If we needed to include the last - unclosed - bar, 
                                        class=class="str">"cmt">//   we would set the condition i<=rates_total-class="num">1)
  {
   class=class="str">"cmt">// First, let&class="macro">#x27;s clear both indicator buffers(initialize to an empty value) 
   UpBuffer[i] = EMPTY_VALUE;
   DownBuffer[i] = EMPTY_VALUE;
   
   if(inp_barsTypeSelector==Inside) class=class="str">"cmt">// If the user wants to display inside bars
   {
    class=class="str">"cmt">// Check if the current bar is inside
    if(high[i] <= high[i-class="num">1] && low[i] >= low[i-class="num">1])
    {
     class=class="str">"cmt">// And if yes, we mark the previous(larger) candlestick
     UpBuffer[i-class="num">1] = high[i-class="num">1];
     DownBuffer[i-class="num">1] = low[i-class="num">1];
    }
   }
   else class=class="str">"cmt">// If outside bars are needed
   {
    class=class="str">"cmt">// Check if the current bar is outside
    if(high[i] >= high[i-class="num">1] && low[i] <= low[i-class="num">1])
    {
     class=class="str">"cmt">// Mark the current candlestick if necessary
     UpBuffer[i] = high[i];
     DownBuffer[i] = low[i];
    }
   }
  }
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
  class="kw">return(rates_total);

◍ 下一步该碰 EA 和对象模型了

把算术、赋值、条件分支、循环、函数与对象这几个基础件吃透,你基本能读懂绝大多数 MT5 脚本的逻辑骨架。再往前走,难点不在语法,而在怎么把以前写过的指标复用进新 EA,以及怎么用类把策略状态和外围数据收拢清楚。 这个系列不会停在语法层。下一篇会拆 EA 交易的创建流程,之后再聊 OOP 在 MQL5 里的实际写法,通用部分收掉后就会钻进终端自带的 Include 标准库和具体平台功能。 外汇与贵金属杠杆交易高风险,用这套基础去写自动策略前,务必先在策略测试器跑历史回测,别把能编译通过当成能实盘。

把形态扫描交给小布盯盘
这些内含柱、外包柱的识别逻辑,小布盯盘的 AIGC 已内置到对应品种页,打开就能直接看标注,不必自己从头编译指标。把重复劳动交给小布,你专注决策。

常见问题

数字 0 代表 false,其余正负数字都视为 true;字符串不能转布尔,强行转换会编译报错,但字符串之间可做比较。
可以,小布盯盘对应品种页已内置这类形态识别,无需自行写指标即可在盘面上看到标记,适合快速复核。
未闭合的烛形范围会变,提前标记易误判;等关闭再用前一根高低点比对,才能稳定区分内含柱与外包柱。
在枚举式分支上可以替换,但涉及区间比较(如高低点大小关系)仍依赖条件表达式,混用更清晰。