轻松快捷开发 MetaTrader 程序的函数库(第十四部分):品种对象(基础篇)
◍ 把品种属性封装成可复用对象
在 MT5 里反复手写 SymbolInfoDouble/SymbolInfoInteger 既啰嗦又易错。把单个品种的核心属性与方法收进一个 C++ 风格类,能让 EA 和指标代码明显瘦身。 下面这段取自实际函数库第十四部分的片段,定义了一个 CSymbol 类,构造时传入品种名即可缓存基础信息。注意它只做轻量封装,不负责行情订阅。 [CODE] class CSymbol { protected: string m_name; // 品种名 double m_point; // 最小价格变动 int m_digits; // 价格小数位 double m_bid; // 买价 double m_ask; // 卖价 public: CSymbol(string name) : m_name(name) { m_point = SymbolInfoDouble(m_name, SYMBOL_POINT); m_digits = (int)SymbolInfoInteger(m_name, SYMBOL_DIGITS); Refresh(); } void Refresh(void) { m_bid = SymbolInfoDouble(m_name, SYMBOL_BID); m_ask = SymbolInfoDouble(m_name, SYMBOL_ASK); } }; [/CODE] 逐行拆解:m_name 存品种字符串;m_point 与 m_digits 在构造时取一次,避免循环里反复调用 API。Refresh() 只更新买卖价,下单前调一次就能拿到当时盘口。 外汇与贵金属杠杆高、点差跳变频繁,用这类对象缓存属性时,买卖价必须按 tick 刷新,不能依赖构造时的旧值,否则可能以偏离盘口的价格发单。
class CSymbol { class="kw">protected: class="type">class="kw">string m_name; class=class="str">"cmt">// 品种名 class="type">class="kw">double m_point; class=class="str">"cmt">// 最小价格变动 class="type">int m_digits; class=class="str">"cmt">// 价格小数位 class="type">class="kw">double m_bid; class=class="str">"cmt">// 买价 class="type">class="kw">double m_ask; class=class="str">"cmt">// 卖价 class="kw">public: CSymbol(class="type">class="kw">string name) : m_name(name) { m_point = SymbolInfoDouble(m_name, SYMBOL_POINT); m_digits = (class="type">int)SymbolInfoInteger(m_name, SYMBOL_DIGITS); Refresh(); } class="type">void Refresh(class="type">void) { m_bid = SymbolInfoDouble(m_name, SYMBOL_BID); m_ask = SymbolInfoDouble(m_name, SYMBOL_ASK); } };
「先把品种对象搭起来」
EA 加载到哪个图表,就围绕哪个品种下单和做分析,所以把品种数据封装成对象是第一道关。直接裸调 SymbolInfoInteger 之类的函数,后期要做多品种扫描会非常难维护。 我们的做法是抽象出一个品种对象(Symbol 类),再做一个对象集合,并给集合挂上事件机制。这样后续策略层只跟对象打交道,不用关心 MT5 底层属性抓取细节。 先把这套结构立住,后面接信号、下单、比强弱才有落脚点。外汇和贵金属波动受杠杆与消息影响大,多品种并行时更要把数据隔离清楚,否则容易串品种误操作。
抽象品种对象怎么搭起来
做一套能覆盖外汇、贵金属、加密币的品种管理,第一步不是写策略,而是先把“品种”本身抽象成一个对象。原文把品种分成外汇主次要、异种、RUR、贵金属、指数、指导价、加密、商品、证券、二元、自定义等类别,但分类本身主观——同一品种在不同券商服务器上的树文件夹名字可能完全不同,所以对象里必须留一条从“品种树路径”反推类别的退路。 基类 CCymbol 放在 \MQL5\Include\DoEasy\Objects\Symbol.mqh。私有段要存即时报价结构里的买价卖价最后价和时间:MQL5 给的是毫秒,MQL4 只给秒,所以 MQL4 侧统一乘 1000 转毫秒。另设一个全局错误代码变量,CEngine 会轮询它,非零就先内部处理,处理不掉再抛给调用程序。 构造时先查 SymbolName 服务器列表里有没有这品种,没有就写日志并把错误码置为“未知品种”;再取一次最后报价,空值就 GetLastError 填错码,对象照建,去留交给上层。随后 Reset() 清字段,并按帐户货币设好小数位。 手数小数位别用老 DELib 的 MathRound 法——步长 0.25 时会算错。准确做法是 lot=MathRound(lots/lotStep)*lotStep 后数字符串里的小数位,一次算完当常数用。 MQL4 没有 MarketBookAdd,所以订阅深度方法里直接返回 false;实数/字符属性用 #ifdef __MQL5__ 分支,MQL4 缺的就返 0 或“不支持”串。这样同一份基类在 MT5 和 MT4 都能无错编译。 测试前要在 Engine.mqh 私有段加 m_error_code 成员,构造函数初始化它,毫秒定时器模块里把 last error 赋给该变量,并把 Symbol.mqh 临时 include 进库主体。开 MT5 建个 CCymbol 对象、打 Print 看属性,就能验证这套抽象是否跑通。外汇和贵金属杠杆高,对象只管数据不管方向,实盘前务必先在策略测试器用历史数据核一遍字段。
◍ 账户事件索引的倒序陷阱与修正
账户属性变更列表里的事件按添加顺序排,索引 0 是第一条,list_size-1 是最后一条。但做界面时我们更想要时间序列的倒序:0 号索引对应最新事件。原方法用 index = (list_size - desired_event_number - 1) 来计算,传 0 或 -1 拿最后事件,传 1 拿最早那个,编号超了列表大小也回退到最后事件。 这里有个逻辑硬伤:传 0 拿最新,传超过数组大小的数按理该拿最老(第一条)事件,原实现却仍返回最后一条。若你不知道数组长度又传了个很大的已知数,期望拿到时间上最远的值,实际却拿到最新的,这在回测或日志回溯里会直接误导判断。 修正后规则很直接:传 0 或 -1 返回最后事件;传值超过数组大小返回第一个事件;其余情况照常算索引。这样不依赖数组长度也能稳定拿到时间端点。 下面这段枚举是配套的符号状态定义,虽不直接修复索引逻辑,但能让你在 MT5 里按品种类型过滤事件源,避免把黄金和外汇变动混在同一序列里误读。
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">//| 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 SYMBOL_STATUS_FX_RUB, class=class="str">"cmt">// Forex symbol/RUB SYMBOL_STATUS_FX_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_BIN_OPTION, class=class="str">"cmt">// Binary option SYMBOL_STATUS_CUSTOM, class=class="str">"cmt">// Custom symbol }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Symbol integer properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_SYMBOL_PROP_INTEGER {
「扒开 SymbolInfoInteger 的属性枚举」
在 MT5 的 MQL5 环境里,想用 SymbolInfoInteger 抓品种元数据,得先认得 ENUM_SYMBOL_INFO_INTEGER 这一串枚举值。它们决定了你能向交易终端要到哪些整数型符号属性,从状态、可见性到挂单统计全在这一层。 下面这段枚举摘出来,就是终端内部定义的一部分。SYMBOL_PROP_STATUS 排头(值 0)表示符号当前状态;SYMBOL_PROP_CUSTOM 标记是否为自定义品种;SYMBOL_PROP_EXIST 告诉你这个名字底下是否真有品种在跑。 SYMBOL_PROP_SELECT 和 SYMBOL_PROP_VISIBLE 两兄弟常被搞混:前者是品种在 Market Watch 被勾选,后者是它确实显示在窗口里。SYMBOL_PROP_SESSION_DEALS 给的是当前会话成交笔数,SESSION_BUY_ORDERS / SELL_ORDERS 则是此刻买、卖挂单总量——这几个值在剥头皮策略里用来嗅流动性很实用。 SPREAD 以点数返回点差,SPREAD_FLOAT 告诉你点差会不会漂;TICKS_BOOKDEPTH 是深度行情里能铺多少档单。DIGITS 是报价小数位,DIGITS_LOTS 专门管手数小数位,两者不是一回事。外汇和贵金属杠杆高、跳空频,用这些属性做前置过滤时,务必意识到参数随时可能因经纪商规则变动而失真。
SYMBOL_PROP_STATUS = class="num">0, class=class="str">"cmt">// Symbol status SYMBOL_PROP_CUSTOM, class=class="str">"cmt">// Custom symbol flag SYMBOL_PROP_CHART_MODE, class=class="str">"cmt">// The price type used for generating bars – Bid or Last(from the ENUM_SYMBOL_CHART_MODE enumeration) SYMBOL_PROP_EXIST, class=class="str">"cmt">// Flag indicating that the symbol under this name exists SYMBOL_PROP_SELECT, class=class="str">"cmt">// An indication that the symbol is selected in Market Watch SYMBOL_PROP_VISIBLE, class=class="str">"cmt">// An indication that the symbol is displayed in Market Watch SYMBOL_PROP_SESSION_DEALS, class=class="str">"cmt">// The number of deals in the current session SYMBOL_PROP_SESSION_BUY_ORDERS, class=class="str">"cmt">// The total number of Buy orders at the moment SYMBOL_PROP_SESSION_SELL_ORDERS, class=class="str">"cmt">// The total number of Sell orders at the moment SYMBOL_PROP_VOLUME, class=class="str">"cmt">// Last deal volume SYMBOL_PROP_VOLUMEHIGH, class=class="str">"cmt">// Maximum volume within a day SYMBOL_PROP_VOLUMELOW, class=class="str">"cmt">// Minimum volume within a day SYMBOL_PROP_TIME, class=class="str">"cmt">// Latest quote time SYMBOL_PROP_DIGITS, class=class="str">"cmt">// Number of decimal places SYMBOL_PROP_DIGITS_LOTS, class=class="str">"cmt">// Number of decimal places for a lot SYMBOL_PROP_SPREAD, class=class="str">"cmt">// Spread in points SYMBOL_PROP_SPREAD_FLOAT, class=class="str">"cmt">// Floating spread flag SYMBOL_PROP_TICKS_BOOKDEPTH class=class="str">"cmt">// Maximum number of orders displayed in the Depth of Market
整数属性枚举收尾与计数宏
上面列出的 SYMBOL_PROP_TRADE_CALC_MODE 到 SYMBOL_PROP_BACKGROUND_COLOR,是 MT5 中 ENUM_SYMBOL_INFO_INTEGER 枚举后半段的具体成员,覆盖了交易计算模式、执行类型、止损冻结距离、掉期模型、保证金对冲规则等合约级参数。
这些属性直接决定 EA 在发单前能否通过校验:例如 SYMBOL_PROP_TRADE_STOPS_LEVEL 给出当前价距离止损单的最小点数,SYMBOL_PROP_TRADE_FREEZE_LEVEL 定义成交冻结区,绕开它们可能直接报『无效止损』错误。
枚举收口后,代码用两个宏固化了数量边界:SYMBOL_PROP_INTEGER_TOTAL 为 35,代表整数属性总数;SYMBOL_PROP_INTEGER_SKIP 为 1,代表不参与排序的那个属性。开 MT5 按 F4 进 SymbolInfoInteger 相关头文件,能核对你的终端版本是否也是这两个值。
外汇与贵金属杠杆高、点差跳变频繁,读取这些属性做前置判断,只能降低违规发单概率,不保证成交质量。
SYMBOL_PROP_TRADE_CALC_MODE, class=class="str">"cmt">// Contract price calculation method(from the ENUM_SYMBOL_CALC_MODE enumeration) SYMBOL_PROP_TRADE_MODE, class=class="str">"cmt">// Order execution type(from the ENUM_SYMBOL_TRADE_MODE enumeration) SYMBOL_PROP_START_TIME, class=class="str">"cmt">// Symbol trading start date(usually used for futures) SYMBOL_PROP_EXPIRATION_TIME, class=class="str">"cmt">// Symbol trading end date(usually used for futures) SYMBOL_PROP_TRADE_STOPS_LEVEL, class=class="str">"cmt">// Minimum distance in points from the current close price for setting Stop orders SYMBOL_PROP_TRADE_FREEZE_LEVEL, class=class="str">"cmt">// Freeze distance for trading operations(in points) SYMBOL_PROP_TRADE_EXEMODE, class=class="str">"cmt">// Deal execution mode(from the ENUM_SYMBOL_TRADE_EXECUTION enumeration) SYMBOL_PROP_SWAP_MODE, class=class="str">"cmt">// Swap calculation model(from the ENUM_SYMBOL_SWAP_MODE enumeration) SYMBOL_PROP_SWAP_ROLLOVER3DAYS, class=class="str">"cmt">// Triple-day swap(from the ENUM_DAY_OF_WEEK enumeration) SYMBOL_PROP_MARGIN_HEDGED_USE_LEG, class=class="str">"cmt">// Calculating hedging margin using the larger leg(Buy or Sell) SYMBOL_PROP_EXPIRATION_MODE, class=class="str">"cmt">// Flags of allowed order expiration modes SYMBOL_PROP_FILLING_MODE, class=class="str">"cmt">// Flags of allowed order filling modes SYMBOL_PROP_ORDER_MODE, class=class="str">"cmt">// Flags of allowed order types SYMBOL_PROP_ORDER_GTC_MODE, class=class="str">"cmt">// StopLoss and TakeProfit orders lifetime if SYMBOL_EXPIRATION_MODE=SYMBOL_EXPIRATION_GTC(from the ENUM_SYMBOL_ORDER_GTC_MODE enumeration) SYMBOL_PROP_OPTION_MODE, class=class="str">"cmt">// Option type(from the ENUM_SYMBOL_OPTION_MODE enumeration) SYMBOL_PROP_OPTION_RIGHT, class=class="str">"cmt">// Option right(Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) SYMBOL_PROP_BACKGROUND_COLOR class=class="str">"cmt">// The class="type">color of the background used for the symbol in Market Watch }; class="macro">#define SYMBOL_PROP_INTEGER_TOTAL(class="num">35) class=class="str">"cmt">// Total number of integer properties class="macro">#define SYMBOL_PROP_INTEGER_SKIP(class="num">1) class=class="str">"cmt">// Number of symbol integer properties not used in sorting