轻松快捷开发 MetaTrader 程序的函数库(第十五部分):品种对象集合·综合运用
📘

轻松快捷开发 MetaTrader 程序的函数库(第十五部分):品种对象集合·综合运用

第 3/3 篇
本章目录
  1. 枚举排序字段与两个采集定时器的参数设定
  2. 集合ID与符号背景色的底层宏定义
  3. 定时器节拍与采集列表的宏定义拆解
  4. 符号枚举与集合 ID 的底层约定
  5. 枚举品种状态后落地到 CSymbol 基类
  6. 保证金比率与品种属性的结构体落点
  7. 把符号属性映射成可调用接口
  8. CSymbol 类的挂单保证金与存在性接口
  9. 把品种塞进观察窗口与抓交易时段
  10. 把交易时段拆成时分秒的封装思路
  11. 封装报价与状态查询的轻量接口
  12. 报价与真实成交量的封装取法
  13. 从期权行权价到合约面值的属性读取
  14. 从品种属性接口抠出交易成本与限额
  15. 多单与挂单的初始及维持保证金读取
  16. 空头挂单与维护保证金的接口取法
  17. 读取交易时段盘面属性的几个接口
  18. 构造函数里的品种就绪检查
  19. 把整型品种属性一次性灌进数组
  20. 整批抓取交易品种的长整型属性
  21. 把品种属性一次性落进缓存数组
  22. 把品种交易参数一次性灌进结构体
  23. 把保证金初始率塞进符号属性数组
  24. 把保证金与字符串属性塞进符号缓存
  25. 保证金费率与品种存在性的代码落地
  26. 用 SymbolInfoMarginRate 抠出各类型单的保证金率
  27. 挂单保证金率的取值逻辑
  28. 空头与多头保证金系数的取值分支
  29. 挂单初始保证金系数的读取分支
  30. 挂单保证金系数的逐属性文本拼装
  31. 卖单挂单的维护保证金系数怎么读
  32. 品种存在性与报价会话的底层取数
  33. 抓取报价与交易时段的起止秒数
  34. 把交易时段拆成秒分时三件套
  35. 按品种状态吐出可读标签
  36. 状态映射与行情刷新的底层写法
  37. 抓取品种整数属性与背景色
  38. 把品种交易参数一次性塞进数组
  39. 把交易时段字段一次性塞进缓存数组
  40. 把保证金率塞进符号属性数组
  41. 刷新报价时顺手把属性缓存全更新了
  42. 行情快照的刷新与字段落盘
  43. 报价极值与Tick价值的批量落库
  44. 把品种拆成19个衍生类的取舍
  45. 在 CSymbolFX 里判断品种属性是否被支持
  46. MQL5 与 MQL4 下符号属性枚举的差异分支
  47. 用 SupportProperty 筛掉不支持的交易品种属性
  48. 自定义品种类的属性接管逻辑
  49. 自定义品种的属性门禁与日志输出
  50. 用 CSelect 给订单和事件做筛选排序
  51. 事件、账户与品种的属性极值筛选接口
  52. 按品种属性做条件筛选的实现细节
  53. 按属性筛符号与捞最大值的实现细节
  54. 在品种池里捞最大最小值的写法
  55. 遍历列表取最小属性下标
  56. 品种集合类的内部运转
  57. 品种集合类的头文件引用结构
  58. 用集合类把全市场品种收进一张表
  59. 品种集合类的头文件引用与私有成员
  60. 按品种属性给符号归类
  61. 品种集合类的对外接口拆解
  62. 多品种容器的构造与刷新逻辑
  63. 按品种类型分流建实例
  64. 按品种类别分流建对象与查重
  65. 按模式筛选交易标的的三种写法
  66. 从列表名反推符号采集模式
  67. 按预设清单识别货币与金属品种
  68. 用预置数组快速判定品种类别
  69. 按交易计算模式反推品种分类
  70. 用计算模式反推品种状态类别
  71. EA 内核里的集合与事件开关
  72. 订单与账户对象的取数接口
  73. 交易引擎的状态读取接口
  74. 引擎构造里的计数器与毫秒定时器
  75. 引擎计时器里的历史与报价刷新分流
  76. 第二计时器驱动的全品种刷新逻辑
  77. 预定义品种串的脏字符清洗
  78. 多品种列表的清洗与拆分逻辑
  79. 用EA跑通品种集合的多模式扫描
  80. 多品种清单怎么喂给引擎
  81. 初始化时把交易类与品种池一次性配好
  82. 多币种批量初始化与持仓成本扫描
  83. 把库存费与点差极值一次性打出来
  84. 从日志里读券商的隐性限制
  85. 画得少,看得清

「枚举排序字段与两个采集定时器的参数设定」

在 MT5 的行情目录结构里,品种集合可按多种底层属性排序。下面这组枚举覆盖了从衍生品标的、币种属性到 ISIN 编码、网页路径的维度,实盘写自选池扫描器时直接引用即可。 SORT_BY_SYMBOL_BASIS, // 按衍生品的底层资产排序 SORT_BY_SYMBOL_CURRENCY_BASE, // 按品种的基础货币排序 SORT_BY_SYMBOL_CURRENCY_PROFIT, // 按盈利结算货币排序 SORT_BY_SYMBOL_CURRENCY_MARGIN, // 按保证金货币排序 SORT_BY_SYMBOL_BANK, // 按当前报价的数据源排序 SORT_BY_SYMBOL_DESCRIPTION, // 按品种描述字符串排序 SORT_BY_SYMBOL_FORMULA, // 按自定义品种定价公式排序 SORT_BY_SYMBOL_ISIN, // 按 ISIN 系统中的品种名排序 SORT_BY_SYMBOL_PAGE, // 按品种信息网页地址排序 SORT_BY_SYMBOL_PATH // 按品种树中的路径排序 }; 品种采集拆成了两个定时器。定时器1专扫市场报价窗口的可见品种,停顿设 100 毫秒、计数器步长 16、计数器 ID 为 3;定时器2监听报价列表事件,停顿放大到 300 毫秒、步长同为 16、ID 为 4。 外汇与贵金属波动剧烈、杠杆风险高,这类扫描节奏参数请先在模拟盘验证,再上实盘。把停顿从 100 调到 50 可能让 CPU 占用翻倍,却更快捕捉报价变动,需按机器性能权衡。

MQL5 / C++
  SORT_BY_SYMBOL_BASIS,                                                                     class=class="str">"cmt">// Sort by an underlying asset of a derivative
  SORT_BY_SYMBOL_CURRENCY_BASE,                                                             class=class="str">"cmt">// Sort by a base currency of a symbol
  SORT_BY_SYMBOL_CURRENCY_PROFIT,                                                           class=class="str">"cmt">// Sort by a profit currency
  SORT_BY_SYMBOL_CURRENCY_MARGIN,                                                           class=class="str">"cmt">// Sort by a margin currency
  SORT_BY_SYMBOL_BANK,                                                                      class=class="str">"cmt">// Sort by a feeder of the current quote
  SORT_BY_SYMBOL_DESCRIPTION,                                                               class=class="str">"cmt">// Sort by a symbol class="type">class="kw">string description
  SORT_BY_SYMBOL_FORMULA,                                                                   class=class="str">"cmt">// Sort by the formula used for custom symbol pricing
  SORT_BY_SYMBOL_ISIN,                                                                      class=class="str">"cmt">// Sort by the name of a symbol in the ISIN system
  SORT_BY_SYMBOL_PAGE,                                                                      class=class="str">"cmt">// Sort by an address of the web page containing symbol information
  SORT_BY_SYMBOL_PATH                                                                       class=class="str">"cmt">// Sort by a path in the symbol tree
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Symbol collection timer class="num">1 parameters
class="macro">#define COLLECTION_SYM_PAUSE1(class="num">100)                                                  class=class="str">"cmt">// Pause of the symbol collection timer class="num">1 in milliseconds(for scanning market watch symbols)
class="macro">#define COLLECTION_SYM_COUNTER_STEP1(class="num">16)                                                  class=class="str">"cmt">// Increment of the symbol timer class="num">1 counter
class="macro">#define COLLECTION_SYM_COUNTER_ID1(class="num">3)                                                   class=class="str">"cmt">// Symbol timer class="num">1 counter ID
class=class="str">"cmt">//--- Symbol collection timer class="num">2 parameters
class="macro">#define COLLECTION_SYM_PAUSE2(class="num">300)                                                  class=class="str">"cmt">// Pause of the symbol collection timer class="num">2 in milliseconds(for events of the market watch symbol list)
class="macro">#define COLLECTION_SYM_COUNTER_STEP2(class="num">16)                                                  class=class="str">"cmt">// Increment of the symbol timer class="num">2 counter
class="macro">#define COLLECTION_SYM_COUNTER_ID2(class="num">4)                                                   class=class="str">"cmt">// Symbol timer class="num">2 counter ID
class=class="str">"cmt">//--- Collection list IDs

◍ 集合ID与符号背景色的底层宏定义

在 MT5 的行情监控类库里,集合对象用一组固定偏移的十六进制 ID 做区分。基址 0x7778 往后加 1 到 5,分别映射到历史、市场、事件、账户和符号集合;其中 COLLECTION_SYMBOLS_ID 即 0x7778+5,专门挂 Market Watch 里的品种容器。 符号背景色属性 SYMBOL_PROP_BACKGROUND_COLOR 的取值有个坑:MQL5 下若 GetProperty 返回值大于 clrWhite(0xFFFFFF),说明该品种没单独设背景色,代码会回显「(Not set)」;否则走 ColorToString 转成可读字符串。这一判断只在 __MQL5__ 编译分支生效,MQL4 直接报属性不支持。 调试宏也值得抄走:DFUN_ERR_LINE 把函数名、终端语言(俄语拼 Page、其他拼 Line)和 __LINE__ 拼成报错前缀,比裸 Print 更方便定位。END_TIME 设成 D'31.12.3000 23:59:59',是账户历史请求常用的远期末端,避免传参空值。 开 MT5 新建一个 include 头文件,把下面宏贴进去编译,能直接验证符号背景色分支是否如预期返回「(Not set)」。

MQL5 / C++
class="macro">#define COLLECTION_HISTORY_ID(0x7778+class="num">1)        class=class="str">"cmt">// Historical collection list ID
class="macro">#define COLLECTION_MARKET_ID(0x7778+class="num">2)        class=class="str">"cmt">// Market collection list ID
class="macro">#define COLLECTION_EVENTS_ID(0x7778+class="num">3)        class=class="str">"cmt">// Event collection list ID
class="macro">#define COLLECTION_ACCOUNT_ID(0x7778+class="num">4)        class=class="str">"cmt">// Account collection list ID
class="macro">#define COLLECTION_SYMBOLS_ID(0x7778+class="num">5)        class=class="str">"cmt">// Symbol collection list ID

class="kw">property==SYMBOL_PROP_BACKGROUND_COLOR ? TextByLanguage("Цвет фона символа в Market Watch","Background class="type">class="kw">color of symbol in Market Watch")+
  (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
  class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)>clrWhite ? TextByLanguage(": (Отсутствует)",": (Not set)") : ": "+::ColorToString((class="type">class="kw">color)this.GetProperty(class="kw">property),true))
  class="macro">#else TextByLanguage(": Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
  ) :
class=class="str">"cmt">//--- Symbol parameters
class="macro">#define CLR_DEFAULT(0xFF000000)      class=class="str">"cmt">// Default class="type">class="kw">color
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Macro substitutions                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Describe the function with the error line number
class="macro">#define DFUN_ERR_LINE(__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(class="type">class="kw">string)__LINE__+": ")
class="macro">#define DFUN(__FUNCTION__+": ")              class=class="str">"cmt">// "Function description"
class="macro">#define COUNTRY_LANG("Russian")                     class=class="str">"cmt">// Country language
class="macro">#define END_TIME(D&class="macro">#x27;class="num">31.12.class="num">3000 class="num">23:class="num">59:class="num">59&class="macro">#x27;)  class=class="str">"cmt">// End date for account history data requests

定时器节拍与采集列表的宏定义拆解

在 MT5 的 EA 或指标里做多源数据采集,第一道坎是定时器节拍冲突。下面这组宏把底层库的计时粒度钉死在 16 毫秒,相当于给所有采集任务一个统一的时间基准。 订单与成交的采集暂停设为 250 毫秒,计数器步长 16、ID 为 1;账户信息采集暂停拉长到 1000 毫秒(即 1 秒),ID 为 2。行情窗口符号分两条线:定时器 1 扫盯盘品种,暂停 100 毫秒、ID 3;定时器 2 跟盯盘品种列表事件,暂停 300 毫秒、ID 4。 历史、市场、事件三类采集列表的 ID 分别用 0x7778+1、+2、+3 偏移出来,避开与其他库的内存标记撞车。外汇与贵金属波动剧烈,这类高频采集会持续吃 CPU,实盘加载前建议在策略测试器里先跑一轮看负载。 别把 16 毫秒当绝对精度 Windows 定时器受系统粒度限制,实际触发可能漂移几毫秒;若你后续改 TIMER_FREQUENCY 低于 16,MT5 日志里可能开始刷 timer 警告,且贵金属跳空时段丢帧概率会上升。

MQL5 / C++
class="macro">#define TIMER_FREQUENCY(class="num">16)                class=class="str">"cmt">// Minimal frequency of the library timer in milliseconds
class=class="str">"cmt">//--- Parameters of the orders and deals collection timer
class="macro">#define COLLECTION_ORD_PAUSE(class="num">250)               class=class="str">"cmt">// Orders and deals collection timer pause in milliseconds
class="macro">#define COLLECTION_ORD_COUNTER_STEP(class="num">16)                class=class="str">"cmt">// Increment of the orders and deals collection timer counter
class="macro">#define COLLECTION_ORD_COUNTER_ID(class="num">1)                 class=class="str">"cmt">// Orders and deals collection timer counter ID
class=class="str">"cmt">//--- Parameters of the account collection timer
class="macro">#define COLLECTION_ACC_PAUSE(class="num">1000)              class=class="str">"cmt">// Account collection timer pause in milliseconds
class="macro">#define COLLECTION_ACC_COUNTER_STEP(class="num">16)                class=class="str">"cmt">// Account timer counter increment
class="macro">#define COLLECTION_ACC_COUNTER_ID(class="num">2)                 class=class="str">"cmt">// Account timer counter ID
class=class="str">"cmt">//--- Symbol collection timer class="num">1 parameters
class="macro">#define COLLECTION_SYM_PAUSE1(class="num">100)               class=class="str">"cmt">// Pause of the symbol collection timer class="num">1 in milliseconds(for scanning market watch symbols)
class="macro">#define COLLECTION_SYM_COUNTER_STEP1(class="num">16)                class=class="str">"cmt">// Increment of the symbol timer class="num">1 counter
class="macro">#define COLLECTION_SYM_COUNTER_ID1(class="num">3)                 class=class="str">"cmt">// Symbol timer class="num">1 counter ID
class=class="str">"cmt">//--- Symbol collection timer class="num">2 parameters
class="macro">#define COLLECTION_SYM_PAUSE2(class="num">300)               class=class="str">"cmt">// Pause of the symbol collection timer class="num">2 in milliseconds(for events of the market watch symbol list)
class="macro">#define COLLECTION_SYM_COUNTER_STEP2(class="num">16)                class=class="str">"cmt">// Increment of the symbol timer class="num">2 counter
class="macro">#define COLLECTION_SYM_COUNTER_ID2(class="num">4)                 class=class="str">"cmt">// Symbol timer class="num">2 counter ID
class=class="str">"cmt">//--- Collection list IDs
class="macro">#define COLLECTION_HISTORY_ID(0x7778+class="num">1)          class=class="str">"cmt">// Historical collection list ID
class="macro">#define COLLECTION_MARKET_ID(0x7778+class="num">2)          class=class="str">"cmt">// Market collection list ID
class="macro">#define COLLECTION_EVENTS_ID(0x7778+class="num">3)          class=class="str">"cmt">// Event collection list ID

「符号枚举与集合 ID 的底层约定」

在自建 MT5 工具库时,先把符号相关的宏和枚举钉死,能省掉后续大量判空和类型混乱。下面这段预处理定义把账户集合、品种集合的 ID 固定在 0x7778 偏移地址上:账户列表用 0x7778+4,品种列表用 0x7778+5,库文件统一落进 DoEasy\ 目录,默认画笔色给 0xFF000000。 工作模式用 ENUM_SYMBOLS_MODE 区分四种范围:只跑当前图表的 SYMBOLS_MODE_CURRENT、按自定义列表的 SYMBOLS_MODE_DEFINES、跟着市场报价窗口走的 SYMBOLS_MODE_MARKET_WATCH,以及全品种扫的 SYMBOLS_MODE_ALL。回测时若切到 SYMBOLS_MODE_ALL,EA 初始化耗时会比 CURRENT 模式多 3~8 倍,取决于券商品种数。 品种状态另用 ENUM_SYMBOL_STATUS 打标:普通外汇、主要货币对、次要货币对、 exotic 外汇各占一档。外汇与贵金属杠杆高、滑点跳空频繁,用这些状态过滤信号源时,建议先剔除 exotic 类以降低流动性风险。

MQL5 / C++
class="macro">#define COLLECTION_ACCOUNT_ID(0x7778+class="num">4)            class=class="str">"cmt">// Account collection list ID
class="macro">#define COLLECTION_SYMBOLS_ID(0x7778+class="num">5)            class=class="str">"cmt">// Symbol collection list ID
class=class="str">"cmt">//--- Data parameters for file operations
class="macro">#define DIRECTORY("DoEasy\")          class=class="str">"cmt">// Library directory for storing object folders
class=class="str">"cmt">//--- Symbol parameters
class="macro">#define CLR_DEFAULT(0xFF000000)          class=class="str">"cmt">// Default class="type">class="kw">color
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Data for working with symbols                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Modes of working with symbols                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                                            class=class="str">"cmt">// Work with the current symbol only
   SYMBOLS_MODE_DEFINES,                                            class=class="str">"cmt">// Work with the specified symbol list
   SYMBOLS_MODE_MARKET_WATCH,                                       class=class="str">"cmt">// Work with the Market Watch window symbols
   SYMBOLS_MODE_ALL                                                 class=class="str">"cmt">// Work with the full symbol list
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Abstract symbol type(status)                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_SYMBOL_STATUS
  {
   SYMBOL_STATUS_FX,                                                class=class="str">"cmt">// Forex symbol
   SYMBOL_STATUS_FX_MAJOR,                                          class=class="str">"cmt">// Major Forex symbol
   SYMBOL_STATUS_FX_MINOR,                                          class=class="str">"cmt">// Minor Forex symbol
   SYMBOL_STATUS_FX_EXOTIC                                          class=class="str">"cmt">// Exotic Forex symbol
   };

◍ 枚举品种状态后落地到 CSymbol 基类

上面一列 SYMBOL_STATUS_* 常量把 MT5 里能碰到的品种做了粗分:从外汇对卢布(SYMBOL_STATUS_FX_RUB)、贵金属(SYMBOL_STATUS_METAL)到指数、加密、商品、期货、CFD、股票、债券、期权,再到不可交易抵押品(SYMBOL_STATUS_COLLATERAL)和自定义品种(SYMBOL_STATUS_CUSTOM),最后用 SYMBOL_STATUS_COMMON 兜住通用类。写 EA 或指标时,先读这个状态位,就能判断当前图表品种能不能下单、是不是衍生品类。 分完类,代码紧接着定义了抽象品种类 CSymbol,它继承自 CObject。类里先把两个私有结构体摆出来:SMarginRate 存初始保证金率 Initial 与维持保证金率 Maintenance,都是 double;SMarginRateMode 则准备描述保证金计算模式,结构体在 private 段未被展开完。 开 MT5 按 F4 进编辑器,搜 SYMBOL_STATUS_METAL 和 CSymbol,能直接看到这两段在同一头文件里前后衔接。贵金属与外汇品种的高杠杆伴随高风险,状态识别后仍需自行校验保证金参数再决定仓位。

MQL5 / C++
   SYMBOL_STATUS_FX_RUB,                                                                     class=class="str">"cmt">// Forex symbol/RUB
   SYMBOL_STATUS_METAL,                                                                         class=class="str">"cmt">// Metal
   SYMBOL_STATUS_INDEX,                                                                         class=class="str">"cmt">// Index
   SYMBOL_STATUS_INDICATIVE,                                                                    class=class="str">"cmt">// Indicative
   SYMBOL_STATUS_CRYPTO,                                                                        class=class="str">"cmt">// Cryptocurrency symbol
   SYMBOL_STATUS_COMMODITY,                                                                     class=class="str">"cmt">// Commodity symbol
   SYMBOL_STATUS_EXCHANGE,                                                                      class=class="str">"cmt">// Exchange symbol
   SYMBOL_STATUS_FUTURES,                                                                       class=class="str">"cmt">// Futures
   SYMBOL_STATUS_CFD,                                                                           class=class="str">"cmt">// CFD
   SYMBOL_STATUS_STOCKS,                                                                        class=class="str">"cmt">// Security
   SYMBOL_STATUS_BONDS,                                                                         class=class="str">"cmt">// Bond
   SYMBOL_STATUS_OPTION,                                                                        class=class="str">"cmt">// Option
   SYMBOL_STATUS_COLLATERAL,                                                                    class=class="str">"cmt">// Non-tradable asset
   SYMBOL_STATUS_CUSTOM,                                                                        class=class="str">"cmt">// Custom symbol
   SYMBOL_STATUS_COMMON                                                                         class=class="str">"cmt">// General category
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Abstract symbol class                                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSymbol : class="kw">public CObject
  {
class="kw">private:
   class="kw">struct SMarginRate
     {
      class="type">class="kw">double         Initial;         class=class="str">"cmt">// initial margin rate
      class="type">class="kw">double         Maintenance;     class=class="str">"cmt">// maintenance margin rate
     };
   class="kw">struct SMarginRateMode
     {

保证金比率与品种属性的结构体落点

在自封装的 CSymbol 类里,保证金比率不是散落在各处的变量,而是被收进一个 SMarginRateMode 结构体。该结构体对多空持仓、四种挂单(BuyStop / BuyLimit / BuyStopLimit / SellStop / SellLimit / SellStopLimit)分别给出 MarginRate 字段,方便按订单类型取不同的保证金系数。 下方声明的 m_margin_rate 就是这个结构体的实例,紧接着的 MqlTick m_tick 存最新 tick,MqlBookInfo m_book_info_array[] 存市场深度数据。品种名、整数/实数/字符串三类属性分别用 m_long_prop[SYMBOL_PROP_INTEGER_TOTAL]、m_double_prop[SYMBOL_PROP_DOUBLE_TOTAL]、m_string_prop[SYMBOL_PROP_STRING_TOTAL] 数组承接,数组长度由系统枚举总数决定。 m_digits_currency 记录账户货币小数位,m_global_error 存全局错误码。开 MT5 新建 EA 时,直接套这个成员布局,能少写一半 SymbolInfo 重复调用;外汇与贵金属杠杆波动大,保证金比率改动会直接影响爆仓线,调参数前先在策略测试器跑一遍。

MQL5 / C++
SMarginRate       Long;             class=class="str">"cmt">// MarginRate of class="type">long positions
SMarginRate       Short;            class=class="str">"cmt">// MarginRate of class="type">class="kw">short positions
SMarginRate       BuyStop;          class=class="str">"cmt">// MarginRate of BuyStop orders
SMarginRate       BuyLimit;         class=class="str">"cmt">// MarginRate of BuyLimit orders
SMarginRate       BuyStopLimit;     class=class="str">"cmt">// MarginRate of BuyStopLimit orders
SMarginRate       SellStop;         class=class="str">"cmt">// MarginRate of SellStop orders
SMarginRate       SellLimit;        class=class="str">"cmt">// MarginRate of SellLimit orders
SMarginRate       SellStopLimit;    class=class="str">"cmt">// MarginRate of SellStopLimit orders
};
SMarginRateMode   m_margin_rate;    class=class="str">"cmt">// Margin ratio structure
SMarginRateMode   m_margin_rate;    class=class="str">"cmt">// Margin ratio structure
class="type">MqlTick           m_tick;           class=class="str">"cmt">// Symbol tick structure
MqlBookInfo       m_book_info_array[]; class=class="str">"cmt">// Array of the market depth data structures
class="type">class="kw">string            m_symbol_name;    class=class="str">"cmt">// Symbol name
class="type">long              m_long_prop[SYMBOL_PROP_INTEGER_TOTAL];  class=class="str">"cmt">// Integer properties
class="type">class="kw">double            m_double_prop[SYMBOL_PROP_DOUBLE_TOTAL]; class=class="str">"cmt">// Real properties
class="type">class="kw">string            m_string_prop[SYMBOL_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties
class="type">int               m_digits_currency; class=class="str">"cmt">// Number of decimal places in an account currency
class="type">int               m_global_error;   class=class="str">"cmt">// Global error code
class=class="str">"cmt">//--- Return the index of the array the symbol&class="macro">#x27;s(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are located at

「把符号属性映射成可调用接口」

在封装交易品种对象时,先用两个 IndexProp 重载把枚举偏移到本地数组下标,double 类属性要再减去整数与 double 总量的累计值,否则会越界读到错误内存。 下面的代码段给出了品种对象里与保证金、交易日、精度相关的声明与公开取值接口,黄底两个 Margin 函数会在运行期回填多空保证金比率。 外汇与贵金属杠杆品种在 MT5 里保证金属性可能随经纪商动态变化,直接读 SymbolInfoDouble 不一定稳定,包一层对象方法更利于复盘和参数回溯。 开 MT5 把这段代码贴进自定义 CSymbol 类,编译后调用 SymbolMarginLong 看返回是否为 true,就能验证你的品种对象有没有正确初始化保证金比率。

MQL5 / C++
  class="type">int                IndexProp(ENUM_SYMBOL_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-SYMBOL_PROP_INTEGER_TOTAL; }
  class="type">int                IndexProp(ENUM_SYMBOL_PROP_STRING class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_DOUBLE_TOTAL; }
class=class="str">"cmt">//--- (class="num">1) Fill in all the "margin ratio" symbol properties, (class="num">2) initialize the ratios
  class="type">bool              MarginRates(class="type">void);
  class="type">void              InitMarginRates(class="type">void);
class=class="str">"cmt">//--- Reset all symbol object data
  class="type">void              Reset(class="type">void);
class=class="str">"cmt">//--- Return the current day of the week
  ENUM_DAY_OF_WEEK  CurrentDayOfWeek(class="type">void)              class="kw">const;
class=class="str">"cmt">//--- Returns the number of decimal places in the &class="macro">#x27;class="type">class="kw">double&class="macro">#x27; value
  class="type">int               GetDigits(class="kw">const class="type">class="kw">double value)       class="kw">const;
class="kw">public:
class=class="str">"cmt">//--- Get and class="kw">return real properties of a selected symbol from its parameters
  class="type">class="kw">double            SymbolBidHigh(class="type">void)                 class="kw">const;
  class="type">class="kw">double            SymbolBidLow(class="type">void)                  class="kw">const;
  class="type">class="kw">double            SymbolVolumeReal(class="type">void)              class="kw">const;
  class="type">class="kw">double            SymbolVolumeHighReal(class="type">void)          class="kw">const;
  class="type">class="kw">double            SymbolVolumeLowReal(class="type">void)           class="kw">const;
  class="type">class="kw">double            SymbolOptionStrike(class="type">void)            class="kw">const;
  class="type">class="kw">double            SymbolTradeAccruedInterest(class="type">void)    class="kw">const;
  class="type">class="kw">double            SymbolTradeFaceValue(class="type">void)          class="kw">const;
  class="type">class="kw">double            SymbolTradeLiquidityRate(class="type">void)      class="kw">const;
  class="type">class="kw">double            SymbolMarginHedged(class="type">void)            class="kw">const;
  class="type">bool              SymbolMarginLong(class="type">void);
  class="type">bool              SymbolMarginShort(class="type">void);

◍ CSymbol 类的挂单保证金与存在性接口

在 MT5 的 CSymbol 封装里,挂单占用的保证金不是靠手算合约值,而是直接调一组 SymbolMargin* 布尔方法。它们覆盖 buy stop / buy limit / buy stop limit / sell stop / sell limit / sell stop limit 六种_pending_单,返回是否成功取到对应保证金要求,实盘里用来在发单前预判账户净值是否够扛。 存在性检查有两层:无参 Exist() 看当前选中品种在不在服务器,带 name 参数的 Exist(const string name) 则按字符串去查。底层还分了 SymbolExists 的 long 与 bool 重载,protected 构造强制走枚举状态加名称,避免随便 new 一个野品种。 描述与日志方面,GetPropertyDescription 按整数、双精度、字符串三类枚举把属性名翻成可读文本;Print(bool full_prop) 可把全量或仅支持属性甩到日志,PrintShort 留了虚空实现给子类填。这些接口在 EA 初始化阶段批量扫品种时很实用,能直接打印出哪些品种因服务器无数据被跳过。 外汇与贵金属杠杆高,保证金接口返回值只反映当下经纪商设定,遇节假日或扩容品种可能瞬时空跑,验证时建议先在策略测试器用真实点差重放。

MQL5 / C++
class="type">bool SymbolMarginBuyStop(class="type">void);
class="type">bool SymbolMarginBuyLimit(class="type">void);
class="type">bool SymbolMarginBuyStopLimit(class="type">void);
class="type">bool SymbolMarginSellStop(class="type">void);
class="type">bool SymbolMarginSellLimit(class="type">void);
class="type">bool SymbolMarginSellStopLimit(class="type">void);
class=class="str">"cmt">//--- Get and class="kw">return class="type">class="kw">string properties of a selected symbol from its parameters
class=class="str">"cmt">//--- Search for a symbol and class="kw">return the flag indicating its presence on the server
class="type">bool Exist(class="type">void) class="kw">const;
class="type">bool Exist(class="kw">const class="type">class="kw">string name) class="kw">const;
class="kw">protected:
class=class="str">"cmt">//--- Protected parametric constructor
 CSymbol(ENUM_SYMBOL_STATUS symbol_status,class="kw">const class="type">class="kw">string name);
class=class="str">"cmt">//--- Get and class="kw">return integer properties of a selected symbol from its parameters
class="type">bool SymbolExists(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">long SymbolExists(class="type">void) class="kw">const;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Description of symbol object properties                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Get description of a symbol(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
class="type">class="kw">string GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER class="kw">property);
class="type">class="kw">string GetPropertyDescription(ENUM_SYMBOL_PROP_DOUBLE class="kw">property);
class="type">class="kw">string GetPropertyDescription(ENUM_SYMBOL_PROP_STRING class="kw">property);
class=class="str">"cmt">//--- Send description of symbol properties to the journal(full_prop=true - all properties, false - only supported ones)
class="type">void Print(class="kw">const class="type">bool full_prop=false);
class=class="str">"cmt">//--- Display a class="type">class="kw">short symbol description in the journal(implementation in the descendants)
class="kw">virtual class="type">void PrintShort(class="type">void) {;}
class=class="str">"cmt">//--- Compare CSymbol objects by all possible properties(for sorting lists by a specified symbol object class="kw">property)
class=class="str">"cmt">//--- (class="num">1) Add, (class="num">2) remove a symbol from the Market Watch window, (class="num">3) class="kw">return the data synchronization flag by a symbol

把品种塞进观察窗口与抓交易时段

封装 CSymbol 类时,把品种加进 Market Watch 只需一行:调用 SymbolSelect(name, true) 返回布尔值,失败多半是品种名拼错或被经纪商隐藏。反过来 RemoveFromMarketWatch 传 false 即可把噪声品种清出窗口,做多品种扫描时这能省掉大量视觉干扰。 IsSynchronized 直接包了 SymbolIsSynchronized,用来确认该品种的历史与实时报价是否已和服务器对齐;在 EA 初始化阶段若返回 false,后续取 OHLC 可能拿到空值,这是实盘里容易踩的坑。 SessionQuoteTimeFrom / SessionQuoteTimeTo 以及对应的 GetSessionQuote,按 session_index 和 ENUM_DAY_OF_WEEK 返回报价时段起止。注意 day_of_week 默认 WRONG_VALUE,不填就取整周该序号会话;黄金 XAUUSD 在 MT5 默认配置下周一报价会话常从周日 23:00 起,和贸易会话差几分钟,回测写错会漏掉跳空。 SessionTradeTimeFrom / SessionTradeTimeTo / GetSessionTrade 同理管交易会话,两者分开是因为有些品种报价连续但交易中断(如周末碱金属)。BookAdd 则是订阅市场深度,做 scalping 或流动性分析前必须先调,否则 MqlBookInfo 数组永远是空的。

MQL5 / C++
class="type">bool SetToMarketWatch(class="type">void) class="kw">const { class="kw">return ::SymbolSelect(this.m_symbol_name,true); }
class="type">bool RemoveFromMarketWatch(class="type">void) class="kw">const { class="kw">return ::SymbolSelect(this.m_symbol_name,false); }
class="type">bool IsSynchronized(class="type">void) class="kw">const { class="kw">return ::SymbolIsSynchronized(this.m_symbol_name); }
class="type">long SessionQuoteTimeFrom(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week=WRONG_VALUE) class="kw">const;
class="type">long SessionQuoteTimeTo(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week=WRONG_VALUE) class="kw">const;
class="type">bool GetSessionQuote(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week,class="type">class="kw">datetime &from,class="type">class="kw">datetime &to);
class="type">long SessionTradeTimeFrom(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week=WRONG_VALUE) class="kw">const;
class="type">long SessionTradeTimeTo(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week=WRONG_VALUE) class="kw">const;
class="type">bool GetSessionTrade(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week,class="type">class="kw">datetime &from,class="type">class="kw">datetime &to);
class="type">bool BookAdd(class="type">void) class="kw">const;

「把交易时段拆成时分秒的封装思路」

在 MT5 自定义品种类里,经常需要把一段以秒为单位的会话时长转成 hh:mm:ss 文本。下面这组方法把这件事拆成了对外只读、对内计算的双层结构。 对外暴露的 SessionDurationDescription 接收 ulong duration_sec,返回描述字符串;它内部不直接算,而是分别调用三个 private 方法 SessionHours、SessionMinutes、SessionSeconds 各自只负责取出时、分、秒的整数部分。 这种拆法的好处是:如果你想在「小布盯盘」里单独显示「本周平均会话分钟数」这类指标,可以直接复用 SessionMinutes,不必重写取模逻辑。外汇与贵金属品种切换会话时流动性突变,属于高风险场景,任何时长统计都只能作为参考概率。 下面是从类声明里抽出的关键片段,注意 public 与 private 的分工:

MQL5 / C++
class="type">bool              BookClose(class="type">void)                                              class="kw">const;
class=class="str">"cmt">//--- Return(class="num">1) a session duration description in the hh:mm:ss format, number of(class="num">1) hours, (class="num">2) minutes and(class="num">3) seconds in the session duration time
  class="type">class="kw">string            SessionDurationDescription(class="kw">const class="type">ulong duration_sec) class="kw">const;
class="kw">private:
  class="type">int               SessionHours(class="kw">const class="type">ulong duration_sec)        class="kw">const;
  class="type">int               SessionMinutes(class="kw">const class="type">ulong duration_sec)      class="kw">const;
  class="type">int               SessionSeconds(class="kw">const class="type">ulong duration_sec)      class="kw">const;
class="kw">public:
class=class="str">"cmt">//--- Integer properties
  class="type">long              Status(class="type">void)                                                  class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_STATUS); }
  class="type">bool              IsCustom(class="type">void)                                                class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_CUSTOM); }
  class="type">class="kw">color             ColorBackground(class="type">void)                                         class="kw">const { class="kw">return (class="type">class="kw">color)this.GetProperty(SYMBOL_PROP_BACKGROUND_COLOR); }
  ENUM_SYMBOL_CHART_MODE ChartMode(class="type">void)                                          class="kw">const { class="kw">return (ENUM_SYMBOL_CHART_MODE)this.GetProperty(SYMBOL_PROP_CHART_MODE); }
  class="type">bool              IsExist(class="type">void)                                                 class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_EXIST); }

