掌握 MQL5:从入门到精通(第五部分):基本控制流操作符(基础篇)
◍ MQL5 控制流怎么搭骨架
MQL5 里真正驱动 EA 逻辑的不是指标计算,而是控制流操作符。if、else、switch、while、for 这几样决定了行情来了之后程序走哪条分支、循环多少次,是写策略最底层的骨架。 以最常见的条件判断为例,下面这段演示了如何在每根新柱上做一次方向过滤,只在满足条件时打印信号。注意 MQL5 里获取新柱要用 iTime 比对,而不是像 MQL4 那样用 Bars 变量。 控制流写错一个边界,回测就可能多跑几百单或少跑关键单。建议在 MT5 策略测试器里把日志打开,用 2024 年 EURUSD 的 H1 数据跑一遍,观察新柱触发次数是否和 K 线根数一致(一年约 6000+ 根 H1 K 线)。外汇与贵金属杠杆高,实盘前务必用历史数据验证逻辑分支无死循环。
class="type">int prev_bars = class="num">0; class="type">void OnTick() { class="type">int bars = Bars(Symbol(), PERIOD_H1); if(bars != prev_bars) { prev_bars = bars; class="type">class="kw">double ma = iMA(Symbol(), PERIOD_H1, class="num">14, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0); class="type">class="kw">double price = iClose(Symbol(), PERIOD_H1, class="num">0); if(price > ma) Print("H1 价格站上 MA14"); } }
「让程序自己认出内含柱与外包柱」
这一节要做的指标只干一件事:在图表上标出已经收盘的内含柱(inside bar)和外包柱(outside bar)。外包柱就是高低点同时突破前一根烛形高低范围的 K 线;内含柱则是收完盘后,整根还待在前一根烛形高低区间里的 K 线。 标记只打在“长”柱上——外包柱的记号落在当前这根,内含柱的记号回标到前一根。这个放法是作者主观定的,你觉得别扭,直接改代码里的判定分支就行,MT5 里编译完就能看到不同。 为了让指标通用,会用输入参数切换显示哪种柱,不写死。控制流这里主要用 if 条件句加循环扫每根柱;想练手可以把循环换成别的写法,或者拿 switch-case 替掉部分条件判断。 程序到目前为止都是线性跑完拉倒,没有判断能力。读完这篇,你至少能在 MT5 里写出一个会“看形态”的指标原型。 先得把布尔表达式讲透,它是后面所有条件分支的地基:真或假,决定代码走哪条路。
MQL5 里真假值怎么算出来
布尔类型在 MQL5 里只有 true 和 false 两种状态,程序跑逻辑分支时本质上就在反复问“这个条件成立吗”。数字 0 被当作 false,其余无论正负都视为 true;但字符串不能强转成布尔,硬转会直接编译报错,不过字符串之间是可以比较大小的。 比较字符串时按 ASCII 码走:单字符里 'A' > '1' 为 true,'Y' < 'A' 为 false;多字符从左往右逐个比,直到出现分歧,空串 "" 比任何串都小。比较运算符和标准数学一致:>、<、==、!=、>=、<=,两个值相等时 a==b 才返回 true。
| 三种逻辑操作要分清:! 取反,&& 两边皆真才真, | 有一边真就真。下面这段例程把运算优先级和常见坑都摊开了,建议直接丢进 MT5 跑一遍看 Print 输出。 |
|---|
[CODE] int a = 3; int b = 5; bool e = ( a*b ) > ( a+b+b ); // true Print ( (a>b) ); // false
| Print ( (a<b) | ((a/b) > (3/2)) ); // true |
|---|
Print ( !a ); // false Print ( ! (!e) ); // true
| Print ( (b>a) && ((a<=3) | (b<=3)) ); // true |
|---|
Print ( "Trust" > "Training" ); // true Print ( a==b ); // false Print ( b=a ); // 3 (!!!) (therefore, for any logical operator this is always true) // In the last example, a very common and hard-to-detect mistake has been made. // Instead of a comparison, an assignment was used, which results in a value of type int! // Fortunately, the compiler will usually warn about this substitution. [/CODE]
| 逐行拆一下:a=3、b=5,e 判断 15 > 13 成立得 true;a>b 为假打印 false;a<b 真所以 | 整体真;!a 因 a 非零取反得 false;!!e 双重否定回到 true;b>a 且 (a<=3 真) 让 && 成立;"Trust" 首字母 T 的 ASCII 高于 "Training" 的 T 之后第二位 r 对 a,r>a 故为真;a==b 显然 false。 |
|---|
最后一行 b=a 是赋值不是比较,把 3 赋给 b 后返回 int 3,在逻辑语境里永远当 true——这是新手最难查的隐蔽笔误,编译器虽会警告但别指望每次都拦住。
| 不带括号时求值顺序固定:先算术(* / % 再 + -),再比较,再 !、&&、 | ,赋值最后。拿不准就加括号,既锁顺序又利读。外汇与贵金属杠杆高,EA 里逻辑写错可能瞬间错单,上线前务必在策略测试器跑通再实盘。 |
|---|
class="type">int a = class="num">3; class="type">int b = class="num">5; class="type">bool e = ( a*b ) > ( a+b+b ); class=class="str">"cmt">// true Print( (a>b) ); class=class="str">"cmt">// false Print( (a<b) || ((a/b) > (class="num">3/class="num">2)) ); class=class="str">"cmt">// true Print( !a ); class=class="str">"cmt">// false Print( ! (!e) ); class=class="str">"cmt">// true Print( (b>a) && ((a<=class="num">3) || (b<=class="num">3)) ); class=class="str">"cmt">// true Print( "Trust" > "Training" ); class=class="str">"cmt">// true Print( a==b ); class=class="str">"cmt">// false Print( b=a ); class=class="str">"cmt">// class="num">3 (!!!) (therefore, for any logical class="kw">operator this is always true) class=class="str">"cmt">// In the last example, a very common and hard-to-detect mistake has been made. class=class="str">"cmt">// Instead of a comparison, an assignment was used, which results in a value of type class="type">int! class=class="str">"cmt">// Fortunately, the compiler will usually warn about this substitution.
◍ if 分支与三元运算符的实战写法
MQL5 的 if 语句本质是程序流的分支点:满足条件走一条路,否则走另一条(else 可选)。它和流程图里的决策树同构——每个判断点把执行主干劈成独立路径,块内变量对外界不可见,这是作用域的基本边界。 花括号决定执行范围。若省略括号,if 之后只有第一条语句受条件约束,后续语句无条件跑。原文给出的等待新烛形代码里,prev_calculated == rates_total 时进入块内执行 Comment 与 return;若删掉花括号,return 会无条件触发,分钟图上你永远看不到“I can start to do anything”的日志。 别把花括号当可选装饰 哪怕 if 只包一句,也强制写 {}。等你在代码库里读熟别人写的程序后再考虑简写。括号让调试直观,也避免日后加调试输出或提醒时改出作用域 bug。 三元运算符适合二选一赋值:condition ? 值1 : 值2,顺序与 if 一致但必须返回与左值同类型的结果,只能嵌在表达式里;传统 if 正文可放任意操作但不能直接返回值。下面代码块里两组示例(a=9, b=45)输出完全一致,只是书写密度不同。 外汇与贵金属 EA 开发属高风险活动,条件逻辑错误可能在无流动性时段误发订单,请在策略测试器逐步验证分支行为。
<span class="keyword">if</span> (condition) action_if_condition_is_true; <span class="keyword">else</span> action_in_all_other_cases; <span class="comment">class=class="str">"cmt">// The "else" branch is optional</span> <span class="keyword">if</span> (prev_calculated == rates_total) <span class="comment">class=class="str">"cmt">// _If_ they are equal, </span> { <span class="comment">class=class="str">"cmt">// _then_ do nothing, wait for a new candlestick</span> <span class="functions">Comment</span>(<span class="class="type">class="kw">string">"Nothing to do"</span>); <span class="comment">class=class="str">"cmt">// Display a relevant message in a comment</span> <span class="keyword">class="kw">return</span>(rates_total); <span class="comment"> class=class="str">"cmt">// Since nothing needs to be done, exit the function class=class="str">"cmt">// and inform the terminal that all bars have been calculated(class="kw">return rates_total)</span> } <span class="comment">class=class="str">"cmt">// A new candle has arrived. Execute necessary actions</span> <span class="functions">Comment</span>(<span class="class="type">class="kw">string">""</span>); <span class="comment">class=class="str">"cmt">// Clear comments</span> <span class="functions">Print</span>(<span class="class="type">class="kw">string">"I can start to do anything"</span>); <span class="comment"> class=class="str">"cmt">// Log a message</span> <span class="comment">class=class="str">"cmt">// Since the required actions will be executed </span> <span class="comment">class=class="str">"cmt">// only if prev_calculated is not equal to rates_total, class=class="str">"cmt">// else branch is not needed in this example.</span> <span class="comment">class=class="str">"cmt">// We just perform the actions.</span> <span class="keyword">class="type">int</span> a = <span class="number">class="num">9</span>; <span class="keyword">class="type">int</span> b = <span class="number">class="num">45</span>; <span class="keyword">class="type">class="kw">string</span> message; <span class="keyword">if</span>( <span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);">(b%a) == <span class="number">class="num">0</span></span> ) <span class="comment">class=class="str">"cmt">// (class="num">1)</span> { message = <span class="class="type">class="kw">string" style="background-class="type">class="kw">color:rgb(class="num">255, class="num">235, class="num">85);">"b divides by a without remainder"</span>; <span class="comment">class=class="str">"cmt">// (class="num">2)</span> } <span class="keyword">else</span> { message = <span class="class="type">class="kw">string" style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">"b is NOT divisible by a"</span>; <span class="comment">class=class="str">"cmt">// (class="num">3)</span> } <span class="functions">Print</span> (message); <span class="keyword">class="type">int</span> a = <span class="number">class="num">9</span>; <span class="keyword">class="type">int</span> b = <span class="number">class="num">45</span>; <span class="keyword">class="type">class="kw">string</span> message; message = <span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);">( (b%a) == <span class="number">class="num">0</span> )</span> <span class="comment">class=class="str">"cmt">/* (class="num">1) */</span> <span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);">?</span> <span class="class="type">class="kw">string" style="background-class="type">class="kw">color:rgb(class="num">255, class="num">235, class="num">85);">"b divides by a without remainder"</span> <span class="comment">class=class="str">"cmt">/* (class="num">2) */</span> <span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">:</span> <span class="class="type">class="kw">string" style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">"b is NOT divisible by a"</span> <span class="comment">class=class="str">"cmt">/* (class="num">3) */</span> ; <span class="functions">Print</span> (message);
「用 switch 收拾多分支判断」
当选项超过两个,嵌套 if 会让 EA 逻辑读起来像毛线团。switch 作为选择器,把整型变量的多种取值摊平到并列的 case 下,代码路径一眼能数清。 语法上,匹配到的 case 会从那一行顺序往下跑,所以大多数分支末尾要跟 break 拦住穿透;没命中任何 case 就走最后的 default,且 default 必须压在结尾。漏写 break 可能导致相邻分支被连带执行,这类 bug 在回测里不易察觉,实盘却可能重复发单。 在 MT5 里,switch-case 常见于交易错误分流与用户输入处理。下面这段是标准错误处理函数的骨架,用 GetLastError() 拿整型错误码,再逐一映射成日志说明。 [CODE] switch (integer_variable) { case value_1: operation_list_1; case value_2 operation_list_2; // … default: default_operation_list; } void PrintErrorDescription() { int lastError = GetLastError(); // If (lastError == 0), there are no errors… if(lastError == 0) { return; // …no need to load cpu with unnecessary computations. } // If there are errors, output an explanatory message to the log. switch(lastError) { case ERR_INTERNAL_ERROR: Print("Unexpected internal error"); // You can select any appropriate action here break; case ERR_WRONG_INTERNAL_PARAMETER: Print("Wrong parameter in the inner call of the client terminal function"); break; case ERR_INVALID_PARAMETER: Print("Wrong parameter when calling the system function"); break; case ERR_NOT_ENOUGH_MEMORY: Print("Not enough memory to perform the system function"); break; default: Print("I don't know anything about this error"); } } [/CODE] 逐行拆解:第 1 行声明对整型变量做分支选择;case 后跟常量值,命中即入该块。PrintErrorDescription 中先取 lastError,若为 0 直接 return 省 CPU;后续 switch 按错误宏分流,每个已知错误打印对应描述并 break,未知错误落 default。外汇与贵金属杠杆高,错误未捕获可能让订单逻辑失控,建议把这段直接塞进自己的 EA 测一遍错误码映射。
class="kw">switch (integer_variable) { case value_1: operation_list_1; case value_2 operation_list_2; class=class="str">"cmt">// … class="kw">default: default_operation_list; } class="type">void PrintErrorDescription() { class="type">int lastError = GetLastError(); class=class="str">"cmt">// If(lastError == class="num">0), there are no errors… if(lastError == class="num">0) { class="kw">return; class=class="str">"cmt">// …no need to load cpu with unnecessary computations. } class=class="str">"cmt">// If there are errors, output an explanatory message to the log. class="kw">switch(lastError) { case ERR_INTERNAL_ERROR: Print("Unexpected internal error"); class=class="str">"cmt">// You can select any appropriate action here class="kw">break; case ERR_WRONG_INTERNAL_PARAMETER: Print("Wrong parameter in the inner call of the client terminal function"); class="kw">break; case ERR_INVALID_PARAMETER: Print("Wrong parameter when calling the system function"); class="kw">break; case ERR_NOT_ENOUGH_MEMORY: Print("Not enough memory to perform the system function"); class="kw">break; class="kw">default: Print("I don&class="macro">#x27;t know anything about this error"); } }