掌握 MQL5:从入门到精通(第五部分):基本控制流操作符·进阶篇
(2/3)· 还在写只能线性执行的脚本?本篇用 inside/outside bar 指标拆解 while、do-while、for 与 break 的真实用法
接上篇,我们继续深挖 MQL5 的程序大脑。很多交易者抄来的指标脚本只能一根筋从头跑到尾,遇到「柱体还在形成」就乱标信号,根源是没搞懂控制流怎么让程序做判断。
◍ 先判条件再跑体的 while 循环
MQL5 里 while 属于「带前提条件的循环」:进循环体之前先查条件,为真才执行,为假就一次都不跑。它适合那些事先算不出要重复多少回的场景,比如逐行读一个未知长度的 EA 参数文件,程序只能一直读直到碰上文件结束标记才停。 语法骨架就是 while(condition) 后跟单句或复合语句。关键约束只有一条:循环体里必须让某个参与条件的变量发生变化,否则条件永远为真,终端会被锁死在无限循环里,严重时连键鼠操作都无响应,只能强制重启。稳妥做法是补一个 && !IsStopped() 之类的退出口。 下面这段把 1 到 5 累加,演示了最小可用机制:a 从 1 起、sum 从 0 起,每次把 a 加进 sum 然后 a++,条件 a<=5 为假时跳出,最后用 Comment 把算式和结果吐到图表左上。外汇与贵金属代码跑在 MT5 实盘前,请先在策略测试器验证逻辑,这类脚本风险随杠杆放大。
while (condition) action_if_condition_is_true; class=class="str">"cmt">//--- Variables declaration class="type">int a = class="num">1; class=class="str">"cmt">// initialize(!) the variable parameter used in the condition class="type">int sum = class="num">0; class=class="str">"cmt">// result class="type">class="kw">string message = "The sum of "+ (class="type">class="kw">string) a; class=class="str">"cmt">// show message class=class="str">"cmt">//--- Perform main operations while (a <= class="num">5) class=class="str">"cmt">// While a is less than class="num">5 { sum += a; class=class="str">"cmt">// Add the value to the sum if ( a != class="num">1) class=class="str">"cmt">// The first value is already added at the time of initialization { message += " + " + class="type">class="kw">string (a); class=class="str">"cmt">// Further operations } a++; class=class="str">"cmt">// Increase parameter(very important!) } class=class="str">"cmt">//-- After the loop is completed, output a message message += " is " + (class="type">class="kw">string) sum; class=class="str">"cmt">// message text Comment(message); class=class="str">"cmt">// show the comment
「do…while 至少跑一次的底气」
后置条件循环和前置的 while 最大差别在于:循环体先动手,再回头看条件。也就是说,哪怕条件一开始就为假,花括号里的操作也已经实打实执行了一轮。 这个特性在 MT5 里很适合处理「已知至少有一个元素」的场景。比如市场观察窗口里的交易品种遍历:你确定窗口非空,就先取出第一个品种做订单检查,再判断后面还有没有,有就继续,直到列表走完。 下面这段 MQL5 把 1 到 5 累加,a 初始化为 1,do 块里先 sum+=a,再把 a 自增,while 卡在 a<=5。最终 sum 等于 15,图表左上角用 Comment 打出「The sum of 1 + 2 + 3 + 4 + 5 is 15」。注意 a++ 那行不能省,否则条件永远为真,MT5 会卡死在这条脚本里。 别把正态当圣经:do…while 不是写起来更省事,而是当你「至少要算一次」的逻辑成立时,它比先判后做更不容易漏掉首元素。外汇与贵金属自动化脚本高风险,任何循环漏掉自增变量都可能造成终端假死。
do actions_if_condition_is_true; while (condition); class=class="str">"cmt">//--- Variables declaration class="type">int a = class="num">1; class=class="str">"cmt">// initialize(!) the variable parameter used in the condition class="type">int sum = class="num">0; class=class="str">"cmt">// result class="type">class="kw">string message = "The sum of "+ (class="type">class="kw">string) a; class=class="str">"cmt">// show message class=class="str">"cmt">//--- Perform main operations do class=class="str">"cmt">// Execute { sum += a; class=class="str">"cmt">// Add value to the sum if ( a != class="num">1) class=class="str">"cmt">// The first value is already added at the time of initialization { message += " + " + class="type">class="kw">string (a); class=class="str">"cmt">// Further operations } a++; class=class="str">"cmt">// Increase parameter(very important!) } while (a <= class="num">5) class=class="str">"cmt">// While a is less than class="num">5 class=class="str">"cmt">//-- After the loop is completed, output a message message += " is " + (class="type">class="kw">string) sum; class=class="str">"cmt">// message text Comment(message); class=class="str">"cmt">// show the comment
计数器挪到循环头里省心
MT5 里 for 循环适合已知迭代次数的场景,比如数组长度固定或要跑明确次数的扫描。它和 while 本质一样,都是先判条件再跑主体,但把计数器的初始化、判断、自增全塞进循环头,主体里只留真正要算的事。
上面例子用 a 从 1 加到 5,循环头写 a=1; a<=5; a++,黄底部分就是计数器相关动作。跑完 sum 等于 15,message 拼成 "1 + 2 + 3 + 4 + 5 is 15" 用 Comment 打到图表左上。
计数器变量 a 也可以循环外先声明不初始化,只在头里赋值;若图省事直接在头里写 int a=1 也行。把变更逻辑收拢到头部,主体读起来不容易漏掉步长。
开 MT5 新建脚本粘下面代码,跑完看左上角注释,能直观确认 for 头三步的执行顺序。
<span class="keyword">for</span> (initialize_counter ; conditions ; change_counter) action_if_condition_is_true; <span class="comment">class=class="str">"cmt">//--- Variables declaration</span> <span class="keyword">class="type">int</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">a; </span><span class="comment"><span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">class=class="str">"cmt">// do NOT initialize </span>the variable parameter, only describe class=class="str">"cmt">// (this is optional as you can describe it in the loop header)</span> <span class="keyword">class="type">int</span> sum = <span class="number">class="num">0</span>; <span class="comment">class=class="str">"cmt">// result</span> <span class="keyword">class="type">class="kw">string</span> message = <span class="class="type">class="kw">string">"The sum of <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">"</span></span><span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">;</span> <span class="comment">class=class="str">"cmt">// a shorter message for the user, class=class="str">"cmt">// as the value is not yet known</span> <span class="comment">class=class="str">"cmt">//--- Perform main operations</span> <span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);"><span class="keyword">for</span> (a=<span class="number">class="num">1</span>; a<=<span class="number">class="num">5</span>; a++)</span><span class="comment">class=class="str">"cmt">// For(each `a` from class="num">1 to class="num">5) /loop header/</span> { sum += a; <span class="comment">class=class="str">"cmt">// Add `a` to the sum</span> <span class="keyword">if</span> ( a != <span class="number">class="num">1</span>) <span class="comment">class=class="str">"cmt">// the first value does not contain "+" </span> { message += <span class="class="type">class="kw">string">" + <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">"</span></span><span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">; </span> <span class="comment">class=class="str">"cmt">// add "+" before all sequence members starting from the second</span> } <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">message += <span class="keyword">class="type">class="kw">string</span> (a); <span class="comment">class=class="str">"cmt">// Add sequence elements to the message</span></span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);"> <span class="comment">class=class="str">"cmt">// Changes to the counter are described not here, but in the loop header as the last parameter.</span></span> } <span class="comment">class=class="str">"cmt">//-- After the loop is completed, output a message</span> message += <span class="class="type">class="kw">string">" is "</span> + (<span class="keyword">class="type">class="kw">string</span>) sum; <span class="comment">class=class="str">"cmt">// message text</span> <span class="functions">Comment</span> (message); <span class="comment">class=class="str">"cmt">// show the comment</span>
◍ 循环里提前跳出的两种写法
写 EA 或指标时,循环未必每次都要跑满。比如扫一个品种数组找某个满足条件的 K 线,中途命中就可以直接收手,没必要把剩余元素全遍历一遍,这时候用 break 能立刻退出整个循环。 如果只想丢掉某几次迭代、循环还得接着跑,就得换 continue。像下面这段代码,i 从 0 到 3,遇到 2 就跳过,最终拼出的字符串是「0 1 3」,数字 2 被排除但 3 仍然参与。 break 除了待在 for / while 里,也能用在 switch 的 case 中截断分支;continue 则只能在循环体内用,搁在 case 外面会直接编译报错。 开 MT5 把下面代码丢进脚本跑一遍,终端会先打印「0 1」再打印「0 1 3」,两个结果差着一个被跳过的索引,肉眼就能确认语义。
class="type">class="kw">string message = ""; for (class="type">int i=class="num">0; i<=class="num">3; i++) { if ( i == class="num">2 ) { class="kw">break; } message += " " + IntegerToString( i ); } Print(message); class=class="str">"cmt">// Result: class="num">0 class="num">1 (no further execution) class="type">class="kw">string message = ""; for (class="type">int i=class="num">0; i<=class="num">3; i++) { if ( i == class="num">2 ) { class="kw">continue; } message += " " + IntegerToString( i ); } Print(message); class=class="str">"cmt">// Output: class="num">0 class="num">1 class="num">3 (the number class="num">2 is skipped)
「向导里把曲线选择权交给用户」
MQL5 向导建指标时,第二屏的参数管理值得多停留——参数名随意,但加 inp_ 前缀能在代码里一眼区分输入量与内部变量,直接编译后 EA 调用也省事。 OnCalculate 的格式选择是分水岭。选上层格式,终端自动塞进 open/high/low/close/volume/time 数组,覆盖大多数场景;选下层格式,则把用户可选曲线的数组传进来,编译出的指标启动窗会多出一个曲线选择标签,移动平均挂在高价或某指标输出上都行。 缓冲区的概念常被低估。它本质就是个数字数组,存每根烛形的计算结果,不一定用来画图,也能塞颜色或中间量。画一条线至少 1 个缓冲区,画方向箭头通常要 2 个(Up/Down 各一),只用一个箭头缓冲区就丢掉了方向信息。 新手用向导加缓冲区最稳,手动加虽简单但要额外绑定。顺着这个思路,可以搭一个叫 InsideOutsideBar 的指标:参数 inp_barsTypeSelector(int,默认 0),OnCalculate 取上层数组格式,Plots 里放 Up 和 Down 两个 Arrow 类型缓冲区,方向分类直接由这两个缓冲区承接。