◍ 封装报价与状态查询的轻量接口

在 MT5 的自定义品种类里,把底层 GetProperty 再包一层,能少写很多重复代码。下面这组方法只做一件事:把符号是否存在、是否被选中、是否可见,以及当前买卖价和当日高低买价,直接以类型安全的方式吐出来。 IsExist 调 SymbolExists 判断品种在 Market Watch 里是否登记;IsSelect 和 IsVisible 分别读 SYMBOL_PROP_SELECT、SYMBOL_PROP_VISIBLE,返回布尔。SessionDeals 取 SYMBOL_PROP_SESSION_DEALS,是长整型,反映当前会话成交笔数。 Real properties 这一段全是 double:Bid 取 SYMBOL_PROP_BID,BidHigh / BidLow 取当日买价极值,Ask 取 SYMBOL_PROP_ASK。实盘里 EURUSD 的 Bid 与 Ask 点差常在 0.0 至 2.0 点浮动,贵金属 XAUUSD 点差可能扩大到 30 点以上,读取这些值做风控前先确认品种已 IsExist,否则可能拿到脏零值。 把这些接口塞进你自己的 CSymbol 类,EA 里就能直接 sym.Bid() 而不是每次手写 SymbolInfoDouble(_Symbol,SYMBOL_BID),编译后跑一遍几类品种验证返回值是否合理。

MQL5 / C++
class="type">bool IsExist(class="kw">const class="type">class="kw">string name) class="kw">const { class="kw">return this.SymbolExists(name); }
class="type">bool IsSelect(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_SELECT); }
class="type">bool IsVisible(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_VISIBLE); }
class="type">long SessionDeals(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_DEALS); }
class=class="str">"cmt">//--- Real properties
class="type">class="kw">double Bid(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_BID); }
class="type">class="kw">double BidHigh(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_BIDHIGH); }
class="type">class="kw">double BidLow(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_BIDLOW); }
class="type">class="kw">double Ask(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_ASK); }

报价与真实成交量的封装取法

在 MT5 的自定义品种类里,把卖价极值、最新成交价和真实成交量都收进了 const 方法,调用时不会改动对象内部状态。 下面这段是直接从类定义里抽出来的八个取值函数,全走 GetProperty 读符号属性: double AskHigh(void) const { return this.GetProperty(SYMBOL_PROP_ASKHIGH); } // 返回当前卖价区间最高值,用于识别日内卖压上沿 double AskLow(void) const { return this.GetProperty(SYMBOL_PROP_ASKLOW); } // 返回卖价区间最低值,配合 AskHigh 可算买卖价差宽度 double Last(void) const { return this.GetProperty(SYMBOL_PROP_LAST); } // 返回最近一笔成交价,是盯盘时跳动最频繁的数值 double LastHigh(void) const { return this.GetProperty(SYMBOL_PROP_LASTHIGH); } // 返回最新成交价的历史最高,判断突破是否有效 double LastLow(void) const { return this.GetProperty(SYMBOL_PROP_LASTLOW); } // 返回最新成交价的历史最低,支撑位参考之一 double VolumeReal(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_REAL); } // 返回真实成交量(手数),不是 tick 数,外汇贵金属高频段常看这个 double VolumeHighReal(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH_REAL); } // 返回真实成交量峰值,异动放量的阈值锚点 double VolumeLowReal(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMELOW_REAL); } // 返回真实成交量谷值,地量区可能酝酿变盘 实盘里若发现 VolumeReal 在伦敦盘前 1 小时冲到 VolumeHighReal 的 80% 以上,价格却未破 LastHigh,后续回撤概率倾向偏高。外汇与贵金属杠杆高,用这些属性做过滤时务必先开 MT5 策略测试器验证。

MQL5 / C++
class="type">class="kw">double AskHigh(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_ASKHIGH); }
class="type">class="kw">double AskLow(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_ASKLOW); }
class="type">class="kw">double Last(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_LAST); }
class="type">class="kw">double LastHigh(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_LASTHIGH); }
class="type">class="kw">double LastLow(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_LASTLOW); }
class="type">class="kw">double VolumeReal(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_REAL); }
class="type">class="kw">double VolumeHighReal(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH_REAL); }
class="type">class="kw">double VolumeLowReal(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMELOW_REAL); }

「从期权行权价到合约面值的属性读取」

在 MT5 的自定义品种封装里,这一组方法把底层 GetProperty 调用包装成直观的只读函数,直接吐出交易所需的数值属性。 下面这段是实际可用的类方法片段,每一个都对应一个 SYMBOL_PROP_* 枚举:

MQL5 / C++
class="type">class="kw">double OptionStrike(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_OPTION_STRIKE); }
class="type">class="kw">double Point(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_POINT); }
class="type">class="kw">double TradeTickValue(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE); }
class="type">class="kw">double TradeTickValueProfit(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT); }
class="type">class="kw">double TradeTickValueLoss(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS); }
class="type">class="kw">double TradeTickSize(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_SIZE); }
class="type">class="kw">double TradeContractSize(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_CONTRACT_SIZE); }
class="type">class="kw">double TradeAccuredInterest(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_ACCRUED_INTEREST); }
class="type">class="kw">double TradeFaceValue(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_FACE_VALUE); }
逐行看:OptionStrike 取期权行权价,做期权品种时缺了它就无法核算盈亏;Point 返回价格最小变动单位,外汇品种常见为 1e-5 或 1e-4。TradeTickValue 系列三个方法分别给出每跳价值、盈利跳价值和亏损跳价值,三者可能不同——贵金属在极端点差下亏损跳价值往往偏高。 TradeTickSize 是每跳对应的价格距离,TradeContractSize 是一手合约规模,债券类品种还会用到 TradeAccuredInterest(应计利息)和 TradeFaceValue(面值)。开 MT5 接上自己写的 CSymbol 类,打印这几个返回值,能立刻核对经纪商下外汇或贵金属品种的参数是否和文档一致;外汇贵金属杠杆高,参数读错会放大仓位风险。

MQL5 / C++
class="type">class="kw">double OptionStrike(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_OPTION_STRIKE); }
class="type">class="kw">double Point(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_POINT); }
class="type">class="kw">double TradeTickValue(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE); }
class="type">class="kw">double TradeTickValueProfit(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT); }
class="type">class="kw">double TradeTickValueLoss(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS); }
class="type">class="kw">double TradeTickSize(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_SIZE); }
class="type">class="kw">double TradeContractSize(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_CONTRACT_SIZE); }
class="type">class="kw">double TradeAccuredInterest(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_ACCRUED_INTEREST); }
class="type">class="kw">double TradeFaceValue(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_FACE_VALUE); }

◍ 从品种属性接口抠出交易成本与限额

做仓位和风控之前,得先拿到品种本身的硬约束。下面这组类方法把 MT5 symbol 属性一次性封装成只读函数,调用成本极低,适合在 EA 初始化阶段批量读取。 外汇与贵金属属于高杠杆高风险品种,同一经纪商下 EURUSD 的 LotsMin 可能返回 0.01,而 XAUUSD 的 LotsMin 常为 0.1,不读准就直接下单会触发 4756 报错。 代码逐行拆一下:TradeLiquidityRate 取 SYMBOL_PROP_TRADE_LIQUIDITY_RATE,即当前交易流动性系数,数值偏低时滑点可能放大;LotsMin / LotsMax / LotsStep 分别对应最小、最大、手数步进,三者决定你仓位可调的颗粒度。 VolumeLimit 返回该品种单方向总持仓上限,击穿后开仓会被拒;SwapLong / SwapShort 是隔夜多空仓息,黄金持仓过周末利息可能吃掉小级别盈利。 MarginInitial 与 MarginMaintenance 给出开仓与维持保证金,前者算占用、后者算强平线,调杠杆前先打印这两个值比对账户净值。

MQL5 / C++
class="type">class="kw">double TradeLiquidityRate(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_LIQUIDITY_RATE); }
class="type">class="kw">double LotsMin(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_MIN); }
class="type">class="kw">double LotsMax(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_MAX); }
class="type">class="kw">double LotsStep(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_STEP); }
class="type">class="kw">double VolumeLimit(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_LIMIT); }
class="type">class="kw">double SwapLong(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SWAP_LONG); }
class="type">class="kw">double SwapShort(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SWAP_SHORT); }
class="type">class="kw">double MarginInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_INITIAL); }
class="type">class="kw">double MarginMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_MAINTENANCE); }

多单与挂单的初始及维持保证金读取

在 MT5 的自定义品种类封装里,保证金相关属性被拆成了初始(Initial)与维持(Maintenance)两套,且按市价多单、Buy Stop、Buy Limit、Buy Stop Limit 分别暴露接口。下面这组方法全部走 this.GetProperty() 读取符号属性枚举,返回值为 double 类型的保证金要求(通常以账户币种计)。 以 MarginLongInitial 为例,它映射 SYMBOL_PROP_MARGIN_LONG_INITIAL,返回开市价多单时 broker 要求的初始保证金;MarginBuyStopInitial 对应挂单 Buy Stop 的初始保证金,二者在裸点差扩大或节假日流动性变差时可能不同。 维持保证金系列(如 MarginLongMaintenanceMarginBuyStopMaintenance)返回的是持仓过夜或追加前的最低权益占用,实战中若维护值低于初始值,说明 broker 允许开仓后释放部分保证金,但强平线往往盯的是维护值。 外汇与贵金属自带高杠杆高风险,上述属性只是读取 broker 设定,不预示任何价格方向;开 MT5 用 SymbolInfoDouble(_Symbol,SYMBOL_PROP_MARGIN_LONG_INITIAL) 可直接核对返回值是否与封装方法一致。

MQL5 / C++
class="type">class="kw">double MarginLongInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_INITIAL); }
class="type">class="kw">double MarginBuyStopInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL); }
class="type">class="kw">double MarginBuyLimitInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL); }
class="type">class="kw">double MarginBuyStopLimitInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL); }
class="type">class="kw">double MarginLongMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE); }
class="type">class="kw">double MarginBuyStopMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE); }
class="type">class="kw">double MarginBuyLimitMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE); }
class="type">class="kw">double MarginBuyStopLimitMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE); }
class="type">class="kw">double MarginShortInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_INITIAL); }

「空头挂单与维护保证金的接口取法」

在 MT5 的自定义品种封装类里,空头方向的挂单初始保证金和维持保证金被拆成了六类接口,分别对应市价卖、卖停、卖限、卖停限以及它们各自的维护阶段。 MarginSellStopInitial 取 SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL,返回卖止损挂单开仓所需的初始保证金;MarginSellLimitInitial 对应 SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL,是卖限价挂单的初始保证金。 MarginSellStopLimitInitial 读 SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL,覆盖卖停限单;MarginShortMaintenance 取 SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE,是普通空头持仓的维持保证金。 剩下三个 Maintenance 接口分别绑定卖停、卖限、卖停限的维持保证金属性。外汇与贵金属杠杆高,这类数值随品种和经纪商变动,实盘前应在 MT5 符号属性里核对具体返回值。 SessionVolume、SessionTurnover、SessionInterest 三个接口不归保证金类,它们取当日会话的成交量、成交额与利息数据,可用于盘后复盘脚本快速拉取。

MQL5 / C++
class="type">class="kw">double MarginSellStopInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL); }
class="type">class="kw">double MarginSellLimitInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL); }
class="type">class="kw">double MarginSellStopLimitInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL); }
class="type">class="kw">double MarginShortMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE); }
class="type">class="kw">double MarginSellStopMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE); }
class="type">class="kw">double MarginSellLimitMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE); }
class="type">class="kw">double MarginSellStopLimitMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE); }
class="type">class="kw">double SessionVolume(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_VOLUME); }
class="type">class="kw">double SessionTurnover(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_TURNOVER); }
class="type">class="kw">double SessionInterest(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_INTEREST); }

◍ 读取交易时段盘面属性的几个接口

在 MT5 的自定义品种类封装里,这一组 const 方法直接透传了底层 SYMBOL_PROP_* 属性,用来拿当前交易时段的委托量与结算价。外汇和贵金属属于高杠杆品种,这些数值随交易所或经纪商时段切换会跳变,读之前先确认品种处于可交易状态。 SessionBuyOrdersVolume 返回买挂单总成交量,SessionSellOrdersVolume 对应卖挂单;SessionOpen 与 SessionClose 给出该时段的起点与终点价,SessionAW 是累计加权成交价。SessionPriceSettlement 是结算价,SessionPriceLimitMin / Max 则是时段涨跌停边界。 MarginHedged 返回对冲单的保证金占用系数,NormalizedPrice 负责对外部传入价格做规范化(如按点值四舍五入)。把这些接口接进小布盯盘脚本,就能在时段切换瞬间比对挂单失衡,概率上更早发现流动性拐点。

MQL5 / C++
class="type">class="kw">double SessionBuyOrdersVolume(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME); }
class="type">class="kw">double SessionSellOrdersVolume(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME); }
class="type">class="kw">double SessionOpen(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_OPEN); }
class="type">class="kw">double SessionClose(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_CLOSE); }
class="type">class="kw">double SessionAW(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_AW); }
class="type">class="kw">double SessionPriceSettlement(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT); }
class="type">class="kw">double SessionPriceLimitMin(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN); }
class="type">class="kw">double SessionPriceLimitMax(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX); }
class="type">class="kw">double MarginHedged(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED); }
class="type">class="kw">double NormalizedPrice(class="kw">const class="type">class="kw">double price) class="kw">const;

构造函数里的品种就绪检查

CSymbol 的带参构造函数一进来就先做存在性校验:把传入的 name 赋给 m_symbol_name,立刻调 Exist() 看服务器上有没有这个品种。若返回 false,直接把 m_global_error 置为 ERR_MARKET_UNKNOWN_SYMBOL 并打印双语报错,后续逻辑基本失效。 接着用 SymbolInfoInteger 读 SYMBOL_SELECT,判断该品种是否已在市场报价窗。若没选入,会尝试 SetToMarketWatch() 把它塞进观察列表;失败则抓 GetLastError() 存错码。这里每次跨系统调用前后都插了 ResetLastError(),目的是避免上一次残留错误码污染本次诊断。 真正拉行情的是 SymbolInfoTick,它将最新 tick 写进 m_tick,拿不到就记错误并提示。随后 ZeroMemory 清空 tick 再 Reset(),并把账户币种小数位用 AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) 读出来存 m_digits_currency(MQL4 下硬编码为 2)。 MQL5 分支里还会跑 MarginRates() 初始化保证金系数,调不成功直接 return,等于构造函数半途而废。开 MT5 把这段塞进自己的品种类,断点跟一遍 Exist 与 SymbolInfoTick 的返回,就能确认本地品种表和服务端是否同步。

MQL5 / C++
CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,class="kw">const class="type">class="kw">string name) : m_global_error(ERR_SUCCESS)
  {
   this.m_symbol_name=name;
   if(!this.Exist())
     {
      ::Print(DFUN_ERR_LINE,"\"",this.m_symbol_name,"\": ",TextByLanguage("Ошибка. Такого символа нет на сервере","Error. There is no such symbol on the server"));
      this.m_global_error=ERR_MARKET_UNKNOWN_SYMBOL;
     }
   class="type">bool select=::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SELECT);
   ::ResetLastError();
   if(!select)
     {
      if(!this.SetToMarketWatch())
        {
         this.m_global_error=::GetLastError();
         ::Print(DFUN_ERR_LINE,"\"",this.m_symbol_name,"\": ",TextByLanguage("Не удалось поместить в обзор рынка. Ошибка: ","Failed to put in the market watch. Error: "),this.m_global_error);
        }
     }
   ::ResetLastError();
   if(!::SymbolInfoTick(this.m_symbol_name,this.m_tick))
     {
      this.m_global_error=::GetLastError();
      ::Print(DFUN_ERR_LINE,"\"",this.m_symbol_name,"\": ",TextByLanguage("Не удалось получить текущие цены. Ошибка: ","Could not get current prices. Error: "),this.m_global_error);
     }
class=class="str">"cmt">//--- Initialize data
   ::ZeroMemory(this.m_tick);
   this.Reset();
   this.m_digits_currency=(class="macro">#ifdef __MQL5__(class="type">int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) class="macro">#else class="num">2 class="macro">#endif);
   this.InitMarginRates();
   ::ResetLastError();
class="macro">#ifdef __MQL5__
   if(!this.MarginRates())
     {
      this.m_global_error=::GetLastError();
      ::Print(DFUN_ERR_LINE,this.Name(),": ",TextByLanguage("Не удалось получить коэффициенты взимания маржи. Ошибка: ","Failed to get margin rates. Error: "),this.m_global_error);
      class="kw">return;
     }
class="macro">#endif 

class=class="str">"cmt">//--- Save integer properties
   this.m_long_prop[SYMBOL_PROP_STATUS]                                      = symbol_status;

「把整型品种属性一次性灌进数组」

这段逻辑出现在自定义品种封装类的刷新函数里,作用是把当前 tick 与 SymbolInfoInteger 能拿到的整型字段,统一写进 m_long_prop 数组,方便后续用枚举下标直接读取。 注意时间字段的兼容写法:MQL5 下取 m_tick.time_msc(毫秒精度),MQL4 则把 m_tick.time 乘 1000 凑毫秒,避免跨编译器时间基准错位。 从 SYMBOL_SELECT 到 SYMBOL_TICKS_BOOKDEPTH 共 14 个整型属性被批量拉取,其中 SYMBOL_SPREAD_FLOAT 与 SYMBOL_TICKS_BOOKDEPTH 在部分贵金属品种上可能返回 0,实盘前应在 MT5 用 SymbolInfoInteger 单独验证。 外汇与贵金属杠杆高、点差跳变频繁,直接用数组下标读 SPREAD 比每次调函数更省,但需自行保证刷新频率跟得上 tick 节奏。

MQL5 / C++
this.m_long_prop[SYMBOL_PROP_VOLUME]                                          = (class="type">long)this.m_tick.volume;
this.m_long_prop[SYMBOL_PROP_TIME]                                           = class="macro">#ifdef __MQL5__ this.m_tick.time_msc class="macro">#else this.m_tick.time*class="num">1000 class="macro">#endif ;
this.m_long_prop[SYMBOL_PROP_SELECT]                                         = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SELECT);
this.m_long_prop[SYMBOL_PROP_VISIBLE]                                        = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_VISIBLE);
this.m_long_prop[SYMBOL_PROP_SESSION_DEALS]                                  = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SESSION_DEALS);
this.m_long_prop[SYMBOL_PROP_SESSION_BUY_ORDERS]                             = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SESSION_BUY_ORDERS);
this.m_long_prop[SYMBOL_PROP_SESSION_SELL_ORDERS]                            = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SESSION_SELL_ORDERS);
this.m_long_prop[SYMBOL_PROP_VOLUMEHIGH]                                     = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_VOLUMEHIGH);
this.m_long_prop[SYMBOL_PROP_VOLUMELOW]                                      = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_VOLUMELOW);
this.m_long_prop[SYMBOL_PROP_DIGITS]                                         = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_DIGITS);
this.m_long_prop[SYMBOL_PROP_SPREAD]                                         = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SPREAD);
this.m_long_prop[SYMBOL_PROP_SPREAD_FLOAT]                                   = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SPREAD_FLOAT);
this.m_long_prop[SYMBOL_PROP_TICKS_BOOKDEPTH]                                = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TICKS_BOOKDEPTH);

◍ 整批抓取交易品种的长整型属性

在封装交易品种类时,把一类固定的长整型字段一次性塞进成员变量数组,比每次调用都去问终端要数据更省事。下面这段就是把 16 个整型属性一次性写进 m_long_prop 的典型写法。 其中 SYMBOL_TRADE_STOPS_LEVEL 和 SYMBOL_TRADE_FREEZE_LEVEL 返回的是点数,实盘里欧美对常见 stops level 为 0、freeze level 也为 0,但部分贵金属如 XAUUSD 在多数经纪商下 freeze level 会落在 0~10 点区间,下单前不读这两个值就可能触发无效止损距错误。 代码里前 7 项走 ::SymbolInfoInteger 直接问内核,后 9 项走类内的二次封装方法(如 SymbolExists、SymbolCustom),本质也是取对应整型标记。开 MT5 把这段贴进你的 CSymbol 类初始化函数,打印 m_long_prop 数组,就能核对当前品种的真实交易权限与到期结构。 外汇与贵金属杠杆高、规则随经纪商变,属性值仅代表当前环境快照,换服务器后必须重抓。

MQL5 / C++
  this.m_long_prop[SYMBOL_PROP_TRADE_MODE]                  = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_MODE);
  this.m_long_prop[SYMBOL_PROP_START_TIME]                = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_START_TIME);
  this.m_long_prop[SYMBOL_PROP_EXPIRATION_TIME]           = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_EXPIRATION_TIME);
  this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL]         = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_STOPS_LEVEL);
  this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL]        = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_FREEZE_LEVEL);
  this.m_long_prop[SYMBOL_PROP_TRADE_EXEMODE]             = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_EXEMODE);
  this.m_long_prop[SYMBOL_PROP_SWAP_ROLLOVER3DAYS]        = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SWAP_ROLLOVER3DAYS);
  this.m_long_prop[SYMBOL_PROP_EXIST]                     = this.SymbolExists();
  this.m_long_prop[SYMBOL_PROP_CUSTOM]                    = this.SymbolCustom();
  this.m_long_prop[SYMBOL_PROP_MARGIN_HEDGED_USE_LEG]     = this.SymbolMarginHedgedUseLEG();
  this.m_long_prop[SYMBOL_PROP_ORDER_MODE]                = this.SymbolOrderMode();
  this.m_long_prop[SYMBOL_PROP_FILLING_MODE]              = this.SymbolOrderFillingMode();
  this.m_long_prop[SYMBOL_PROP_EXPIRATION_MODE]           = this.SymbolExpirationMode();
  this.m_long_prop[SYMBOL_PROP_ORDER_GTC_MODE]            = this.SymbolOrderGTCMode();
  this.m_long_prop[SYMBOL_PROP_OPTION_MODE]               = this.SymbolOptionMode();
  this.m_long_prop[SYMBOL_PROP_OPTION_RIGHT]              = this.SymbolOptionRight();

把品种属性一次性落进缓存数组

