MQL5中的错误处理和日志记录(基础篇)
📘

MQL5中的错误处理和日志记录(基础篇)

第 1/3 篇

MQL5 里先把错误和日志这关过了

在 MQL5 写 EA 或指标,最容易被忽略的不是策略逻辑,而是运行时错误怎么接住、怎么留痕。MT5 内核通过 GetLastError() 返回整型错误码,常见如 4014(数组越界)、4301(未选中品种),不主动读就只能在日志里看到一句含糊的失败。 真实项目里建议每次调用 OrderSend / CopyBuffer 等后立即取错误码并写文件或 Print。日志量不用大,但必须带时间戳和错误码,否则三天后你根本分不清是哪根 K 线触的雷。 下面这段是最朴素的兜底写法,开 MT5 新建脚本粘进去就能跑,验证错误捕获是否生效。

MQL5 / C++
class="type">int err = GetLastError();
if(err != class="num">0)
  Print("Error: ", err, " at ", TimeCurrent());

「为什么错误处理决定EA稳定性」

在 MT5 上跑 EA 或指标时,错误不是偶发意外,而是程序生命周期里的常态。订单 rejected、报价断流、数组越界,任何一步没接住就会让策略在实盘里静默失效。外汇与贵金属杠杆高、跳空频繁,这类失效带来的回撤风险被显著放大。 错误处理本身没有银弹。MQL5 里既有返回错误码的隐式检查,也有 throw/try-catch 的异常风格,还能配合标准库的日志工具落盘。不同写法各有代价:错误码轻量但易被忽略,异常清晰却要求调用链统一。实际任务里往往要混用,而不是套某个统一标准。 本文后续会拆开讲具体手法与日志配置,但核心先摆清:把错误当一等公民对待,才可能让你的程序在真实行情里活过三个月。

◍ MQL5里兜住错误的几种实战手法

EA跑在MT5上,最怕的不是策略亏,而是某根K线触发了数组越界或除零,整个程序直接被终端中断。除了这类运行时硬中断,开发者自己也得在逻辑层把错误接住,否则账户可能在无人察觉时被错单啃掉。 遇到致命问题,直接调 ExpertRemove() 让EA自我退市比硬扛更稳妥。比如开仓量算出来超出 SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX) 的上限,而账户保证金又不够,这时别强行下单,写一行 Print 记录然后退出,外汇和贵金属杠杆高,这种越界单对本金是实打实威胁。 把越界值夹回合法区间是最常用的软处理:用 MathMax 和 MathMin 把交易量钳在最小和最大手数之间。但注意,如果夹完仍大于资金容许,就必须放弃执行而不是默默成交。 返回错误标记也有讲究。字符串函数出错返 NULL,数值函数可返一个预定义失败常量;但光返错误值容易让调用方漏判,尤其接手老代码时。更好的做法是 bool 型返回值搭配引用传出结果,或者 SetUserError() 写全局错误码,调用处用 GetLastError() 比对。错误类型多的时候,直接返回枚举值最干净,不碰全局变量就少一处隐患。 文件被别的进程占着读不了?别立刻放弃,用循环隔几百毫秒重试几次,很可能对方释放了就好用了。但像错误5002(文件名非法)、5003(文件名过长)这种,重试纯属浪费,实时性要求高的系统也别用这招。 最后,参数填错或可用保证金不足,一定用 Alert 或图表对象把问题甩到用户脸上,别让EA在后台哑火。下面这段代码把上面几种路子揉在一起,逐行看一遍就能照抄。

MQL5 / C++
class="type">void OnTick()
  {
   class="type">bool resultOfSomeOperation=SomeOperation();
   if(!resultOfSomeOperation)
     {
      Alert("fail");
      ExpertRemove();
      class="kw">return;
     }
  }
class="type">class="kw">double VolumeCalculation()
  {
   class="type">class="kw">double result=...;
   result=MathMax(result,SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN));
   result=MathMin(result,SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX));
   class="kw">return result;
  }
class="macro">#define SOME_STR_FUNCTION_FAIL_RESULT(NULL)
class="type">class="kw">string SomeStrFunction()
  {
   class="type">class="kw">string result="";
   class="type">bool resultOfSomeOperation=SomeOperation();
   if(!resultOfSomeOperation)
     {
      class="kw">return SOME_STR_FUNCTION_FAIL_RESULT;
     }
   class="kw">return result;
  }
class="type">void OnTick()
  {
   class="type">class="kw">string someStr=SomeStrFunction();
   if(someStr==SOME_STR_FUNCTION_FAIL_RESULT)
     {
      Print("fail");
      class="kw">return;
     }
  }
