在MQL5程序中使用断言·进阶篇
📘

在MQL5程序中使用断言·进阶篇

第 2/2 篇

断言该盯哪儿、别盯哪儿

断言在 MQL5 里是用来抓「程序自己犯傻」的,不是用来抓市场风险的。它该查的是输入参数越界、数组长度失控、对象指针失效、除数为零这类开发期就能定位的破绽。比如下方代码里对 a 的限制:小于 10 或大于 100 直接 assert 拦下,返回结果小于 0 也拦,这属于把「契约」写进执行流。 契约式设计(DbC)把函数分成前置条件和后置条件:调用前客户端必须保证参数合规,返回后函数必须保证结果合规。用断言记录这些约定,比源码里写注释更硬——注释不会在运行时喊停,断言会在调试版里直接报错。 别拿断言去查外部因素:开仓是否成功、历史数据是否够 1000 根 bar,这些不取决于程序员,应该用报错处理而非 assert。曾有例子显示,若强行用断言查历史不足,最终版编译关掉断言后漏洞就裸奔了。 一个隐蔽坑:断言里塞了函数调用,而发布版编译会把断言整段删掉,函数副作用就消失了。正确写法是先把 bool isOK = obj.IsCorrect() 存变量,再 assert(isOK)。这样关掉断言也不影响程序行为。 断言和处理预期错误是两件事。传入 8 给「要求 >10」的方法,是码农的错,断言喊「朋友你写错了」;历史数据不够是环境的事,得走错误处理让 EA 平稳跑。两者组合用,开发期查内鬼、发布期抗外患,程序才稳。

MQL5 / C++
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,双保险才稳。

MQL5 / C++
  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 分支宏改为返回空字符串并在调用处补分号,这类细节不核对极易在回测时误导你。 外汇与贵金属杠杆高、滑点随机,断言只能管代码层错误,管不了市场风险,实盘前务必用策略测试器跑一遍带断言的版本确认无残留。

MQL5 / C++
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 展开成空,否则一次误触就可能中断盯盘。

MQL5 / C++
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

常见问题

只在对输入参数、关键数组边界、指针非空这些会直接导致崩盘的地方加;循环计数、普通算术别加,反而拖慢。
在方法开头用断言钳制入参区间(如索引≥0且<大小),并在访问成员前断言this指针非空,能把大部分崩溃挡在运行前。
小布可扫描你的代码逻辑,标出未做空指针判断和越界访问的位置,并提示该补断言的地方,省去人工逐行查。
在获取数据句柄后立即断言返回值有效,无效直接退出当前tick,别让脏数据进历史回测污染统计。
在断言失败分支调用专用退出函数释放资源并返回,不要继续跑后续逻辑,避免连环报错。