在封装交易品种类时,把配置型长整型属性和实时行情型双精度属性分开灌入成员变量,能避免后续反复调用终端接口。上面这段代码先写背景色、图表模式、计算模式、掉期模式到 m_long_prop,再批量拉取 Ask/Last 高低点、点值、合约大小、成交量限制等到 m_double_prop。 注意 SYMBOL_PROP_POINT 通常外汇对是 1e-5(五位数平台)或 1e-4(四位数平台),黄金 XAUUSD 多为 1e-3;这些数字直接决定你下单手数和止损点距的换算,缓存后回测不必每次问终端。 别把正态当圣经:SymbolInfoDouble 取到的 VOLUME_MIN 在贵金属上可能到 0.01,外汇欧美也常是 0.01,但某些券商原油类会设 0.1,写 EA 时若硬编码最小手数会直接报无效成交量。 让小布替你跑这套:把下面片段贴进你的 CSymbol 类构造函数,终端里切几个品种打印 m_double_prop,核对和右下角规格是否一致,能省掉后续不少隐性 bug。

MQL5 / C++
  this.m_long_prop[SYMBOL_PROP_BACKGROUND_COLOR]                 = this.SymbolBackgroundColor();
  this.m_long_prop[SYMBOL_PROP_CHART_MODE]                         = this.SymbolChartMode();
  this.m_long_prop[SYMBOL_PROP_TRADE_CALC_MODE]                    = this.SymbolCalcMode();
  this.m_long_prop[SYMBOL_PROP_SWAP_MODE]                          = this.SymbolSwapMode();
class=class="str">"cmt">//--- Save real properties
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKHIGH)]          = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_ASKHIGH);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKLOW)]           = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_ASKLOW);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTHIGH)]         = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_LASTHIGH);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTLOW)]          = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_LASTLOW);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_POINT)]            = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_POINT);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE_PROFIT);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)]   = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE_LOSS);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_SIZE)]  = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_SIZE);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_CONTRACT_SIZE)]     = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_CONTRACT_SIZE);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MIN)]       = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_VOLUME_MIN);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MAX)]       = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_VOLUME_MAX);

「把品种交易参数一次性灌进结构体」

在封装品种信息的类里,这一段负责把 MT5 终端里 double 类型的品种属性批量读入本地数组 m_double_prop,键由 IndexProp() 映射。 覆盖的内容很实用:VOLUME_STEP 决定下单手数最小步长,VOLUME_LIMIT 是净持仓上限;SWAP_LONG / SWAP_SHORT 对应多空隔夜利息,MARGIN_INITIAL 与 MARGIN_MAINTENANCE 是开仓和维持保证金。 后面那串 SESSION_ 开头属性容易被忽略,但它们藏着盘口真相:SESSION_BUY_ORDERS_VOLUME 与 SESSION_SELL_ORDERS_VOLUME 给出当前 session 买卖挂单量,SESSION_PRICE_LIMIT_MIN / MAX 则是交易所品种的价格笼边界。外汇与贵金属杠杆高,这些数值随品种和券商策略变动,实盘前务必在 MT5 用 SymbolInfoDouble 自测一遍。

MQL5 / C++
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_STEP)]                 = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_VOLUME_STEP);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_LIMIT)]                = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_VOLUME_LIMIT);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_LONG)]                   = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SWAP_LONG);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SHORT)]                  = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SWAP_SHORT);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_INITIAL)]              = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_MARGIN_INITIAL);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_MAINTENANCE)]          = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_MARGIN_MAINTENANCE);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_VOLUME)]              = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_VOLUME);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_TURNOVER)]            = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_TURNOVER);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_INTEREST)]            = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_INTEREST);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)]   = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME)]  = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_OPEN)]                = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_OPEN);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_CLOSE)]               = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_CLOSE);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_AW)]                  = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_AW);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT)]    = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_PRICE_SETTLEMENT);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN)]     = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_PRICE_LIMIT_MIN);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX)]     = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_PRICE_LIMIT_MAX);

◍ 把保证金初始率塞进符号属性数组

在封装行情符号的类里,会把即时 tick 与衍生计算值统一写进 m_double_prop 双精度数组,用 IndexProp 做枚举偏移寻址,避免散落各处读取。上面这段高亮部分专门处理多头与买入挂单的保证金初始/维护率,数据源来自成员结构 m_margin_rate。 具体看,SYMBOL_PROP_MARGIN_LONG_INITIAL 取 m_margin_rate.Long.Initial,而 BuyStop、BuyLimit、BuyStopLimit 三类挂单也各有 Initial 与 Maintenance 两组,分别映射到对应子结构。Maintenance 是持仓过夜或净值回撤时 broker 可能追加的维持保证金,和开仓 Initial 不是一回事。 外汇与贵金属杠杆品种里,同一符号多空初始率可能不同,黄金 XAUUSD 在部分券商 Long.Initial 与 Short.Initial 能差出 0.1%~0.3%,直接决定你 0.01 手占用的冻结保证金。开 MT5 在策略测试器外挂个 Print 脚本把这几项打出来,比对账户规格窗口,能验证类封装有没有漏字段。

MQL5 / C++
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_INITIAL)]           = this.m_margin_rate.Long.Initial;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL)]         = this.m_margin_rate.BuyStop.Initial;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL)]        = this.m_margin_rate.BuyLimit.Initial;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL)]    = this.m_margin_rate.BuyStopLimit.Initial;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE)]         = this.m_margin_rate.Long.Maintenance;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE)]     = this.m_margin_rate.BuyStop.Maintenance;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE)]    = this.m_margin_rate.BuyLimit.Maintenance;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE)] = this.m_margin_rate.BuyStopLimit.Maintenance;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_INITIAL)]            = this.m_margin_rate.Short.Initial;

把保证金与字符串属性塞进符号缓存

这段逻辑出现在自定义符号类的内部刷新函数里,职责是把持仓维护保证金、挂单初始保证金,以及一批描述性字符串一次性写进本地数组,避免每次读属性都去调终端接口。 双精度数组那段覆盖的是卖停、卖限、卖停限的初始保证金,以及对应持仓的维护保证金。例如 SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL 取自 m_margin_rate.SellStop.InitialSYMBOL_PROP_MARGIN_SHORT_MAINTENANCE 取自 m_margin_rate.Short.Maintenance,七个字段逐一映射,顺序和结构体成员对应。 字符串属性则混用了两种来源:符号名、计算基础、银行、ISIN、公式、页面走的是类内方法(如 SymbolBasis()),而基础货币、利润货币、保证金货币、描述、路径直接调 SymbolInfoString 向终端要。最后把 SYMBOL_PROP_DIGITS_LOTS 这种扩展整型单独存进 m_long_prop,代表开仓手数精度位数。 开 MT5 把这段贴进你自己的 CSymbol 派生类,断点跑一遍,能看到 m_double_prop 长度至少多出 7 个有效位、m_string_propSYMBOL_CURRENCY_MARGIN 通常返回账户币种如 USD。外汇与贵金属杠杆品种保证金参数随经纪商浮动,高波动时段可能临时上调,验证时以实时返回值为准。

MQL5 / C++
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL)]        = this.m_margin_rate.SellStop.Initial;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL)]        = this.m_margin_rate.SellLimit.Initial;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL)]    = this.m_margin_rate.SellStopLimit.Initial;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE)]         = this.m_margin_rate.Short.Maintenance;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE)]     = this.m_margin_rate.SellStop.Maintenance;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE)]    = this.m_margin_rate.SellLimit.Maintenance;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE)]= this.m_margin_rate.SellStopLimit.Maintenance;
class=class="str">"cmt">//--- Save class="type">class="kw">string properties
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_NAME)]                            = this.m_symbol_name;
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_BASE)]                   = ::SymbolInfoString(this.m_symbol_name,SYMBOL_CURRENCY_BASE);
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_PROFIT)]                 = ::SymbolInfoString(this.m_symbol_name,SYMBOL_CURRENCY_PROFIT);
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_MARGIN)]                 = ::SymbolInfoString(this.m_symbol_name,SYMBOL_CURRENCY_MARGIN);
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_DESCRIPTION)]                     = ::SymbolInfoString(this.m_symbol_name,SYMBOL_DESCRIPTION);
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_PATH)]                            = ::SymbolInfoString(this.m_symbol_name,SYMBOL_PATH);
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_BASIS)]                           = this.SymbolBasis();
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_BANK)]                            = this.SymbolBank();
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_ISIN)]                            = this.SymbolISIN();
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_FORMULA)]                         = this.SymbolFormula();
  this.m_string_prop[this.IndexProp(SYMBOL_PROP_PAGE)]                            = this.SymbolPage();
class=class="str">"cmt">//--- Save additional integer properties
  this.m_long_prop[SYMBOL_PROP_DIGITS_LOTS]                                       = this.SymbolDigitsLot();
class=class="str">"cmt">//---

「保证金费率与品种存在性的代码落地」

在封装交易品种类时,保证金相关字段必须分多头、空头及四类挂单(BuyStop / BuyLimit / BuyStopLimit / SellStop / SellLimit / SellStopLimit)逐一填充。MQL5 下通过 SymbolMarginLong() 等 8 个方法链式赋值,并用 &= 累积成功标志,任一项失败都会让 MarginRates() 返回 false。 非 MQL5 分支(如 MQL4 兼容层)直接调 InitMarginRates() 把 8 类费率的 Initial 与 Maintenance 全部置 0,同时返回 false,说明该环境不支持自动拉取保证金。 InitMarginRates() 里 16 个赋值全部写死为 0,这是兜底初始化,避免未连接品种时结构体里残留脏数据。实际跑 EA 前应在 MQL5 环境验证 SymbolMarginLong() 等返回 true,否则仓位计算会基于 0 保证金得出错误手数。 SymbolExists() 用预编译指令切换:MQL5 走 SymbolInfoInteger(name, SYMBOL_EXIST),其他环境走 this.Exist()。外汇与贵金属杠杆高,品种若被后台临时移除(EXIST=0),继续下单可能触发离岸报价或拒绝,开 MT5 用此函数轮询自选列表能提前规避。

MQL5 / C++
class="type">bool CSymbol::MarginRates(class="type">void)
  {
   class="type">bool res=true;
   class="macro">#ifdef __MQL5__
      res &=this.SymbolMarginLong();
      res &=this.SymbolMarginBuyStop();
      res &=this.SymbolMarginBuyLimit();
      res &=this.SymbolMarginBuyStopLimit();
      res &=this.SymbolMarginShort();
      res &=this.SymbolMarginSellStop();
      res &=this.SymbolMarginSellLimit();
      res &=this.SymbolMarginSellStopLimit();
   class="macro">#else
      this.InitMarginRates();
      res=false;
   class="macro">#endif
   class="kw">return res;
  }
class="type">void CSymbol::InitMarginRates(class="type">void)
  {
   this.m_margin_rate.Long.Initial=class="num">0;         this.m_margin_rate.Long.Maintenance=class="num">0;
   this.m_margin_rate.BuyStop.Initial=class="num">0;       this.m_margin_rate.BuyStop.Maintenance=class="num">0;
   this.m_margin_rate.BuyLimit.Initial=class="num">0;      this.m_margin_rate.BuyLimit.Maintenance=class="num">0;
   this.m_margin_rate.BuyStopLimit.Initial=class="num">0;  this.m_margin_rate.BuyStopLimit.Maintenance=class="num">0;
   this.m_margin_rate.Short.Initial=class="num">0;         this.m_margin_rate.Short.Maintenance=class="num">0;
   this.m_margin_rate.SellStop.Initial=class="num">0;      this.m_margin_rate.SellStop.Maintenance=class="num">0;
   this.m_margin_rate.SellLimit.Initial=class="num">0;     this.m_margin_rate.SellLimit.Maintenance=class="num">0;
   this.m_margin_rate.SellStopLimit.Initial=class="num">0; this.m_margin_rate.SellStopLimit.Maintenance=class="num">0;
  }
class="type">long CSymbol::SymbolExists(class="type">void) class="kw">const
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_EXIST) class="macro">#else this.Exist() class="macro">#endif);
  }

◍ 用 SymbolInfoMarginRate 抠出各类型单的保证金率

在 MT5 里,不同订单类型的初始保证金与维持保证金往往不是同一个数。做多、做空、挂单里的 BuyStop / BuyLimit / BuyStopLimit,券商都可能给不同比例,靠肉眼翻规格页面容易漏。 下面这组 CSymbol 成员函数,统一通过 SymbolInfoMarginRate 把对应类型的 Initial 与 Maintenance 两个值写进结构体成员。MQL4 分支直接返回 false,因为旧接口没有这套按单类型取保证金率的机制。 以 SymbolMarginLong 为例:传入 ORDER_TYPE_BUY,函数把多头初始和维持保证金率分别填进 m_margin_rate.Long.Initial 和 m_margin_rate.Long.Maintenance。其余四个函数只是把订单类型换成 SELL、BUY_STOP、BUY_LIMIT、BUY_STOP_LIMIT,逻辑完全一致。 开 MT5 按 F4 把这段代码塞进你的符号封装类,跑完之后直接打印 m_margin_rate.Long.Initial,就能看到当前品种多头建仓的真实保证金系数。外汇与贵金属杠杆波动大,实盘前先核保证金率,可避免极端行情下强平概率被低估。

MQL5 / C++
class="type">bool CSymbol::SymbolExists(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="kw">return(class="macro">#ifdef __MQL5__(class="type">bool)::SymbolInfoInteger(name,SYMBOL_EXIST) class="macro">#else this.Exist(name) class="macro">#endif);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Fill in the margin ratios for class="type">long positions                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbol::SymbolMarginLong(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_BUY,this.m_margin_rate.Long.Initial,this.m_margin_rate.Long.Maintenance) class="macro">#else false class="macro">#endif);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Fill in the margin ratios for class="type">class="kw">short positions                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbol::SymbolMarginShort(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_SELL,this.m_margin_rate.Short.Initial,this.m_margin_rate.Short.Maintenance) class="macro">#else false class="macro">#endif);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Fill in the margin ratios for BuyStop orders                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbol::SymbolMarginBuyStop(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_BUY_STOP,this.m_margin_rate.BuyStop.Initial,this.m_margin_rate.BuyStop.Maintenance) class="macro">#else false class="macro">#endif);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Fill in the margin ratios for BuyLimit orders                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbol::SymbolMarginBuyLimit(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_BUY_LIMIT,this.m_margin_rate.BuyLimit.Initial,this.m_margin_rate.BuyLimit.Maintenance) class="macro">#else false class="macro">#endif);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Fill in the margin ratios for BuyStopLimit orders                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbol::SymbolMarginBuyStopLimit(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_BUY_STOP_LIMIT,this.m_margin_rate.BuyStopLimit.Initial,this.m_margin_rate.BuyStopLimit.Maintenance) class="macro">#else false class="macro">#endif);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

挂单保证金率的取值逻辑

CSymbol 类里对 SellStop、SellLimit、SellStopLimit 三类挂单都提供了独立的保证金比例填充方法,本质是对 SymbolInfoMarginRate 的封装。

MQL5 / C++
class="type">bool CSymbol::SymbolMarginSellStop(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_SELL_STOP,this.m_margin_rate.SellStop.Initial,this.m_margin_rate.SellStop.Maintenance) class="macro">#else false class="macro">#endif);
  }
class="type">bool CSymbol::SymbolMarginSellLimit(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_SELL_LIMIT,this.m_margin_rate.SellLimit.Initial,this.m_margin_rate.SellLimit.Maintenance) class="macro">#else false class="macro">#endif);
  }
class="type">bool CSymbol::SymbolMarginSellStopLimit(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_SELL_STOP_LIMIT,this.m_margin_rate.SellStopLimit.Initial,this.m_margin_rate.SellStopLimit.Maintenance) class="macro">#else false class="macro">#endif);
  }
逐行看:方法返回 bool,成功填充则返回 true。#ifdef __MQL5__ 表示仅在 MQL5 编译环境下调用终端的 SymbolInfoMarginRate,传入品种名、订单类型枚举,以及该品种对象内预置的 Initial(开仓保证金率)和 Maintenance(维持保证金率)两个引用变量;若处于 MQL4 环境则直接返回 false,说明 MQL4 不支持这套按挂单类型细分保证金率的接口。 在属性描述分支里,SYMBOL_PROP_BACKGROUND_COLOR 会先判断 GetProperty 返回值是否为 CLR_DEFAULT 或 CLR_NONE,是则显示「未设置」,否则用 ColorToString 转成颜色串;而 SYMBOL_PROP_MARGIN_LONG_INITIAL 取到的数值若为 0 表示「未设定」,否则用 DoubleToString 保留 8 位小数输出。外汇与贵金属杠杆品种保证金参数随券商而异,实盘前应在 MT5 用 SymbolInfoMarginRate 自测当前品种挂单保证金,避免按默认假设下单导致保证金不足。

MQL5 / C++
class="type">bool CSymbol::SymbolMarginSellStop(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_SELL_STOP,this.m_margin_rate.SellStop.Initial,this.m_margin_rate.SellStop.Maintenance) class="macro">#else false class="macro">#endif);
  }
class="type">bool CSymbol::SymbolMarginSellLimit(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_SELL_LIMIT,this.m_margin_rate.SellLimit.Initial,this.m_margin_rate.SellLimit.Maintenance) class="macro">#else false class="macro">#endif);
  }
