DoEasy 函数库中的价格(第六十二部分):实时更新即时报价序列,为操控市场深度做准备·进阶篇
(2/3)· 默认 20 万条报价上限如何自动滚动淘汰旧数据,避免多品种内存爆仓
「交易品种整数属性的边界与跳过项」
在 MT5 的 ENUM_SYMBOL_INFO_INTEGER 体系里,止损挂单最小距离、冻结层级、成交模式、掉期计算模型等都被定义为整数属性。其中 SYMBOL_PROP_BACKGROUND_COLOR 之前的 SYMBOL_PROP_TRADE_STOPS_LEVEL 到 SYMBOL_PROP_OPTION_RIGHT 覆盖了订单执行与期权类型的核心约束。
代码里用宏明确标出:整数属性总数为 37,排序时跳过 1 个(即背景色这类非交易属性),实际可用于排序的整数属性为 36。
如果你在写自定义品种扫描器,直接引用 SYMBOL_PROP_INTEGER_TOTAL 和 SYMBOL_PROP_INTEGER_SKIP 两个宏,能避免硬编数字导致版本升级后越界。外汇与贵金属品种受经纪商参数限制,止损距离和冻结层级可能随时调整,属高风险变量,开 MT5 用 SymbolInfoInteger() 实时读取比缓存更稳妥。
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">// Expiration of Stop Loss and Take Profit orders 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) class=class="str">"cmt">//--- skip the class="kw">property 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">37) 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 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Possible symbol sorting criteria | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define FIRST_SYM_DBL_PROP(SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP) class="macro">#define FIRST_SYM_STR_PROP(SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP+SYMBOL_PROP_DOUBLE_TOTAL-SYMBOL_PROP_DOUBLE_SKIP) enum ENUM_SORT_SYMBOLS_MODE { class=class="str">"cmt">//--- Sort by integer properties
◍ 枚举排序字段的实战含义
MT5 的 ENUM_SYMBOL_SORT_MODE 里,SORT_BY_SYMBOL_STATUS = 0 是排序基准的起点,后续每个枚举值对应一种市场观察窗口(Market Watch)内可排序的维度。 从 SORT_BY_SYMBOL_INDEX_MW 到 SORT_BY_SYMBOL_TICKS_BOOKDEPTH,一共罗列了 22 个排序键。它们覆盖的状态包括:自定义属性、图表价源(Bid 或 Last)、品种是否存在、是否被选中、是否可见,以及当前会话的成交与多空挂单数量。 成交量相关有 SORT_BY_SYMBOL_VOLUME(最近一笔成交量)、VOLUMEHIGH(当日最大)、VOLUMELOW(当日最小);时间维度用 SORT_BY_SYMBOL_TIME 取最后报价时间;点值精度则由 DIGITS 与 DIGITS_LOT 分别控制报价小数位和手数小数位。 点差层面提供 SPREAD(以点计的固定/当前点差)与 SPREAD_FLOAT(浮动点差标志),而 TICKS_BOOKDEPTH 决定市场深度窗口能刷新的最大挂单层数。 开 MT5 按 F4 进 MetaEditor,搜 ENUM_SYMBOL_SORT_MODE 可直接核对这串枚举;写自定义观察面板时,挑 SORT_BY_SYMBOL_SPREAD 或 SESSION_SELL_ORDERS 做排序键,能更快筛出流动性异常的外汇或贵金属品种——这类品种跳空与滑点风险偏高,参数验证前先开模拟盘跑一遍。
SORT_BY_SYMBOL_STATUS = class="num">0, class=class="str">"cmt">// Sort by symbol status SORT_BY_SYMBOL_INDEX_MW, class=class="str">"cmt">// Sort by index in the Market Watch window SORT_BY_SYMBOL_CUSTOM, class=class="str">"cmt">// Sort by custom symbol class="kw">property SORT_BY_SYMBOL_CHART_MODE, class=class="str">"cmt">// Sort by price type for constructing bars – Bid or Last(from the ENUM_SYMBOL_CHART_MODE enumeration) SORT_BY_SYMBOL_EXIST, class=class="str">"cmt">// Sort by the flag that a symbol with such a name exists SORT_BY_SYMBOL_SELECT, class=class="str">"cmt">// Sort by the flag indicating that a symbol is selected in Market Watch SORT_BY_SYMBOL_VISIBLE, class=class="str">"cmt">// Sort by the flag indicating that a selected symbol is displayed in Market Watch SORT_BY_SYMBOL_SESSION_DEALS, class=class="str">"cmt">// Sort by the number of deals in the current session SORT_BY_SYMBOL_SESSION_BUY_ORDERS, class=class="str">"cmt">// Sort by the total number of current buy orders SORT_BY_SYMBOL_SESSION_SELL_ORDERS, class=class="str">"cmt">// Sort by the total number of current sell orders SORT_BY_SYMBOL_VOLUME, class=class="str">"cmt">// Sort by last deal volume SORT_BY_SYMBOL_VOLUMEHIGH, class=class="str">"cmt">// Sort by maximum volume for a day SORT_BY_SYMBOL_VOLUMELOW, class=class="str">"cmt">// Sort by minimum volume for a day SORT_BY_SYMBOL_TIME, class=class="str">"cmt">// Sort by the last quote time SORT_BY_SYMBOL_DIGITS, class=class="str">"cmt">// Sort by a number of decimal places SORT_BY_SYMBOL_DIGITS_LOT, class=class="str">"cmt">// Sort by a number of decimal places in a lot SORT_BY_SYMBOL_SPREAD, class=class="str">"cmt">// Sort by spread in points SORT_BY_SYMBOL_SPREAD_FLOAT, class=class="str">"cmt">// Sort by floating spread SORT_BY_SYMBOL_TICKS_BOOKDEPTH, class=class="str">"cmt">// Sort by a maximum number of requests displayed in the market depth
按交易属性给品种列表排序的枚举项
在 MT5 的 ENUM_SYMBOLS_SORT_MODE 体系里,除了基础名称、点值等字段,还有一批围绕交易规则本身的排序键。它们直接对应品种配置里的执行与风控参数,写脚本批量扫描品种时很好用。
例如 SORT_BY_SYMBOL_TRADE_STOPS_LEVEL 按当前收盘价到止损单的最小距离(点数)排序,SORT_BY_SYMBOL_TRADE_FREEZE_LEVEL 按交易冻结距离(点数)排序,这两个值能快速筛出受限较严的品种。
SORT_BY_SYMBOL_FILLING_MODE 与 SORT_BY_SYMBOL_ORDER_MODE 分别按允许的成交填充模式、允许的下单类型标志位排序;做跨品种套利或自动化下单前,用它们归类品种比人工翻规格书快得多。外汇与贵金属杠杆高、规则差异大,实盘使用前建议在策略测试器或模拟环境先验证排序结果。
SORT_BY_SYMBOL_BOOKDEPTH_STATE, class=class="str">"cmt">// Sort by the DOM subscription flag SORT_BY_SYMBOL_TRADE_CALC_MODE, class=class="str">"cmt">// Sort by contract price calculation method(from the ENUM_SYMBOL_CALC_MODE enumeration) SORT_BY_SYMBOL_TRADE_MODE, class=class="str">"cmt">// Sort by order execution type(from the ENUM_SYMBOL_TRADE_MODE enumeration) SORT_BY_SYMBOL_START_TIME, class=class="str">"cmt">// Sort by an instrument trading start date(usually used for futures) SORT_BY_SYMBOL_EXPIRATION_TIME, class=class="str">"cmt">// Sort by an instrument trading end date(usually used for futures) SORT_BY_SYMBOL_TRADE_STOPS_LEVEL, class=class="str">"cmt">// Sort by the minimum indent from the current close price(in points) for setting Stop orders SORT_BY_SYMBOL_TRADE_FREEZE_LEVEL, class=class="str">"cmt">// Sort by trade operation freeze distance(in points) SORT_BY_SYMBOL_TRADE_EXEMODE, class=class="str">"cmt">// Sort by trade execution mode(from the ENUM_SYMBOL_TRADE_EXECUTION enumeration) SORT_BY_SYMBOL_SWAP_MODE, class=class="str">"cmt">// Sort by swap calculation model(from the ENUM_SYMBOL_SWAP_MODE enumeration) SORT_BY_SYMBOL_SWAP_ROLLOVER3DAYS, class=class="str">"cmt">// Sort by week day for accruing a triple swap(from the ENUM_DAY_OF_WEEK enumeration) SORT_BY_SYMBOL_MARGIN_HEDGED_USE_LEG, class=class="str">"cmt">// Sort by the calculation mode of a hedged margin using the larger leg(Buy or Sell) SORT_BY_SYMBOL_EXPIRATION_MODE, class=class="str">"cmt">// Sort by flags of allowed order expiration modes SORT_BY_SYMBOL_FILLING_MODE, class=class="str">"cmt">// Sort by flags of allowed order filling modes SORT_BY_SYMBOL_ORDER_MODE, class=class="str">"cmt">// Sort by flags of allowed order types SORT_BY_SYMBOL_ORDER_GTC_MODE, class=class="str">"cmt">// Sort by StopLoss and TakeProfit orders lifetime SORT_BY_SYMBOL_OPTION_MODE, class=class="str">"cmt">// Sort by option type(from the ENUM_SYMBOL_OPTION_MODE enumeration) SORT_BY_SYMBOL_OPTION_RIGHT, class=class="str">"cmt">// Sort by option right(Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) class=class="str">"cmt">//--- Sort by real properties
「用毫秒偏移避免重复灌入即时报价」
即时报价在 MT5 里经常以一束(batch)形式并发到达,若逐个往序列里塞,会漏掉同批内的其他报价。可行做法是:记录上一次 OnTick() 收到的最后一个即时报价的毫秒时间,从该时间 +1 毫秒起调用 CopyTicksRange() 向历史末尾复制,从而不重复复制已处理的那根。 类私密成员里用 ulong m_last_time 存最后报价毫秒时间;CreateNewTickObj(const MqlTick &tick) 负责新建数据对象并加入列表,成功返指针、失败返 NULL。构造函数里先指定品种再一次性刷新“New tick”对象,把 m_last_time 初始化好。 更新逻辑很直接:用 m_last_time+1 作为复制起点,复制完若列表总数超过预设上限,就从最旧一端剔除。原文调试段只针对 AUDUSD 打印复制数量与时间窗,验证完下一版会摘掉。 集合类里另写了一个跳过当前图表品种、只刷新其余品种序列的方法,挂在库计时器上跑;当前品种则在 OnTick() 里调更新。这样非本源品种的报价变动也能在后台持续补全,外汇与贵金属报价受流动性影响跳变频繁,用这套机制时务必留意点差放大带来的复制空洞风险。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| "Tick data series" class |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> CTickSeries : <span class="keyword">class="kw">public</span> CBaseObj { <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">class="kw">string</span> m_symbol; <span class="comment">class=class="str">"cmt">// Symbol</span> <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword">class="type">ulong</span> m_last_time; <span class="comment">class=class="str">"cmt">// Last tick time</span></span> <span class="keyword">class="type">uint</span> m_amount; <span class="comment">class=class="str">"cmt">// Amount of applied tick series data</span> <span class="keyword">class="type">uint</span> m_required; <span class="comment">class=class="str">"cmt">// Required number of days for tick series data</span> CArrayObj m_list_ticks; <span class="comment">class=class="str">"cmt">// List of tick data</span> CNewTickObj m_new_tick_obj; <span class="comment">class=class="str">"cmt">// "New tick" object</span> <span class="comment">class=class="str">"cmt">//--- Create a new tick data object</span> <span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);">CDataTick *CreateNewTickObj(<span class="keyword">const</span> <span class="predefines">class="type">MqlTick</span> &tick);</span> <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--- Return the object of tick data by(class="num">1) index in the list, (class="num">2) time,</span> <span class="comment">class=class="str">"cmt">//--- (class="num">3) time in milliseconds, (class="num">4) the last one in the list and(class="num">5) the list size</span>
◍ 逐笔tick序列的取数与构造细节
CTickSeries 对外暴露了多组取数接口:按列表下标取 CDataTick* 用 GetTickByListIndex(uint index),按秒级时间 datetime 或毫秒时间 ulong time_msc 检索用两个重载的 GetTick,实时最新一笔直接调 GetLastTick(void)。当前已缓存的 tick 总数由 DataTotal() 返回,内部就是 m_list_ticks.Total(),调用零开销。 构造器 CTickSeries(string symbol, uint required=0) 在初始化时清空链表、按 SORT_BY_TICK_TIME_MSC 排序,并通过 SetRequiredUsedDays(required) 设定回看天数;随后把 m_new_tick_obj 绑定到对应 symbol 并立刻 Refresh(),意味着对象一建好就带有一份新鲜行情快照。 往序列里插新 tick 走 CreateNewTickObj(MqlTick &tick):先 new CDataTick 包装传入结构,失败则打印错误并返回 NULL;成功后再做 InsertSort 插入有序链表,若插入失败会 delete 掉刚建的对象并回 NULL,避免野指针堆积。 这套机制在 MT5 里可直接验证:挂一个 EA 在 EURUSD 上 new CTickSeries("EURUSD", 1),循环里打印 GetLastTick().time_msc,应能观察到毫秒级 tick 流。外汇与贵金属 tick 流速受流动性影响波动极大,高频序列处理不当可能拖慢回测,实盘前务必在策略测试器跑一遍。
CDataTick *GetTickByListIndex(const class="type">uint index); CDataTick *GetTick(const class="type">class="kw">datetime time); CDataTick *GetTick(const class="type">ulong time_msc); CDataTick *GetLastTick(class="type">void); class="type">int DataTotal(class="type">void) const { class="kw">return this.m_list_ticks.Total(); } class=class="str">"cmt">//--- The comparison method for searching identical tick series objects by a symbol class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CTickSeries::CTickSeries(const class="type">class="kw">string symbol,const class="type">uint required=class="num">0) : m_symbol(symbol),m_last_time(class="num">0) { this.m_list_ticks.Clear(); this.m_list_ticks.Sort(SORT_BY_TICK_TIME_MSC); this.SetRequiredUsedDays(required); this.m_new_tick_obj.SetSymbol(this.m_symbol); this.m_new_tick_obj.Refresh(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a new tick data object | class=class="str">"cmt">//+------------------------------------------------------------------+ CDataTick *CTickSeries::CreateNewTickObj(const class="type">MqlTick &tick) { class=class="str">"cmt">//--- create a new object of tick data out of the class="type">MqlTick structure passed to the method class="type">int err=ERR_SUCCESS; ::ResetLastError(); class=class="str">"cmt">//--- If failed to create an object, inform of that and class="kw">return NULL CDataTick* tick_obj=new CDataTick(this.m_symbol,tick); if(tick_obj==NULL) { ::Print( DFUN,CMessage::Text(MSG_TICKSERIES_FAILED_CREATE_TICK_DATA_OBJ)," ",this.Header()," ",::TimeMSCtoString(tick.time_msc),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError()) ); class="kw">return NULL; } class=class="str">"cmt">//--- If failed to add a new tick data object to the list, class=class="str">"cmt">//--- display the appropriate message with the error description in the journal, class=class="str">"cmt">//--- remove the newly created object and class="kw">return NULL this.m_list_ticks.Sort(); if(!this.m_list_ticks.InsertSort(tick_obj)) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_TICKSERIES_FAILED_ADD_TO_LIST)," ",tick_obj.Header()," ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); class="kw">delete tick_obj; class="kw">return NULL; }
用 CopyTicksRange 把历史 tick 灌进自定义序列
CTickSeries::Create() 负责把指定天数起的历史 tick 拉进内存链表。先判 m_available,未启用就 Print 提示并返 false,避免空跑浪费资源。 起点时间由 iTime(m_symbol, PERIOD_D1, m_required) 取日线开盘,再把时分许秒清零乘 1000 转成毫秒;若算出来小于 1 则强制置 1,防止 CopyTicksRange 传参异常。 核心只有一行:m_amount = CopyTicksRange(m_symbol, ticks_array, COPY_TICKS_ALL, date_from)。返回小于 1 说明取数失败,用 GetLastError() 打码后直接 return 0,这时链表是空的,后续更新逻辑必须判空。 循环里每根 tick 调 CreateNewTickObj() 入链,并持续用 m_last_time 记录最大 TimeMSC。这个毫秒时间戳就是下次增量更新的游标——开 MT5 把 m_required 改成 0 能拉当天全量 tick,改大则回溯更多历史日,验证链表长度随参数线性增长即可。外汇与贵金属 tick 数据受流动性影响,断点缺失概率不低,实盘使用前建议先打印 m_amount 确认覆盖度。
class="type">int CTickSeries::Create(const class="type">uint required=class="num">0) { if(!this.m_available) { ::Print(DFUN,this.m_symbol,": ",CMessage::Text(MSG_TICKSERIES_TEXT_IS_NOT_USE)); class="kw">return false; } class="type">MqlTick ticks_array[]; this.m_list_ticks.Clear(); this.m_list_ticks.Sort(SORT_BY_TICK_TIME_MSC); this.m_last_time=class="num">0; ::ResetLastError(); class="type">int err=ERR_SUCCESS; class="type">MqlDateTime date_str={class="num">0}; class="type">class="kw">datetime date=::iTime(m_symbol,PERIOD_D1,this.m_required); ::TimeToStruct(date,date_str); date_str.hour=date_str.min=date_str.sec=class="num">0; date=::StructToTime(date_str); class="type">long date_from=(class="type">long)date*class="num">1000; if(date_from<class="num">1) date_from=class="num">1; this.m_amount=::CopyTicksRange(m_symbol,ticks_array,COPY_TICKS_ALL,date_from); if(this.m_amount<class="num">1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_TICKSERIES_ERR_GET_TICK_DATA),": ",CMessage::Text(err),CMessage::Retcode(err)); class="kw">return class="num">0; } for(class="type">int i=class="num">0; i<(class="type">int)this.m_amount; i++) { CDataTick *tick_obj=this.CreateNewTickObj(ticks_array[i]); if(tick_obj==NULL) class="kw">continue; if(this.m_last_time<(class="type">ulong)tick_obj.TimeMSC()) this.m_last_time=tick_obj.TimeMSC(); } class="kw">return this.m_list_ticks.Total(); }