掌握 MQL5:从入门到精通(第五部分):基本控制流操作符·综合运用
(3/3)·把线性脚本升级成能识别 inside/outside bar 的决策程序,收尾篇只讲落地写法
很多交易者把 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 引用传入,数据只读不可改。外汇与贵金属杠杆高、滑点跳空频繁,直接挂这类半成品指标前先在策略测试器跑一轮历史回放。
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 不影响编译。外汇与贵金属品种波动剧烈,这类未接信号的指标仅作结构验证,实盘前务必自测。
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 这类高波动品种上,外包柱出现后短期反向刺穿的概率倾向更高,但外汇保证金本身高风险,参数只是过滤噪音,不预示方向。
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)则反过来,满足条件时标当前柱。外汇与贵金属波动大,这类形态信号仅作概率参考,实盘前请在策略测试器用历史数据验证命中率。
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 标准库和具体平台功能。 外汇与贵金属杠杆交易高风险,用这套基础去写自动策略前,务必先在策略测试器跑历史回测,别把能编译通过当成能实盘。