在MQL5程序中使用断言·进阶篇
断言该盯哪儿、别盯哪儿
断言在 MQL5 里是用来抓「程序自己犯傻」的,不是用来抓市场风险的。它该查的是输入参数越界、数组长度失控、对象指针失效、除数为零这类开发期就能定位的破绽。比如下方代码里对 a 的限制:小于 10 或大于 100 直接 assert 拦下,返回结果小于 0 也拦,这属于把「契约」写进执行流。 契约式设计(DbC)把函数分成前置条件和后置条件:调用前客户端必须保证参数合规,返回后函数必须保证结果合规。用断言记录这些约定,比源码里写注释更硬——注释不会在运行时喊停,断言会在调试版里直接报错。 别拿断言去查外部因素:开仓是否成功、历史数据是否够 1000 根 bar,这些不取决于程序员,应该用报错处理而非 assert。曾有例子显示,若强行用断言查历史不足,最终版编译关掉断言后漏洞就裸奔了。 一个隐蔽坑:断言里塞了函数调用,而发布版编译会把断言整段删掉,函数副作用就消失了。正确写法是先把 bool isOK = obj.IsCorrect() 存变量,再 assert(isOK)。这样关掉断言也不影响程序行为。 断言和处理预期错误是两件事。传入 8 给「要求 >10」的方法,是码农的错,断言喊「朋友你写错了」;历史数据不够是环境的事,得走错误处理让 EA 平稳跑。两者组合用,开发期查内鬼、发布期抗外患,程序才稳。
class="type">class="kw">double CMyClass::SomeMethod(const class="type">class="kw">double a) { class=class="str">"cmt">//--- 检查输入参数值 assert(a>=class="num">10,"") assert(a<=class="num">100,"") class=class="str">"cmt">//---计算结果值 class="type">class="kw">double result=...; class=class="str">"cmt">//---检查结果值 assert(result>=class="num">0,"") class="kw">return result; } class="type">void CMyClass::SomeMethod(const class="type">class="kw">string &incomingArray[]) { class=class="str">"cmt">//--- 检查数组边界 assert(ArraySize(incomingArray)>class="num">0,"") assert(ArraySize(incomingArray)<=class="num">10,"") ... } class="type">void OnTick() { class=class="str">"cmt">//--- 创建对象a CMyClass *a=new CMyClass(); class=class="str">"cmt">//--- 一些操作 ... ... ... class=class="str">"cmt">//--- 检查对象是否仍旧存在 assert(CheckPointer(a),"") class=class="str">"cmt">//--- 删除对象a class="kw">delete a; } class="type">void CMyClass::SomeMethod(const class="type">class="kw">double a, const class="type">class="kw">double b) { class=class="str">"cmt">//--- 检查b是否为零 assert(b!=class="num">0,"") class=class="str">"cmt">//--- 将a除以b class="type">class="kw">double c=a/b; ... ... ... } class="type">void OnTick() { CMyClass someObject; class=class="str">"cmt">//--- 检查某些运算的正确性 assert(someObject.IsSomeCalculationsAreCorrect(),"") ... ... ... } class="type">void OnTick() { CMyClass someObject; class=class="str">"cmt">//--- 检查某些运算的正确性 class="type">bool isSomeCalculationsAreCorrect = someObject.IsSomeCalculationsAreCorrect(); assert(isSomeCalculationsAreCorrect,"") ... ... ... } class="type">void CMyClass::SomeMethod(const class="type">class="kw">double a) { class=class="str">"cmt">//--- 我们检查a是否超过10 assert(a>class="num">10,"") ... ... ... } class="type">void OnTick() { CMyClass someObject;
◍ 类方法里用断言和区间钳制保命
在 MT5 自定义类的方法里,输入参数若不设防,很容易在实盘里算出离谱值。下面这段把入参 a 限定在 [10,100],结果 result 下限锁在 0,属于写 EA 时的基础防御。 OnTick 里先卡 Bars(Symbol(),Period())<1000 就 Comment 提示并返回,避免历史 K 线不足导致指标计算失真。外汇和贵金属波动大,历史样本不够时任何统计结论都倾向不可靠,这种前置检查能省掉不少诡异 bug。 断言 assert 用于开发期快速暴露逻辑错误:a 不在 [10,100] 或 result<0 会直接触发。但正式跑盘时不能只靠断言,所以同时用 if 把 aValue 钳到边界、result 兜底为 0,双保险才稳。
someObject.SomeMethod(class="num">8); ... ... ... } class="type">void OnTick() { if(Bars(Symbol(),Period())<class="num">1000) { Comment("Insufficient history for correct operation of the program"); class="kw">return; } } class="type">class="kw">double CMyClass::SomeMethod(const class="type">class="kw">double a) { class=class="str">"cmt">//--- 用断言检查输入参数的值 assert(a>=class="num">10,"") assert(a<=class="num">100,"") class=class="str">"cmt">//--- 检查输入参数的值,如果有必要,修正它 class="type">class="kw">double aValue = a; if(aValue<class="num">10) { aValue = class="num">10; } else if(aValue>class="num">100) { aValue = class="num">100; } class=class="str">"cmt">//--- 计算结果值 class="type">class="kw">double result=...; class=class="str">"cmt">//--- 用断言检查结果值 assert(result>=class="num">0,"") class=class="str">"cmt">//--- 检查结果值,如果有必要,修正它 if(result<class="num">0) { result = class="num">0; } class="kw">return result; }
「断言的边界与退出机制落地」
断言在 MQL5 里首要用来抓开发者自己写进程序的逻辑错误,而不是处理行情断线、点差突变这类跟编码者无关的运行期异常。发布实盘版本前,带断言的调试代码应当剥离,否则可能拖慢执行并在日志刷无意义报错。
论坛里一个典型争论是:在 OnInit 里不必调 ExpertRemove(),直接 return(INIT_FAILED) 就能中止初始化;在其它事件处理函数里,调完 ExpertRemove() 后跟一句 return 或 return(x) 即可停掉 EA。但指标没有统一 ShortName 获取函数,想写一行通用断言宏去删图表指标并不直白。
有用户指出原 assert 宏在 if(true) assert(1==1,"") else Print("Never executed"); 这种写法下会触发「Never executed」误报,根因是宏展开后 else 分支接到了空语句。修正方式是将 else 分支宏改为返回空字符串并在调用处补分号,这类细节不核对极易在回测时误导你。
外汇与贵金属杠杆高、滑点随机,断言只能管代码层错误,管不了市场风险,实盘前务必用策略测试器跑一遍带断言的版本确认无残留。
class="type">int OnInit() { class=class="str">"cmt">//--- ... if(somethign wrong) { class=class="str">"cmt">//ExpertRemove(); class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 无需在 OnInit() 中使用 ExpertRemove() } ... } ExpertRemove(); class="kw">return; class=class="str">"cmt">//--- 只需返回即可完成当前事件处理程序 or ExpertRemove(); class="kw">return(x); class=class="str">"cmt">//--- 只需返回即可完成当前事件处理程序 if(true) assert(class="num">1==class="num">1, "") else Print("Never executed"); class="macro">#define assert(condition, message) \ do if(!(condition)) \ { \ class="type">class="kw">string fullMessage= \ class="macro">#condition+", " \ +__FILE__+", " \
用断言把野指针挡在回测外
上面这段宏把 __FUNCSIG__ 和 __LINE__ 拼进提示串,断言失败时直接弹 Alert 并故意越界写 x[1] 让 EA 当场崩停,比静默跳过安全得多。
在 MT5 里开 #define DEBUG 编译,条件不满足就会输出类似 Assertion failed! void OnTick(), line: 42, 保证金不足 的弹窗,定位比看日志快。
外汇与贵金属杠杆高,这类硬断点只该留在调试版;正式挂实盘前务必切到 #else 分支,让 assert 展开成空,否则一次误触就可能中断盯盘。
class="macro">#define assert(condition, message) do { \ if(!(condition)) { \ class="type">class="kw">string fullMessage = __FUNCSIG__+", " \ +"line: "+(class="type">class="kw">string)__LINE__ \ +(message=="" ? "" : ", "+message); \ Alert("Assertion failed! "+fullMessage); \ class="type">class="kw">double x[]; \ ArrayResize(x, class="num">0); \ x[class="num">1] = class="num">0.0; \ } while(false) class="macro">#else class="macro">#define assert(condition, message) class="macro">#endif