轻松快捷开发 MetaTrader 程序的函数库(第十二部分)。(基础篇)
📘

轻松快捷开发 MetaTrader 程序的函数库(第十二部分)。(基础篇)

第 1/3 篇

◍ 从零搭一个 MT5 函数库骨架

在 MT5 里做重复策略开发时,把通用功能抽成函数库能省掉大量复制粘贴。官方示例工程从 2019-09-02 发布至今累计浏览 3378 次,说明这套基础封装对实盘脚本编写者一直有参考价值。 新建一个 Include 目录下的 .mqh 文件,用 #include 在 EA 或指标里引用即可。库文件不编译成独立 exe,只在编译主程序时静态链入,因此改动库后需重新编译调用方。 外汇与贵金属杠杆高、滑点随机,任何封装都只是工程便利,不预示信号胜率。先开 MT5 在 MQL5 编辑器建个空 .mqh,写个返回均线句柄的小函数,比直接读教程更快建立直觉。

先把账户对象和集合搭起来

在写交易类之前,得先有账户和品种的数据底座。本文先把账户对象(Account)立起来:它不是一个静态快照,而是能在交易过程中感知变化的活对象。 考虑到同一终端可能切换或管理多个账户,我们把单个账户对象放进一个账户对象集合里统一管理。这样后续扩展多账户监控时,不用改底层结构。 账户对象内部要挂上事件跟踪:杠杆、余额、盈亏、净值、账户限制这几项一旦变动,对象能自己捕获。对 MT5 实盘或策略测试来说,这意味着你能在 OnTrade 之外,用对象状态差分拿到变化明细,而不是每次去 AccountInfoInteger 轮询。 外汇和贵金属杠杆可调、净值波动快,这类账户事件的高频变化本身就是风险信号,用对象封装后更容易做阈值告警。

「把账户拆成一个可比较的对象」