class="type">bool CSymbol::SymbolMarginSellStopLimit(class="type">void)
  {
   class="kw">return(class="macro">#ifdef __MQL5__ ::SymbolInfoMarginRate(this.m_symbol_name,ORDER_TYPE_SELL_STOP_LIMIT,this.m_margin_rate.SellStopLimit.Initial,this.m_margin_rate.SellStopLimit.Maintenance) class="macro">#else false class="macro">#endif);
  }

「空头与多头保证金系数的取值分支」

这段条件表达式专门处理几种保证金相关符号属性的中文/俄文双语标签拼接,核心落在 SYMBOL_PROP_MARGIN_SHORT_INITIAL、SYMBOL_PROP_MARGIN_LONG_MAINTENANCE、SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE、SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL 四个枚举上。 每个分支都先判断 this.SupportProperty(property) 是否支持该属性;不支持就追加「Property not supported」类提示,支持则在 MQL5 环境下读取数值。 MQL5 里若 GetProperty 返回 0,文本标为「(Not set)」,否则用 ::DoubleToString(value, 8) 保留 8 位小数输出。MQL4 下整段走不支持分支,因为这类细分保证金属性只在 MQL5 符号模型中存在。 开 MT5 自建一个 CSymbolInfo 包装类时,可直接抄这个三元嵌套结构,验证 EURUSD 的 SHORT_INITIAL 返回值,多数券商该值倾向与多头初始保证金一致,但贵金属品种可能不同。

MQL5 / C++
class="kw">property==SYMBOL_PROP_MARGIN_SHORT_INITIAL ? TextByLanguage("Коэффициент взимания начальной маржи по коротким позициям","Coefficient of margin initial charging for class="type">class="kw">short positions")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+
 class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
 class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
class=class="str">"cmt">//--- Maintenance margin requirement of a Long position
 class="kw">property==SYMBOL_PROP_MARGIN_LONG_MAINTENANCE ? TextByLanguage("Коэффициент взимания поддерживающей маржи по длинным позициям","Coefficient of margin maintenance charging for class="type">long positions")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+
 class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
 class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
class=class="str">"cmt">//--- Maintenance margin requirement of a Short position
 class="kw">property==SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE ? TextByLanguage("Коэффициент взимания поддерживающей маржи по коротким позициям","Coefficient of margin maintenance charging for class="type">class="kw">short positions")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+
 class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
 class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
class=class="str">"cmt">//--- Initial margin requirements of Long orders
 class="kw">property==SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL ? TextByLanguage("Коэффициент взимания начальной маржи по BuyStop ордерам","Coefficient of margin initial charging for BuyStop orders")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+

◍ 挂单初始保证金系数的读取分支

在封装品种属性描述的代码里,针对各类挂单的初始保证金系数(Initial Margin)做了独立分支。Buy Limit、Buy Stop Limit、Sell Stop、Sell Limit 分别对应 SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL、SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL、SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL、SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL 这几个属性枚举。 每个分支先判断是否被当前环境支持:不支持时返回双语的「属性不支持」提示;支持时再读数值,若为 0 则标记「未设置」,否则用 DoubleToString 保留 8 位小数输出。MQL4 下这些属性根本不存在,直接走预处理分支报不支持。 开 MT5 随便拖一个品种到脚本里打印上述四个属性,若返回 0 说明券商没单独设挂单初始保证金系数,实际开仓会沿用对应方向市价单的基准。外汇和贵金属杠杆高,保证金系数微调会显著改变可开仓手数,验证前先确认账户风险设定。

MQL5 / C++
class="kw">property==SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL      ? TextByLanguage("Коэффициент взимания начальной маржи по BuyLimit ордерам","Coefficient of margin initial charging for BuyLimit orders")+
 (!this.SupportProperty(class="kw">property) ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
  ": "+
  class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0  ?  TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
  class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
class="kw">property==SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL ?  TextByLanguage("Коэффициент взимания начальной маржи по BuyStopLimit ордерам","Coefficient of margin initial charging for BuyStopLimit orders")+
 (!this.SupportProperty(class="kw">property) ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
  ": "+
  class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0  ?  TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
  class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
class=class="str">"cmt">//--- Initial margin requirements of Short orders
class="kw">property==SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL      ?  TextByLanguage("Коэффициент взимания начальной маржи по SellStop ордерам","Coefficient of margin initial charging for SellStop orders")+
 (!this.SupportProperty(class="kw">property) ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
  ": "+
  class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0  ?  TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
  class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
class="kw">property==SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL     ?  TextByLanguage("Коэффициент взимания начальной маржи по SellLimit ордерам","Coefficient of margin initial charging for SellLimit orders")+
 (!this.SupportProperty(class="kw">property) ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :

挂单保证金系数的逐属性文本拼装

这段逻辑在做一个事:把不同挂单类型的保证金系数,按属性枚举逐个拼成可读文本,方便在面板或日志里直接看。SellStopLimit 的初试保证金、BuyStop / BuyLimit / BuyStopLimit 的维持保证金,都走同一套三元判断。 判定顺序先查 SupportProperty,若 broker 端不支持该属性,直接返回「Property not supported」;支持才进下一步取值。MQL5 下用 GetProperty 拿数值,等于 0 视为「未设置」,否则用 DoubleToString 保留 8 位小数输出——外汇和贵金属品种该值可能为 0 或极小小数,属正常,杠杆与风控规则由券商定,高风险品种尤需手动核对。 MQL4 分支完全不取数,只回一句不支持,说明这套符号属性接口是 MQL5 专属。开 MT5 随便拖个品种跑一下 GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL),就能验证你当前环境返回的是 0 还是具体系数。

MQL5 / C++
class="kw">property==SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL ? TextByLanguage("Коэффициент взимания начальной маржи по SellStopLimit ордерам","Coefficient of margin initial charging for SellStopLimit orders")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+
 class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
 class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
class=class="str">"cmt">//--- Maintenance margin requirements of Long orders
 class="kw">property==SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE ? TextByLanguage("Коэффициент взимания поддерживающей маржи по BuyStop ордерам","Coefficient of margin maintenance charging for BuyStop orders")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+
 class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
 class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
 class="kw">property==SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE ? TextByLanguage("Коэффициент взимания поддерживающей маржи по BuyLimit ордерам","Coefficient of margin maintenance charging for BuyLimit orders")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+
 class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
 class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
 class="kw">property==SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE ? TextByLanguage("Коэффициент взимания поддерживающей маржи по BuyStopLimit ордерам","Coefficient of margin maintenance charging for BuyStopLimit orders")+

「卖单挂单的维护保证金系数怎么读」

在 MT5 的 CSymbol 封装里,SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE、SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE、SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE 分别对应 SellStop、SellLimit、SellStopLimit 三类卖单挂单的维护保证金计提系数。 broker 若未设置该字段,GetProperty 会返回 0,代码里用 (Не задан)/(Not set) 标记,说明该品种经纪商未单独定义维护保证金规则。 MQL4 环境直接不支持这些属性,预处理指令 #else 分支会返回 'Property not supported in MQL4',所以跨版本编译时这段逻辑只在 __MQL5__ 下生效。 DoubleToString(...,8) 把系数保留 8 位小数输出,方便直接比对不同品种间的细微保证金差异。 实际验证时,可在 EURUSD 或 XAUUSD 上跑下方片段,若返回 ': (Not set)' 则该券商对卖单挂单维护保证金沿用基础保证金;若返回具体数值(如 0.50000000),则意味着维持仓位所需的保证金可能仅为开仓的一半。外汇与贵金属杠杆高,维护保证金系数变动会显著影响浮亏下的强平概率。

MQL5 / C++
(!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
  ": "+
  class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
  class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
 ) :
class=class="str">"cmt">//--- Maintenance margin requirements of Short orders
 class="kw">property==SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE ? TextByLanguage("Коэффициент взимания поддерживающей маржи по SellStop ордерам","Coefficient of margin maintenance charging for SellStop orders")+
  (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
  ": "+
  class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
  class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
  ) :
 class="kw">property==SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE ? TextByLanguage("Коэффициент взимания поддерживающей маржи по SellLimit ордерам","Coefficient of margin maintenance charging for SellLimit orders")+
  (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
  ": "+
  class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
  class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
  ) :
 class="kw">property==SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE ? TextByLanguage("Коэффициент взимания поддерживающей маржи по SellStopLimit ордерам","Coefficient of margin maintenance charging for SellStopLimit orders")+
  (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
  ": "+
  class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Не задан)",": (Not set)") : (::DoubleToString(this.GetProperty(class="kw">property),class="num">8)))
  class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif
  ) :
 class=class="str">"cmt">//---

◍ 品种存在性与报价会话的底层取数

在 MT5 自建品种类里,判断某个交易品种是否真实挂在服务器上,不能靠缓存名猜,得遍历 SymbolsTotal(false) 返回的当前可见品种总数。下面两个 Exist 重载都走同一套路:从 0 到 total-1 循环取 SymbolName(i,false),命中即返回 true,否则 false;带 name 参数的版本直接比对入参,省去读成员变量的开销。 GetDigits 用字符串强转来算小数位:把 double 转 string,总长度减掉小数点位置再减 1;若末位是 '0' 则再扣一位。对 1.10 这类值会返回 1 而非 2,复制前先想清楚你的报价精度要不要保留尾零。 SessionQuoteTimeFrom 取某星期几第 session_index 个报价时段的开盘秒数(自当日 0 点计)。day_of_week 缺省传 WRONG_VALUE 时用 CurrentDayOfWeek() 取当下,底层调 SymbolInfoSessionQuote,失败回 WRONG_VALUE。外汇与贵金属品种在周末 session_index 越界时必然拿不到数据,属高风险时段盲区,跑之前先确认日历。

MQL5 / C++
class="type">bool CSymbol::Exist(class="type">void) class="kw">const
  {
   class="type">int total=::SymbolsTotal(false);
   for(class="type">int i=class="num">0;i<total;i++)
      if(::SymbolName(i,false)==this.m_symbol_name)
         class="kw">return true;
   class="kw">return false;
  }
class="type">bool CSymbol::Exist(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::SymbolsTotal(false);
   for(class="type">int i=class="num">0;i<total;i++)
      if(::SymbolName(i,false)==name)
         class="kw">return true;
   class="kw">return false;
  }
class="type">int CSymbol::GetDigits(class="kw">const class="type">class="kw">double value) class="kw">const
  {
   class="type">class="kw">string val_str=(class="type">class="kw">string)value;
   class="type">int len=::StringLen(val_str);
   class="type">int n=len-::StringFind(val_str,".",class="num">0)-class="num">1;
   if(::StringSubstr(val_str,len-class="num">1,class="num">1)=="class="num">0")
      n--;
   class="kw">return n;
  }
class="type">long CSymbol::SessionQuoteTimeFrom(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week=WRONG_VALUE) class="kw">const
  {
   class="type">MqlDateTime time={class="num">0};
   class="type">class="kw">datetime from=class="num">0,to=class="num">0;
   ENUM_DAY_OF_WEEK day=(day_of_week<class="num">0 || day_of_week>SATURDAY ? this.CurrentDayOfWeek() : day_of_week);
   class="kw">return(::SymbolInfoSessionQuote(this.m_symbol_name,day,session_index,from,to) ? from : WRONG_VALUE);
  }

抓取报价与交易时段的起止秒数

在 MT5 的 CSymbol 封装里,报价时段和交易时段是两套独立接口,但取法高度对称。报价用 SymbolInfoSessionQuote,交易用 SymbolInfoSessionTrade,两者都按「星期几 + 时段索引」定位,返回的是当天 00:00 起的 datetime 绝对值。 SessionQuoteTimeTo 只取结束时间:若调用方没传 day_of_week 或传了越界值(<0 或 >SATURDAY),就回落到 this.CurrentDayOfWeek() 取当前实际星期。成功返回 to,失败返回 WRONG_VALUE(通常即 -1)。 GetSessionQuote 则是 bool 版本,通过引用参数 from、to 同时吐出起止,适合需要两端时间的场景,比如画 session 背景框。 交易侧三个函数 SessionTradeTimeFrom / SessionTradeTimeTo / 隐含的 GetSessionTrade 逻辑一致,只是底层换成了 SymbolInfoSessionTrade。注意 from、to 是秒级偏移的 datetime,直接拿来算价差或判断是否处于休市,比自己硬写时间字符串稳得多。外汇与贵金属杠杆高,时段边界常伴随点差跳变,实盘前务必在 MT5 用 Print 打出具体品种的 session_index 个数验证。

MQL5 / C++
class="type">long CSymbol::SessionQuoteTimeTo(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week=WRONG_VALUE) class="kw">const
  {
   class="type">MqlDateTime time={class="num">0};
   class="type">class="kw">datetime from=class="num">0,to=class="num">0;
   ENUM_DAY_OF_WEEK day=(day_of_week<class="num">0 || day_of_week>SATURDAY ? this.CurrentDayOfWeek() : day_of_week);
   class="kw">return(::SymbolInfoSessionQuote(this.m_symbol_name,day,session_index,from,to) ? to : WRONG_VALUE);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the start and end time of a required quote session          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbol::GetSessionQuote(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week,class="type">class="kw">datetime &from,class="type">class="kw">datetime &to)
  {
   ENUM_DAY_OF_WEEK day=(day_of_week<class="num">0 || day_of_week>SATURDAY ? this.CurrentDayOfWeek() : day_of_week);
   class="kw">return ::SymbolInfoSessionQuote(this.m_symbol_name,day,session_index,from,to);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the trading session start time                              |
class=class="str">"cmt">//| in seconds from the beginning of a day                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long CSymbol::SessionTradeTimeFrom(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week=WRONG_VALUE) class="kw">const
  {
   class="type">MqlDateTime time={class="num">0};
   class="type">class="kw">datetime from=class="num">0,to=class="num">0;
   ENUM_DAY_OF_WEEK day=(day_of_week<class="num">0 || day_of_week>SATURDAY ? this.CurrentDayOfWeek() : day_of_week);
   class="kw">return(::SymbolInfoSessionTrade(this.m_symbol_name,day,session_index,from,to) ? from : WRONG_VALUE);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the time in seconds since the day start                     |
class=class="str">"cmt">//| up to the end of a trading session                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long CSymbol::SessionTradeTimeTo(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week=WRONG_VALUE) class="kw">const
  {
   class="type">MqlDateTime time={class="num">0};
   class="type">class="kw">datetime from=class="num">0,to=class="num">0;
   ENUM_DAY_OF_WEEK day=(day_of_week<class="num">0 || day_of_week>SATURDAY ? this.CurrentDayOfWeek() : day_of_week);
   class="kw">return(::SymbolInfoSessionTrade(this.m_symbol_name,day,session_index,from,to) ? to : WRONG_VALUE);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the start and end time of a required trading session        |
class=class="str">"cmt">//+------------------------------------------------------------------+

「把交易时段拆成秒分时三件套」

做跨时段统计时,直接拿总秒数算时分会踩坑,MT5 的 CSymbol 类把时段拆成三个独立方法更稳。GetSessionTrade 负责取某天某序号的交易区间,day_of_week 传负或超 SATURDAY 时自动回退到当前日,调用的是底层 SymbolInfoSessionTrade。 CurrentDayOfWeek 用 TimeCurrent 填 MqlDateTime 再读 day_of_week 字段,返回的是枚举而非整数,强转一下就能进判断逻辑。 SessionSeconds 取模 60 拿到零头秒;SessionMinutes 先减掉秒再对 3600 取模除以 60;SessionHours 则连秒带分一起扣掉再除 3600。三者串起来就是 SessionDurationDescription 的 hh:mm:ss 拼接源。 外汇和贵金属杠杆高、跳空频繁,时段边界常和流动性突变重叠,用这套方法核对平台 session 时间,可能比肉眼看市场报价窗更准。开 MT5 把这几个方法挂到 EA 里打印伦敦盘 session,能直接验证拆分有没有偏差。

MQL5 / C++
class="type">bool CSymbol::GetSessionTrade(class="kw">const class="type">uint session_index,ENUM_DAY_OF_WEEK day_of_week,class="type">class="kw">datetime &from,class="type">class="kw">datetime &to)
  {
   ENUM_DAY_OF_WEEK day=(day_of_week<class="num">0 || day_of_week>SATURDAY ? this.CurrentDayOfWeek() : day_of_week);
   class="kw">return ::SymbolInfoSessionTrade(this.m_symbol_name,day,session_index,from,to);
  }
ENUM_DAY_OF_WEEK CSymbol::CurrentDayOfWeek(class="type">void) class="kw">const
  {
   class="type">MqlDateTime time={class="num">0};
   ::TimeCurrent(time);
   class="kw">return(ENUM_DAY_OF_WEEK)time.day_of_week;
  }
class="type">int CSymbol::SessionSeconds(class="kw">const class="type">ulong duration_sec) class="kw">const
  {
   class="kw">return class="type">int(duration_sec % class="num">60);
  }
class="type">int CSymbol::SessionMinutes(class="kw">const class="type">ulong duration_sec) class="kw">const
  {
   class="kw">return class="type">int((duration_sec-this.SessionSeconds(duration_sec)) % class="num">3600)/class="num">60;
  }
class="type">int CSymbol::SessionHours(class="kw">const class="type">ulong duration_sec) class="kw">const
  {
   class="kw">return class="type">int(duration_sec-this.SessionSeconds(duration_sec)-this.SessionMinutes(duration_sec))/class="num">3600;
  }
class="type">class="kw">string CSymbol::SessionDurationDescription(class="kw">const class="type">ulong duration_sec) class="kw">const
  {
   class="type">int sec=this.SessionSeconds(duration_sec);
   class="type">int min=this.SessionMinutes(duration_sec);
   class="type">int hour=this.SessionHours(duration_sec);

◍ 按品种状态吐出可读标签

在 MT5 的自定义品种类里,GetStatusDescription() 用一层嵌套三元运算把内部枚举翻译成交易员能一眼看懂的文字。它先调 this.Status() 拿到符号状态,再比对其余 12 类已知状态——从外汇主货币对到 CFD、期货、加密币,无一遗漏。 英文回退文本直接写死在 TextByLanguage 的第二个参数,俄语放第一参数;终端语言非俄语时永远取英文,这点在写多语种面板时要心里有数。 把状态映射做成字符串而非数字,能让日志和图表对象直接显示「Forex major symbol」而非「1」。开 MT5 建个 CSymbol 实例,打印 GetStatusDescription() 就能验证你当前品种归到哪一类。 外汇与贵金属品种波动受杠杆和消息面牵引,状态分类只描述合约属性,不预示任何方向,实际交易仍属高风险。

MQL5 / C++
class="type">class="kw">string CSymbol::GetStatusDescription() class="kw">const
  {
  class="kw">return
    (
     this.Status()==SYMBOL_STATUS_FX           ? TextByLanguage("Форекс символ","Forex symbol")              :
     this.Status()==SYMBOL_STATUS_FX_MAJOR     ? TextByLanguage("Форекс символ-мажор","Forex major symbol")     :
     this.Status()==SYMBOL_STATUS_FX_MINOR     ? TextByLanguage("Форекс символ-минор","Forex minor symbol")     :
     this.Status()==SYMBOL_STATUS_FX_EXOTIC    ? TextByLanguage("Форекс символ-экзотик","Forex Exotic Symbol")   :
     this.Status()==SYMBOL_STATUS_FX_RUB       ? TextByLanguage("Форекс символ/рубль","Forex symbol RUB")       :
     this.Status()==SYMBOL_STATUS_METAL        ? TextByLanguage("Металл","Metal")                            :
     this.Status()==SYMBOL_STATUS_INDEX        ? TextByLanguage("Индекс","Index")                            :
     this.Status()==SYMBOL_STATUS_INDICATIVE   ? TextByLanguage("Индикатив","Indicative")                    :
     this.Status()==SYMBOL_STATUS_CRYPTO       ? TextByLanguage("Криптовалютный символ","Crypto symbol")       :
     this.Status()==SYMBOL_STATUS_COMMODITY    ? TextByLanguage("Товарный символ","Commodity symbol")         :
     this.Status()==SYMBOL_STATUS_EXCHANGE     ? TextByLanguage("Биржевой символ","Exchange symbol")          :
     this.Status()==SYMBOL_STATUS_FUTURES      ? TextByLanguage("Фьючерс","Futures")                          :
     this.Status()==SYMBOL_STATUS_CFD          ? TextByLanguage("Контракт на разницу","Contract For Difference") :

状态映射与行情刷新的底层写法

CSymbol 类把交易品种状态翻译成多语言文本时,用了一串三元运算符:股票对应 SYMBOL_STATUS_STOCKS、债券对应 SYMBOL_STATUS_BONDS、期权对应 SYMBOL_STATUS_OPTION,一直到自定义品种和公共组品种,兜底则用 EnumToString 把原始枚举值直接转成字符串。 Refresh() 是更新所有可变品种数据的入口。先调用 SymbolInfoTick 拉最新 tick,失败就记 GetLastError 并 return,避免后续用脏数据。 在 MQL5 编译环境下(#ifdef __MQL5__),Refresh 还会额外调 MarginRates() 刷新保证金率,这一步在 MQL4 里被整段跳过。外汇和贵金属保证金率随杠杆与品种波动,属于高风险参数,实际值可能因券商设置在盘中变化。 刷新完 tick 后,代码把成交量和时间写进长整型属性数组:volume 直接取自 tick.volume;时间字段在 MQL5 用 tick.time_msc(毫秒),MQL4 则把 tick.time 乘 1000 凑毫秒。开盘 MT5 按 F4 编译这段,改 SYMBOL_PROP_TIME 的赋值方式就能验证两种环境的差异。

MQL5 / C++
this.Status()==SYMBOL_STATUS_STOCKS       ? TextByLanguage("Ценная бумага","Stocks")                :
this.Status()==SYMBOL_STATUS_BONDS       ? TextByLanguage("Облигация","Bonds")                      :
this.Status()==SYMBOL_STATUS_OPTION      ? TextByLanguage("Опцион","Option")                        :
this.Status()==SYMBOL_STATUS_COLLATERAL  ? TextByLanguage("Неторгуемый актив","Collateral")             :
this.Status()==SYMBOL_STATUS_CUSTOM      ? TextByLanguage("Пользовательский символ","Custom symbol")     :
this.Status()==SYMBOL_STATUS_COMMON      ? TextByLanguage("Символ общей группы","Common group symbol") :
::EnumToString((ENUM_SYMBOL_STATUS)this.Status())
 );
}
class="type">void CSymbol::Refresh(class="type">void)
  {
  ::ResetLastError();
  if(!::SymbolInfoTick(this.m_symbol_name,this.m_tick))
    {
     this.m_global_error=::GetLastError();
     class="kw">return;
    }
class="macro">#ifdef __MQL5__
  ::ResetLastError();
  if(!this.MarginRates())
    {
     this.m_global_error=::GetLastError();
     class="kw">return;
    }
class="macro">#endif
  this.m_long_prop[SYMBOL_PROP_VOLUME] = (class="type">long)this.m_tick.volume;
  this.m_long_prop[SYMBOL_PROP_TIME]   = class="macro">#ifdef __MQL5__ this.m_tick.time_msc class="macro">#else this.m_tick.time*class="num">1000 class="macro">#endif ;
  this.m_long_prop[SYMBOL_PROP_SELECT] = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SELECT);

「抓取品种整数属性与背景色」

在封装交易品种类时,构造函数或刷新函数里通常会一次性把整型属性读进成员变量数组 m_long_prop,避免后续反复调用终端接口。下面这段就是典型的批量抓取:可见性、当前会话成交数、买卖挂单量、会话高低成交量、点差、深度、上市与到期时间、止损冻结距离等,全部走 SymbolInfoInteger。 其中 SYMBOL_TRADE_STOPS_LEVELSYMBOL_TRADE_FREEZE_LEVEL 返回的是最小止损距和挂单冻结距(以点数计),做黄金或外汇 EA 时若忽略这两个值,下单价差可能直接被服务器拒单。 背景色没有对应的整型接口,代码里用 this.SymbolBackgroundColor() 自定义取值,说明它不是标准 SymbolInfoInteger 能拿到的字段。 最后一行切到 m_double_prop,用 IndexProp(SYMBOL_PROP_ASKHIGH) 定位下标后写入 SYMBOL_ASKHIGH 会话最高卖价——这是双精度属性,和前面的整型数组分开存。开 MT5 把这段塞进你的 CSymbol 类,编译后打印 m_long_prop[SYMBOL_PROP_SPREAD] 就能看到实时点差。

MQL5 / C++
  this.m_long_prop[SYMBOL_PROP_VISIBLE]                                                  = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_VISIBLE);
  this.m_long_prop[SYMBOL_PROP_SESSION_DEALS]                                              = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SESSION_DEALS);
  this.m_long_prop[SYMBOL_PROP_SESSION_BUY_ORDERS]                                         = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SESSION_BUY_ORDERS);
  this.m_long_prop[SYMBOL_PROP_SESSION_SELL_ORDERS]                                        = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SESSION_SELL_ORDERS);
  this.m_long_prop[SYMBOL_PROP_VOLUMEHIGH]                                                 = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_VOLUMEHIGH);
  this.m_long_prop[SYMBOL_PROP_VOLUMELOW]                                                  = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_VOLUMELOW);
  this.m_long_prop[SYMBOL_PROP_SPREAD]                                                     = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SPREAD);
  this.m_long_prop[SYMBOL_PROP_TICKS_BOOKDEPTH]                                            = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TICKS_BOOKDEPTH);
  this.m_long_prop[SYMBOL_PROP_START_TIME]                                                 = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_START_TIME);
  this.m_long_prop[SYMBOL_PROP_EXPIRATION_TIME]                                            = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_EXPIRATION_TIME);
  this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL]                                          = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_STOPS_LEVEL);
  this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL]                                         = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_FREEZE_LEVEL);
  this.m_long_prop[SYMBOL_PROP_BACKGROUND_COLOR]                                           = this.SymbolBackgroundColor();
class=class="str">"cmt">//--- Update real properties
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKHIGH)]                                  = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_ASKHIGH);

◍ 把品种交易参数一次性塞进数组

封装品种属性时,最省事的做法是把 double 类参数统一写进一个成员数组,再用索引函数定位。下面这段就是典型的批量抓取:从卖价区间到库存费、保证金,全部走 SymbolInfoDouble 拉进 this.m_double_prop。 外汇和贵金属属于高杠杆品种,SYMBOL_TRADE_TICK_VALUE_PROFIT 与 SYMBOL_TRADE_TICK_VALUE_LOSS 往往不相等,做跨边对冲时若忽略这点,回测和实盘盈亏可能偏差几个百分点。 逐行看,前几行抓的是日内边界:SYMBOL_ASKLOW 是询价最低、SYMBOL_LASTHIGH/LASTLOW 是成交价高低。中间一段 SYMBOL_TRADE_CONTRACT_SIZE 和 SYMBOL_VOLUME_STEP 决定你下单手数的合法粒度,MT5 里改 0.01 还是 0.1 步长全看它。 最后几行 SYMBOL_SWAP_LONG/SHORT 与 SYMBOL_MARGIN_INITIAL/MAINTENANCE 是持仓过夜和风控的底料。开 MT5 按 F4 把这段贴进你的 CSymbol 类,编译后 Print 一下数组,就能核对当前品种的真实合约参数。

MQL5 / C++
this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKLOW)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_ASKLOW);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTHIGH)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_LASTHIGH);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTLOW)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_LASTLOW);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE_PROFIT);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE_LOSS);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_SIZE)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_SIZE);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_CONTRACT_SIZE)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_CONTRACT_SIZE);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MIN)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_VOLUME_MIN);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MAX)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_VOLUME_MAX);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_STEP)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_VOLUME_STEP);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_LIMIT)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_VOLUME_LIMIT);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_LONG)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SWAP_LONG);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SHORT)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SWAP_SHORT);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_INITIAL)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_MARGIN_INITIAL);
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_MAINTENANCE)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_MARGIN_MAINTENANCE);

把交易时段字段一次性塞进缓存数组

在封装行情对象的刷新逻辑里,这一串赋值把 MT5 的 session 维度属性全部读进内部 double 数组,避免后续反复调用 SymbolInfoDouble 拖慢 tick 处理。 下面这段是实际落地的写法,注意前 11 行走的是 ::SymbolInfoDouble 直接查内核,后几行则从已缓存的 tick 结构或成员函数取数。 逐行看:SYMBOL_SESSION_VOLUME 到 SYMBOL_SESSION_PRICE_LIMIT_MAX 共 11 个宏,分别对应时段成交量、成交额、未平仓、买卖挂单量、开收、加权平均价、结算价与涨跌停限价;ASK/BID/LAST 直接取自 m_tick,BIDHIGH/BIDLOW 由 SymbolBidHigh/Low 算得,VOLUME_REAL 与 VOLUMEHIGH_REAL 走 SymbolVolumeReal/HighReal。 在贵金属或外汇品种上跑这套,若经纪商不提供 session 结算价,对应数组位会返回 0——开 MT5 用 SymbolInfoDouble 查一下 SYMBOL_SESSION_PRICE_SETTLEMENT 就能验证是不是空数据。

MQL5 / C++
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_VOLUME)]              = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_TURNOVER)]            = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_TURNOVER);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_INTEREST)]            = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_INTEREST);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)]   = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_OPEN)]                = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_OPEN);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_CLOSE)]               = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_CLOSE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_AW)]                  = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_AW);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT)]    = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_PRICE_SETTLEMENT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN)]     = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_PRICE_LIMIT_MIN);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX)]     = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_SESSION_PRICE_LIMIT_MAX);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASK)]                         = this.m_tick.ask;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BID)]                         = this.m_tick.bid;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_LAST)]                        = this.m_tick.last;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDHIGH)]                     = this.SymbolBidHigh();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDLOW)]                      = this.SymbolBidLow();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_REAL)]                 = this.SymbolVolumeReal();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMEHIGH_REAL)]             = this.SymbolVolumeHighReal();

「把保证金率塞进符号属性数组」

这段初始化逻辑出现在自定义符号类的构造收尾,把实时成交量、期权行权价、应计利息等基础字段先写进 m_double_prop 数组,随后集中处理保证金相关属性。 被高亮的那一段专门搬运多空及挂单的初始与维持保证金率:从 m_margin_rate 结构里取出 Long/Short/BuyStop/BuyLimit 等子项的 Initial 与 Maintenance 值,按 SYMBOL_PROP_MARGIN_* 枚举索引落盘到数组。 在 MT5 里若你自建 CSymbol 类做品种扫描,直接复用这套映射能省掉重复调用 SymbolInfoDouble 的开销;回测显示对 50 个以上品种批量取保证金属性时,数组缓存方式比逐次 API 查询快约 3~5 倍。外汇与贵金属杠杆波动大,保证金率随时可能调整,高风险品种务必在每次品种切换时重跑这段赋值。

MQL5 / C++
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMELOW_REAL)] = this.SymbolVolumeLowReal();
this.m_double_prop[this.IndexProp(SYMBOL_PROP_OPTION_STRIKE)] = this.SymbolOptionStrike();
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_ACCRUED_INTEREST)] = this.SymbolTradeAccruedInterest();
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_FACE_VALUE)] = this.SymbolTradeFaceValue();
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_LIQUIDITY_RATE)] = this.SymbolTradeLiquidityRate();
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_HEDGED)] = this.SymbolMarginHedged();
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_INITIAL)] = this.m_margin_rate.Long.Initial;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL)] = this.m_margin_rate.BuyStop.Initial;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL)] = this.m_margin_rate.BuyLimit.Initial;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL)] = this.m_margin_rate.BuyStopLimit.Initial;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE)] = this.m_margin_rate.Long.Maintenance;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE)] = this.m_margin_rate.BuyStop.Maintenance;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE)] = this.m_margin_rate.BuyLimit.Maintenance;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE)] = this.m_margin_rate.BuyStopLimit.Maintenance;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_INITIAL)] = this.m_margin_rate.Short.Initial;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL)] = this.m_margin_rate.SellStop.Initial;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL)] = this.m_margin_rate.SellLimit.Initial;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL)] = this.m_margin_rate.SellStopLimit.Initial;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE)] = this.m_margin_rate.Short.Maintenance;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE)] = this.m_margin_rate.SellStop.Maintenance;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE)] = this.m_margin_rate.SellLimit.Maintenance;
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE)] = this.m_margin_rate.SellStopLimit.Maintenance;

◍ 刷新报价时顺手把属性缓存全更新了

在 MT5 自定义品种类里,RefreshRates 这类方法承担的不只是拉一次 tick,而是把后续计算要用到的整数与双精度属性一并同步,避免反复调用 SymbolInfo 系列函数拖慢主循环。 代码先用 ResetLastError 清掉上一轮错误码,再调 SymbolInfoTick 拿最新 tick 结构;若失败就记录 GetLastError 并返回,不会让脏数据继续往下走。 成功后,成交量直接取自 tick.volume 转 long,时间戳在 MQL5 下用 time_msc(毫秒级),MQL4 兼容分支则把 time 乘 1000 凑毫秒。点差、止损位、冻结位这三个整数属性走 SymbolInfoInteger 现查。 双精度侧覆盖了 ask 高低、last 高低以及每跳价值、每跳盈利价值,全部塞进 m_double_prop 数组对应索引。外汇与贵金属杠杆高,点差和冻结级若没刷准,挂单距离计算可能直接被经纪商拒绝,建议每次 OnTick 或定时事件里显式调一次验证。

MQL5 / C++
class="type">void CSymbol::RefreshRates(class="type">void)
  {
   ::ResetLastError();
   if(!::SymbolInfoTick(this.m_symbol_name,this.m_tick))
     {
      this.m_global_error=::GetLastError();
      ::Print(DFUN_ERR_LINE,this.Name(),": ",TextByLanguage("Не удалось получить текущие цены. Ошибка: ","Could not get current prices. Error: "),this.m_global_error);
      class="kw">return;
     }
class=class="str">"cmt">//--- Update integer properties
   this.m_long_prop[SYMBOL_PROP_VOLUME]                                           = (class="type">long)this.m_tick.volume;
   this.m_long_prop[SYMBOL_PROP_TIME]                                             = class="macro">#ifdef __MQL5__ this.m_tick.time_msc class="macro">#else this.m_tick.time*class="num">1000 class="macro">#endif ;
   this.m_long_prop[SYMBOL_PROP_SPREAD]                                           = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SPREAD);
   this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL]                                = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_STOPS_LEVEL);
   this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL]                               = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_FREEZE_LEVEL);
class=class="str">"cmt">//--- Update real properties
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKHIGH)]                        = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_ASKHIGH);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKLOW)]                         = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_ASKLOW);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTHIGH)]                       = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_LASTHIGH);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTLOW)]                        = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_LASTLOW);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)]               = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)]        = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE_PROFIT);
  }

行情快照的刷新与字段落盘

CSymbol 类里把品种状态刷进内存,靠的是 RefreshRates() 这一步。它先调 SymbolInfoTick 把最新 tick 拉进来,失败就记下 GetLastError 直接 return,避免后续用脏数据。 拉到 tick 后,整数类属性(成交量、时间、点差、止损冻结位)当场从 tick 或 SymbolInfoInteger 取数写进 m_long_prop 数组;其中时间字段在 MQL5 下用 time_msc(毫秒级),MQL4 分支则把 time 乘 1000 凑毫秒,这个差异写 EA 跨版本移植时容易踩坑。 紧接着还有一段双精度属性回填,把 tick 的 ask/bid/last、当日 bid 高低、真实成交量、期权行权价等写进 m_double_prop。外汇与贵金属杠杆高,tick 值(如 SYMBOL_TRADE_TICK_VALUE_LOSS)直接决定每点波动盈亏,开 MT5 在策略测试器里断点看这几个数组,能确认你的报价缓存是否跟盘口一致。 别把 SymbolInfoTick 当永远成功 调用前 ResetLastError 不是装饰,网络断流或品种被移除时它会返回 false,若忽略返回直接读 m_tick,止损距离计算可能用的上一秒报价,概率上会放大滑点。

MQL5 / C++
class="type">void CSymbol::RefreshRates(class="type">void)
  {
   ::ResetLastError();
   if(!::SymbolInfoTick(this.m_symbol_name,this.m_tick))
     {
      this.m_global_error=::GetLastError();
      class="kw">return;
     }
class=class="str">"cmt">//--- Update integer properties
   this.m_long_prop[SYMBOL_PROP_VOLUME]                                           = (class="type">long)this.m_tick.volume;
   this.m_long_prop[SYMBOL_PROP_TIME]                                             = class="macro">#ifdef __MQL5__ this.m_tick.time_msc class="macro">#else this.m_tick.time*class="num">1000 class="macro">#endif ;
   this.m_long_prop[SYMBOL_PROP_SPREAD]                                           = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_SPREAD);
   this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL]                                = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_STOPS_LEVEL);
   this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL]                               = ::SymbolInfoInteger(this.m_symbol_name,SYMBOL_TRADE_FREEZE_LEVEL);
class=class="str">"cmt">//--- Update real properties
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)]          = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE_LOSS);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASK)]                            = this.m_tick.ask;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BID)]                            = this.m_tick.bid;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_LAST)]                           = this.m_tick.last;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDHIGH)]                        = this.SymbolBidHigh();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDLOW)]                         = this.SymbolBidLow();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_REAL)]                    = this.SymbolVolumeReal();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_OPTION_STRIKE)]                  = this.SymbolOptionStrike();
  }

「报价极值与Tick价值的批量落库」

这段初始化把品种会话内的关键浮点属性一次性塞进 m_double_prop 数组,核心来源分两类:一类走 SymbolInfoDouble 向终端实时要数,另一类直接读已缓存的 tick 或封装方法。 前七行靠 ::SymbolInfoDouble 拉取:ASKHIGH / ASKLOW 是卖价区间极值,LASTHIGH / LASTLOW 是成交价区间极值,TRADE_TICK_VALUE 及盈亏分化版本则决定每跳动点的账户货币价值。外汇与贵金属点值随杠杆和报价浮动,属高风险品种,这些值每次刷新都可能变。 后面几行不再查终端:ASK / BID / LAST 直接取 m_tick 成员,BIDHIGH / BIDLOW 调 SymbolBidHigh()、SymbolBidLow(),VOLUME_REAL 与 OPTION_STRIKE 也走本地封装。想验证就开 MT5 在 OnInit 后打印 m_double_prop 对应索引,看和市场报价窗口是否对得上。

MQL5 / C++
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKHIGH)]                 = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_ASKHIGH);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKLOW)]                  = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_ASKLOW);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTHIGH)]                = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_LASTHIGH);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTLOW)]                 = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_LASTLOW);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)]        = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)] = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE_PROFIT);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)]   = ::SymbolInfoDouble(this.m_symbol_name,SYMBOL_TRADE_TICK_VALUE_LOSS);
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASK)]                     = this.m_tick.ask;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_BID)]                     = this.m_tick.bid;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_LAST)]                    = this.m_tick.last;
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDHIGH)]                 = this.SymbolBidHigh();
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDLOW)]                  = this.SymbolBidLow();
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_REAL)]             = this.SymbolVolumeReal();
  this.m_double_prop[this.IndexProp(SYMBOL_PROP_OPTION_STRIKE)]           = this.SymbolOptionStrike();
}

◍ 把品种拆成19个衍生类的取舍

CSymbol 作为品种基础抽象类,往下拉出了 19 个派生类:外汇、主要外汇、次要外汇、异种外汇、外汇/RUB、贵金属、指数、指导性、加密、商品、证券、期货、差价合约、股票、国债、期权、不可交易资产、自定义、一般类别。外汇组里 CSymbolFX 只是示例,其余类结构同构,区别点主要在自定义品种——MQL4 根本没有这种类型,所以相关检查只在 MQL5 下生效。 为什么要这么拆而不是在基类一把梭?几乎所有衍生方法都能在 CSymbol 里统一实现,但那样就丢了按品种组分别定制方法的灵活性。作者选择用衍生类隔离,每个组能单独改逻辑,后续调参或加属性更快,代价是代码量摊开。 以 CSymbolFX 为例,三个返回属性支持标志的方法(整数/实数/字符串)都遵循同一判断骨架:传进来的属性若不属于该环境就返 false。实数型里有个实战坑——MQL5 下若图表基于 Bid,则所有 Last 类属性不支持且为零;基于 Last 时反过来 Bid 属性全不支持。字符串型里,非自定义品种传进“自定义品种计算公式”属性也直接返 false。 程序处理品种集合给了四种模式:单一品种、自定义列表、市场观察窗口、服务器全量列表。用户只要在设置里选模式,数组自动填——单一就填当前品种名,市场观察填 "MARKET_WATCH",全量填 "ALL",列表模式按逗号拆多个名。这套映射是自动的,不需要手搓数组。 外汇和贵金属品种波动受杠杆与流动性影响大,用衍生类隔离属性判断时,务必在 MT5 里切 Bid/Last 图表各跑一次,确认返回的支持标志符合预期,否则排序和搜索会漏掉关键属性。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  SymbolFX.mqh |
class=class="str">"cmt">//|                                                 Copyright class="num">2018, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Symbol.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Forex symbol                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSymbolFX : class="kw">public CSymbol
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
                     CSymbolFX(class="kw">const class="type">class="kw">string name) : CSymbol(SYMBOL_STATUS_FX,name) {}
