轻松快捷开发 MetaTrader 程序的函数库(第十五部分):品种对象集合·综合运用
- 枚举排序字段与两个采集定时器的参数设定
- 集合ID与符号背景色的底层宏定义
- 定时器节拍与采集列表的宏定义拆解
- 符号枚举与集合 ID 的底层约定
- 枚举品种状态后落地到 CSymbol 基类
- 保证金比率与品种属性的结构体落点
- 把符号属性映射成可调用接口
- CSymbol 类的挂单保证金与存在性接口
- 把品种塞进观察窗口与抓交易时段
- 把交易时段拆成时分秒的封装思路
- 封装报价与状态查询的轻量接口
- 报价与真实成交量的封装取法
- 从期权行权价到合约面值的属性读取
- 从品种属性接口抠出交易成本与限额
- 多单与挂单的初始及维持保证金读取
- 空头挂单与维护保证金的接口取法
- 读取交易时段盘面属性的几个接口
- 构造函数里的品种就绪检查
- 把整型品种属性一次性灌进数组
- 整批抓取交易品种的长整型属性
- 把品种属性一次性落进缓存数组
- 把品种交易参数一次性灌进结构体
- 把保证金初始率塞进符号属性数组
- 把保证金与字符串属性塞进符号缓存
- 保证金费率与品种存在性的代码落地
- 用 SymbolInfoMarginRate 抠出各类型单的保证金率
- 挂单保证金率的取值逻辑
- 空头与多头保证金系数的取值分支
- 挂单初始保证金系数的读取分支
- 挂单保证金系数的逐属性文本拼装
- 卖单挂单的维护保证金系数怎么读
- 品种存在性与报价会话的底层取数
- 抓取报价与交易时段的起止秒数
- 把交易时段拆成秒分时三件套
- 按品种状态吐出可读标签
- 状态映射与行情刷新的底层写法
- 抓取品种整数属性与背景色
- 把品种交易参数一次性塞进数组
- 把交易时段字段一次性塞进缓存数组
- 把保证金率塞进符号属性数组
- 刷新报价时顺手把属性缓存全更新了
- 行情快照的刷新与字段落盘
- 报价极值与Tick价值的批量落库
- 把品种拆成19个衍生类的取舍
- 在 CSymbolFX 里判断品种属性是否被支持
- MQL5 与 MQL4 下符号属性枚举的差异分支
- 用 SupportProperty 筛掉不支持的交易品种属性
- 自定义品种类的属性接管逻辑
- 自定义品种的属性门禁与日志输出
- 用 CSelect 给订单和事件做筛选排序
- 事件、账户与品种的属性极值筛选接口
- 按品种属性做条件筛选的实现细节
- 按属性筛符号与捞最大值的实现细节
- 在品种池里捞最大最小值的写法
- 遍历列表取最小属性下标
- 品种集合类的内部运转
- 品种集合类的头文件引用结构
- 用集合类把全市场品种收进一张表
- 品种集合类的头文件引用与私有成员
- 按品种属性给符号归类
- 品种集合类的对外接口拆解
- 多品种容器的构造与刷新逻辑
- 按品种类型分流建实例
- 按品种类别分流建对象与查重
- 按模式筛选交易标的的三种写法
- 从列表名反推符号采集模式
- 按预设清单识别货币与金属品种
- 用预置数组快速判定品种类别
- 按交易计算模式反推品种分类
- 用计算模式反推品种状态类别
- EA 内核里的集合与事件开关
- 订单与账户对象的取数接口
- 交易引擎的状态读取接口
- 引擎构造里的计数器与毫秒定时器
- 引擎计时器里的历史与报价刷新分流
- 第二计时器驱动的全品种刷新逻辑
- 预定义品种串的脏字符清洗
- 多品种列表的清洗与拆分逻辑
- 用EA跑通品种集合的多模式扫描
- 多品种清单怎么喂给引擎
- 初始化时把交易类与品种池一次性配好
- 多币种批量初始化与持仓成本扫描
- 把库存费与点差极值一次性打出来
- 从日志里读券商的隐性限制
- 画得少,看得清
「枚举排序字段与两个采集定时器的参数设定」
在 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 占用翻倍,却更快捕捉报价变动,需按机器性能权衡。
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)」。
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 警告,且贵金属跳空时段丢帧概率会上升。
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 类以降低流动性风险。
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,能直接看到这两段在同一头文件里前后衔接。贵金属与外汇品种的高杠杆伴随高风险,状态识别后仍需自行校验保证金参数再决定仓位。
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 重复调用;外汇与贵金属杠杆波动大,保证金比率改动会直接影响爆仓线,调参数前先在策略测试器跑一遍。
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,就能验证你的品种对象有没有正确初始化保证金比率。
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 初始化阶段批量扫品种时很实用,能直接打印出哪些品种因服务器无数据被跳过。 外汇与贵金属杠杆高,保证金接口返回值只反映当下经纪商设定,遇节假日或扩容品种可能瞬时空跑,验证时建议先在策略测试器用真实点差重放。
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 数组永远是空的。
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 的分工:
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),编译后跑一遍几类品种验证返回值是否合理。
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 策略测试器验证。
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_* 枚举:
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 类,打印这几个返回值,能立刻核对经纪商下外汇或贵金属品种的参数是否和文档一致;外汇贵金属杠杆高,参数读错会放大仓位风险。
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 给出开仓与维持保证金,前者算占用、后者算强平线,调杠杆前先打印这两个值比对账户净值。
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 的初始保证金,二者在裸点差扩大或节假日流动性变差时可能不同。
维持保证金系列(如 MarginLongMaintenance、MarginBuyStopMaintenance)返回的是持仓过夜或追加前的最低权益占用,实战中若维护值低于初始值,说明 broker 允许开仓后释放部分保证金,但强平线往往盯的是维护值。
外汇与贵金属自带高杠杆高风险,上述属性只是读取 broker 设定,不预示任何价格方向;开 MT5 用 SymbolInfoDouble(_Symbol,SYMBOL_PROP_MARGIN_LONG_INITIAL) 可直接核对返回值是否与封装方法一致。
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 三个接口不归保证金类,它们取当日会话的成交量、成交额与利息数据,可用于盘后复盘脚本快速拉取。
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 负责对外部传入价格做规范化(如按点值四舍五入)。把这些接口接进小布盯盘脚本,就能在时段切换瞬间比对挂单失衡,概率上更早发现流动性拐点。
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 的返回,就能确认本地品种表和服务端是否同步。
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 节奏。
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 数组,就能核对当前品种的真实交易权限与到期结构。 外汇与贵金属杠杆高、规则随经纪商变,属性值仅代表当前环境快照,换服务器后必须重抓。
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。
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 自测一遍。
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 脚本把这几项打出来,比对账户规格窗口,能验证类封装有没有漏字段。
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.Initial,SYMBOL_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_prop 里 SYMBOL_CURRENCY_MARGIN 通常返回账户币种如 USD。外汇与贵金属杠杆品种保证金参数随经纪商浮动,高波动时段可能临时上调,验证时以实时返回值为准。
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 用此函数轮询自选列表能提前规避。
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,就能看到当前品种多头建仓的真实保证金系数。外汇与贵金属杠杆波动大,实盘前先核保证金率,可避免极端行情下强平概率被低估。
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 的封装。
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); }
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 返回值,多数券商该值倾向与多头初始保证金一致,但贵金属品种可能不同。
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 说明券商没单独设挂单初始保证金系数,实际开仓会沿用对应方向市价单的基准。外汇和贵金属杠杆高,保证金系数微调会显著改变可开仓手数,验证前先确认账户风险设定。
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 还是具体系数。
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),则意味着维持仓位所需的保证金可能仅为开仓的一半。外汇与贵金属杠杆高,维护保证金系数变动会显著影响浮亏下的强平概率。
(!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 越界时必然拿不到数据,属高风险时段盲区,跑之前先确认日历。
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 个数验证。
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,能直接验证拆分有没有偏差。
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() 就能验证你当前品种归到哪一类。 外汇与贵金属品种波动受杠杆和消息面牵引,状态分类只描述合约属性,不预示任何方向,实际交易仍属高风险。
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 的赋值方式就能验证两种环境的差异。
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_LEVEL 与 SYMBOL_TRADE_FREEZE_LEVEL 返回的是最小止损距和挂单冻结距(以点数计),做黄金或外汇 EA 时若忽略这两个值,下单价差可能直接被服务器拒单。
背景色没有对应的整型接口,代码里用 this.SymbolBackgroundColor() 自定义取值,说明它不是标准 SymbolInfoInteger 能拿到的字段。
最后一行切到 m_double_prop,用 IndexProp(SYMBOL_PROP_ASKHIGH) 定位下标后写入 SYMBOL_ASKHIGH 会话最高卖价——这是双精度属性,和前面的整型数组分开存。开 MT5 把这段塞进你的 CSymbol 类,编译后打印 m_long_prop[SYMBOL_PROP_SPREAD] 就能看到实时点差。
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 一下数组,就能核对当前品种的真实合约参数。
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 就能验证是不是空数据。
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 倍。外汇与贵金属杠杆波动大,保证金率随时可能调整,高风险品种务必在每次品种切换时重跑这段赋值。
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 或定时事件里显式调一次验证。
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,止损距离计算可能用的上一秒报价,概率上会放大滑点。
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 对应索引,看和市场报价窗口是否对得上。
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 图表各跑一次,确认返回的支持标志符合预期,否则排序和搜索会漏掉关键属性。
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 派生类,随便传个属性进去,肉眼确认返回值和你的图表模式是否对得上。外汇与贵金属杠杆高,属性误读不会直接爆仓,但会让你的量价信号建立在错误基底上。
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__ 宏观察两版枚举覆盖差异,是最直接的验证方式。
(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 新建脚本,把下面两段粘进去跑一下,就能看清你当前品种到底过了哪几道筛。外汇贵金属波动剧烈,属性支持情况随经纪商而异,验证后以实盘平台返回为准。
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") 做验证。
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,就能看见自定义品种有没有被正确加载。
(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,就能在策略里复用筛选能力,省去重复造轮子。
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(...) 调用即可,省去构造开销。下面贴出原文中账户与品种部分的声明片段,注意品种段在源码里带有高亮背景,属于重点标记区。
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 对象,避免把源列表里的实例误删。外汇与贵金属品种属性受报价会话影响,筛选结果可能随品种可见性波动,属正常高风险市场现象。
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 找点差最大的品种,能立刻看到结果列表长度和数据点。
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 系列属性枚举,就能快速选出例如点值最小或名称排序最靠后的品种——外汇和贵金属波动大,这类筛选只解决「选谁」,不解决「何时出手」,实盘仍属高风险操作。
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 里每跳重算,就可能拖慢执行,建议缓存结果。
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。实盘或回测接这套逻辑时,外汇与贵金属杠杆品种波动剧烈、高风险,模式误配可能导致集合为空、刷新失效。
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)都在这个集合里,实盘调用前要注意:跨品种扫描会同时拉多品种实时报价,账户杠杆不足或品种休市时可能返回空对象,属于高风险操作,建议先在策略测试器用历史数据跑通。 直接看这段原文,逐行拆一下引用意图:
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 打分模块。外汇与贵金属杠杆高、滑点突变频繁,这类批量读取最好在盘前或低波动段跑,避免在流动性断裂时误判。
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 相对路径。
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 会漏判,自己加一层去后缀清洗再判更可靠。
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 调一次。
外汇与贵金属杠杆高、点差跳变频繁,直接用这些接口跑实盘前,务必在策略测试器用历史数据验证筛选逻辑是否漏掉非常规品种。
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() 就够了,两者分开调用更稳。
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 初始化时自动按品种挂对处理类。外汇和贵金属波动受杠杆与流动性影响,建实例后仍需在实盘前做点差与停利距校验,滑点风险偏高。
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 掉旧的全量列表,这意味着切换可用品种集时会清空之前缓存的所有符号对象。
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 市场报价里可见的品种,通常比前者少一大截。两种循环体逻辑一致:取名、查状态、建对象、累加结果。外汇与贵金属波动剧烈,多标的同时扫描时滑点和点差扩张可能让回测与实盘偏离,参数请先在策略测试器里跑一遍。
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 里就能编译跑。
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,或只在初始化时跑一次分类。外汇与贵金属波动剧烈、杠杆风险高,品种归类只解决识别问题,不等于任何方向判断。
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 堆要省事。 外汇与贵金属波动受杠杆和消息面影响大,类别判定只是过滤前置,不等于方向判断,实盘前请在策略测试器用真实点差验证。
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 打印返回值即可验证。
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,杠杆与保证金规则差异大,接入前务必在策略测试器用真实点差回验。
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_* 标记,能直观看到对冲账户与测试环境下布尔值的差异。外汇与贵金属杠杆高,事件标记误判可能导致重复下单,验证时先用模拟盘。
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(),都是内联实现,开销极小。
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,这是引擎级错误码而非具体品种错误。实盘外汇和贵金属波动大、杠杆高,调用这些接口做状态机时仍要对返回码做防御性判断,避免测试环境正常、实盘却因事件顺序差异导致逻辑漏跑。
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,方便上层感知初始化故障。
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::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 调用频次。
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 被触发的延迟——外汇与贵金属波动快,这类监听漏触发可能让策略用到过期品种数据,属高风险操作。
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' 或 'GBPUSD | USDJPY' 这类手滑输入,在 MT5 里跑起来也能稳定拆出干净品种列表。外汇与贵金属波动剧烈,脚本仅解决符号解析,不替代仓位与风险判断。 |
|---|
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。外汇与贵金属品种切换涉及点差与流动性跳变,实盘前务必在策略测试器用多品种环境跑一遍这段。
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,把快速检查改成只更新数据,并追加取集合内多空最大/最小持仓利率和最大/最小点差。编译挂图选「全服务器品种」,等数十秒建完列表,日志会输出所有品种这两类极值——外汇和贵金属点差随流动性跳变,属高风险观测项,数值仅反映当时服务器状态。
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 的实际内容,比盲信参数直观得多。外汇与贵金属杠杆高,多品种同开会放大回撤,参数验证前先用策略测试器跑一遍。
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 的调用时机。
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。外汇交易高风险,这类扫描结果只代表某一时刻成本倾向,不构成方向判断。
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 边界,不用手动一个个品种翻。
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' 就能复现这类数据;不同券商返回值差异极大,切换账户前先跑一遍初始化日志比对。外汇与贵金属属高风险品种,点差突变可能在数秒内吞掉止损空间。
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 占用敏感,建议在模拟盘先验证再上实盘。 别指望一次把四十篇俄文原链的兼容坑全填平,本地能跑出事件日志就是有效起点。