轻松快捷开发 MetaTrader 程序的函数库 (第十九部分) : 函数库消息类(基础篇)
📘

轻松快捷开发 MetaTrader 程序的函数库 (第十九部分) : 函数库消息类(基础篇)

第 1/3 篇

「给函数库加一层消息类封装」

在 MT5 里做自己的 EA / 指标函数库时,最容易被忽略的是运行状态回传。很多自制库只返回 bool 或 int,调用方很难知道失败到底是参数错、句柄失效还是行情未就绪。 这一节落地的做法,是把“消息”单独做成一个类,而不是散落在各函数里用 Print() 硬写。类内部维护一个消息代码与文本映射,外部只需取代码就能在日志或面板里统一呈现。 实际跑过的一版参考实现,在 2019 年 12 月公开示例里被下载 3907 次,说明社区对“可维护的错误反馈”需求很实。外汇与贵金属杠杆高,策略逻辑一旦因静默错误失效,亏损可能快速放大,因此消息类至少应覆盖初始化、订单发送、指标句柄三类返回。 你可以现在打开 MT5 的 MetaEditor,新建一个 CMessage 类,先只写 SetCode() 和 GetText(),把现有库里所有 Print 替换掉,验证日志是否更易读。

把消息文本从类里抽出来

很多人觉得程序里的文本提示只是辅助,无关紧要。但在 MT5 函数库开发中,消息是用户和程序沟通的必要环节,不能随便写死。 目前库里用英文或俄文(跟随终端语言)输出提示的代码,直接散落在各个类的实现位置。这种做法在小规模时够用,但一旦要加第三种语言,或者把俄文翻译成英语之外的语种,就得跨多个文件搜替换,极其麻烦。 另一个被忽略的代价是体积。创建多个同类型对象时,每个对象都内嵌一份相同文本,编译后 EX5 程序会无谓膨胀。重复文本只保留一个副本,由各对象引用,才是合理结构。 现在函数库已有不少文本消息,是时候重排存储方式、支持俄/英之外语言的切换,并留一个能快速加新语言的接口。外汇与贵金属 EA 多语言部署时,这种改动会直接降低维护风险和编译体积。

◍ 用二维数组管好多语言消息

在 MT5 函数库里折腾文本提示,最省事的办法是把消息塞进一个二维数组:第一维是消息编号,第二维放不同语言的文案。实际约定是第二维 0 号位存终端默认语(当前库版本是俄语),1 号位存英语,再往后随便加用户自定义语种。 所有库内预置提示的编号收进一个枚举,调取时直接拿枚举当第一维下标。另一组数组专门接终端和交易服务器抛回的错误码——GetLastError() 的返回值就当作下标去查对应文案,定位很直接。 这类消息处理类至少要能覆盖这些动作:按预置编号以选定语言弹消息;按服务器返回码或 GetLastError 错误码翻成对应语言显示;原样转发外部传入的串;发邮件、推手机通知、走 FTP 传文件、播声音。 扩展语种不用改结构,往二维数组第二维追加一列翻译即可;切换语言也只是改一下第二维的索引值,用户侧成本极低。外汇和贵金属行情波动剧烈,这类提示仅作运行态辅助,不预示任何方向。

「文本消息数据库」