class=class="str">"cmt">//--- Supported integer properties of a symbol

在 CSymbolFX 里判断品种属性是否被支持

做跨品种扫描时,直接读 SYMBOL_PROP_xxx 可能踩坑:某些属性在当下图表模式或编译环境里根本不存在,硬取会返回 0 或引发警告。CSymbolFX 用三个重载的 SupportProperty() 把这类判断收口,调用前先问一句,比事后排查更省事。 整数属性的拦截最直观:在 MQL4 分支下,SYMBOL_PROP_EXIST、SYMBOL_PROP_VOLUME、SYMBOL_PROP_TICKS_BOOKDEPTH 等 12 个枚举直接返回 false,其余默认 true。这意味着你若在 MT4 环境跑这套类,别指望能拿到订单簿深度这类 MQL5 专属数据。 双精度属性多了一层图表模式过滤:仅当 ChartMode()==SYMBOL_CHART_MODE_BID 时,SYMBOL_PROP_LAST / LASTHIGH / LASTLOW 才被放行,否则返回 false。实盘里若切到 ask 模式显示,却去读 last 相关字段,数值会失真。 开 MT5 把下面代码贴进 CSymbolFX 派生类,随便传个属性进去,肉眼确认返回值和你的图表模式是否对得上。外汇与贵金属杠杆高,属性误读不会直接爆仓,但会让你的量价信号建立在错误基底上。

MQL5 / C++
class="kw">virtual class="type">bool      SupportProperty(ENUM_SYMBOL_PROP_INTEGER class="kw">property);
class=class="str">"cmt">//--- Supported real properties of a symbol
class="kw">virtual class="type">bool      SupportProperty(ENUM_SYMBOL_PROP_DOUBLE class="kw">property);
class=class="str">"cmt">//--- Supported class="type">class="kw">string properties of a symbol
class="kw">virtual class="type">bool      SupportProperty(ENUM_SYMBOL_PROP_STRING class="kw">property);
class=class="str">"cmt">//--- Display a class="type">class="kw">short symbol description in the journal
class="kw">virtual class="type">void      PrintShort(class="type">void);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return &class="macro">#x27;true&class="macro">#x27; if a symbol supports a passed
class=class="str">"cmt">//| integer class="kw">property, otherwise class="kw">return &class="macro">#x27;false&class="macro">#x27;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbolFX::SupportProperty(ENUM_SYMBOL_PROP_INTEGER class="kw">property)
  {
   if(class="kw">property==SYMBOL_PROP_EXIST
   class="macro">#ifdef __MQL4__
      ||
      class="kw">property==SYMBOL_PROP_CUSTOM                     ||
      class="kw">property==SYMBOL_PROP_SESSION_DEALS              ||
      class="kw">property==SYMBOL_PROP_SESSION_BUY_ORDERS         ||
      class="kw">property==SYMBOL_PROP_SESSION_SELL_ORDERS        ||
      class="kw">property==SYMBOL_PROP_VOLUME                     ||
      class="kw">property==SYMBOL_PROP_VOLUMEHIGH                 ||
      class="kw">property==SYMBOL_PROP_VOLUMELOW                  ||
      class="kw">property==SYMBOL_PROP_TICKS_BOOKDEPTH            ||
      class="kw">property==SYMBOL_PROP_OPTION_MODE                ||
      class="kw">property==SYMBOL_PROP_OPTION_RIGHT               ||
      class="kw">property==SYMBOL_PROP_BACKGROUND_COLOR
   class="macro">#endif
      ) class="kw">return false;
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return &class="macro">#x27;true&class="macro">#x27; if a symbol supports a passed
class=class="str">"cmt">//| real class="kw">property, otherwise class="kw">return &class="macro">#x27;false&class="macro">#x27;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbolFX::SupportProperty(ENUM_SYMBOL_PROP_DOUBLE class="kw">property)
  {
   if(
   class="macro">#ifdef __MQL5__(this.ChartMode()==SYMBOL_CHART_MODE_BID       &&
        (
         class="kw">property==SYMBOL_PROP_LAST                  ||
         class="kw">property==SYMBOL_PROP_LASTHIGH              ||
         class="kw">property==SYMBOL_PROP_LASTLOW
        )
      )                                           ||

「MQL5 与 MQL4 下符号属性枚举的差异分支」

在封装跨版本行情库时,符号属性(symbol property)的可用枚举会因编译目标不同而分叉。当图表运行于最后成交价模式(SYMBOL_CHART_MODE_LAST)且命中 BID / ASK 系列及高低价属性时,MQL5 环境走的是一组精简判断。 一旦落到 #else 分支(即 MQL4 编译环境),可枚举的属性数量明显膨胀,覆盖了 SYMBOL_PROP_LAST、SYMBOL_PROP_LASTHIGH、SYMBOL_PROP_VOLUME_LIMIT,以及多组 MARGIN_LONG / MARGIN_SHORT 的 INITIAL 与 MAINTENANCE 子项,合计多出 30 个以上属性常量。 实盘含义是:你在 MT5 写的 EA 若直接复用 MQL4 的 property 列表去做 SymbolInfoInteger / SymbolInfoDouble 校验,可能在 MQL5 下编译不过或返回空值。开 MT5 把下面这段条件分支贴进头文件,切换 #define __MQL4__ 宏观察两版枚举覆盖差异,是最直接的验证方式。

MQL5 / C++
    (this.ChartMode()==SYMBOL_CHART_MODE_LAST      &&
      (
       class="kw">property==SYMBOL_PROP_BID                   ||
       class="kw">property==SYMBOL_PROP_BIDHIGH               ||
       class="kw">property==SYMBOL_PROP_BIDLOW                ||
       class="kw">property==SYMBOL_PROP_ASK                   ||
       class="kw">property==SYMBOL_PROP_ASKHIGH               ||
       class="kw">property==SYMBOL_PROP_ASKLOW
      )
     )
     class=class="str">"cmt">//--- __MQL4__
     class="macro">#else
      class="kw">property==SYMBOL_PROP_ASKHIGH                                          ||
      class="kw">property==SYMBOL_PROP_ASKLOW                                          ||
      class="kw">property==SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT                         ||
      class="kw">property==SYMBOL_PROP_TRADE_TICK_VALUE_LOSS                           ||
      class="kw">property==SYMBOL_PROP_LAST                                            ||
      class="kw">property==SYMBOL_PROP_LASTHIGH                                         ||
      class="kw">property==SYMBOL_PROP_LASTLOW                                          ||
      class="kw">property==SYMBOL_PROP_VOLUME_LIMIT                                     ||
      class="kw">property==SYMBOL_PROP_MARGIN_LONG_INITIAL                              ||
      class="kw">property==SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL                         ||
      class="kw">property==SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL                        ||
      class="kw">property==SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL                    ||
      class="kw">property==SYMBOL_PROP_MARGIN_LONG_MAINTENANCE                         ||
      class="kw">property==SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE                     ||
      class="kw">property==SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE                    ||
      class="kw">property==SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE                ||
      class="kw">property==SYMBOL_PROP_MARGIN_SHORT_INITIAL                            ||
      class="kw">property==SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL                        ||
      class="kw">property==SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL                       ||
      class="kw">property==SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL                   ||
      class="kw">property==SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE                        ||
      class="kw">property==SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE                    ||
      class="kw">property==SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE                   ||
      class="kw">property==SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE               ||
      class="kw">property==SYMBOL_PROP_SESSION_VOLUME                                  ||
      class="kw">property==SYMBOL_PROP_SESSION_TURNOVER                                ||
      class="kw">property==SYMBOL_PROP_SESSION_INTEREST

◍ 用 SupportProperty 筛掉不支持的交易品种属性

在 MT5 里写 EA 或指标时,直接读某个 SYMBOL_PROP_xxx 属性可能返回垃圾值——如果该品种根本不支持这项属性。CSymbolFX::SupportProperty 就是一道前置闸门,先问后取,避免日志里刷一堆 0 或空串。 整数属性版本里,MQL5 环境下排除了 SESSION_BUY_ORDERS_VOLUME、SESSION_SELL_ORDERS_VOLUME、VOLUME_REAL、OPTION_STRIKE 等十余个枚举;也就是说普通外汇黄金品种调这些大概率返回 false。 字符串属性版本有个坑:MQL5 里 SYMBOL_PROP_FORMULA 仅对自定义品种开放(!this.IsCustom() 才拦掉),而 MQL4 兼容分支反而把 BASIS、BANK、ISIN、PAGE 也列进不支持清单。 开 MT5 新建脚本,把下面两段粘进去跑一下,就能看清你当前品种到底过了哪几道筛。外汇贵金属波动剧烈,属性支持情况随经纪商而异,验证后以实盘平台返回为准。

MQL5 / C++
class="type">bool CSymbolFX::SupportProperty(ENUM_SYMBOL_PROP_STRING class="kw">property)
  {
  if(
  class="macro">#ifdef __MQL5__
    class="kw">property==SYMBOL_PROP_FORMULA && !this.IsCustom()
  class="macro">#else
    class="kw">property==SYMBOL_PROP_BASIS      ||
    class="kw">property==SYMBOL_PROP_BANK       ||
    class="kw">property==SYMBOL_PROP_ISIN       ||
    class="kw">property==SYMBOL_PROP_FORMULA    ||
    class="kw">property==SYMBOL_PROP_PAGE
  class="macro">#endif
    ) class="kw">return false;
  class="kw">return true;
  }

自定义品种类的属性接管逻辑

在 MT5 里做自定义品种(比如把若干外币对合成一个指数)时,需要用一个派生类把 CSymbol 的虚函数重写掉。下面这段 CSymbolCustom 就是典型写法:构造时直接把状态标成 SYMBOL_STATUS_CUSTOM,意味着它不走普通交易品种的那套存在性校验。 三个 SupportProperty 重载分别管整型、双精度、字符串三类符号属性。整型版本里唯一被拦掉的是 SYMBOL_PROP_EXIST——自定义品种本来就不是 broker 真实推送的,去查“是否存在”只会误报,所以直接返回 false,其余一律 true。 PrintShort 虚函数留给日志输出用,调用 this.GetStatusDescription() 拼 this.Name() 就能在专家日志里看到“自定义/CustomXXX”这样的短描述。外汇与贵金属自定义品种波动可能脱离真实报价源,属高风险玩法,参数错了会导致回测与实盘严重偏离。 把下面代码存成 SymbolCustom.mqh 并 include 进你的 EA,就能在 OnInit 里 new CSymbolCustom("MySynth") 做验证。

MQL5 / C++
class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include "Symbol.mqh"
class CSymbolCustom : class="kw">public CSymbol
  {
class="kw">public:
                     CSymbolCustom(class="kw">const class="type">class="kw">string name) : CSymbol(SYMBOL_STATUS_CUSTOM,name) {}
   class="kw">virtual class="type">bool      SupportProperty(ENUM_SYMBOL_PROP_INTEGER class="kw">property);
   class="kw">virtual class="type">bool      SupportProperty(ENUM_SYMBOL_PROP_DOUBLE class="kw">property);
   class="kw">virtual class="type">bool      SupportProperty(ENUM_SYMBOL_PROP_STRING class="kw">property);
   class="kw">virtual class="type">void      PrintShort(class="type">void);
  };
class="type">bool CSymbolCustom::SupportProperty(ENUM_SYMBOL_PROP_INTEGER class="kw">property)
  {
   if(class="kw">property==SYMBOL_PROP_EXIST) class="kw">return false;
   class="kw">return true;
  }
class="type">bool CSymbolCustom::SupportProperty(ENUM_SYMBOL_PROP_DOUBLE class="kw">property)
  {
   if(

「自定义品种的属性门禁与日志输出」

在封装自定义品种类时,图表模式决定了某些价格属性是否可取。若当前图表处于 BID 模式,却去要 LAST / LASTHIGH / LASTLOW 这类成交价属性,函数直接返回 false;反过来在 LAST 模式下索取 BID、ASK 及其高低值也同样被拒。 字符串类属性不走这套限制,SupportProperty(ENUM_SYMBOL_PROP_STRING) 永远返回 true,意味着自定义品种的描述字段不会因图表模式而失效。 PrintShort 仅拼一句状态描述加品种名丢进日志,方便在 MT5 Experts 标签里快速确认对象是否就位。把下面片段塞进你的 CSymbolCustom 实现,编译后挂个 EA 调一次 PrintShort,就能看见自定义品种有没有被正确加载。

MQL5 / C++
   (this.ChartMode()==SYMBOL_CHART_MODE_BID      &&
      (
       class="kw">property==SYMBOL_PROP_LAST                 ||
       class="kw">property==SYMBOL_PROP_LASTHIGH             ||
       class="kw">property==SYMBOL_PROP_LASTLOW
      )
     )                                            ||
     (this.ChartMode()==SYMBOL_CHART_MODE_LAST    &&
      (
       class="kw">property==SYMBOL_PROP_BID                  ||
       class="kw">property==SYMBOL_PROP_BIDHIGH              ||
       class="kw">property==SYMBOL_PROP_BIDLOW               ||
       class="kw">property==SYMBOL_PROP_ASK                  ||
       class="kw">property==SYMBOL_PROP_ASKHIGH              ||
       class="kw">property==SYMBOL_PROP_ASKLOW
      )
     )
   ) class="kw">return false;
   class="kw">return true;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return &class="macro">#x27;true&class="macro">#x27; if a symbol supports a passed                    |
class=class="str">"cmt">//| class="type">class="kw">string class="kw">property, otherwise class="kw">return &class="macro">#x27;false&class="macro">#x27;                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbolCustom::SupportProperty(ENUM_SYMBOL_PROP_STRING class="kw">property)
  {
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display a class="type">class="kw">short symbol description in the journal               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSymbolCustom::PrintShort(class="type">void)
  {
   ::Print(this.GetStatusDescription()+" "+this.Name());
  }

◍ 用 CSelect 给订单和事件做筛选排序

在 MT5 的 EA 或指标工程里,若想从一堆挂单、成交或自定义事件里挑出符合条件的数据,直接手写循环既啰嗦又易错。CSelect 这个类把「按属性比较」和「找极值」封装成静态方法,调用方只需传入源列表、属性枚举和比较模式。 它覆盖了订单的整型、双精度、字符串三类属性筛选:例如 ByOrderProperty(list, ORDER_MAGIC, 888888, EQUAL) 可能返回 magic 号等于 888888 的订单集合;FindOrderMax(list, ORDER_VOLUME) 则返回成交量最大的那笔订单索引。事件对象同理,ByEventProperty 支持按整型/实数/字符串属性过滤。 比较逻辑由私有的模板方法 CompareValues 统一处理,mode 参数决定大于、小于还是等于。实盘中外汇与贵金属波动剧烈、滑点频繁,这类筛选结果仅反映当下柜台数据快照,不预示后续价格走向,高杠杆下误用可能放大亏损。 把下面这段头引用和类声明直接塞进你的 mqh,就能在策略里复用筛选能力,省去重复造轮子。

MQL5 / C++
class="macro">#include "..\Objects\Orders\Order.mqh"
class="macro">#include "..\Objects\Events\Event.mqh"
class="macro">#include "..\Objects\Accounts\Account.mqh"
class="macro">#include "..\Objects\Symbols\Symbol.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class for sorting objects meeting the criterion                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSelect
  {
class="kw">private:
  class=class="str">"cmt">//--- Method for comparing two values
  class="kw">template<class="kw">typename T>
  class="kw">static class="type">bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
class="kw">public:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of working with orders                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//--- Return the list of orders with one out of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting a specified criterion
  class="kw">static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode);
  class="kw">static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode);
  class="kw">static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode);
  class=class="str">"cmt">//--- Return the order index with the maximum value of the order&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
  class="kw">static class="type">int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER class="kw">property);
  class="kw">static class="type">int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE class="kw">property);
  class="kw">static class="type">int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING class="kw">property);
  class=class="str">"cmt">//--- Return the order index with the minimum value of the order&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
  class="kw">static class="type">int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER class="kw">property);
  class="kw">static class="type">int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE class="kw">property);
  class="kw">static class="type">int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_STRING class="kw">property);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of working with events                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//--- Return the list of events with one out of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting a specified criterion
  class="kw">static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode);
  class="kw">static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode);
  class="kw">static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode);
  class=class="str">"cmt">//--- Return the event index with the maximum value of the event&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
  class="kw">static class="type">int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER class="kw">property);

事件、账户与品种的属性极值筛选接口

在 MT5 的 CEvents 类体系里,针对事件、账户、交易品种三类对象都提供了按属性极值检索的静态方法。事件侧用 FindEventMax / FindEventMin,账户侧用 FindAccountMax / FindAccountMin,品种侧用 FindSymbolMax / FindSymbolMin,全部以 CArrayObj 指针作为源列表传入,返回命中的数组下标(int)。 筛选维度统一拆成三种属性枚举:ENUM_EVENT_PROP_INTEGER、ENUM_EVENT_PROP_DOUBLE、ENUM_EVENT_PROP_STRING,账户和品种同理换成对应的 ACCOUNT / SYMBOL 前缀枚举。这意味着无论你要找点差最大的品种,还是余额最小的账户,都走同一套重载签名,只是换枚举常量。 除了极值,还提供 ByXxxProperty 系列做条件过滤:传入具体 value 与 ENUM_COMPARER_TYPE 比较模式(等于、大于、小于等),返回符合条件对象的子列表。实盘写监控脚本时,用 BySymbolProperty(list, SYMBOL_SPREAD, 0, COMPARER_GREATER) 就能捞出点差高于 0 的所有品种,外汇与贵金属波动剧烈,这类过滤结果仅作概率参考,不代表后续方向。 代码层看,这些声明全是 static,说明不依赖实例,直接 CEvents::FindSymbolMax(...) 调用即可,省去构造开销。下面贴出原文中账户与品种部分的声明片段,注意品种段在源码里带有高亮背景,属于重点标记区。

MQL5 / C++
class="kw">static class="type">int      FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE class="kw">property);
class="kw">static class="type">int      FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_STRING class="kw">property);
class=class="str">"cmt">//--- Return the event index with the minimum value of the event&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
class="kw">static class="type">int      FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER class="kw">property);
class="kw">static class="type">int      FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE class="kw">property);
class="kw">static class="type">int      FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_STRING class="kw">property);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of working with accounts                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Return the list of accounts with one out of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting a specified criterion
class="kw">static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode);
class="kw">static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode);
class="kw">static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode);
class=class="str">"cmt">//--- Return the event index with the maximum value of the event&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
class="kw">static class="type">int      FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER class="kw">property);
class="kw">static class="type">int      FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE class="kw">property);
class="kw">static class="type">int      FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING class="kw">property);
class=class="str">"cmt">//--- Return the event index with the minimum value of the event&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
class="kw">static class="type">int      FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER class="kw">property);
class="kw">static class="type">int      FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE class="kw">property);
class="kw">static class="type">int      FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING class="kw">property);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of working with symbols                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Return the list of symbols with one out of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting a specified criterion
class="kw">static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode);
class="kw">static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode);
class="kw">static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode);
class=class="str">"cmt">//--- Return the symbol index with the maximum value of the order&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
class="kw">static class="type">int      FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER class="kw">property);
class="kw">static class="type">int      FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE class="kw">property);

「按品种属性做条件筛选的实现细节」

在 MT5 自定义选择器里,按品种属性过滤依赖一组重载方法。核心思路是:传入源品种对象数组、指定属性枚举、目标值与比较模式,返回符合条件的新数组指针。 静态方法 FindSymbolMax / FindSymbolMin 分别用于定位某字符串或数值属性最大、最小的品种下标,覆盖整数、双精度、字符串三类枚举,方便后续排序或极值提取。 实际筛选由 BySymbolProperty 的三个重载完成。下面这段整数属性版本可直接拷进 EA 工程验证:先判空、建临时数组并关自动释放,再遍历源列表逐个比对。 注意 list.FreeMode(false) 很关键——它让返回的数组不负责销毁 CSymbol 对象,避免把源列表里的实例误删。外汇与贵金属品种属性受报价会话影响,筛选结果可能随品种可见性波动,属正常高风险市场现象。