在 MT5 里做多账户横向比对时,最省事的做法是把每个交易账户封装成独立对象,而不是靠全局函数随用随取。账户对象不依赖后代类去设定状态,它没有 status 属性,构造函数也不接收参数,只在初始化时把 AccountInfoInteger / Double / String 三类接口拿到的字段一次性填进去。 账户属性覆盖整数、实数、字符串三套枚举。整数侧包含 ACCOUNT_LOGIN(账号,long)、ACCOUNT_LEVERAGE(杠杆,long)、ACCOUNT_LIMIT_ORDERS(最大活动挂单,int)、ACCOUNT_TRADE_ALLOWED(服务器侧交易许可,bool)等 9 项;实数侧有 ACCOUNT_BALANCE、ACCOUNT_EQUITY、ACCOUNT_MARGIN_LEVEL(保证金百分比)等 14 项;字符串侧含 ACCOUNT_NAME、ACCOUNT_SERVER、ACCOUNT_CURRENCY、ACCOUNT_COMPANY 共 4 项。 MQL4 没有 ENUM_ACCOUNT_MARGIN_MODE,要做跨平台编译就得手补。在 ToMQL4.mqh 末尾补上该枚举后,条件编译里给 MQL4 的“保证金计算模式”返回 ACCOUNT_MARGIN_MODE_RETAIL_HEDGING,账户货币小数位直接写死 2 位,MQL5 则取真实属性。 判断两个账户对象是否指向同一账户,只比三个不变字段:公司名、账号、客户名。任一不等就返回 false 视为不同账户,三次全过才返回 true。这样在账户集合里做查重和排序时,不会把同经纪商的不同登录搞混。 外汇与贵金属杠杆账户存在强平与滑点风险,对象里的 ACCOUNT_MARGIN_SO_SO(爆仓线)和 ACCOUNT_MARGIN_LEVEL 仅作监控参考,不预示账户安全边界。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Data for working with accounts                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Account integer properties                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_INTEGER
  {
   ACCOUNT_PROP_LOGIN,                                                       class=class="str">"cmt">// Account number
   ACCOUNT_PROP_TRADE_MODE,                                                  class=class="str">"cmt">// Trading account type
   ACCOUNT_PROP_LEVERAGE,                                                    class=class="str">"cmt">// Provided leverage
   ACCOUNT_PROP_LIMIT_ORDERS,                                                class=class="str">"cmt">// Maximum allowed number of active pending orders
   ACCOUNT_PROP_MARGIN_SO_MODE,                                              class=class="str">"cmt">// Mode of setting the minimum available margin level
   ACCOUNT_PROP_TRADE_ALLOWED,                                               class=class="str">"cmt">// Permission to trade for the current account from the server side
   ACCOUNT_PROP_TRADE_EXPERT,                                                class=class="str">"cmt">// Permission to trade for an EA from the server side

◍ 账户双精度属性的枚举划分

在 MQL5 里,账户的实数类属性被单独收进 ENUM_ACCOUNT_PROP_DOUBLE 枚举,起点紧接整数属性的总数 ACCOUNT_PROP_INTEGER_TOTAL(定义为 9),也就是从下标 9 开始排。这样设计让整数与双精度属性共用一套索引空间,排序和遍历时不用分两套数组。 枚举里 ACCOUNT_PROP_BALANCE 对应下标 9,往后依次是 CREDIT、PROFIT、EQUITY、MARGIN、MARGIN_FREE、MARGIN_LEVEL(以百分比表示)、MARGIN_SO_CALL(触发追加保证金的阈值)、MARGIN_SO_SO(触发强制平仓的阈值)等。外汇和贵金属杠杆高,MARGIN_LEVEL 掉到券商设定的 SO 水平附近时,账户可能被迫砍仓,开 MT5 后在账户观测窗口能直接核对这些数值。 ACCOUNT_PROP_MARGIN_INITIAL 与 ACCOUNT_PROP_MARGIN_MAINTENANCE 分别描述挂单占用与持仓最低维护保证金,ASSETS 和 LIABILITIES 则给出账户资产负债快照。写 EA 时若想动态读取浮亏下的权益曲线,直接按枚举名调 AccountInfoDouble 即可,不用硬背下标。

MQL5 / C++
enum ENUM_ACCOUNT_PROP_DOUBLE
  {
  ACCOUNT_PROP_BALANCE = ACCOUNT_PROP_INTEGER_TOTAL,       class=class="str">"cmt">// Account balance in a deposit currency
  ACCOUNT_PROP_CREDIT,                                     class=class="str">"cmt">// Credit in a deposit currency
  ACCOUNT_PROP_PROFIT,                                     class=class="str">"cmt">// Current profit on an account in the account currency
  ACCOUNT_PROP_EQUITY,                                     class=class="str">"cmt">// Equity on an account in the deposit currency
  ACCOUNT_PROP_MARGIN,                                     class=class="str">"cmt">// Reserved margin on an account in a deposit currency
  ACCOUNT_PROP_MARGIN_FREE,                                class=class="str">"cmt">// Free funds available for opening a position in a deposit currency
  ACCOUNT_PROP_MARGIN_LEVEL,                               class=class="str">"cmt">// Margin level on an account in %
  ACCOUNT_PROP_MARGIN_SO_CALL,                             class=class="str">"cmt">// Margin level, at which a deposit to an account is required(Margin Call)
  ACCOUNT_PROP_MARGIN_SO_SO,                               class=class="str">"cmt">// Margin level, at which the most loss-making position is closed(Stop Out)
  ACCOUNT_PROP_MARGIN_INITIAL,                             class=class="str">"cmt">// Funds reserved on an account to ensure a guarantee amount for all pending orders
  ACCOUNT_PROP_MARGIN_MAINTENANCE,                         class=class="str">"cmt">// Funds reserved on an account to ensure a minimum amount for all open positions
  ACCOUNT_PROP_ASSETS,                                     class=class="str">"cmt">// Current assets on an account
  ACCOUNT_PROP_LIABILITIES                                class=class="str">"cmt">// Current liabilities on an account
  };

账户属性枚举与排序偏移的底层定义

在 MT5 的账户信息封装里,字符串类属性被安排在所有整型和双精度型属性之后。上面这段枚举把 ACCOUNT_PROP_NAME 的起始值显式写成 (ACCOUNT_PROP_INTEGER_TOTAL+ACCOUNT_PROP_DOUBLE_TOTAL),而这两个宏分别被定义为 14 和 4,所以字符串属性实际从索引 18 开始排,后面依次是 SERVER(19)、CURRENCY(20)、COMPANY(21)。 宏 FIRST_ACC_STR_PROP 的计算式为整型可用数(14-0)加双精度可用数(14-0),结果为 28,说明若把跳过项也算进来,字符串段在扁平数组里的逻辑起点是 28。实际取字符串属性时,用 ACCOUNT_PROP_NAME 等枚举值直接当索引即可,不用手算。 排序枚举 ENUM_SORT_ACCOUNT_MODE 从 0 到 4 映射了登录号、账户类型、杠杆、挂单上限、强平模式五种依据。写多账户扫描器时,若想按杠杆倒序,把模式设为 2 再对返回的账户数组做降序即可,外汇和贵金属品种受杠杆与保证金规则影响大,回测前务必确认券商实际参数。

MQL5 / C++
  ACCOUNT_PROP_COMMISSION_BLOCKED                                                                 class=class="str">"cmt">// Current sum of blocked commissions on an account
  };