class="macro">#define SOME_STR_FUNCTION_FAIL_CODE(class="num">123)
class="type">class="kw">string SomeStrFunction()
  {
   class="type">class="kw">string result="";
   class="type">bool resultOfSomeOperation=SomeOperation();
   if(!resultOfSomeOperation)
     {
      SetUserError(SOME_STR_FUNCTION_FAIL_CODE);
      class="kw">return "";
     }
   class="kw">return result;
  }
class="type">void OnTick()
  {
   ResetLastError();
   class="type">class="kw">string someStr=SomeStrFunction();
   if(GetLastError()==ERR_USER_ERROR_FIRST+SOME_STR_FUNCTION_FAIL_CODE)
     {
      Print("fail");
      class="kw">return;
     }
  }
class="type">bool SomeStrFunction(class="type">class="kw">string &value)
  {
   class="type">class="kw">string resultValue="";
   class="type">bool resultOfSomeOperation=SomeOperation();
   if(!resultOfSomeOperation)
     {
      value="";

用枚举和结构体替函数报错松绑

MQL5 里函数想向外传递失败原因,最偷懒的办法是 return false 然后在外部猜。但真要定位两步操作哪一步挂了,靠 bool 返回值就只剩瞎蒙。 原文先用 #define 定义了 123、124 两个用户错误码,在函数内调 SetUserError 后返回 false;OnTick 里用 GetLastError()-ERR_USER_ERROR_FIRST 把数字打出来。这样能区分失败类型,但错误码散在宏里,字符串拼接也容易写错。 更干净的做法是直接上枚举。把成功、失败码1、失败码2写进 ENUM_SOME_STR_FUNCTION_RESULT,函数返回枚举而非 bool。OnTick 拿到返回值后用 EnumToString 打印,连错误码含义都自带可读文本,调参排错时一眼能看清。 若还想顺带把结果数据一起带回,可以套个结构体:code 字段放枚举,value[255] 放 char 数组。函数返回结构体实例,调用方一次拿到状态与数据,省掉引用参数来回传 string 的开销。外汇与贵金属 EA 涉及高杠杆高风险,任何错误处理疏忽都可能让实盘在未预期分支里静默止损。

MQL5 / C++
class="macro">#define SOME_STR_FUNCTION_FAIL_CODE_1(class="num">123)
class="macro">#define SOME_STR_FUNCTION_FAIL_CODE_2(class="num">124)
class="type">bool SomeStrFunction(class="type">class="kw">string &value)
  {
   class="type">class="kw">string resultValue="";
   class="type">bool resultOfSomeOperation=SomeOperation();
   if(!resultOfSomeOperation)
     {
      value="";
      SetUserError(SOME_STR_FUNCTION_FAIL_CODE_1);
      class="kw">return false;
     }
   class="type">bool resultOfSomeOperation2=SomeOperation2();
   if(!resultOfSomeOperation2)
     {
      value="";
      SetUserError(SOME_STR_FUNCTION_FAIL_CODE_2);
      class="kw">return false;
     }
   value=resultValue;
   class="kw">return true;
  }
class="type">void OnTick()
  {
   class="type">class="kw">string someStr="";
   class="type">bool result=SomeStrFunction(someStr);
   if(!result)
     {
      Print("fail, code = "+(class="type">class="kw">string)(GetLastError()-ERR_USER_ERROR_FIRST));
      class="kw">return;
     }
  }
enum ENUM_SOME_STR_FUNCTION_RESULT
  {
   SOME_STR_FUNCTION_SUCCES,
   SOME_STR_FUNCTION_FAIL_CODE_1,
   SOME_STR_FUNCTION_FAIL_CODE_2
  };
ENUM_SOME_STR_FUNCTION_RESULT SomeStrFunction(class="type">class="kw">string &value)
  {
   class="type">class="kw">string resultValue="";
   class="type">bool resultOfSomeOperation=SomeOperation();
   if(!resultOfSomeOperation)
     {
      value="";
      class="kw">return SOME_STR_FUNCTION_FAIL_CODE_1;
     }
   class="type">bool resultOfSomeOperation2=SomeOperation2();
   if(!resultOfSomeOperation2)
     {
      value="";
      class="kw">return SOME_STR_FUNCTION_FAIL_CODE_2;
     }
   value=resultValue;
   class="kw">return SOME_STR_FUNCTION_SUCCES;
  }
class="type">void OnTick()
  {
   class="type">class="kw">string someStr="";
   ENUM_SOME_STR_FUNCTION_RESULT result=SomeStrFunction(someStr);
   if(result!=SOME_STR_FUNCTION_SUCCES)
     {
      Print("fail, error = "+EnumToString(result));
      class="kw">return;
     }
  }
enum ENUM_SOME_STR_FUNCTION_RESULT
  {
   SOME_STR_FUNCTION_SUCCES,
   SOME_STR_FUNCTION_FAIL_CODE_1,
   SOME_STR_FUNCTION_FAIL_CODE_2
  };
class="kw">struct SomeStrFunctionResult
  {
   ENUM_SOME_STR_FUNCTION_RESULT code;
   class="type">char               value[class="num">255];
  };
SomeStrFunctionResult SomeStrFunction()
  {
   SomeStrFunctionResult result;
   class="type">class="kw">string resultValue="";
   class="type">bool resultOfSomeOperation=SomeOperation();
   if(!resultOfSomeOperation)
     {
      result.code=SOME_STR_FUNCTION_FAIL_CODE_1;

「把错误码和重试逻辑写进函数返回结构」

上面这段 MT5 代码把『字符串处理函数 + 文件打开重试 + 均线参数校验』三种常见坑位拼在了一起,核心思路是:别让函数只返回 bool,而是返回一个带 code 和 value 的结构体,调用方在 OnTick 里先判 code 再取字符串。 文件打开那段用了 0 到 10 次递增退避重试,第 iTry 次睡 iTry*200 毫秒,也就是最多约 11 秒(0+200+400+…+2000=11000ms)后放弃,避免文件被占用时直接崩。 均线初始化里 FAST=10、SLOW=200 是硬约束,若把 MAFastPeriod 调到 ≥200,OnInit 会弹 Alert 并返回 INIT_PARAMETERS_INCORRECT,EA 根本不会加载——外汇和贵金属波动大、杠杆高,参数错配可能让策略在极端行情里反向暴露,实盘前务必在策略测试器里先跑一遍 OnInit 校验。 打开 MT5 把这段代码贴进 EA,故意把 MAFastPeriod 设成 250,你会发现编译器不报错但图表加载失败,这就是结构返回 + 初始化拦截在帮你排雷。

MQL5 / C++
    class="kw">return result;
   }
  class="type">bool resultOfSomeOperation2=SomeOperation2();
  if(!resultOfSomeOperation2)
   {
    result.code=SOME_STR_FUNCTION_FAIL_CODE_2;
    class="kw">return result;
   }
  result.code=SOME_STR_FUNCTION_SUCCES;
  StringToCharArray(resultValue,result.value);
  class="kw">return result;
  }
class="type">void OnTick()
  {
  SomeStrFunctionResult result=SomeStrFunction();
  if(result.code!=SOME_STR_FUNCTION_SUCCES)
   {
    Print("fail, error = "+EnumToString(result.code));
    class="kw">return;
   }
  class="type">class="kw">string someStr=CharArrayToString(result.value);
  }
class="type">class="kw">string fileName="test.txt";
class="type">int fileHandle=INVALID_HANDLE;
for(class="type">int iTry=class="num">0; iTry<=class="num">10; iTry++)
  {
  fileHandle=FileOpen(fileName,FILE_TXT|FILE_READ|FILE_WRITE);
  if(fileHandle!=INVALID_HANDLE)
   {
    class="kw">break;
   }
  Sleep(iTry*class="num">200);
  }
class="kw">input class="type">uint MAFastPeriod = class="num">10;
class="kw">input class="type">uint MASlowPeriod = class="num">200;
class="type">int OnInit()
  {
class=class="str">"cmt">//---
  if(MAFastPeriod>=MASlowPeriod)
   {
    Alert("A period of fast moving average has to be less than a period of slow moving average!");
    class="kw">return INIT_PARAMETERS_INCORRECT;
   }
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }

常见问题

没做错误捕获时单点失败会中断主流程。用返回值判断和 try-catch 类结构兜住异常,让 EA 遇错继续跑。
高频写盘确实占资源。按错误级别分流,只把警告和致命错误落盘,调试信息用内存缓冲就行。
小布可对接你的运行日志,自动标红异常码并提示可能原因,省去人工翻记录。
枚举让调用方一眼看懂失败类型,配合结构体还能顺带带回重试次数,比裸数字好维护。
定义一个含数据字段和错误字段的结构体统一返回,调用处拆包即可,新增信息不改旧调用。