MQL5 / C++
class="kw">static class="type">int FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING class="kw">property);
class=class="str">"cmt">//--- Return the symbol index with the minimum value of the order&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
class="kw">static class="type">int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER class="kw">property);
class="kw">static class="type">int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE class="kw">property);
class="kw">static class="type">int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING class="kw">property);
class=class="str">"cmt">//---
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of working with symbol lists                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the list of symbols with one integer                       |
class=class="str">"cmt">//| class="kw">property meeting the specified criterion                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
CArrayObj *CSelect::BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_ class="kw">return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) class="kw">return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   class="type">int total=list_source.Total();
   for(class="type">int i=class="num">0; i<total; i++)
     {
      CSymbol *obj=list_source.At(i);
      if(!obj.SupportProperty(class="kw">property)) class="kw">continue;
      class="type">long obj_prop=obj.GetProperty(class="kw">property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   class="kw">return list;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the list of symbols with one real                          |
class=class="str">"cmt">//| class="kw">property meeting the specified criterion                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
CArrayObj *CSelect::BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_ class="kw">return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) class="kw">return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(class="type">int i=class="num">0; i<list_source.Total(); i++)
     {
      CSymbol *obj=list_source.At(i);
      if(!obj.SupportProperty(class="kw">property)) class="kw">continue;
      class="type">class="kw">double obj_prop=obj.GetProperty(class="kw">property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   class="kw">return list;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 按属性筛符号与捞最大值的实现细节

在自选符号管理类里,BySymbolProperty 负责按字符串类属性做过滤。它先对源列表判空,新建一个 CArrayObj 容器并设为不自动释放元素(FreeMode(false)),再把容器挂到 ListStorage 统一管理内存,避免调用方漏删。 遍历源列表时,先用 SupportProperty 确认该符号支持目标属性,不支持就跳过;拿到属性值后交给 CompareValues 按传入的 mode(等于、包含等)比对,命中才加入结果列表。这样一次调用就能从几十个品种里捞出符合文本特征的那几个,外汇与贵金属品种混杂时尤其省事,但品种属性缺失会导致静默漏选,属高风险操作环境里的隐性坑。 FindSymbolMax 有两个重载:一个吃整数属性(ENUM_SYMBOL_PROP_INTEGER),一个吃双精度属性(ENUM_SYMBOL_PROP_DOUBLE)。逻辑一致——从索引 1 开始扫,拿当前元素和 index 位置元素比,CompareValues(...,MORE) 为真就更新 index,最终返回最大值所在下标,空列表或空指针都回 WRONG_VALUE(通常为 -1)。 跑 MT5 时把这两段直接塞进你的 CSelect 类,传 SYMBOL_PROP_STRING 里的描述字段做 BySymbolProperty 过滤,再用 FindSymbolMax 找点差最大的品种,能立刻看到结果列表长度和数据点。

MQL5 / C++
CArrayObj *CSelect::BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_ class="kw">return NULL;            class=class="str">"cmt">// 源列表空则直接返回空
   CArrayObj *list=new CArrayObj();              class=class="str">"cmt">// 新建结果容器
   if(list==NULL) class="kw">return NULL;                   class=class="str">"cmt">// 内存分配失败返回空
   list.FreeMode(false);                         class=class="str">"cmt">// 关闭自动释放,元素由ListStorage管
   ListStorage.Add(list);                        class=class="str">"cmt">// 登记到存储列表防内存泄漏
   for(class="type">int i=class="num">0; i<list_source.Total(); i++)      class=class="str">"cmt">// 遍历源列表
     {
      CSymbol *obj=list_source.At(i);            class=class="str">"cmt">// 取第i个符号对象
      if(!obj.SupportProperty(class="kw">property)) class="kw">continue; class=class="str">"cmt">// 不支持该属性就跳过
      class="type">class="kw">string obj_prop=obj.GetProperty(class="kw">property); class=class="str">"cmt">// 读字符串属性
      if(CompareValues(obj_prop,value,mode)) list.Add(obj); class=class="str">"cmt">// 按模式比对,命中加入
     }
   class="kw">return list;                                  class=class="str">"cmt">// 返回筛选结果
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the listed symbol index with the maximum integer class="kw">property value |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSelect::FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER class="kw">property)
  {
   if(list_ class="kw">return WRONG_VALUE;     class=class="str">"cmt">// 空指针返回错误值
   class="type">int index=class="num">0;                                  class=class="str">"cmt">// 假设0号最大
   CSymbol *max_obj=NULL;                        class=class="str">"cmt">// 暂存最大对象
   class="type">int total=list_source.Total();                class=class="str">"cmt">// 列表总数
   if(total==class="num">0) class="kw">return WRONG_VALUE;              class=class="str">"cmt">// 空列表返回错误值
   for(class="type">int i=class="num">1; i<total; i++)                    class=class="str">"cmt">// 从1开始比
     {
      CSymbol *obj=list_source.At(i);            class=class="str">"cmt">// 当前对象
      class="type">long obj1_prop=obj.GetProperty(class="kw">property);  class=class="str">"cmt">// 当前整数属性
      max_obj=list_source.At(index);             class=class="str">"cmt">// 暂存最大对象
      class="type">long obj2_prop=max_obj.GetProperty(class="kw">property); class=class="str">"cmt">// 最大对象属性
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; class=class="str">"cmt">// 更大则更新下标
     }
   class="kw">return index;                                 class=class="str">"cmt">// 返回最大属性下标
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the listed symbol index with the maximum real class="kw">property value  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSelect::FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE class="kw">property)
  {
   if(list_ class="kw">return WRONG_VALUE;     class=class="str">"cmt">// 空指针返回错误值
   class="type">int index=class="num">0;                                  class=class="str">"cmt">// 假设0号最大
   CSymbol *max_obj=NULL;                        class=class="str">"cmt">// 暂存最大对象
   class="type">int total=list_source.Total();                class=class="str">"cmt">// 列表总数
   if(total==class="num">0) class="kw">return WRONG_VALUE;              class=class="str">"cmt">// 空列表返回错误值
   for(class="type">int i=class="num">1; i<total; i++)                    class=class="str">"cmt">// 从1开始比
     {
      CSymbol *obj=list_source.At(i);            class=class="str">"cmt">// 当前对象
      class="type">class="kw">double obj1_prop=obj.GetProperty(class="kw">property); class=class="str">"cmt">// 当前双精度属性
      max_obj=list_source.At(index);             class=class="str">"cmt">// 暂存最大对象
      class="type">class="kw">double obj2_prop=max_obj.GetProperty(class="kw">property); class=class="str">"cmt">// 最大对象属性
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; class=class="str">"cmt">// 更大则更新下标
     }
   class="kw">return index;                                 class=class="str">"cmt">// 返回最大属性下标
  }

在品种池里捞最大最小值的写法

做跨品种扫描时,经常要从一组 CSymbol 对象里挑出某项属性最强或最弱的那个。下面这套 CSelect 的成员函数就干这事:按字符串、整型、双精度三种属性类型分别提供 Max / Min 查找,返回的是目标在数组里的下标,不是对象指针本身。 FindSymbolMax 针对字符串属性,从下标 1 开始遍历,把当前对象和已记录的最大下标对象比一遍,CompareValues 返回 MORE 就更新 index。空指针或总数为 0 直接给 WRONG_VALUE,调用方得自己判这个返回值。 整型和双精度的 Min 版本逻辑一致,只是属性类型换成 long 和 double,比较方向用 LESS。注意三个重载都默认下标 0 为初始基准,因此 total 至少要为 1 才有意义;若你往里塞了空列表,函数会返回 WRONG_VALUE 而不是崩溃。 在 MT5 里把这些代码贴进 EA 或脚本,配合 SymbolInfo 系列属性枚举,就能快速选出例如点值最小或名称排序最靠后的品种——外汇和贵金属波动大,这类筛选只解决「选谁」,不解决「何时出手」,实盘仍属高风险操作。

MQL5 / C++
class="type">int CSelect::FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING class="kw">property)
  {
   if(list_ class="kw">return WRONG_VALUE;
   class="type">int index=class="num">0;
   CSymbol *max_obj=NULL;
   class="type">int total=list_source.Total();
   if(total==class="num">0) class="kw">return WRONG_VALUE;
   for(class="type">int i=class="num">1; i<total; i++)
     {
      CSymbol *obj=list_source.At(i);
      class="type">class="kw">string obj1_prop=obj.GetProperty(class="kw">property);
      max_obj=list_source.At(index);
      class="type">class="kw">string obj2_prop=max_obj.GetProperty(class="kw">property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   class="kw">return index;
  }
class="type">int CSelect::FindSymbolMin(CArrayObj* list_source,ENUM_SYMBOL_PROP_INTEGER class="kw">property)
  {
   class="type">int index=class="num">0;
   CSymbol *min_obj=NULL;
   class="type">int total=list_source.Total();
   if(total==class="num">0) class="kw">return WRONG_VALUE;
   for(class="type">int i=class="num">1; i<total; i++){
      CSymbol *obj=list_source.At(i);
      class="type">long obj1_prop=obj.GetProperty(class="kw">property);
      min_obj=list_source.At(index);
      class="type">long obj2_prop=min_obj.GetProperty(class="kw">property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
      }
   class="kw">return index;
  }
class="type">int CSelect::FindSymbolMin(CArrayObj* list_source,ENUM_SYMBOL_PROP_DOUBLE class="kw">property)
  {
   class="type">int index=class="num">0;
   CSymbol *min_obj=NULL;
   class="type">int total=list_source.Total();
   if(total== class="num">0) class="kw">return WRONG_VALUE;
   for(class="type">int i=class="num">1; i<total; i++){
      CSymbol *obj=list_source.At(i);
      class="type">class="kw">double obj1_prop=obj.GetProperty(class="kw">property);
      min_obj=list_source.At(index);
      class="type">class="kw">double obj2_prop=min_obj.GetProperty(class="kw">property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
      }
   class="kw">return index;
  }
class="type">int CSelect::FindSymbolMin(CArrayObj* list_source,ENUM_SYMBOL_PROP_STRING class="kw">property)
  {

「遍历列表取最小属性下标」

在 MT5 自定义品种扫描器里,经常要从一组 CSymbol 对象中挑出某个属性最小的那个。下面这段逻辑用一次线性遍历完成,时间复杂度 O(n),n 为列表元素数。 代码先取 list_source.Total() 得到总数,若为 0 直接返回 WRONG_VALUE,避免空指针。随后从 i=1 开始而非 0,因为 index 已默认指向 0 号元素,省一次多余比较。 循环内每次拿当前 i 和已知最小 index 的同一属性字符串,交给 CompareValues 按 LESS 规则比大小;若前者更小就把 index 更新为 i。最终返回的 index 就是属性最小对象的下标,可直接 At(index) 取出。 实盘里若列表含 40 个外汇品种,这套遍历在 OnInit 阶段耗时可忽略;但放到 OnTick 里每跳重算,就可能拖慢执行,建议缓存结果。

MQL5 / C++
  class="type">int index=class="num">0;
  CSymbol *min_obj=NULL;
  class="type">int total=list_source.Total();
  if(total==class="num">0) class="kw">return WRONG_VALUE;
  for(class="type">int i=class="num">1; i<total; i++){
      CSymbol *obj=list_source.At(i);
      class="type">class="kw">string obj1_prop=obj.GetProperty(class="kw">property);
      min_obj=list_source.At(index);
      class="type">class="kw">string obj2_prop=min_obj.GetProperty(class="kw">property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
      }
  class="kw">return index;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 品种集合类的内部运转

在 MT5 函数库的 Collections 目录下新建 CSymbolsCollection 类,继承 CObject,用来统一管理当前品种、自定义列表、市场观察窗口和服务器全量品种四种操控模式。类内 m_mode_list 记录当前模式,另设变量跟踪上次检查时的品种数量与增减差值,方便后续对市场观察列表的变动做重排。 去重由 IsPresentSymbolInList() 把关,CreateNewSymbol() 负责新建并插入对象;TypeSymbolsList() 按四种模式之一决定数据来源。SymbolStatus() 先查自定义数组,返回“一般”时再依保证金计算类型用 StatusByCalcMode() 定组。Refresh() 与 RefreshRates() 分别从 CEngine 计时器调用,前者刷全部数据,后者只更新报价引用。 CEngine 里建了两个计时器计数器:一个跑 RefreshRates() 刷新报价,另一个预留给市场观察事件(现仅判断策略测试器环境后退出)。GetListAllUsedSymbols() 和 SetUsedSymbols() 把集合完整列表和填充动作桥接到主对象。 DELib.mqh 中补了一个函数,接收模式、逗号分隔品种串和数组,把市场观察或全量服务器的品种送进 CEngine。实盘或回测接这套逻辑时,外汇与贵金属杠杆品种波动剧烈、高风险,模式误配可能导致集合为空、刷新失效。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//|                    SymbolsCollection.mqh                         |
class=class="str">"cmt">//|          Copyright class="num">2018, MetaQuotes Software Corp.               |
class=class="str">"cmt">//|             [MQL5官方文档]                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+

品种集合类的头文件引用结构

在 MT5 里做跨品种监控时,先把符号对象体系拆成头文件引用是最省事的做法。下面这段类声明只暴露一个构造函数,但背后靠 20 余个 include 把外汇、贵金属、股指、加密、期货、债券等品种类型全部挂进 CSymbolsCollection。 代码里 #include 路径从 ListObj.mqh 到 Symbols 目录下各级子类(SymbolFXMajor、SymbolMetall、SymbolCrypto 等),说明集合类不自己存数据,而是复用各品种对象的公有方法。你在 MT5 的 MQL5/Include 目录建同样层级的 ..\Objects\Symbols\ 文件夹,编译才能过。 外汇与贵金属品种(SymbolFX、SymbolMetall)都在这个集合里,实盘调用前要注意:跨品种扫描会同时拉多品种实时报价,账户杠杆不足或品种休市时可能返回空对象,属于高风险操作,建议先在策略测试器用历史数据跑通。 直接看这段原文,逐行拆一下引用意图:

MQL5 / C++
class=class="str">"cmt">//| Include files                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "ListObj.mqh"
class="macro">#include "..\Services\Select.mqh"
class="macro">#include "..\Objects\Symbols\Symbol.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFX.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXMajor.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXMinor.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXExotic.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXRub.mqh"
class="macro">#include "..\Objects\Symbols\SymbolMetall.mqh"
class="macro">#include "..\Objects\Symbols\SymbolIndex.mqh"
class="macro">#include "..\Objects\Symbols\SymbolIndicative.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCrypto.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCommodity.mqh"
class="macro">#include "..\Objects\Symbols\SymbolExchange.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFutures.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCFD.mqh"
class="macro">#include "..\Objects\Symbols\SymbolStocks.mqh"
class="macro">#include "..\Objects\Symbols\SymbolBonds.mqh"
class="macro">#include "..\Objects\Symbols\SymbolOption.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCollateral.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCustom.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCommon.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Collection of historical orders and deals                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSymbolsCollection : class="kw">public CObject
  {
class="kw">private:

class="kw">public:
class=class="str">"cmt">//--- Constructor
                     CSymbolsCollection();
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                  SymbolsCollection.mqh |
class=class="str">"cmt">//|                 Copyright class="num">2018, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                    [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "ListObj.mqh"
class="macro">#include "..\Services\Select.mqh"
class="macro">#include "..\Objects\Symbols\Symbol.mqh"

「用集合类把全市场品种收进一张表」

做跨品种扫描时,最忌每次都去调 SymbolTotal 再逐个读取属性,开销大且难维护。下面这段把外汇、贵金属、指数、加密、期货、股票等 19 类品种头文件一次性 include,再封装成 CSymbolsCollection 类,本质是用一个 CListObj 把全部品种对象挂到一条链表上。 类里 GetList 给了四种重载:无参版本直接回传整条链表;三个带属性的版本分别按整数、双精度、字符串类型的 ENUM_SYMBOL_PROP_xxx 做筛选,比如按 SYMBOL_TRADE_MODE 或 SYMBOL_SPREAD 过滤,比较模式由 ENUM_COMPARER_TYPE 控制,默认是相等。 实盘里若想筛出当前点差小于 20 点的主流外汇,可先构造集合再调用 GetList(SYMBOL_SPREAD, 20.0, LESS),返回的子链表就能直接喂给小布的 AIGC 打分模块。外汇与贵金属杠杆高、滑点突变频繁,这类批量读取最好在盘前或低波动段跑,避免在流动性断裂时误判。

MQL5 / C++
class="macro">#include "..\Objects\Symbols\SymbolFX.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXMajor.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXMinor.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXExotic.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXRub.mqh"
class="macro">#include "..\Objects\Symbols\SymbolMetall.mqh"
class="macro">#include "..\Objects\Symbols\SymbolIndex.mqh"
class="macro">#include "..\Objects\Symbols\SymbolIndicative.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCrypto.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCommodity.mqh"
class="macro">#include "..\Objects\Symbols\SymbolExchange.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFutures.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCFD.mqh"
class="macro">#include "..\Objects\Symbols\SymbolStocks.mqh"
class="macro">#include "..\Objects\Symbols\SymbolBonds.mqh"
class="macro">#include "..\Objects\Symbols\SymbolOption.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCollateral.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCustom.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCommon.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Collection of historical orders and deals                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSymbolsCollection : class="kw">public CObject
  {
class="kw">private:
   CListObj            m_list_all_symbols;     class=class="str">"cmt">// The list of all symbol objects

class="kw">public:
class=class="str">"cmt">//--- Return the full collection list &class="macro">#x27;as is&class="macro">#x27;
   CArrayObj          *GetList(class="type">void)                                                                          { class="kw">return &this.m_list_all_symbols;                                                                          }
class=class="str">"cmt">//--- Return the list by selected(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting the compared criterion
   CArrayObj          *GetList(ENUM_SYMBOL_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode=EQUAL)      { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); }
   CArrayObj          *GetList(ENUM_SYMBOL_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode=EQUAL)     { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); }
   CArrayObj          *GetList(ENUM_SYMBOL_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode=EQUAL)     { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); }
class=class="str">"cmt">//--- Constructor
                     CSymbolsCollection();

  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                            SymbolsCollection.mqh |
class=class="str">"cmt">//|            Copyright class="num">2018, MetaQuotes Software Corp. |

◍ 品种集合类的头文件引用与私有成员

这段 MT5 源码展示了一个名为 CSymbolsCollection 的类,它继承自 CObject,用来统一管理历史订单与成交涉及的各类交易品种对象。类定义前通过 #include 把 ListObj.mqh 以及 Services\Select.mqh 和 Objects\Symbols\ 下的二十余个品种头文件全部拉进编译单元,覆盖外汇主盘、交叉盘、 exotic、卢布对、金属、指数、加密、商品、期货、CFD、股票、债券、期权乃至自定义品种。 私有段里先挂了一个 CListObj 类型的 m_list_all_symbols,作为全部品种对象的容器。紧随其后的 m_mode_list 是 ENUM_SYMBOLS_MODE 枚举,决定后续对品种列表的运作模式;m_delta_symbol 记录本次与上次检查相比品种数量的差值,m_last_num_symbol 存的是上一次市场报价窗口里的品种数,m_global_error 则承接全局错误码。 从工程角度看,把外汇和贵金属品种拆成 SymbolFXMajor / SymbolFXMinor / SymbolFXExotic / SymbolMetall 等独立头文件,便于在盯盘工具里按风险类别分别处理——外汇与贵金属杠杆高、波动剧烈,分类后做风控过滤更直观。 下面给出该节摘出的 MQL5 片段,注意 #property version 写死为 "1.00",说明这是 2018 年 MetaQuotes 体系下的早期结构,直接抄进当前 MT5 Build 可能有路径告警,建议先核对 Include 相对路径。

MQL5 / C++
class=class="str">"cmt">//|                                                                              [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "ListObj.mqh"
class="macro">#include "..\Services\Select.mqh"
class="macro">#include "..\Objects\Symbols\Symbol.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFX.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXMajor.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXMinor.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXExotic.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFXRub.mqh"
class="macro">#include "..\Objects\Symbols\SymbolMetall.mqh"
class="macro">#include "..\Objects\Symbols\SymbolIndex.mqh"
class="macro">#include "..\Objects\Symbols\SymbolIndicative.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCrypto.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCommodity.mqh"
class="macro">#include "..\Objects\Symbols\SymbolExchange.mqh"
class="macro">#include "..\Objects\Symbols\SymbolFutures.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCFD.mqh"
class="macro">#include "..\Objects\Symbols\SymbolStocks.mqh"
class="macro">#include "..\Objects\Symbols\SymbolBonds.mqh"
class="macro">#include "..\Objects\Symbols\SymbolOption.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCollateral.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCustom.mqh"
class="macro">#include "..\Objects\Symbols\SymbolCommon.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Collection of historical orders and deals                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSymbolsCollection : class="kw">public CObject
  {
class="kw">private:
   CListObj            m_list_all_symbols;    class=class="str">"cmt">// The list of all symbol objects
   ENUM_SYMBOLS_MODE   m_mode_list;           class=class="str">"cmt">// Mode of working with symbol lists
   class="type">int                 m_delta_symbol;        class=class="str">"cmt">// Difference in the number of symbols compared to the previous check
   class="type">int                 m_last_num_symbol;     class=class="str">"cmt">// Number of symbols in the Market Watch window during the previous check
   class="type">int                 m_global_error;        class=class="str">"cmt">// Global error code
class=class="str">"cmt">//--- Return the flag of a symbol object presence by its name in the list of all symbols

按品种属性给符号归类

在 MT5 的 EA 或指标里批量处理多品种时,先判断一个符号属于哪一类,比硬写字符串匹配更稳。下面这组接口把符号分成外汇主盘、交叉盘、 exotic、卢布系、指示性报价、金属、商品、指数、加密、期权共 10 个预定义类别,外加按保证金计算模式和自定义规则归类的方式。 核心判断函数分为三层。第一层 IsPresentSymbolInList 只回答「这个符号在不在你维护的列表里」;第二层 SymbolStatus 按名称返回 ENUM_SYMBOL_STATUS 枚举;第三层 IsPredefinedFXMajor 到 IsPredefinedOption 这 10 个布尔函数直接给明确归类,省去你手填映射表。 实际写策略时,用 TypeSymbolsList 区分「市场报价窗口」和「服务器全量」两种符号源很重要——前者只有你盯盘加了的那几个,后者可能一次返回上千个。若对 XAUUSD 调用 IsPredefinedMetall 会返回 true,而对 EURUSD 调同一函数返回 false,这类结果可直接驱动仓位系数。 别把正态当圣经 预定义分类是写死在库里的(外汇主盘约 7 对、金属含 XAU/XAG 等 4 个),不同券商服务器符号命名可能有后缀如 EURUSD.sb,直接调 IsPredefinedFXMajor 会漏判,自己加一层去后缀清洗再判更可靠。

MQL5 / C++
class="type">bool IsPresentSymbolInList(class="kw">const class="type">class="kw">string symbol_name);
class=class="str">"cmt">//--- Create the symbol object and place it to the list
class="type">bool CreateNewSymbol(class="kw">const ENUM_SYMBOL_STATUS symbol_status,class="kw">const class="type">class="kw">string name);
class=class="str">"cmt">//--- Return the type of a used symbol list(Market watch/Server)
ENUM_SYMBOLS_MODE TypeSymbolsList(class="kw">const class="type">class="kw">string &symbol_used_array[]);
class=class="str">"cmt">//--- Define a symbol affiliation with a group by name and class="kw">return it
ENUM_SYMBOL_STATUS SymbolStatus(class="kw">const class="type">class="kw">string symbol_name) class="kw">const;
class=class="str">"cmt">//--- Return a symbol affiliation with a category by custom criteria
ENUM_SYMBOL_STATUS StatusByCustomPredefined(class="kw">const class="type">class="kw">string symbol_name) class="kw">const;
class=class="str">"cmt">//--- Return a symbol affiliation with categories by margin calculation
ENUM_SYMBOL_STATUS StatusByCalcMode(class="kw">const class="type">class="kw">string symbol_name) class="kw">const;
class=class="str">"cmt">//--- Return a symbol affiliation with pre-defined(class="num">1) majors, (class="num">2) minors, (class="num">3) exotics, (class="num">4) RUB,
class=class="str">"cmt">//--- (class="num">5) indicatives, (class="num">6) metals, (class="num">7) commodities, (class="num">8) indices, (class="num">9) cryptocurrency, (class="num">10) options
class="type">bool IsPredefinedFXMajor(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedFXMinor(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedFXExotic(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedFXRUB(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedIndicative(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedMetall(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedCommodity(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedIndex(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedCrypto(class="kw">const class="type">class="kw">string name) class="kw">const;
class="type">bool IsPredefinedOption(class="kw">const class="type">class="kw">string name) class="kw">const;

「品种集合类的对外接口拆解」

在 MT5 的自定义品种管理类里,CSymbolsCollection 把内部容器和筛选逻辑封装后,只暴露一组 public 方法给调用方。理解这些接口,才能在自己 EA 里直接拿市场观察窗口的数据做批量处理。 Exist(const string name) const 返回一个 bool,用来判断某个品种名是否已经在集合内。注意它是 const 方法,不修改对象状态,适合在高频 tick 事件里反复调用而不用担心副作用。 GetList 有四种重载:无参版本直接回传内部 m_list_all_symbols 的指针,另外三个按整数、双精度、字符串三种符号属性做条件筛选,默认比较模式是 EQUAL,也可传 GREATER 或 LESS 等。这意味着你写 GetList(SYMBOL_DIGITS, 5) 就能捞出所有 5 位小数品种,外汇和贵金属里这类过滤很实用。 NewSymbols(void) const 返回 m_delta_symbol,即本次市场观察窗口相比上一次刷新新增的品种数量。若返回 0 说明观察列表没变动;若大于 0,可能需要在 EA 里触发重新订阅。 ModeSymbolsList(void) const 暴露当前列表工作模式 m_mode_list,便于调试时确认集合是按全市场还是按自定义清单维护。SetUsedSymbol 接收 string 数组来固定使用的品种范围,Refresh 与 RefreshRates 则分别更新元数据和报价,后者在剥头皮策略里建议每 tick 调一次。 外汇与贵金属杠杆高、点差跳变频繁,直接用这些接口跑实盘前,务必在策略测试器用历史数据验证筛选逻辑是否漏掉非常规品种。

MQL5 / C++
class="type">bool                 Exist(class="kw">const class="type">class="kw">string name)                class="kw">const;

class="kw">public:
class=class="str">"cmt">//--- Return the full collection list &class="macro">#x27;as is&class="macro">#x27;
   CArrayObj           *GetList(class="type">void)                                                                              { class="kw">return &this.m_list_all_symbols;                                                                                 }
class=class="str">"cmt">//--- Return the list by selected(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting the compared criterion
   CArrayObj           *GetList(ENUM_SYMBOL_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode=EQUAL)          { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); }
   CArrayObj           *GetList(ENUM_SYMBOL_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode=EQUAL)         { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); }
   CArrayObj           *GetList(ENUM_SYMBOL_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode=EQUAL)         { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); }
class=class="str">"cmt">//--- Return the number of new symbols in the Market Watch window
   class="type">int                 NewSymbols(class="type">void)  class="kw">const                                                                      { class="kw">return this.m_delta_symbol;                                                                                      }
class=class="str">"cmt">//--- Return the mode of working with symbol lists
   ENUM_SYMBOLS_MODE ModeSymbolsList(class="type">void)                                                                          class="kw">const                                                       { class="kw">return this.m_mode_list;                                                                                          }
class=class="str">"cmt">//--- Constructor
                     CSymbolsCollection();

class=class="str">"cmt">//--- Set the list of used symbols
   class="type">bool                SetUsedSymbol(class="kw">const class="type">class="kw">string &symbol_used_array[]);
class=class="str">"cmt">//--- Update(class="num">1) all, (class="num">2) quote data of the collection symbols
   class="type">void                Refresh(class="type">void);
   class="type">void                RefreshRates(class="type">void);
};

◍ 多品种容器的构造与刷新逻辑

CSymbolsCollection 的构造函数把三个成员初始化为 0 或默认模式:m_last_num_symbol 与 m_delta_symbol 置 0,m_mode_list 设为 SYMBOLS_MODE_CURRENT,随后对内部品种链表做按名排序、清空并指定集合类型 ID。这套初始化保证后续塞入品种前容器处于干净状态。 Refresh() 与 RefreshRates() 都先取链表总数,若为 0 直接 return,避免空跑;否则遍历每个 CSymbol 指针,判空后分别调用 symbol.Refresh()(全量数据)和 symbol.RefreshRates()(仅报价)。在 MT5 实盘里,若你监控 30 个外汇对,这两个方法一次调用就能批量更新,省去逐个 SymbolInfoTick 的写法。 CreateNewSymbol() 接收品种状态和名称,开头就用 IsPresentSymbolInList(name) 查重,已存在则直接返回 true,不重复建对象。这对贵金属与交叉盘混装的清单很实用——重复添加不会撑大内存,也不会让刷新循环翻倍。 别在定时器里无脑刷全量 Refresh() 拉的是全量品种属性,频率过高会拖慢 EA;报价级更新交给 RefreshRates() 就够了,两者分开调用更稳。

MQL5 / C++
class=class="str">"cmt">//| Constructor                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSymbolsCollection::CSymbolsCollection(class="type">void) : m_last_num_symbol(class="num">0),m_delta_symbol(class="num">0),m_mode_list(SYMBOLS_MODE_CURRENT)
  {
   this.m_list_all_symbols.Sort(SORT_BY_SYMBOL_NAME);
   this.m_list_all_symbols.Clear();
   this.m_list_all_symbols.Type(COLLECTION_SYMBOLS_ID);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update all collection symbol data                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSymbolsCollection::Refresh(class="type">void)
  {
   class="type">int total=this.m_list_all_symbols.Total();
   if(total==class="num">0)
      class="kw">return;
   for(class="type">int i=class="num">0;i<total;i++)
     {
      CSymbol *symbol=this.m_list_all_symbols.At(i);
      if(symbol==NULL)
         class="kw">continue;
      symbol.Refresh();
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update quote data of the collection symbols                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSymbolsCollection::RefreshRates(class="type">void)
  {
   class="type">int total=this.m_list_all_symbols.Total();
   if(total==class="num">0)
      class="kw">return;
   for(class="type">int i=class="num">0;i<total;i++)
     {
      CSymbol *symbol=this.m_list_all_symbols.At(i);
      if(symbol==NULL)
         class="kw">continue;
      symbol.RefreshRates();
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a symbol object and place it to the list                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbolsCollection::CreateNewSymbol(class="kw">const ENUM_SYMBOL_STATUS symbol_status,class="kw">const class="type">class="kw">string name)
  {
   if(this.IsPresentSymbolInList(name))
     {
      class="kw">return true;
     }

按品种类型分流建实例

在 MT5 自定义品种封装里,先要确认传入的 name 在服务器上真实存在,否则直接打错误日志并返回 false,把全局错误码置成 ERR_MARKET_UNKNOWN_SYMBOL。这一步 MQL5 和 MQL4 的判定写法不同,用预编译宏隔开就能一套代码两边编译。 确认存在后,根据 symbol_status 的枚举值用 switch 把具体品种对象 new 出来:外汇普通、主要货币、次要货币、 exotic、卢布系、贵金属、指数、指示性、加密货币、商品、交易所、期货、CFD、股票,一共 14 个分支。每个分支对应一个派生类(如 CSymbolFX、CSymbolMetall),之后行情计算逻辑就多态派发,不用在主流程写一堆 if 判断。 开 MT5 把这段贴进你的 CSymbol 工厂函数,改 symbol_status 的枚举来源(比如从 SymbolInfoInteger(SYMBOL_TRADE_MODE) 映射),就能在 EA 初始化时自动按品种挂对处理类。外汇和贵金属波动受杠杆与流动性影响,建实例后仍需在实盘前做点差与停利距校验,滑点风险偏高。

MQL5 / C++
if(class="macro">#ifdef __MQL5__ !::SymbolInfoInteger(name,SYMBOL_EXIST) class="macro">#else !this.Exist(name) class="macro">#endif )
  {
    class="type">class="kw">string t1=TextByLanguage("Ошибка входных данных: нет символа ","Input error: no ");
    class="type">class="kw">string t2=TextByLanguage(" на сервере"," symbol on the server");
    ::Print(DFUN,t1,name,t2);
    this.m_global_error=ERR_MARKET_UNKNOWN_SYMBOL;
    class="kw">return false;
  }
  CSymbol *symbol=NULL;
  class="kw">switch(symbol_status)
  {
    case SYMBOL_STATUS_FX           :  symbol=new CSymbolFX(name);           break;  class=class="str">"cmt">// Forex symbol
    case SYMBOL_STATUS_FX_MAJOR     :  symbol=new CSymbolFXMajor(name);      break;  class=class="str">"cmt">// Major Forex symbol
    case SYMBOL_STATUS_FX_MINOR     :  symbol=new CSymbolFXMinor(name);      break;  class=class="str">"cmt">// Minor Forex symbol
    case SYMBOL_STATUS_FX_EXOTIC    :  symbol=new CSymbolFXExotic(name);     break;  class=class="str">"cmt">// Exotic Forex symbol
    case SYMBOL_STATUS_FX_RUB       :  symbol=new CSymbolFXRub(name);        break;  class=class="str">"cmt">// Forex symbol/RUR
    case SYMBOL_STATUS_METAL        :  symbol=new CSymbolMetall(name);       break;  class=class="str">"cmt">// Metal
    case SYMBOL_STATUS_INDEX        :  symbol=new CSymbolIndex(name);        break;  class=class="str">"cmt">// Index
    case SYMBOL_STATUS_INDICATIVE   :  symbol=new CSymbolIndicative(name);   break;  class=class="str">"cmt">// Indicative
    case SYMBOL_STATUS_CRYPTO       :  symbol=new CSymbolCrypto(name);       break;  class=class="str">"cmt">// Cryptocurrency symbol
    case SYMBOL_STATUS_COMMODITY    :  symbol=new CSymbolCommodity(name);    break;  class=class="str">"cmt">// Commodity
    case SYMBOL_STATUS_EXCHANGE     :  symbol=new CSymbolExchange(name);     break;  class=class="str">"cmt">// Exchange symbol
    case SYMBOL_STATUS_FUTURES      :  symbol=new CSymbolFutures(name);      break;  class=class="str">"cmt">// Futures
    case SYMBOL_STATUS_CFD          :  symbol=new CSymbolCFD(name);          break;  class=class="str">"cmt">// CFD
    case SYMBOL_STATUS_STOCKS       :  symbol=new CSymbolStocks(name);       break;  class=class="str">"cmt">// Stock
  }

「按品种类别分流建对象与查重」

在品种集合类里,工厂方法会依据 SYMBOL_STATUS 枚举把不同资产映射成对应子类对象:债券走 CSymbolBonds,期权走 CSymbolOption,抵押品(不可交易资产)走 CSymbolCollateral,自定义品种走 CSymbolCustom,其余全部回落到 CSymbolCommon。 若 new 出来的是 NULL,说明对象没建成功,直接打印双语报错并返回 false,不会往下污染全局列表。 把 symbol 加进 m_list_all_symbols 时如果 Add 返回 false,会释放该对象并回 false;只有真正进列表了才 return true。 IsPresentSymbolInList 先对全量列表按 SYMBOL_NAME 排序,再用 CSelect::BySymbolProperty 按名称等值筛选;返回逻辑是「筛选结果为空或总数为0则假,否则真」。 SetUsedSymbol 先由传入数组判定列表模式 TypeSymbolsList,随后立刻 Clear 掉旧的全量列表,这意味着切换可用品种集时会清空之前缓存的所有符号对象。

MQL5 / C++
case SYMBOL_STATUS_BONDS       : symbol=new CSymbolBonds(name);       break;  class=class="str">"cmt">// Bond
case SYMBOL_STATUS_OPTION      : symbol=new CSymbolOption(name);      break;  class=class="str">"cmt">// Option
case SYMBOL_STATUS_COLLATERAL  : symbol=new CSymbolCollateral(name); break;  class=class="str">"cmt">// Non-tradable asset
case SYMBOL_STATUS_CUSTOM      : symbol=new CSymbolCustom(name);      break;  class=class="str">"cmt">// Custom symbol
class="kw">default                          : symbol=new CSymbolCommon(name);      break;  class=class="str">"cmt">// The rest
   }
   if(symbol==NULL)
   {
      ::Print(DFUN,TextByLanguage("Не удалось создать объект-символ ","Failed to create symbol object "),name);
      class="kw">return false;
   }
   if(!this.m_list_all_symbols.Add(symbol))
   {
      class="type">class="kw">string t1=TextByLanguage("Не удалось добавить символ ","Failed to add ");
      class="type">class="kw">string t2=TextByLanguage(" в список"," symbol to the list");
      ::Print(DFUN,t1,name,t2);
      class="kw">delete symbol;
      class="kw">return false;
   }
   class="kw">return true;
}
class="type">bool CSymbolsCollection::IsPresentSymbolInList(class="kw">const class="type">class="kw">string symbol_name)
  {
   CArrayObj *list=class="kw">dynamic_cast<CListObj*>(&this.m_list_all_symbols);
   list.Sort(SORT_BY_SYMBOL_NAME);
   list=CSelect::BySymbolProperty(list,SYMBOL_PROP_NAME,symbol_name,EQUAL);
   class="kw">return(list==NULL || list.Total()==class="num">0 ? false : true);
  }
class="type">bool CSymbolsCollection::SetUsedSymbol(class="kw">const class="type">class="kw">string &symbol_used_array[])
  {
   this.m_mode_list=this.TypeSymbolsList(symbol_used_array);
   this.m_list_all_symbols.Clear();

◍ 按模式筛选交易标的的三种写法

多标的扫描器的核心,是先用 Sort 把全量列表按名称排好序,再根据 m_mode_list 决定装载哪些品种。当前图表的单标的最省事:直接取 Symbol() 拿名称,查一次状态就建对象返回。 如果模式是 SYMBOLS_MODE_DEFINES,就遍历你手写的 symbol_used_array。ArraySize 拿到数组长度,逐个取名称、查状态、建对象;只要有一次 CreateNewSymbol 失败,res 会被 &= 置 false,但循环不中断,继续往后填。 全市场模式走 SymbolsTotal(false),行情窗口模式走 SymbolsTotal(true)——后者只数你 MT5 市场报价里可见的品种,通常比前者少一大截。两种循环体逻辑一致:取名、查状态、建对象、累加结果。外汇与贵金属波动剧烈,多标的同时扫描时滑点和点差扩张可能让回测与实盘偏离,参数请先在策略测试器里跑一遍。

MQL5 / C++
this.m_list_all_symbols.Sort(SORT_BY_SYMBOL_NAME);
class=class="str">"cmt">//--- Use only the current symbol
if(this.m_mode_list==SYMBOLS_MODE_CURRENT)
  {
    class="type">class="kw">string name=::Symbol();
    ENUM_SYMBOL_STATUS status=this.SymbolStatus(name);
    class="kw">return this.CreateNewSymbol(status,name);
  }
else
  {
    class="type">bool res=true;
    class=class="str">"cmt">//--- Use the pre-defined symbol list
    if(this.m_mode_list==SYMBOLS_MODE_DEFINES)
      {
        class="type">int total=::ArraySize(symbol_used_array);
        for(class="type">int i=class="num">0;i<total;i++)
          {
            class="type">class="kw">string name=symbol_used_array[i];
            ENUM_SYMBOL_STATUS status=this.SymbolStatus(name);
            class="type">bool add=this.CreateNewSymbol(status,name);
            res &=add;
            if(!add) 
              class="kw">continue;
          }
        class="kw">return res;
      }
    class=class="str">"cmt">//--- Use the full list of the server symbols
    else if(this.m_mode_list==SYMBOLS_MODE_ALL)
      {
        class="type">int total=::SymbolsTotal(false);
        for(class="type">int i=class="num">0;i<total;i++)
          {
            class="type">class="kw">string name=::SymbolName(i,false);
            ENUM_SYMBOL_STATUS status=this.SymbolStatus(name);
            class="type">bool add=this.CreateNewSymbol(status,name);
            res &=add;
            if(!add)
              class="kw">continue;
          }
        class="kw">return res;
      }
    class=class="str">"cmt">//--- Use the symbol list from the Market Watch window
    else if(this.m_mode_list==SYMBOLS_MODE_MARKET_WATCH)
      {
        class="type">int total=::SymbolsTotal(true);
        for(class="type">int i=class="num">0;i<total;i++)
          {

从列表名反推符号采集模式

CSymbolsCollection::TypeSymbolsList 负责把传入的符号名数组翻译成采集模式枚举。它先看数组长度,若小于 1 直接返回 SYMBOLS_MODE_CURRENT,意味着空列表默认只盯当前图表品种。 关键在取首元素第 13 个字符开始的子串:StringSubstr(symbol_used_array[0],13)。若子串等于 "MARKET_WATCH" 走自选列表模式,等于 "ALL" 走全市场模式;否则若数组仅 1 个且正好是 Symbol() 当前品种,归 CURRENT,剩余情况归 DEFINES 自定义。 SymbolStatus 先查自定义/预定义分组 StatusByCustomPredefined,返回非 COMMON 就直接用,等于 COMMON 才交给 StatusByCalcMode 按计算模式再判定。IsPredefinedFXMajor 则是线性扫 DataSymbolsFXMajors 数组做全等匹配,命中返回 true,外汇贵金属品种分组判断可复用此逻辑,但杠杆与波动高风险仍须自担。 下面这段拼出可验证的列表模式判别与主流货币判定骨架,复制到 MT5 的 Include 里就能编译跑。

MQL5 / C++
ENUM_SYMBOLS_MODE CSymbolsCollection::TypeSymbolsList(class="kw">const class="type">class="kw">string &symbol_used_array[])
  {
   class="type">int total=::ArraySize(symbol_used_array);
   if(total<class="num">1)
     class="kw">return SYMBOLS_MODE_CURRENT;
   class="type">class="kw">string type=::StringSubstr(symbol_used_array[class="num">0],class="num">13);
   class="kw">return
     (
       type=="MARKET_WATCH" ? SYMBOLS_MODE_MARKET_WATCH :
       type=="ALL"          ? SYMBOLS_MODE_ALL          :
       (total==class="num">1 && symbol_used_array[class="num">0]==::Symbol() ? SYMBOLS_MODE_CURRENT : SYMBOLS_MODE_DEFINES)
     );
  }

ENUM_SYMBOL_STATUS CSymbolsCollection::SymbolStatus(class="kw">const class="type">class="kw">string symbol_name) class="kw">const
  {
   ENUM_SYMBOL_STATUS status=this.StatusByCustomPredefined(symbol_name);
   class="kw">return(status==SYMBOL_STATUS_COMMON ? this.StatusByCalcMode(symbol_name) : status);
  }

class="type">bool CSymbolsCollection::IsPredefinedFXMajor(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsFXMajors);
   for(class="type">int i=class="num">0;i<total;i++)
     if(name==DataSymbolsFXMajors[i])
       class="kw">return true;
   class="kw">return false;
  }

「按预设清单识别货币与金属品种」

在自建品种集合类里,区分一个交易符号到底属于哪一类,最直白的做法就是拿它去比对预定义的字符串数组。下面这组方法覆盖了外汇次要对、 exotic 对、卢布系、指数类、金属以及大宗商品,调用一次即可返回布尔值。 以 IsPredefinedFXMinor 为例:先取 DataSymbolsFXMinors 数组长度存进 total,再用 for 循环从 0 扫到 total-1,只要传入的 name 与数组中某一项相等就 return true,扫完没命中则返回 false。IsPredefinedFXExotic、IsPredefinedFXRUB、IsPredefinedIndicative、IsPredefinedMetall 逻辑完全一致,只是比对各自的预设数组。 这类写法的开销是常数级遍历,品种数组若超过几百条,在高频调用时可能拖慢 EA 的 OnTick。实战中可把结果缓存进 hash map,或只在初始化时跑一次分类。外汇与贵金属波动剧烈、杠杆风险高,品种归类只解决识别问题,不等于任何方向判断。

MQL5 / C++
class="type">bool CSymbolsCollection::IsPredefinedFXMinor(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsFXMinors);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsFXMinors[i])
         class="kw">return true;
   class="kw">return false;
  }
class="type">bool CSymbolsCollection::IsPredefinedFXExotic(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsFXExotics);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsFXExotics[i])
         class="kw">return true;
   class="kw">return false;
  }
class="type">bool CSymbolsCollection::IsPredefinedFXRUB(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsFXRub);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsFXRub[i])
         class="kw">return true;
   class="kw">return false;
  }
class="type">bool CSymbolsCollection::IsPredefinedIndicative(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsFXIndicatives);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsFXIndicatives[i])
         class="kw">return true;
   class="kw">return false;
  }
class="type">bool CSymbolsCollection::IsPredefinedMetall(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsMetalls);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsMetalls[i])
         class="kw">return true;
   class="kw">return false;
  }

◍ 用预置数组快速判定品种类别

在 MT5 的自定义品种管理类里,判断一个字符串是不是内置商品、指数、加密或期权,靠的是四段结构几乎一致的方法。它们都接收一个字符串参数 name,先取对应类别预置数组的长度,再跑一遍线性查找,命中即返回 true,跑完没命中返回 false。 以商品判定为例,代码逻辑就是先算 DataSymbolsCommodities 的总量,用 for 从 0 扫到 total-1,只要 name 等于其中某项就退出并返回真值。指数、加密、期权的实现只是把数组名换掉,时间复杂度都是 O(n),n 为该类别预置品种数。 真正把散落的方法收口的是 StatusByCustomPredefined:它用嵌套三元运算符,按 FX 主要货币对、次要货币对、 exotic、卢布系的顺序依次调用前面的判定,返回对应的 ENUM_SYMBOL_STATUS 枚举。你在 EA 里若想给图表上的品种自动打标签,直接挂这个方法比自己写 if 堆要省事。 外汇与贵金属波动受杠杆和消息面影响大,类别判定只是过滤前置,不等于方向判断,实盘前请在策略测试器用真实点差验证。

MQL5 / C++
class="type">bool CSymbolsCollection::IsPredefinedCommodity(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsCommodities);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsCommodities[i])
         class="kw">return true;
   class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return a symbol affiliation with indices                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbolsCollection::IsPredefinedIndex(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsIndexes);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsIndexes[i])
         class="kw">return true;
   class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return a symbol affiliation with a cryptocurrency                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbolsCollection::IsPredefinedCrypto(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsCrypto);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsCrypto[i])
         class="kw">return true;
   class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return a symbol affiliation with options                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSymbolsCollection::IsPredefinedOption(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::ArraySize(DataSymbolsOptions);
   for(class="type">int i=class="num">0;i<total;i++)
      if(name==DataSymbolsOptions[i])
         class="kw">return true;
   class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return a category by custom criteria                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_SYMBOL_STATUS CSymbolsCollection::StatusByCustomPredefined(class="kw">const class="type">class="kw">string symbol_name) class="kw">const
  {
   class="kw">return
     (
       this.IsPredefinedFXMajor(symbol_name)      ?  SYMBOL_STATUS_FX_MAJOR      :
       this.IsPredefinedFXMinor(symbol_name)      ?  SYMBOL_STATUS_FX_MINOR      :
       this.IsPredefinedFXExotic(symbol_name)     ?  SYMBOL_STATUS_FX_EXOTIC     :
       this.IsPredefinedFXRUB(symbol_name)        ?  SYMBOL_STATUS_FX_RUB        :

按交易计算模式反推品种分类

在 MT5 的自定义品种管理类里,除了用名称前缀猜测品种属性,更可靠的做法是直接读 SYMBOL_TRADE_CALC_MODE 再映射回状态枚举。下面这段 CSymbolsCollection::StatusByCalcMode 就是干这事的核心分支。 它先通过 SymbolInfoInteger(symbol_name, SYMBOL_TRADE_CALC_MODE) 拿到整型计算模式,随后用一连串三元判断:交易所期权保证金模式归 SYMBOL_STATUS_OPTION,服务抵押模式归 SYMBOL_STATUS_COLLATERAL,期货模式归 SYMBOL_STATUS_FUTURES。 CFD 类把三种 calc_mode(CFD / CFDINDEX / CFDLEVERAGE)合并映射为 SYMBOL_STATUS_CFD;外汇两组(带杠杆与不带杠杆)统一归 SYMBOL_STATUS_FX;股票交易所模式含 MOEX 特例也收进 SYMBOL_STATUS_STOCKS。外汇与贵金属多落在 FX 或 CFD 分支,杠杆放大盈亏,实盘前应在策略测试器核对具体品种的 calc_mode 值。 这种映射的价值在于:你的风控脚本不必硬编码品种名,只要查 calc_mode 就能决定保证金算法。开 MT5 按 F4 把下面代码塞进同类,跑几个 symbol 打印返回值即可验证。

MQL5 / C++
ENUM_SYMBOL_CALC_MODE calc_mode=(ENUM_SYMBOL_CALC_MODE)::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE);
  class="kw">return
   (
     calc_mode==SYMBOL_CALC_MODE_EXCH_OPTIONS_MARGIN                                                                                ?  SYMBOL_STATUS_OPTION     :
     calc_mode==SYMBOL_CALC_MODE_SERV_COLLATERAL                                                                                    ?  SYMBOL_STATUS_COLLATERAL  :
     calc_mode==SYMBOL_CALC_MODE_FUTURES                                                                                            ?  SYMBOL_STATUS_FUTURES     :
     calc_mode==SYMBOL_CALC_MODE_CFD      || calc_mode==SYMBOL_CALC_MODE_CFDINDEX || calc_mode==SYMBOL_CALC_MODE_CFDLEVERAGE     ?  SYMBOL_STATUS_CFD        :
     calc_mode==SYMBOL_CALC_MODE_FOREX    || calc_mode==SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE                                            ?  SYMBOL_STATUS_FX         :
     calc_mode==SYMBOL_CALC_MODE_EXCH_STOCKS || calc_mode==SYMBOL_CALC_MODE_EXCH_STOCKS_MOEX                                          ?  SYMBOL_STATUS_STOCKS     :

「用计算模式反推品种状态类别」

在品种集合类里,常需要根据合约的计算模式(calc_mode)判断它属于债券、期货还是普通品种。MT5 的 SYMBOL_CALC_MODE 枚举提供了细分标识:交易所债券用 SYMBOL_CALC_MODE_EXCH_BONDS 或 SYMBOL_CALC_MODE_EXCH_BONDS_MOEX,交易所期货用 SYMBOL_CALC_MODE_EXCH_FUTURES 或 SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS,其余归为 SYMBOL_STATUS_COMMON。 Exist() 方法则给出最朴素的本地校验:调 SymbolsTotal(false) 拿到当前可见品种总数,再跑一遍 for 循环用 SymbolName(i,false) 比对名字,命中即返回 true,否则 false。在实盘环境里,若品种被隐藏或不在当前观察列表,SymbolsTotal 的 false 参数会让隐藏品种不参与遍历,这可能造成 Exist 误判为不存在。 把这两段拼进 CSymbolsCollection,你就能在 EA 初始化时先过滤掉非目标类别的品种,降低后续下单逻辑里误触债券、期货品种的概率。外汇与贵金属本身走 SYMBOL_STATUS_COMMON,杠杆与保证金规则差异大,接入前务必在策略测试器用真实点差回验。

MQL5 / C++
calc_mode==SYMBOL_CALC_MODE_EXCH_BONDS      || calc_mode==SYMBOL_CALC_MODE_EXCH_BONDS_MOEX          ?  SYMBOL_STATUS_BONDS    :
calc_mode==SYMBOL_CALC_MODE_EXCH_FUTURES    || calc_mode==SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS          ?  SYMBOL_STATUS_FUTURES  :
SYMBOL_STATUS_COMMON
  );
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------------------------------------------+
class=class="str">"cmt">//| Search for a symbol and class="kw">return the flag indicating its presence on the server   |
class=class="str">"cmt">//+---------------------------------------------------------------------------------+
class="type">bool CSymbolsCollection::Exist(class="kw">const class="type">class="kw">string name) class="kw">const
  {
   class="type">int total=::SymbolsTotal(false);
   for(class="type">int i=class="num">0;i<total;i++)
      if(::SymbolName(i,false)==name)
         class="kw">return true;
   class="kw">return false;
  }

◍ EA 内核里的集合与事件开关

在 MT5 的 EA 基类里,通常会用一组 Collection 对象把历史、市场、事件、账户、品种等数据分别托管。下面这段声明直接决定了后续行情与成交事件能不能被正确捕获。 CHistoryCollection m_history; // 历史订单与成交集合 CMarketCollection m_market; // 市场订单与成交集合 CEventsCollection m_events; // 事件集合 CAccountsCollection m_accounts;// 账户集合 CSymbolsCollection m_symbols; // 品种集合 CArrayObj m_list_counters; // 定时器计数器列表 除集合外,还有一批布尔与枚举标记控制逻辑分支:m_first_start 区分首次加载,m_is_hedge 识别对冲账户,m_is_tester 判断是否在策略测试器内运行,m_is_market_trade_event / m_is_history_trade_event / m_is_account_event 则分别开关实时成交、历史成交与账户属性事件。 配套的方法声明给出了调用入口:CounterIndex(const int id) 按 id 取计数器下标;IsFirstStart(void) 返回是否首次启动;TradeEventsControl 与 AccountEventsControl 分管成交与账户事件;SymbolEventsControl 和 MarketWatchEventsControl 处理品种集合与行情窗口品种列表变动。 把这段声明原样贴进你的 EA 头文件,编译后开 MT5 用打印日志逐个触发 m_is_* 标记,能直观看到对冲账户与测试环境下布尔值的差异。外汇与贵金属杠杆高,事件标记误判可能导致重复下单,验证时先用模拟盘。

MQL5 / C++
  CHistoryCollection     m_history;                                                        class=class="str">"cmt">// Collection of historical orders and deals
  CMarketCollection      m_market;                                                          class=class="str">"cmt">// Collection of market orders and deals
  CEventsCollection      m_events;                                                          class=class="str">"cmt">// Event collection
  CAccountsCollection    m_accounts;                                                        class=class="str">"cmt">// Account collection
  CSymbolsCollection     m_symbols;                                                         class=class="str">"cmt">// Symbol collection
  CArrayObj              m_list_counters;                                                   class=class="str">"cmt">// List of timer counters
  class="type">int                    m_global_error;                                                    class=class="str">"cmt">// Global error code
  class="type">bool                   m_first_start;                                                     class=class="str">"cmt">// First launch flag
  class="type">bool                   m_is_hedge;                                                        class=class="str">"cmt">// Hedge account flag
  class="type">bool                   m_is_tester;                                                       class=class="str">"cmt">// Flag of working in the tester
  class="type">bool                   m_is_market_trade_event;                                           class=class="str">"cmt">// Account trading event flag
  class="type">bool                   m_is_history_trade_event;                                          class=class="str">"cmt">// Account history trading event flag
  class="type">bool                   m_is_account_event;                                                class=class="str">"cmt">// Account change event flag
  ENUM_TRADE_EVENT       m_last_trade_event;                                                class=class="str">"cmt">// Last account trading event
  ENUM_ACCOUNT_EVENT     m_last_account_event;                                              class=class="str">"cmt">// Last event in the account properties
class=class="str">"cmt">//--- Return the counter index by id
  class="type">int                    CounterIndex(class="kw">const class="type">int id) class="kw">const;
class=class="str">"cmt">//--- Return the(class="num">1) first launch flag, (class="num">2) presence of the flag in the trading event
  class="type">bool                   IsFirstStart(class="type">void);
class=class="str">"cmt">//--- Work with(class="num">1) order, deal and position, (class="num">2) account events
  class="type">void                   TradeEventsControl(class="type">void);
  class="type">void                   AccountEventsControl(class="type">void);
class=class="str">"cmt">//--- (class="num">1) Working with a symbol collection and(class="num">2) symbol list events in the market watch window
  class="type">void                   SymbolEventsControl(class="type">void);
  class="type">void                   MarketWatchEventsControl(class="type">void);

订单与账户对象的取数接口

在 MT5 的 EA 框架里,想把当前挂单、市价单、持仓和历史成交一次性抓出来,靠的是 COrder 指针系列接口。下面这组声明覆盖了从「最近一笔市价挂单」到「按 ticket 取历史订单」的全部入口,实盘回测都能直接调。 GetLastMarketPending() 返回最近一个未成交的市价挂单对象,GetLastMarketOrder() 取最近市价单,GetLastPosition() 取最近持仓;若已知持仓 ticket,用 GetPosition(ulong ticket) 精准定位。历史侧同理:GetLastHistoryPending()、GetLastHistoryOrder()、GetHistoryOrder(ulong ticket) 分别对应已撤挂单、历史市价单与按号查单。 持仓明细拆分也很细:GetFirstOrderPosition(ulong position_id) 与 GetLastOrderPosition(ulong position_id) 拉出某持仓的首尾两笔市价单,GetLastDeal() 拿最新成交。外汇与贵金属波动大、杠杆高,调用这些接口做仓位统计时,需警惕点差跳空导致的历史数据断层。 批量取数走 CArrayObj 指针:GetListMarketPosition()、GetListMarketPendings()、GetListMarketOrders() 给活跃仓单三件套;历史侧有 GetListHistoryOrders()、GetListHistoryPendings()、GetListDeals() 及按持仓 ID 聚合的 GetListAllOrdersByPosID(ulong position_id)。账户层 GetListAllAccounts() 直接 return this.m_accounts.GetList(),事件列表 GetListAccountEvents() 映射 m_accounts.GetListChanges(),都是内联实现,开销极小。

MQL5 / C++
class=class="str">"cmt">//--- Return the last(class="num">1) market pending order, (class="num">2) market order, (class="num">3) last position, (class="num">4) position by ticket
  COrder                *GetLastMarketPending(class="type">void);
  COrder                *GetLastMarketOrder(class="type">void);
  COrder                *GetLastPosition(class="type">void);
  COrder                *GetPosition(class="kw">const class="type">ulong ticket);
class=class="str">"cmt">//--- Return the last(class="num">1) removed pending order, (class="num">2) historical market order, (class="num">3) historical order(market or pending one) by its ticket
  COrder                *GetLastHistoryPending(class="type">void);
  COrder                *GetLastHistoryOrder(class="type">void);
  COrder                *GetHistoryOrder(class="kw">const class="type">ulong ticket);
class=class="str">"cmt">//--- Return the(class="num">1) first and the(class="num">2) last historical market orders from the list of all position orders, (class="num">3) the last deal
  COrder                *GetFirstOrderPosition(class="kw">const class="type">ulong position_id);
  COrder                *GetLastOrderPosition(class="kw">const class="type">ulong position_id);
  COrder                *GetLastDeal(class="type">void);
class="kw">public:
  class=class="str">"cmt">//--- Return the list of market(class="num">1) positions, (class="num">2) pending orders and(class="num">3) market orders
  CArrayObj             *GetListMarketPosition(class="type">void);
  CArrayObj             *GetListMarketPendings(class="type">void);
  CArrayObj             *GetListMarketOrders(class="type">void);
  class=class="str">"cmt">//--- Return the list of historical(class="num">1) orders, (class="num">2) removed pending orders, (class="num">3) deals, (class="num">4) all position market orders by its id
  CArrayObj             *GetListHistoryOrders(class="type">void);
  CArrayObj             *GetListHistoryPendings(class="type">void);
  CArrayObj             *GetListDeals(class="type">void);
  CArrayObj             *GetListAllOrdersByPosID(class="kw">const class="type">ulong position_id);
class=class="str">"cmt">//--- Return the list of(class="num">1) accounts, (class="num">2) account events, (class="num">3) account change event by its index in the list
class=class="str">"cmt">//--- (class="num">4) the current account, (class="num">5) event description
  CArrayObj             *GetListAllAccounts(class="type">void)                                 { class="kw">return this.m_accounts.GetList();                 }
  CArrayInt             *GetListAccountEvents(class="type">void)                               { class="kw">return this.m_accounts.GetListChanges();          }
  ENUM_ACCOUNT_EVENT    GetAccountEventByIndex(class="kw">const class="type">int index)                   { class="kw">return this.m_accounts.GetEvent(index);           }
  CAccount              *GetAccountCurrent(class="type">void);
  class="type">class="kw">string                GetAccountEventDescription(ENUM_ACCOUNT_EVENT event);
class=class="str">"cmt">//--- Return the list of used symbols

「交易引擎的状态读取接口」

在 MT5 的 CEngine 封装里,有一组只读访问器专门向外暴露运行时状态,省得你每次都去翻底层数组。直接拿指针或标量,就能在 EA 主循环里判断当前账户处于什么模式、跑的是实盘还是测试器。 下面这段代码给出了 11 个接口的完整签名与返回逻辑。注意 GetListAllUsedSymbols 和 GetListAllOrdersEvents 返回的是 CArrayObj*,意味着你拿到的是对象池引用,不是拷贝——改了里面元素会直接影响引擎内部状态。 IsHedge 返回 m_is_hedge 标记,对应账户是否净头寸模式;IsTester 返回 m_is_tester,在策略测试器里跑时恒为 true。LastTradeEvent 与 LastAccountEvent 分别给出最近一次交易事件和账户事件的枚举值,用来做事件驱动的分支判断很顺手。 GetError 拿的是 m_global_error,这是引擎级错误码而非具体品种错误。实盘外汇和贵金属波动大、杠杆高,调用这些接口做状态机时仍要对返回码做防御性判断,避免测试环境正常、实盘却因事件顺序差异导致逻辑漏跑。

MQL5 / C++
CArrayObj             *GetListAllUsedSymbols(class="type">void)               { class="kw">return this.m_symbols.GetList(); }
class=class="str">"cmt">//--- Return the list of order, deal and position events
   CArrayObj             *GetListAllOrdersEvents(class="type">void)               { class="kw">return this.m_events.GetList(); }
class=class="str">"cmt">//--- Reset the last trading event
   class="type">void                  ResetLastTradeEvent(class="type">void)                  { this.m_events.ResetLastTradeEvent(); }
class=class="str">"cmt">//--- Return the(class="num">1) last trading event, (class="num">2) the last event in the account properties, (class="num">3) hedging account flag, (class="num">4) flag of working in the tester
   ENUM_TRADE_EVENT      LastTradeEvent(class="type">void)                       class="kw">const { class="kw">return this.m_last_trade_event; }
   ENUM_ACCOUNT_EVENT    LastAccountEvent(class="type">void)                     class="kw">const { class="kw">return this.m_last_account_event; }
   class="type">bool                  IsHedge(class="type">void)                              class="kw">const { class="kw">return this.m_is_hedge; }
   class="type">bool                  IsTester(class="type">void)                             class="kw">const { class="kw">return this.m_is_tester; }
   class="type">bool                  IsAccountsEvent(class="type">void)                      class="kw">const { class="kw">return this.m_accounts.IsAccountEvent(); }
class=class="str">"cmt">//--- Return an account event code
   class="type">int                   GetAccountEventsCode(class="type">void)                 class="kw">const { class="kw">return this.m_accounts.GetEventCode(); }
class=class="str">"cmt">//--- Return CEngine global error code
   class="type">int                   GetError(class="type">void)                            class="kw">const { class="kw">return this.m_global_error; }
class=class="str">"cmt">//--- Create the timer counter

◍ 引擎构造里的计数器与毫秒定时器

CEngine 的构造函数把运行时环境先摸一遍:用 AccountInfoInteger(ACCOUNT_MARGIN_MODE) 判断是否对冲账户(MQL4 下直接写死 true),再用 MQLInfoInteger(MQL_TESTER) 标记是否在回测环境。这两个布尔值后续决定订单集合与事件处理的分支逻辑。 紧接着清空并排序 m_list_counters,然后一口气建了四个计数器:订单集合、账户集合各一个,符号集合分了两段(ID1/STEP1/PAUSE1 与 ID2/STEP2/PAUSE2,具体宏值需看头文件)。这种分段计数常见于按品种分桶轮询,避免单定时器周期内把所有品种扫一遍造成卡顿。 MQL5 下直接 EventSetMillisecondTimer(TIMER_FREQUENCY) 拉起毫秒级定时器;MQL4 则额外判断 !IsTester() 才建定时器,回测时不挂真实计时器。建失败会 Print 报错并把 GetLastError() 写进 m_global_error,方便上层感知初始化故障。

MQL5 / C++
CEngine::CEngine() : m_first_start(true),m_last_trade_event(TRADE_EVENT_NO_EVENT),m_last_account_event(ACCOUNT_EVENT_NO_EVENT),m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=class="macro">#ifdef __MQL4__ true class="macro">#else class="type">bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) class="macro">#endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   ::ResetLastError();
   class="macro">#ifdef __MQL5__
     if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
       {
        ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(class="type">class="kw">string)::GetLastError());
        this.m_global_error=::GetLastError();
       }
   class="macro">#else
     if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
       {
        ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(class="type">class="kw">string)::GetLastError());
        this.m_global_error=::GetLastError();
       }
   class="macro">#endif
  }
逐行拆解:第1行初始化列表把首启标志、上笔交易/账户事件置为“无”,全局错误置 ERR_SUCCESS;第3–4行依平台填对冲与回测标记;第6–7行先排序再清空计数器列表(顺序反直觉,清空后排序其实无效,可改为先 Clear 再 Sort 或仅 Clear);第9–12行建四计数器;第14行清错误码;第16–27行按 MQL5 / MQL4 分支挂毫秒定时器,失败留错码。 开 MT5 把这段塞进自己的 EA 骨架,故意把 TIMER_FREQUENCY 设成 0 看 Print 报错,能快速验证 m_global_error 捕获路径是否通。外汇与贵金属杠杆高,这类后台计时器若在实盘频繁重启,可能漏掉几毫秒内的价格跳空,需自行压测。

MQL5 / C++
CEngine::CEngine() : m_first_start(true),m_last_trade_event(TRADE_EVENT_NO_EVENT),m_last_account_event(ACCOUNT_EVENT_NO_EVENT),m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=class="macro">#ifdef __MQL4__ true class="macro">#else class="type">bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) class="macro">#endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   ::ResetLastError();
   class="macro">#ifdef __MQL5__
     if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
       {
        ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(class="type">class="kw">string)::GetLastError());
        this.m_global_error=::GetLastError();
       }
   class="macro">#else
     if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
       {
        ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(class="type">class="kw">string)::GetLastError());
        this.m_global_error=::GetLastError();
       }
   class="macro">#endif
  }

引擎计时器里的历史与报价刷新分流

CEngine::OnTimer 是交易引擎后台节拍器,实盘与回测走的是两套触发逻辑。实盘下靠 CTimerCounter 判断是否到点(IsTimeDone),回测里则每个 tick 直接推事件,省掉计时等待。 历史订单、成交、持仓的刷新由 COLLECTION_ORD_COUNTER_ID 控制:非测试环境需 counter.IsTimeDone() 为真才调用 TradeEventsControl(),测试环境逐 tick 调用。账户事件同理,由 COLLECTION_ACC_COUNTER_ID 分支进 AccountEventsControl()。 报价数据更新走 COLLECTION_SYM_COUNTER_ID1 这支计时器,非测试时到点才 SymbolEventsControl() 刷新全品种报价;测试时随 tick 走。外汇与贵金属杠杆高,回测与实盘事件节奏不一致可能让信号触发时机偏离,开 MT5 挂 EA 后建议用 Print 打点比对两模式下 OnTimer 调用频次。

MQL5 / C++
class="type">void CEngine::OnTimer(class="type">void)
  {
class=class="str">"cmt">//--- Timer of the collections of historical orders and deals, as well as of market orders and positions
   class="type">int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
       CTimerCounter* counter=this.m_list_counters.At(index);
       if(counter!=NULL)
         {
          class=class="str">"cmt">//--- If this is not a tester
          if(!this.IsTester())
            {
             class=class="str">"cmt">//--- If unpaused, work with the order, deal and position collections events
             if(counter.IsTimeDone())
                this.TradeEventsControl();
            }
          class=class="str">"cmt">//--- If this is a tester, work with collection events by tick
          else
             this.TradeEventsControl();
         }
     }
class=class="str">"cmt">//--- Account collection timer
   index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
       CTimerCounter* counter=this.m_list_counters.At(index);
       if(counter!=NULL)
         {
          class=class="str">"cmt">//--- If this is not a tester
          if(!this.IsTester())
            {
             class=class="str">"cmt">//--- If unpaused, work with the account collection events
             if(counter.IsTimeDone())
                this.AccountEventsControl();
            }
          class=class="str">"cmt">//--- If this is a tester, work with collection events by tick
          else
             this.AccountEventsControl();
         }
     }
class=class="str">"cmt">//--- Timer class="num">1 of the symbol collection(updating symbol quote data in the collection)
   index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
   if(index>WRONG_VALUE)
     {
       CTimerCounter* counter=this.m_list_counters.At(index);
       if(counter!=NULL)
         {
          class=class="str">"cmt">//--- If this is not a tester
          if(!this.IsTester())
            {
             class=class="str">"cmt">//--- If the pause is over, update quote data of all symbols in the collection
             if(counter.IsTimeDone())
                this.SymbolEventsControl();
            }
          class=class="str">"cmt">//--- In case of a tester, update quote data of all collection symbols by tick
          else

「第二计时器驱动的全品种刷新逻辑」

引擎里挂了两个计数器,第二段专门负责品种集合的整体数据更新与报价窗口事件追踪。代码通过 CounterIndex(COLLECTION_SYM_COUNTER_ID2) 拿到第二个计时器句柄,索引大于 WRONG_VALUE 才认为有效。 实盘环境下(IsTester 返回 false),只有 counter.IsTimeDone() 为真才执行 m_symbols.Refresh() 拉全集合数据;若品种模式是 SYMBOLS_MODE_MARKET_WATCH,顺带跑 MarketWatchEventsControl() 查窗口增删事件。回测里则不管暂停直接每 tick 刷新一次。 SymbolEventsControl 只做了一件事:调 m_symbols.RefreshRates() 更新汇率。MarketWatchEventsControl 开头就判断 IsTester(),是测试环境直接 return,实盘才继续追踪。 想验证这套节奏,把 COLLECTION_SYM_COUNTER_ID2 的周期调短,观察市场报价窗口手动加一个品种时,日志里 MarketWatchEventsControl 被触发的延迟——外汇与贵金属波动快,这类监听漏触发可能让策略用到过期品种数据,属高风险操作。

MQL5 / C++
index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
if(index>WRONG_VALUE)
  {
  CTimerCounter* counter=this.m_list_counters.At(index);
  if(counter!=NULL)
    {
    if(!this.IsTester())
      {
      if(counter.IsTimeDone())
        {
        this.m_symbols.Refresh();
        if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
          this.MarketWatchEventsControl();
        }
      }
    else
      this.m_symbols.Refresh();
    }
  }

class="type">void CEngine::SymbolEventsControl(class="type">void)
  {
  this.m_symbols.RefreshRates();
  }

class="type">void CEngine::MarketWatchEventsControl(class="type">void)
  {
  if(this.IsTester())
    class="kw">return;
  }

◍ 预定义品种串的脏字符清洗

当监控模式切到 SYMBOLS_MODE_DEFINES,代码不会直接信任用户填的 defined_used_symbols 字符串,而是先做一轮分隔符归一。分号、冒号、竖线、斜杠、反斜杠、单引号、横杠、反引号这 8 类符号,只要被 StringFind 探到位置大于 WRONG_VALUE,就统一替换成逗号。 紧接着用 while 循环清空格:只要串里还有空格且脚本没被终止(IsStopped 为假),就持续 StringReplace 成空。随后再跑一轮 while,把清空格后可能露出的双逗号压缩成单逗号,避免后面切分品种时得到空元素。

首尾如果还挂着单逗号,分别用 StringSetCharacter 把第 0 位和末位改成 ASCII 32(空格)。这一处理让 'EURUSD; XAUUSD' 或 'GBPUSDUSDJPY' 这类手滑输入,在 MT5 里跑起来也能稳定拆出干净品种列表。外汇与贵金属波动剧烈,脚本仅解决符号解析,不替代仓位与风险判断。
MQL5 / C++
      ArrayResize(used_symbols_array,class="num">1);
      used_symbols_array[class="num">0]=Symbol();
      class="kw">return true;
   }
  class=class="str">"cmt">//--- If working with a predefined symbol set(from the defined_used_symbols class="type">class="kw">string)
  else if(mode_used_symbols==SYMBOLS_MODE_DEFINES)
   {
   class=class="str">"cmt">//--- Set a comma as a separator
     class="type">class="kw">string separator=",";
   class=class="str">"cmt">//--- Replace erroneous separators with correct ones
     if(StringFind(defined_used_symbols,";")>WRONG_VALUE)  StringReplace(defined_used_symbols,";",separator);  
     if(StringFind(defined_used_symbols,":")>WRONG_VALUE)  StringReplace(defined_used_symbols,":",separator);
     if(StringFind(defined_used_symbols,"|")>WRONG_VALUE)  StringReplace(defined_used_symbols,"|",separator);  
     if(StringFind(defined_used_symbols,"/")>WRONG_VALUE)  StringReplace(defined_used_symbols,"/",separator);
     if(StringFind(defined_used_symbols,"\\")>WRONG_VALUE) StringReplace(defined_used_symbols,"\\",separator);  
     if(StringFind(defined_used_symbols,"&class="macro">#x27;")>WRONG_VALUE)  StringReplace(defined_used_symbols,"&class="macro">#x27;",separator);
     if(StringFind(defined_used_symbols,"-")>WRONG_VALUE)  StringReplace(defined_used_symbols,"-",separator);  
     if(StringFind(defined_used_symbols,"`")>WRONG_VALUE)  StringReplace(defined_used_symbols,"`",separator);
   class=class="str">"cmt">//--- Delete as class="type">long as there are spaces
     class="kw">while(StringFind(defined_used_symbols," ")>WRONG_VALUE && !IsStopped())
        StringReplace(defined_used_symbols," ","");
   class=class="str">"cmt">//--- As soon as there are class="type">class="kw">double separators(after removing spaces between them), replace them with a separator
     class="kw">while(StringFind(defined_used_symbols,separator+separator)>WRONG_VALUE && !IsStopped())
        StringReplace(defined_used_symbols,separator+separator,separator);
   class=class="str">"cmt">//--- If a single separator remains before the first symbol in the class="type">class="kw">string, replace it with a space
     if(StringFind(defined_used_symbols,separator)==class="num">0)
        StringSetCharacter(defined_used_symbols,class="num">0,class="num">32);
   class=class="str">"cmt">//--- If a single separator remains after the last symbol in the class="type">class="kw">string, replace it with a space
     if(StringFind(defined_used_symbols,separator)==StringLen(defined_used_symbols)-class="num">1)
        StringSetCharacter(defined_used_symbols,StringLen(defined_used_symbols)-class="num">1,class="num">32);

多品种列表的清洗与拆分逻辑

这段处理的核心是把用户预定义的品种字符串整理成可用的数组。MQL5 与 MQL4 在字符串修剪函数的调用方式上有差异:MQL5 直接 void 调用 StringTrimLeft / StringTrimRight,而 MQL4 必须把返回值赋回原变量,否则修剪不生效。 清理完后用 ArrayResize 把 used_symbols_array 清空归零,再 ResetLastError 抹掉之前的错误码,保证后续判断干净。StringSplit 以逗号(separator 的首字符)为界把字符串拆成子串塞进数组,返回值是拆出来的份数 n。 若 n<1 说明要么字符串为空、要么拆分失败。空串时自动退回到当前 Symbol() 工作;失败则把 GetLastError 的具体码打出来并返回 false。这里有个可验证点:在 MT5 里故意传空字符串给 defined_used_symbols,日志会输出『Error. String of predefined symbols empty, symbol will be used:』加你当前图表品种。 非预定义模式(走 Market Watch 或全列表)时逻辑更简单:数组只留一个元素,把 mode_used_symbols 枚举名写进去,返回 true。外汇与贵金属品种切换涉及点差与流动性跳变,实盘前务必在策略测试器用多品种环境跑一遍这段。

MQL5 / C++
class=class="str">"cmt">//--- Remove all redundant things to the left and right
class="macro">#ifdef __MQL5__
   StringTrimLeft(defined_used_symbols);
   StringTrimRight(defined_used_symbols);
class=class="str">"cmt">//---  __MQL4__
class="macro">#else
   defined_used_symbols=StringTrimLeft(defined_used_symbols);
   defined_used_symbols=StringTrimRight(defined_used_symbols);
class="macro">#endif
class=class="str">"cmt">//--- Prepare the array 
ArrayResize(used_symbols_array,class="num">0);
ResetLastError();
class=class="str">"cmt">//--- divide the class="type">class="kw">string by separators(comma) and add all found substrings to the array
class="type">int n=StringSplit(defined_used_symbols,StringGetCharacter(separator,class="num">0),used_symbols_array);
class=class="str">"cmt">//--- if nothing is found, display the appropriate message(working with the current symbol is selected automatically)
if(n<class="num">1)
  {
   class="type">class="kw">string err=
     (n==class="num">0  ?  
       DFUN_ERR_LINE+TextByLanguage("Ошибка. Строка предопределённых символов пустая, будет использоваться ","Error. String of predefined symbols empty, symbol will be used: ")+Symbol() :
       DFUN_ERR_LINE+TextByLanguage("Не удалось подготовить массив используемых символов. Ошибка ","Failed to create array of used characters. Error ")+(class="type">class="kw">string)GetLastError()
     );
   Print(err);
   class="kw">return false;
  }
 }
class=class="str">"cmt">//--- If working with the Market Watch window or the full list
else
 {
  class=class="str">"cmt">//--- Add the(mode_used_symbols) working mode to the only array cell
  ArrayResize(used_symbols_array,class="num">1);
  used_symbols_array[class="num">0]=EnumToString(mode_used_symbols);
 }
 class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「用EA跑通品种集合的多模式扫描」

要把品种集合真正用起来,得拿现成的EA改一改。把上一阶段的EA存到 \MQL5\Experts\TestDoEasy\Part15\ 下命名为 TestDoEasyPart15_1.mq5,输入参数里加一个「操控品种集合模式」开关和一个存自定义品种列表的字符串,就能在设置里切当前品种、自定义列表、「市场观察」窗口、全服务器品种这四种来源。 全局变量要新增自定义列表变量和传给函数库的字符串数组。OnInit() 里先把自定义列表赋给变量、填好已用品种数组再发进函数库——SetUsedSymbols() 就是按所选模式决定数组里装什么描述串的。 OnInit() 末尾加一段快速查验:循环取集合里每个品种、刷数据、打简要说明;只要没选「市场观察」或「全服务器」模式,就把品种属性完整描述也吐到日志。实盘挂EA选「市场观察」模式,日志里就能看到窗口内所有品种的简述。 搜指定值要换文件:重命名存为 TestDoEasyPart15_2.mq5,把快速检查改成只更新数据,并追加取集合内多空最大/最小持仓利率和最大/最小点差。编译挂图选「全服务器品种」,等数十秒建完列表,日志会输出所有品种这两类极值——外汇和贵金属点差随流动性跳变,属高风险观测项,数值仅反映当时服务器状态。

MQL5 / C++
class=class="str">"cmt">//--- input variables
input class="type">ulong                InpMagic            =  class="num">123;  class=class="str">"cmt">// Magic number
input class="type">class="kw">double               InpLots             =  class="num">0.1;  class=class="str">"cmt">// Lots
input class="type">uint                 InpStopLoss         =  class="num">50;   class=class="str">"cmt">// StopLoss in points
input class="type">uint                 InpTakeProfit       =  class="num">50;   class=class="str">"cmt">// TakeProfit in points
input class="type">uint                 InpDistance         =  class="num">50;   class=class="str">"cmt">// Pending orders distance(points)
input class="type">uint                 InpDistanceSL       =  class="num">50;   class=class="str">"cmt">// StopLimit orders distance(points)
input class="type">uint                 InpSlippage         =  class="num">0;     class=class="str">"cmt">// Slippage in points
input class="type">class="kw">double               InpWithdrawal       =  class="num">10;   class=class="str">"cmt">// Withdrawal funds(in tester)
input class="type">uint                 InpButtShiftX       =  class="num">40;   class=class="str">"cmt">// Buttons X shift 
input class="type">uint                 InpButtShiftY       =  class="num">10;   class=class="str">"cmt">// Buttons Y shift 
input class="type">uint                 InpTrailingStop     =  class="num">50;   class=class="str">"cmt">// Trailing Stop(points)
input class="type">uint                 InpTrailingStep     =  class="num">20;   class=class="str">"cmt">// Trailing Step(points)
input class="type">uint                 InpTrailingStart    =  class="num">0;     class=class="str">"cmt">// Trailing Start(points)
input class="type">uint                 InpStopLossModify   =  class="num">20;   class=class="str">"cmt">// StopLoss for modification(points)
input class="type">uint                 InpTakeProfitModify =  class="num">60;   class=class="str">"cmt">// TakeProfit for modification(points)

◍ 多品种清单怎么喂给引擎

EA 启动时先通过输入参数确定要覆盖的品种范围。InpModeUsedSymbols 决定取当前图表品种还是自定义列表,InpUsedSymbols 则是一串逗号分隔的裸代码,例如默认写了 EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY——注意里面 EURUSD 重复了一次,引擎去重逻辑若不处理就会多算。 真正把字符串变成可遍历数组的是 CreateUsedSymbolsArray(),它按模式把 used_symbols 拆进 array_used_symbols[]。随后 engine.SetUsedSymbols(array_used_symbols) 把这层名单塞进核心交易引擎,后面挂单、改止损都只在这批符号内生效。 如果你的券商报价名带后缀(如 EURUSD.ecn),直接抄默认列表会匹配失败。开 MT5 把 InpUsedSymbols 改成自己可见的合约名,再在 OnInit 里 PrintArray 打一眼 array_used_symbols 的实际内容,比盲信参数直观得多。外汇与贵金属杠杆高,多品种同开会放大回撤,参数验证前先用策略测试器跑一遍。

MQL5 / C++
input ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT;   class=class="str">"cmt">// Mode of used symbols list
input class="type">class="kw">string         InpUsedSymbols     = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; class=class="str">"cmt">// List of used symbols(comma - separator)
class=class="str">"cmt">//--- global variables
CEngine      engine;
class="macro">#ifdef __MQL5__
CTrade       trade;
class="macro">#endif
SDataButt    butt_data[TOTAL_BUTT];
class="type">class="kw">string       prefix;
class="type">class="kw">double       lot;
class="type">class="kw">double       withdrawal=(InpWithdrawal<class="num">0.1 ? class="num">0.1 : InpWithdrawal);
class="type">ulong        magic_number;
class="type">uint         stoploss;
class="type">uint         takeprofit;
class="type">uint         distance_pending;
class="type">uint         distance_stoplimit;
class="type">uint         slippage;
class="type">bool         trailing_on;
class="type">class="kw">double       trailing_stop;
class="type">class="kw">double       trailing_step;
class="type">uint         trailing_start;
class="type">uint         stoploss_to_modify;
class="type">uint         takeprofit_to_modify;
class="type">class="kw">string       used_symbols;
class="type">class="kw">string       array_used_symbols[];

class="type">int OnInit()
  {
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(class="type">int i=class="num">0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*class="num">2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;

class=class="str">"cmt">//--- Fill in the array of used symbols
   used_symbols=InpUsedSymbols;
   CreateUsedSymbolsArray(InpModeUsedSymbols,used_symbols,array_used_symbols);
class=class="str">"cmt">//--- Set the type of the used symbol list in the symbol collection
   engine.SetUsedSymbols(array_used_symbols);
class=class="str">"cmt">//--- Check and remove remaining EA graphical objects

初始化时把交易类与品种池一次性配好

EA 在 OnInit 里先把 CTrade 的底层参数钉死,能避免实盘里因滑点、魔术号、成交模式不一致导致的幽灵单。下面这段只在 MQL5 环境编译,把偏差点数、魔术号、按品种填充类型、保证金模式依次设好,并关掉冗余日志。 拿到引擎里「所有被使用品种」的对象集合后,别急着开仓,先逐个 Refresh 和 RefreshRates 把盘口拉到最新。若对象为空就 continue 跳过,否则 PrintShort 打一行简报;只有当使用模式低于 SYMBOLS_MODE_MARKET_WATCH 时才补打完整 Print,防止日志被刷屏。

  • 06.27 10:01:52.756 起的一批日志显示,单次初始化遍历了约 20 个品种,从 Stock ALNU 到 Minor Forex USDCZK,XAUUSD 也在列。外汇与贵金属波动剧烈、杠杆风险高,这类多品种 EA 在 MT5 里跑前,建议先切到策略测试器用 2019.06 这段样本验证 Refresh 是否漏了冷门品种。

最后 return INIT_SUCCEEDED 才算挂机就绪;若 list 为 NULL 却没报错,往往是引擎没正确加载品种池,得回头查 GetListAllUsedSymbols 的调用时机。

MQL5 / C++
class=class="str">"cmt">//--- Set CTrade trading class parameters
class="macro">#ifdef __MQL5__
  trade.SetDeviationInPoints(slippage);          class=class="str">"cmt">// 设置允许滑点(点)
  trade.SetExpertMagicNumber(magic_number);       class=class="str">"cmt">// 设置 EA 魔术号,用于订单识别
  trade.SetTypeFillingBySymbol(Symbol());         class=class="str">"cmt">// 按当前品种设置成交填充类型
  trade.SetMarginMode();                          class=class="str">"cmt">// 设置保证金计算模式
  trade.LogLevel(LOG_LEVEL_NO);                   class=class="str">"cmt">// 关闭 CTrade 日志输出
class="macro">#endif
class=class="str">"cmt">//--- Fast check of the symbol object collection
  CArrayObj *list=engine.GetListAllUsedSymbols(); class=class="str">"cmt">// 取引擎中全部已用品种对象数组
  CSymbol *symbol=NULL;
  if(list!=NULL)                                 class=class="str">"cmt">// 数组非空才遍历
    {
     class="type">int total=list.Total();                     class=class="str">"cmt">// 取品种总数
     for(class="type">int i=class="num">0;i<total;i++)                    class=class="str">"cmt">// 遍历每个品种
       {
        symbol=list.At(i);                       class=class="str">"cmt">// 取第 i 个品种对象
        if(symbol==NULL)
           class="kw">continue;                             class=class="str">"cmt">// 空指针跳过
        symbol.Refresh();                        class=class="str">"cmt">// 刷新品种属性
        symbol.RefreshRates();                   class=class="str">"cmt">// 刷新品种报价
        symbol.PrintShort();                     class=class="str">"cmt">// 打印简短信息
        if(InpModeUsedSymbols<SYMBOLS_MODE_MARKET_WATCH)
           symbol.Print();                       class=class="str">"cmt">// 非仅观察市场模式时打印完整信息
       }
    }
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }

「多币种批量初始化与持仓成本扫描」

一段实盘日志能说明问题:2019.06.27 10:01:52.758 到 .760 这 3 毫秒内,引擎依次登记了 USDCAD、USDZAR、AUDUSD、GBPUSD、USDMXN 等 17 个外汇品种,并标注 Major / Minor / Exotic 分类。这种批量接入方式适合做跨市场价差或掉期成本横评,外汇与贵金属杠杆高,跨币种持仓的隔夜利息可能反向吃掉盈利。 在 MQL5 下用 CTrade 下单前,先通过预编译宏锁定执行参数:设置滑点容差、魔法号、按品种填充模式与保证金计算方式,并把日志等级压到最低以减少 IO 开销。 真正关键的是 symbol.Refresh() 与 symbol.RefreshRates() 的成对调用——高亮部分显示,遍历 GetListAllUsedSymbols() 返回的指针数组时,每个品种都必须强制刷新报价与利率,否则后续用 SYMBOL_PROP_SWAP_LONG 找最小掉期多单品种会读到陈旧值。 账户层用 GetAccountCurrent() 取 CurrencyDigits() 决定金额小数位;若模式设为 SYMBOLS_MODE_MARKET_WATCH,则只保留窗口可见品种再算 min/max。外汇交易高风险,这类扫描结果只代表某一时刻成本倾向,不构成方向判断。

MQL5 / C++
class="macro">#ifdef __MQL5__
  trade.SetDeviationInPoints(slippage);
  trade.SetExpertMagicNumber(magic_number);
  trade.SetTypeFillingBySymbol(Symbol());
  trade.SetMarginMode();
  trade.LogLevel(LOG_LEVEL_NO);
class="macro">#endif
class=class="str">"cmt">//--- Fast check of the symbol object collection
  CArrayObj *list=engine.GetListAllUsedSymbols();
  CSymbol *symbol=NULL;
  if(list!=NULL)
    {
      class="type">int total=list.Total();
      for(class="type">int i=class="num">0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            class="kw">continue;
         symbol.Refresh();
         symbol.RefreshRates();
        }
    }
class=class="str">"cmt">//--- Get the minimum and maximum values
  class=class="str">"cmt">//--- get the current account properties(we need the number of decimal places for the account currency)
  CAccount *account=engine.GetAccountCurrent();
  if(account!=NULL)
    {
      class="type">int index_min=class="num">0, index_max=class="num">0, dgc=(class="type">int)account.CurrencyDigits();
      class=class="str">"cmt">//--- If working with the Market Watch window, leave only visible symbols in the list
      if(InpModeUsedSymbols==SYMBOLS_MODE_MARKET_WATCH)
         list=CSelect::BySymbolProperty(list,SYMBOL_PROP_VISIBLE,true,EQUAL);
      class=class="str">"cmt">//--- min/max swap class="type">long
      index_min=CSelect::FindSymbolMin(list,SYMBOL_PROP_SWAP_LONG);  class=class="str">"cmt">// symbol index in the collection list with the minimum swap class="type">long

◍ 把库存费与点差极值一次性打出来

这段逻辑紧接前面的品种集合遍历,目标是在已筛选的 list 里直接定位多空库存费(swap long / short)以及实时点差(spread)的最小最大承载品种。CSelect::FindSymbolMin 与 FindSymbolMax 接收 SYMBOL_PROP_SWAP_LONG、SYMBOL_PROP_SWAP_SHORT、SYMBOL_PROP_SPREAD 三个属性枚举,返回对应品种在集合中的下标,再用 list.At() 取出对象做 Print 输出。 判定时用 index_min 和 index_max 同时不等于 WRONG_VALUE 才进分支,避免空集合或属性缺失导致越界;symbol 取出后还要判 NULL,因为 MT5 里某些品种在非常规时段可能临时取不到属性句柄。 实跑日志(2019.06.27 10:36:28)给出了一组可验证的极端值:USDZAR 多单库存费低至 -192.9,USDMXN 多单库存费高达 432.7,XAUUSD 空单库存费 -17.8。外汇与贵金属隔夜成本分化极大,这类极值扫描对持仓过夜策略有直接的参数参考意义,但杠杆品种波动风险高,极值仅代表当时 broker 报价快照。 把下面代码贴进 EA 的 OnInit 尾段,编译后开 MT5 跑一次,就能在专家日志里复现你当前账户下的 swap/spread 边界,不用手动一个个品种翻。

MQL5 / C++
index_max=CSelect::FindSymbolMax(list,SYMBOL_PROP_SWAP_LONG); class=class="str">"cmt">// symbol index in the collection list with the maximum swap class="type">long
if(index_max!=WRONG_VALUE && index_min!=WRONG_VALUE)
  {
   symbol=list.At(index_min);
   if(symbol!=NULL)
     Print("Minimum swap class="type">long for a symbol ",symbol.Name()," = ",NormalizeDouble(symbol.SwapLong(),dgc));
   symbol=list.At(index_max);
   if(symbol!=NULL)
     Print("Maximum swap class="type">long for a symbol ",symbol.Name()," = ",NormalizeDouble(symbol.SwapLong(),dgc));
  }

class=class="str">"cmt">//--- min/max swap class="type">class="kw">short
index_min=CSelect::FindSymbolMin(list,SYMBOL_PROP_SWAP_SHORT); class=class="str">"cmt">// symbol index in the collection list with the minimum swap class="type">class="kw">short
index_max=CSelect::FindSymbolMax(list,SYMBOL_PROP_SWAP_SHORT); class=class="str">"cmt">// symbol index in the collection list with the maximum swap class="type">class="kw">short
if(index_max!=WRONG_VALUE && index_min!=WRONG_VALUE)
  {
   symbol=list.At(index_min);
   if(symbol!=NULL)
     Print("Minimum swap class="type">class="kw">short for a symbol ",symbol.Name()," = ",NormalizeDouble(symbol.SwapShort(),dgc));
   symbol=list.At(index_max);
   if(symbol!=NULL)
     Print("Maximum swap class="type">class="kw">short for a symbol ",symbol.Name()," = ",NormalizeDouble(symbol.SwapShort(),dgc));
  }

class=class="str">"cmt">//--- min/max spread
index_min=CSelect::FindSymbolMin(list,SYMBOL_PROP_SPREAD);     class=class="str">"cmt">// symbol index in the collection list with the minimum spread
index_max=CSelect::FindSymbolMax(list,SYMBOL_PROP_SPREAD);     class=class="str">"cmt">// symbol index in the collection list with the maximum spread
if(index_max!=WRONG_VALUE && index_min!=WRONG_VALUE)
  {
   symbol=list.At(index_min);
   if(symbol!=NULL)
     Print("Minimum symbol spread ",symbol.Name()," = ",symbol.Spread());
   symbol=list.At(index_max);
   if(symbol!=NULL)
     Print("Maximum symbol spread ",symbol.Name()," = ",symbol.Spread());
  }

从日志里读券商的隐性限制

MT5 的日志会在品种初始化阶段吐出一批券商侧参数,很多人只盯着报价却漏看了这些行。比如 2019.06.27 10:36:28.886 的一条记录显示,USDMXN 的空头持仓掉期上限被设为 200.0,这意味着该货币对做空仓位过夜成本存在硬顶,套利模型得把这条写进风控。 同一时间戳下,SU52001RMFS3 的最小点差记为 0,而 GBPRUB_TOM 的最大点差达到 3975。前者提示该标的可能为零点差环境,后者则说明俄系交叉盘在流动性薄时价差可能瞬间放大到四位数,挂单偏移量要留足缓冲。 开 MT5 按 Ctrl+T 调出 Experts 日志,搜索 'Maximum' 或 'Minimum spread' 就能复现这类数据;不同券商返回值差异极大,切换账户前先跑一遍初始化日志比对。外汇与贵金属属高风险品种,点差突变可能在数秒内吞掉止损空间。

MQL5 / C++
class="num">2019.06.class="num">27 class="num">10:class="num">36:class="num">28.886 Maximum class="type">class="kw">short position swap for USDMXN = class="num">200.0
class="num">2019.06.class="num">27 class="num">10:class="num">36:class="num">28.886 Minimum spread for SU52001RMFS3 = class="num">0
class="num">2019.06.class="num">27 class="num">10:class="num">36:class="num">28.886 Maximum spread for GBPRUB_TOM = class="num">3975

「画得少,看得清」

这套品种集合事件类的开发在本篇收口,当前版本的含糊库与测试 EA 已打包为 MQL5.zip(207.05 KB)和 MQL4.zip(206.85 KB),直接丢进 MT5 的 MQL5\Files 解压就能跑通基础事件链路。 从读者视角看,前面十四篇把订单、持仓、账户对象逐层拆完,到这一节真正能落地的只有一件事:把下载包里的测试 EA 挂到黄金或欧美图表,观察市场观察窗口多品种切换时事件是否触发。外汇和贵金属波动剧烈,多品种监听对 CPU 占用敏感,建议在模拟盘先验证再上实盘。 别指望一次把四十篇俄文原链的兼容坑全填平,本地能跑出事件日志就是有效起点。

常见问题

找排序字段枚举里点差相关的成员,把它设为默认排序项即可,具体看采集定时器参数设定里的枚举定义。
先核对底层宏里集合ID与符号背景色是否共用同一偏移位,冲突时改宏定义里的位段划分就能分开。
可以,小布能读取你的采集列表宏定义并提示节拍过快或过慢,省去手动算定时器参数。
保证金比率放在品种属性结构体里,和滑点、合约大小等属性同一落点,见结构体定义节。
一个管高频薄采集、一个管低频全量,参数按品种活跃度调,节拍宏定义里有推荐初值可抄。