class="macro">#define ACCOUNT_PROP_DOUBLE_TOTAL(class="num">14)                                                          class=class="str">"cmt">// Total number of account&class="macro">#x27;s real properties
class="macro">#define ACCOUNT_PROP_DOUBLE_SKIP(class="num">0)                                                           class=class="str">"cmt">// Number of account&class="macro">#x27;s real properties not used in sorting
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Account class="type">class="kw">string properties                                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_STRING
  {
   ACCOUNT_PROP_NAME = (ACCOUNT_PROP_INTEGER_TOTAL+ACCOUNT_PROP_DOUBLE_TOTAL), class=class="str">"cmt">// Client name
   ACCOUNT_PROP_SERVER,                                                                          class=class="str">"cmt">// Trade server name
   ACCOUNT_PROP_CURRENCY,                                                                        class=class="str">"cmt">// Deposit currency
   ACCOUNT_PROP_COMPANY                                                                          class=class="str">"cmt">// Name of a company serving an account
  };
class="macro">#define ACCOUNT_PROP_STRING_TOTAL(class="num">4)                                                           class=class="str">"cmt">// Total number of account&class="macro">#x27;s class="type">class="kw">string properties
class="macro">#define ACCOUNT_PROP_STRING_SKIP(class="num">0)                                                           class=class="str">"cmt">// Number of account class="type">class="kw">string properties not used in sorting
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Possible account sorting criteria                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define FIRST_ACC_DBL_PROP(ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP)
class="macro">#define FIRST_ACC_STR_PROP(ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_ACCOUNT_MODE
  {
   SORT_BY_ACCOUNT_LOGIN           =  class="num">0,                                                          class=class="str">"cmt">// Sort by account number
   SORT_BY_ACCOUNT_TRADE_MODE      =  class="num">1,                                                          class=class="str">"cmt">// Sort by trading account type
   SORT_BY_ACCOUNT_LEVERAGE        =  class="num">2,                                                          class=class="str">"cmt">// Sort by leverage
   SORT_BY_ACCOUNT_LIMIT_ORDERS    =  class="num">3,                                                          class=class="str">"cmt">// Sort by maximum acceptable number of existing pending orders
   SORT_BY_ACCOUNT_MARGIN_SO_MODE  =  class="num">4                                                           class=class="str">"cmt">// Sort by mode for setting the minimum acceptable margin level
  };

「账户排序枚举的尾部字段」

上面这组枚举值接在账户排序常量序列的后半段,从 SORT_BY_ACCOUNT_TRADE_ALLOWED 到 SORT_BY_ACCOUNT_CURRENCY,编号 5 至 8 是整型属性,后面整型与字符串属性分别挂在 FIRST_ACC_DBL_PROP 与 FIRST_ACC_STR_PROP 基址上递增。 其中 FIRST_ACC_DBL_PROP+6 对应 SORT_BY_ACCOUNT_MARGIN_LEVEL,即按账户保证金水平百分比排序;FIRST_ACC_DBL_PROP+7 和 +8 分别是 Margin Call 与 Stop Out 触发线,做多账户扫描时这几个值最常用。 字符串类从 FIRST_ACC_STR_PROP 起算:+0 为客户名,+1 为交易服务器名,+2 为入金货币。写 CTrade 或自定义账户面板时,直接传这些枚举给 AccountsInfoInteger 的排序参数即可,MT5 里编译能立刻验证。 外汇与贵金属杠杆高,账户保证金水平掉到 Stop Out 附近时仓位会被强平,用上述排序做监控脚本前先在模拟盘跑通逻辑。

