DoEasy 即时报价序列实时更新:为市场深度操控铺路的基础架构(基础篇)
(1/3)· 默认 20 万根即时报价上限如何自动滚动淘汰旧数据,避免多品种内存爆仓
「把即时报价序列改成实时刷新」
DoEasy 函数库这一版动了报价序列的底层逻辑:原来靠定时轮询拿 Tick,现在改成事件驱动,新报价一到就直接写进序列容器,延迟从「下一根 K 线才看到」压缩到「当前 Tick 即可用」。对做 scalping 或盯盘的人来说,这意味着你在 MT5 上看到的序列数据和盘口几乎零时差。 库里的品种类(CSymbol)这次也预留了市场深度(DOM)的接口骨架,虽然本小节还没真正接 DOM 数据,但序列结构已经按「可叠加买卖挂单」的方式重排。你在 MT5 里打开专家顾问日志,能看到 2021-03-31 那版示例跑起来后,即时报价序列的更新计数明显比旧版密——旧版每分钟约 8~12 次,新版在活跃品种上能跑到每秒数跳。 想验证很简单:把库里 CSymbol::RefreshTicks() 的调用从 OnTimer 挪到 OnTick,编译挂到 EURUSD 上,开「每笔成交」日志模式看时间戳。外汇和贵金属杠杆高、滑点突发行情多,这种实时性提升在非农秒级波动里可能帮你少接几个不利成交,但也可能让你更频繁地触发策略逻辑,风险敞口反而变大,得自己压参数。
即时报价集合与内存上限的取舍
程序里用到的每个品种都建了独立的即时报价集合,函数库能按品种抓取指定数量的 tick,并统一存进一个可检索的池子里。这个池子支持按对象查找、提取数据,也能直接拉出来做统计整理。 不过原版有个缺口:新品种 tick 进来时,不会自动写进已有数据库。这一节先把“自动入库”补上,让每个新 tick 都扩充集合里的对象数。 对象无限增长会吃内存,所以引入一个常量控制单品种最大存储量,默认 200000 条,大约够覆盖两天的统计研究。若同时跑很多品种且库里已满,函数库会按需要自动淘汰最旧的 tick,始终维持设定上限。 外汇与贵金属 tick 频率高、波动随机,实盘开启多品种采集前应先在 MT5 策略测试器里改这个常量压测内存占用。另外本节开始在品种对象类里预埋市场深度(DOM)广播订阅,具体操控留到下一节。
◍ 给多品种报价与DOM追踪补上底层接口
在 MT5 自建函数库时,OnTick() 只在当前品种来新报价时触发,拿不到别的品种即时 tick。要跟踪跨品种报价,得在库计时器里用之前写的 “New tick” 类对象轮询,这就要求再开一个专管非当前品种报价的定时器。 我们在 \MQL5\Include\DoEasy\Defines.mqh 里加了两个宏:COLLECTION_IND_TS_PAUSE 设为 64 毫秒,是报价序列采集定时器的轮询间隔;COLLECTION_IND_TS_COUNTER_STEP 为 16,是计数器步进值。这两个数字直接决定跨品种 tick 刷新的时效与 CPU 占用,调小可能更跟手但容易拖慢 EA。 品种对象需要记录是否已订阅 DOM 广播。做法是在整数型属性里加一个状态位,并把整数属性总数从 36 改成 37,同时在该品种的排序枚举里挂上这个新属性,后续才能按“是否订阅深度盘口”给品种列表排序。 下面这段代码是往消息索引和文本表里补的 DOM 相关条目,以及定时器宏定义,注意高亮部分就是本次新增的订阅状态消息。
MSG_SYM_EVENT_SYMBOL_ADD, class=class="str">"cmt">// Added symbol to Market Watch window MSG_SYM_EVENT_SYMBOL_DEL, class=class="str">"cmt">// Symbol removed from Market Watch window MSG_SYM_EVENT_SYMBOL_SORT, class=class="str">"cmt">// Changed location of symbols in Market Watch window MSG_SYM_SYMBOLS_MODE_CURRENT, class=class="str">"cmt">// Work with current symbol only MSG_SYM_SYMBOLS_MODE_DEFINES, class=class="str">"cmt">// Work with predefined symbol list MSG_SYM_SYMBOLS_MODE_MARKET_WATCH, class=class="str">"cmt">// Work with Market Watch window symbols MSG_SYM_SYMBOLS_MODE_ALL, class=class="str">"cmt">// Work with full list of all available symbols MSG_SYM_SYMBOLS_BOOK_ADD, class=class="str">"cmt">// Subscribed to Depth of Market MSG_SYM_SYMBOLS_BOOK_DEL, class=class="str">"cmt">// Unsubscribed from Depth of Market MSG_SYM_SYMBOLS_MODE_BOOK, class=class="str">"cmt">// Subscription to Depth of Market class=class="str">"cmt">//--- CAccount {"В окно \"Обзор рынка\" добавлен символ","Added symbol to \"Market Watch\" window"}, {"Из окна \"Обзор рынка\" удалён символ","Removed from \"Market Watch\" window"}, {"Изменено расположение символов в окне \"Обзор рынка\"","Changed arrangement of symbols in \"Market Watch\" window"}, {"Работа только с текущим символом","Work only with the current symbol"}, {"Работа с предопределённым списком символов","Work with predefined list of symbols"}, {"Работа с символами из окна \"Обзор рынка\"","Working with symbols from \"Market Watch\" window"}, {"Работа с полным списком всех доступных символов","Work with full list of all available symbols"}, {"Осуществлена подписка на стакан цен ","Subscribed to Depth of Market"}, {"Осуществлена отписка от стакан цен ","Unsubscribed from Depth of Market"}, {"Подписка на стакан цен","Subscription to Depth of Market"}, class=class="str">"cmt">//--- CAccount class=class="str">"cmt">//--- Parameters of the timer of indicator data timeseries collection class="macro">#define COLLECTION_IND_TS_PAUSE(class="num">64) class=class="str">"cmt">// Pause of the timer of indicator data timeseries collection in milliseconds class="macro">#define COLLECTION_IND_TS_COUNTER_STEP(class="num">16) class=class="str">"cmt">// Increment of indicator data timeseries timer counter
「采集引擎里的定时器与集合清单常量」
在 MT5 用 C++ 风格宏搭一个行情采集框架时,先得把内部计时器和集合容器的 ID 写死成宏,避免运行时字符串查找带来的开销。下面这段定义里,指标数据时间序列的计时器计数器 ID 是 7,而 tick 序列的计时器计数器 ID 是 8,两者分属不同采集通道。 tick 序列采集定时器的暂停间隔被定为 64 毫秒,计数器每次自增步长为 16。也就是说,若按默认参数跑,大约每 64 毫秒触发一次采集、每 16 次计数走一个循环节点,这对外汇或贵金属这种高频 tick 流来说属于可控的轻量轮询,但实盘仍可能因点差跳变产生漏 tick,属正常概率现象。 集合清单用 0x777A 到 0x7783 的一段十六进制 ID 做区分:历史数据、市场、事件、账户、品种、时间序列、缓冲区、指标、指标数据、tick 序列各占一个。文件落地时库目录固定为 "DoEasy\",资源目录为 "DoEasy\Resource\",品种在导航栏的默认背景色宏 CLR_DEFAULT 设为 0xFF000000(不透明黑)。开 MT5 把这套宏贴进头文件,编译零报错即说明你的采集骨架 ID 段没和终端其他库撞车。
class="macro">#define COLLECTION_IND_TS_COUNTER_ID(class="num">7) class=class="str">"cmt">// ID of indicator data timeseries timer counter class=class="str">"cmt">//--- Parameters of the tick series collection timer class="macro">#define COLLECTION_TICKS_PAUSE(class="num">64) class=class="str">"cmt">// Tick series collection timer pause in milliseconds class="macro">#define COLLECTION_TICKS_COUNTER_STEP(class="num">16) class=class="str">"cmt">// Tick series timer counter increment step class="macro">#define COLLECTION_TICKS_COUNTER_ID(class="num">8) class=class="str">"cmt">// Tick series timer counter ID class=class="str">"cmt">//--- Collection list IDs class="macro">#define COLLECTION_HISTORY_ID(0x777A) class=class="str">"cmt">// Historical collection list ID class="macro">#define COLLECTION_MARKET_ID(0x777B) class=class="str">"cmt">// Market collection list ID class="macro">#define COLLECTION_EVENTS_ID(0x777C) class=class="str">"cmt">// Event collection list ID class="macro">#define COLLECTION_ACCOUNT_ID(0x777D) class=class="str">"cmt">// Account collection list ID class="macro">#define COLLECTION_SYMBOLS_ID(0x777E) class=class="str">"cmt">// Symbol collection list ID class="macro">#define COLLECTION_SERIES_ID(0x777F) class=class="str">"cmt">// Timeseries collection list ID class="macro">#define COLLECTION_BUFFERS_ID(0x7780) class=class="str">"cmt">// Indicator buffer collection list ID class="macro">#define COLLECTION_INDICATORS_ID(0x7781) class=class="str">"cmt">// Indicator collection list ID class="macro">#define COLLECTION_INDICATORS_DATA_ID(0x7782) class=class="str">"cmt">// Indicator data collection list ID class="macro">#define COLLECTION_TICKSERIES_ID(0x7783) class=class="str">"cmt">// Tick series 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="macro">#define RESOURCE_DIR("DoEasy\Resource\") class=class="str">"cmt">// Library directory for storing resource folders class=class="str">"cmt">//--- Symbol parameters class="macro">#define CLR_DEFAULT(0xFF000000) class=class="str">"cmt">// Default symbol background class="type">color in the navigator
MQL5底层宏与符号属性的编译期分叉
在写跨版本 EA 或指标时,MT5 终端构建号直接决定了可遍历品种池的上限。Build 2430 之前 TerminalInfoInteger(TERMINAL_BUILD) 返回的值小于 2430,工作品种总数被压在 1000;2430 及之后放开到 5000,MQL4 环境则恒定 1000,这点不查构建号容易在老终端上漏掉 4000 多个可交易标的。
同步逻辑里写死了几个节奏参数:每次同步尝试间隔 16 毫秒,最多连试 5 次;默认时间序列抓 1000 根 Bar,Tick 序列默认只留 1 天,但单品种 Tick 存储上限锁死在 200000 条。外汇与贵金属波动大,Tick 上限到了之后早期数据会被覆盖,回测历史深度可能因此断档,属高风险注意项。
下面这段预处理与枚举是库的头文件基底,编译期就分好了环境:
#ifdef __MQL5__
#define SYMBOLS_COMMON_TOTAL (TerminalInfoInteger(TERMINAL_BUILD)<2430 ? 1000 : 5000) // MQL5工作品种总数随构建号变化
#else
#define SYMBOLS_COMMON_TOTAL (1000) // MQL4工作品种总数固定
#endif
#define PENDING_REQUEST_ID_TYPE_ERR (1) // 基于服务器返回码建的挂起请求类型
#define PENDING_REQUEST_ID_TYPE_REQ (2) // 主动建的挂起请求类型
#define SERIES_DEFAULT_BARS_COUNT (1000) // 默认时序数据量
#define PAUSE_FOR_SYNC_ATTEMPTS (16) // 同步尝试间暂停毫秒
#define ATTEMPTS_FOR_SYNC (5) // 同步尝试次数
#define TICKSERIES_DEFAULT_DAYS_COUNT (1) // 默认Tick天数
#define TICKSERIES_MAX_DATA_TOTAL (200000) // 单品种Tick存储上限
enum ENUM_SYMBOL_PROP_INTEGER
{
SYMBOL_PROP_STATUS = 0, // 符号状态
SYMBOL_PROP_INDEX_MW, // 市场报价窗内索引
SYMBOL_PROP_CUSTOM, // 自定义品种标记
SYMBOL_PROP_CHART_MODE, // 生成Bar用的价格类型(Bid/Last)
SYMBOL_PROP_EXIST // 该名符号是否存在的标记
};
开 MT5 按 F4 把这段贴进一个新 include 头文件,编译后打印 SYMBOLS_COMMON_TOTAL 的值,就能确认你当前终端落在哪个品种上限区间。
class="macro">#ifdef __MQL5__ class="macro">#define SYMBOLS_COMMON_TOTAL(TerminalInfoInteger(TERMINAL_BUILD)<class="num">2430 ? class="num">1000 : class="num">5000) class=class="str">"cmt">// Total number of MQL5 working symbols class="macro">#else class="macro">#define SYMBOLS_COMMON_TOTAL(class="num">1000) class=class="str">"cmt">// Total number of MQL4 working symbols class="macro">#endif class=class="str">"cmt">//--- Pending request type IDs class="macro">#define PENDING_REQUEST_ID_TYPE_ERR(class="num">1) class=class="str">"cmt">// Type of a pending request created based on the server class="kw">return code class="macro">#define PENDING_REQUEST_ID_TYPE_REQ(class="num">2) class=class="str">"cmt">// Type of a pending request created by request class=class="str">"cmt">//--- Timeseries parameters class="macro">#define SERIES_DEFAULT_BARS_COUNT(class="num">1000) class=class="str">"cmt">// Required class="kw">default amount of timeseries data class="macro">#define PAUSE_FOR_SYNC_ATTEMPTS(class="num">16) class=class="str">"cmt">// Amount of pause milliseconds between synchronization attempts class="macro">#define ATTEMPTS_FOR_SYNC(class="num">5) class=class="str">"cmt">// Number of attempts to receive synchronization with the server class=class="str">"cmt">//--- Tick series parameters class="macro">#define TICKSERIES_DEFAULT_DAYS_COUNT(class="num">1) class=class="str">"cmt">// Required number of days for tick data in class="kw">default series class="macro">#define TICKSERIES_MAX_DATA_TOTAL(class="num">200000) class=class="str">"cmt">// Maximum number of stored tick data of a single 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 { SYMBOL_PROP_STATUS = class="num">0, class=class="str">"cmt">// Symbol status SYMBOL_PROP_INDEX_MW, class=class="str">"cmt">// Symbol index in the Market Watch window 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_* 枚举值,是 MT5 里 SymbolInfoInteger 能直接读到的品种属性尾部清单。从 SELECT、VISIBLE 到 EXPIRATION_TIME,覆盖了对市场观察窗口状态、当前会话挂单统计、点差类型、合约计算模式与交易起止日的访问入口。 其中 SYMBOL_PROP_BOOKDEPTH_STATE 标黄那行值得单独看:它返回的是当前是否订阅了指定品种的 DOM(市场深度)数据,而非深度本身。做贵金属 scalping 时若发现 Book 事件不触发,先查这个标志位比怀疑代码逻辑更效率。外汇与贵金属杠杆交易风险偏高,订阅状态异常可能让你漏掉流动性突变信号。 把这些常量原样贴进脚本,配合 SymbolInfoInteger(_Symbol, SYMBOL_PROP_SPREAD_FLOAT) 之类调用,能在开盘前扫一遍品种配置。下面片段就是这一组枚举的原文定义,直接可用于头文件或调试输出。
SYMBOL_PROP_SELECT, class=class="str">"cmt">// The indication that the symbol is selected in Market Watch SYMBOL_PROP_VISIBLE, class=class="str">"cmt">// The 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_BOOKDEPTH_STATE, class=class="str">"cmt">// Flag of subscription to DOM 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)