鉴于我们已决定将所有预定义的消息存储在数组当中,且有了指示所需消息在数组中位置的索引列表,因此在 \MQL5\Include\DoEasy\ Datas.mqh 函数库文件中为其创建所需的枚举( 第一个消息索引应等于用户错误列表开始的索引 — 这将避免函数库消息索引与终端的标准消息代码重叠): 如我们所见,枚举含有所有已有的函数库消息,这些消息按创建来源的文本,以及不同来源函数库类的消息,划分到不同的组。 如同我们创建其他函数库类一样,我们会将描述函数库消息的新常量添加到枚举列表中。 要在日记中显示的消息,现在会按相应的常量名称来设置,其文本则应从数组取得。 现在,我们需要创建一个二维数组,以便来自上述枚举中的每条消息都与其在数组中的文本位置(第一维)相对应,而消息本身将位于第二维中 — 零索引包含以用户所选国家语言显示的消息(在当前版本中为俄语),而第一个索引包含英语的消息。 我们编写所有函数库消息的数组,并 添加用来指定使用语言数量的宏替换,此举更加便利 : 在查找函数库消息的枚举和数组时,我们可以看到每条消息与枚举中声明的常量准确对应。 位于数组中的所有消息都应严格对应于位于枚举中的常量,或与 GetLastError() 函数返回的错误消息代码准确对应 。 若要添加另一种翻译语言,只需在每条消息中添加该语言的文本(在英文文本之后)。 如果要将两种预定义语言中的任何一种替换为另一种,则只需编辑现有消息。 例如( 对于上面突出显示的代码 ),为了添加德语版的文本“未知帐户类型”, 要在相应的英文消息 之后 添加翻译文本 : 添加另一种语言时, 应在所有消息数组中添加对应的消息文本 ,否则消息索引将被破坏,从而在尝试显示消息时会导致不可预测的结果。 我们为交易服务器的返回代码编写消息数组: 交易服务器返回的所有代码自 10004 开始。 一些代码被跳过,且没有说明,例如 10005 和 10037。 但它们仍然应该出现在数组中,因为所有代码都严格按升序安置在数组中。 所以,也为那些未用到的代码添加了“未知的交易服务器返回代码”消息。 更重要的是,交易服务器返回码从 10004 开始,而在数组中,它们从索引 0 开始。 这就是为什么在访问数组时,我们要将获得的代码减去交易服务器第一个返回代码的数值。 在此情况下,我们才能准确地按所需代码位于数组中的描述来获取其索引。 例如,对于 10004,我们获得索引值 0(10004 - 10004 = 0),而对于 10007,我们获得索引值 3(10007 - 10004 = 3)。 执行时间错误消息数组: 执行时间错误消息的数组( 图表错误部分): 执行时间错误消息数组( 图形对象错误部分): 执行时间错误消息数组( MarketInfo 错误部分): 执行时间错误消息数组( 访问历史记录错误部分): 执行时间错误消息数组( 全局变量错误部分): 执行时间错误消息的数组( 自定义指标缓冲区和属性错误部分): 执行时间错误消息的数组( “帐户错误”部分): 执行时间错误消息数组( “指标错误”部分): 执行时间错误消息数组( “市场深度错误”部分): 执行时间错误消息数组( 文件操作错误部分): 执行时间错误消息数组( 字符串转换错误部分): 执行时间错误消息数组( “处理数组错误”部分): 执行时间错误消息的数组( “OpenCL 操作错误”部分): 执行时间错误消息数组( WebRequest() 操作错误部分): 执行时间错误消息的数组( “网络(套接字)从左错误”部分): 执行时间错误消息数组(

标准库抛错清单里藏着哪些系统断点

在 MT5 自建 EA 或指标调用系统标准库时,一组 MSG_LIB_SYS_* 常量直接暴露了底层最容易崩的环节。把这些报错码摊开看,能少走很多弯路。 从价格与保证金读取开始,MSG_LIB_SYS_NOT_GET_PRICE 代表实时报价没拿回来,MSG_LIB_SYS_NOT_GET_MARGIN_RATES 是保证金比率获取失败,MSG_LIB_SYS_NOT_GET_DATAS 则更泛地指向数据缺失。若此时硬跑策略,外汇与贵金属的高杠杆环境下可能直接触发异常平仓。 文件与账户对象创建阶段也密集出错:MSG_LIB_SYS_FAILED_CREATE_STORAGE_FOLDER 说明存储目录建不成,MSG_LIB_SYS_FAILED_OPEN_FILE_FOR_WRITE 是写文件句柄没打开,MSG_LIB_SYS_FAILED_CREATE_CURR_ACC_OBJ 则意味着当前账户对象构造失败。这类错误往往不是行情问题,而是终端权限或路径配置不对。 事件与集合列表管理同样有一串特定码:MSG_LIB_SYS_EVENT_ALREADY_IN_LIST 提示事件重复注册,MSG_LIB_SYS_ERROR_NOT_MARKET_LIST 和 MSG_LIB_SYS_ERROR_NOT_HISTORY_LIST 区分了集合类型用错,MSG_LIB_SYS_FAILED_ADD_ORDER_TO_LIST / _DEAL_TO_LIST 则是订单成交记录塞不进容器。 最后一类偏向序列化:MSG_LIB_SYS_FAILED_WRITE_UARRAY_TO_FILE 与 MSG_LIB_SYS_FAILED_LOAD_UARRAY_FROM_FILE 对应 uchar 数组落盘与回读,MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT_FROM_UARRAY 表示从字节数组重建对象结构失败。遇到 MSG_LIB_SYS_NO_TICKS_YET 时,说明Tick还没来,不宜在 OnInit 里急着算逻辑。 开 MT5 把这几行加进调试输出,哪条先弹出来,就先修哪条依赖。

MQL5 / C++
  MSG_LIB_SYS_NOT_GET_PRICE,                                                                     class=class="str">"cmt">// Failed to get current prices. Error: 
  MSG_LIB_SYS_NOT_GET_MARGIN_RATES,                                                                class=class="str">"cmt">// Failed to get margin ratios. Error: 
  MSG_LIB_SYS_NOT_GET_DATAS,                                                                       class=class="str">"cmt">// Failed to get data
  
  MSG_LIB_SYS_FAILED_CREATE_STORAGE_FOLDER,                                                        class=class="str">"cmt">// Failed to create folder for storing files. Error: 
  MSG_LIB_SYS_FAILED_ADD_ACC_OBJ_TO_LIST,                                                          class=class="str">"cmt">// Error. Failed to add current account object to collection list
  MSG_LIB_SYS_FAILED_CREATE_CURR_ACC_OBJ,                                                          class=class="str">"cmt">// Error. Failed to create account object with current account data
  MSG_LIB_SYS_FAILED_OPEN_FILE_FOR_WRITE,                                                          class=class="str">"cmt">// Could not open file for writing
  MSG_LIB_SYS_INPUT_ERROR_NO_SYMBOL,                                                               class=class="str">"cmt">// Input error: no symbol
  MSG_LIB_SYS_FAILED_CREATE_SYM_OBJ,                                                               class=class="str">"cmt">// Failed to create symbol object
  MSG_LIB_SYS_FAILED_ADD_SYM_OBJ,                                                                  class=class="str">"cmt">// Failed to add symbol
  
  MSG_LIB_SYS_NOT_GET_CURR_PRICES,                                                                 class=class="str">"cmt">// Failed to get current prices by event symbol
  MSG_LIB_SYS_EVENT_ALREADY_IN_LIST,                                                               class=class="str">"cmt">// This event is already in the list
  MSG_LIB_SYS_ERROR_ALREADY_CREATED_COUNTER,                                                       class=class="str">"cmt">// Error. Counter with ID already created
  MSG_LIB_SYS_FAILED_CREATE_COUNTER,                                                               class=class="str">"cmt">// Failed to create timer counter
  MSG_LIB_SYS_FAILED_CREATE_TEMP_LIST,                                                             class=class="str">"cmt">// Error creating temporary list
  MSG_LIB_SYS_ERROR_NOT_MARKET_LIST,                                                               class=class="str">"cmt">// Error. This is not a market collection list
  MSG_LIB_SYS_ERROR_NOT_HISTORY_LIST,                                                              class=class="str">"cmt">// Error. This is not a history collection list
  MSG_LIB_SYS_FAILED_ADD_ORDER_TO_LIST,                                                            class=class="str">"cmt">// Could not add order to the list
  MSG_LIB_SYS_FAILED_ADD_DEAL_TO_LIST,                                                             class=class="str">"cmt">// Could not add deal to the list
  MSG_LIB_SYS_FAILED_ADD_CTRL_ORDER_TO_LIST,                                                       class=class="str">"cmt">// Failed to add control order
  MSG_LIB_SYS_FAILED_ADD_CTRL_POSITION_TO_LIST,                                                    class=class="str">"cmt">// Failed to add control position
  MSG_LIB_SYS_FAILED_ADD_MODIFIED_ORD_TO_LIST,                                                     class=class="str">"cmt">// Could not add modified order to the list of modified orders
  
  MSG_LIB_SYS_NO_TICKS_YET,                                                                        class=class="str">"cmt">// No ticks yet
  MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT,                                                            class=class="str">"cmt">// Could not create object structure
  MSG_LIB_SYS_FAILED_WRITE_UARRAY_TO_FILE,                                                         class=class="str">"cmt">// Could not write class="type">uchar array to file
  MSG_LIB_SYS_FAILED_LOAD_UARRAY_FROM_FILE,                                                        class=class="str">"cmt">// Could not load class="type">uchar array from file
  MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT_FROM_UARRAY,                                                class=class="str">"cmt">// Could not create object structure from class="type">uchar array
  MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,                                                    class=class="str">"cmt">// Failed to save object structure to class="type">uchar array, error
  MSG_LIB_SYS_ERROR_INDEX                                                                          class=class="str">"cmt">// Error. "index" value should be within class="num">0 - class="num">3

◍ 交易库报错枚举的落地写法

在自建 MQL5 交易封装库时,把系统级异常做成枚举常量是最省事的做法。下面这段定义覆盖了从符号数组为空到改单失败的全链路错误,直接贴进头文件就能用。 这些常量名本身即文档:例如 MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_ASK 对应取不到卖价,MSG_LIB_SYS_ERROR_POSITION_ALREADY_CLOSED 说明仓位已被平掉。实盘里若日志频繁刷出 MSG_LIB_SYS_ERROR_FAILED_OPEN_BUY,先查交易环境而非策略逻辑——经纪商拒单或点差突变都可能触发。 外汇与贵金属属高杠杆品种,这类底层报错若被吞掉,会让 EA 在连亏时静默失控。建库时建议每个常量都接一个 Print 或 SendNotification,开 MT5 跑一遍空字符串符号就能验证首条报错是否生效。

MQL5 / C++
  MSG_LIB_SYS_ERROR_EMPTY_STRING,                    class=class="str">"cmt">// Error. Predefined symbols class="type">class="kw">string empty, to be used
  MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY,         class=class="str">"cmt">// Failed to prepare array of used symbols. Error 
  MSG_LIB_SYS_INVALID_ORDER_TYPE,                     class=class="str">"cmt">// Invalid order type:
  
  MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_ASK,             class=class="str">"cmt">// Failed to get Ask price. Error
  MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_BID,             class=class="str">"cmt">// Failed to get Bid price. Error
  MSG_LIB_SYS_ERROR_FAILED_OPEN_BUY,                  class=class="str">"cmt">// Failed to open Buy position. Error
  MSG_LIB_SYS_ERROR_FAILED_PLACE_BUYLIMIT,            class=class="str">"cmt">// Failed to set BuyLimit order. Error
  MSG_LIB_SYS_ERROR_FAILED_PLACE_BUYSTOP,             class=class="str">"cmt">// Failed to set BuyStop order. Error
  MSG_LIB_SYS_ERROR_FAILED_PLACE_BUYSTOPLIMIT,        class=class="str">"cmt">// Failed to set BuyStopLimit order. Error
  MSG_LIB_SYS_ERROR_FAILED_OPEN_SELL,                 class=class="str">"cmt">// Failed to open Sell position. Error
  MSG_LIB_SYS_ERROR_FAILED_PLACE_SELLLIMIT,           class=class="str">"cmt">// Failed to set SellLimit order. Error
  MSG_LIB_SYS_ERROR_FAILED_PLACE_SELLSTOP,            class=class="str">"cmt">// Failed to set SellStop order. Error
  MSG_LIB_SYS_ERROR_FAILED_PLACE_SELLSTOPLIMIT,       class=class="str">"cmt">// Failed to set SellStopLimit order. Error
  MSG_LIB_SYS_ERROR_FAILED_SELECT_POS,                class=class="str">"cmt">// Failed to select position. Error
  MSG_LIB_SYS_ERROR_POSITION_ALREADY_CLOSED,          class=class="str">"cmt">// Position already closed
  MSG_LIB_SYS_ERROR_NOT_POSITION,                     class=class="str">"cmt">// Error. Not a position:
  MSG_LIB_SYS_ERROR_FAILED_CLOSE_POS,                 class=class="str">"cmt">// Failed to closed position. Error 
  MSG_LIB_SYS_ERROR_FAILED_SELECT_POS_BY,             class=class="str">"cmt">// Failed to select opposite position. Error
  MSG_LIB_SYS_ERROR_POSITION_BY_ALREADY_CLOSED,       class=class="str">"cmt">// Opposite position already closed
  MSG_LIB_SYS_ERROR_NOT_POSITION_BY,                  class=class="str">"cmt">// Error. Opposite position is not a position:
  MSG_LIB_SYS_ERROR_FAILED_CLOSE_POS_BY,              class=class="str">"cmt">// Failed to close position by opposite one. Error
  MSG_LIB_SYS_ERROR_FAILED_SELECT_ORD,                class=class="str">"cmt">// Failed to select order. Error 
  MSG_LIB_SYS_ERROR_ORDER_ALREADY_DELETED,            class=class="str">"cmt">// Order already deleted
  MSG_LIB_SYS_ERROR_NOT_ORDER,                        class=class="str">"cmt">// Error. Not an order:
  MSG_LIB_SYS_ERROR_FAILED_DELETE_ORD,                class=class="str">"cmt">// Failed to class="kw">delete order. Error
  MSG_LIB_SYS_ERROR_SELECT_CLOSED_POS_TO_MODIFY,      class=class="str">"cmt">// Error. Closed position selected for modification:
  MSG_LIB_SYS_ERROR_FAILED_MODIFY_POS,                class=class="str">"cmt">// Failed to modify position. Error 
  MSG_LIB_SYS_ERROR_SELECT_DELETED_ORD_TO_MODIFY,     class=class="str">"cmt">// Error. Removed order selected for modification:
  MSG_LIB_SYS_ERROR_FAILED_MODIFY_ORD,                class=class="str">"cmt">// Failed to modify order. Error
  MSG_LIB_SYS_ERROR_CODE_OUT_OF_RANGE,                class=class="str">"cmt">// Return code out of range of error codes

常见问题

建一个独立的消息文本数据库,用二维数组按语言和编号存字符串,类里只调编号取文本,改文案不用动逻辑。
第一维放语言、第二维放消息ID,取用时先定语言再取ID;加个越界检查,返回空串或默认语言避免崩。
可以,小布能扫描你的策略代码消息类结构,标出未覆盖的报错枚举和空文本项,省去人工核对。
多是订单发送失败、账户禁止交易、品种不可选等底层拒绝点,对应固定错误码,交易前应先判这些再动手。
建枚举落地更稳,把系统错误码映射成自己库里的具名常量,后续抛错和日志都可读、易维护。