MQL5 / C++
  SORT_BY_ACCOUNT_TRADE_ALLOWED        =  class="num">5,                class=class="str">"cmt">// Sort by permission to trade for the current account
  SORT_BY_ACCOUNT_TRADE_EXPERT          =  class="num">6,                class=class="str">"cmt">// Sort by permission to trade for an EA
  SORT_BY_ACCOUNT_MARGIN_MODE           =  class="num">7,                class=class="str">"cmt">// Sort by margin calculation mode
  SORT_BY_ACCOUNT_CURRENCY_DIGITS       =  class="num">8,                class=class="str">"cmt">// Sort by number of digits for an account currency
  
  SORT_BY_ACCOUNT_BALANCE              =  FIRST_ACC_DBL_PROP,    class=class="str">"cmt">// Sort by an account balance in the deposit currency
  SORT_BY_ACCOUNT_CREDIT               =  FIRST_ACC_DBL_PROP+class="num">1,  class=class="str">"cmt">// Sort by credit in a deposit currency
  SORT_BY_ACCOUNT_PROFIT               =  FIRST_ACC_DBL_PROP+class="num">2,  class=class="str">"cmt">// Sort by the current profit on an account in the deposit currency
  SORT_BY_ACCOUNT_EQUITY               =  FIRST_ACC_DBL_PROP+class="num">3,  class=class="str">"cmt">// Sort by an account equity in the deposit currency
  SORT_BY_ACCOUNT_MARGIN               =  FIRST_ACC_DBL_PROP+class="num">4,  class=class="str">"cmt">// Served by an account reserved margin in the deposit currency
  SORT_BY_ACCOUNT_MARGIN_FREE          =  FIRST_ACC_DBL_PROP+class="num">5,  class=class="str">"cmt">// Sort by account free funds available for opening a position in the deposit currency
  SORT_BY_ACCOUNT_MARGIN_LEVEL         =  FIRST_ACC_DBL_PROP+class="num">6,  class=class="str">"cmt">// Sort by account margin level in %
  SORT_BY_ACCOUNT_MARGIN_SO_CALL       =  FIRST_ACC_DBL_PROP+class="num">7,  class=class="str">"cmt">// Sort by margin level requiring depositing funds to an account(Margin Call)
  SORT_BY_ACCOUNT_MARGIN_SO_SO         =  FIRST_ACC_DBL_PROP+class="num">8,  class=class="str">"cmt">// Sort by margin level, at which the most loss-making position is closed(Stop Out)
  SORT_BY_ACCOUNT_MARGIN_INITIAL       =  FIRST_ACC_DBL_PROP+class="num">9,  class=class="str">"cmt">// Sort by funds reserved on an account to ensure a guarantee amount for all pending orders 
  SORT_BY_ACCOUNT_MARGIN_MAINTENANCE   =  FIRST_ACC_DBL_PROP+class="num">10, class=class="str">"cmt">// Sort by funds reserved on an account to ensure a minimum amount for all open positions
  SORT_BY_ACCOUNT_ASSETS               =  FIRST_ACC_DBL_PROP+class="num">11, class=class="str">"cmt">// Sort by the amount of the current assets on an account
  SORT_BY_ACCOUNT_LIABILITIES          =  FIRST_ACC_DBL_PROP+class="num">12, class=class="str">"cmt">// Sort by the current liabilities on an account
  SORT_BY_ACCOUNT_COMMISSION_BLOCKED   =  FIRST_ACC_DBL_PROP+class="num">13, class=class="str">"cmt">// Sort by the current amount of blocked commissions on an account
  SORT_BY_ACCOUNT_NAME                 =  FIRST_ACC_STR_PROP,     class=class="str">"cmt">// Sort by a client name
  SORT_BY_ACCOUNT_SERVER               =  FIRST_ACC_STR_PROP+class="num">1,  class=class="str">"cmt">// Sort by a trade server name
  SORT_BY_ACCOUNT_CURRENCY             =  FIRST_ACC_STR_PROP+class="num">2,  class=class="str">"cmt">// Sort by a deposit currency

常见问题

先把账户拆成对象,用枚举划分双精度属性(如余额、净值),再定义排序偏移,骨架就能直接套用。
分开能避免比较时类型错乱,混写会导致排序偏移算错,回测时账户顺序可能完全乱掉。
可以,小布能扫描你的代码结构,对照常见账户属性清单标出缺失项,并提示排序偏移是否合理。
尾部建议留保留偏移位,别写死具体数;新手常把尾部当普通字段填,后续扩展直接冲突。
先写账户对象再写集合,对象字段定清楚后,集合的增删比和排序才不会返工。