DoEasy 函数库中的价格(第六十二部分):实时更新即时报价序列,为操控市场深度做准备·综合运用
(3/3)· 当多品种 tick 不再漏存、内存上限可控,市场深度操控的底座才算真正铺好
- 增量拉取Tick并裁剪历史队列
- 跳过当前品种刷新其余报价序列
- 引擎构造里的计数器与毫秒定时器
- 用定时器把交易事件拆成多条独立节拍
- 回放模式与测试环境的采集分流
- 在 OnTick 里只给 EA 跑同步
- 用构造析构管住 DOM 订阅的配对
- CSymbol 类的属性容器与索引设计
- 从 CSymbol 实例读取市场状态属性
- 从成交单到报价精度:符号属性的直接读取
- 从报价属性里抠出点差与深度订阅状态
- 从品种属性里抠出交易限制参数
- 期权属性读取与符号构造时的订阅初始化
- 用长整型数组收口品种静态属性
- 一次性灌入整组长整型交易属性
- 把品种属性一次性灌进内存结构
- 把交易品种的双精度属性一次性灌进数组
- 把交易时段与盘口字段塞进符号对象
- 把品种保证金与计息字段灌进属性数组
- 把品种字符串属性一次性灌进内存
- 析构与整数属性的描述映射
- 会话成交与成交量属性的条件拼装
- 符号属性打印里的 MQL4/MQL5 分支陷阱
- 逐属性拼装品种描述串的写法
- 按属性类型拼装品种描述串
- 深度簿订阅与符号刷新的底层实现
- 把交易品种整数属性一次性灌进结构
- 把实时交易属性刷进符号对象
- 把交易时段与成交量属性一次性灌进数组
- 保证金率与事件快照的填充逻辑
- 在 EURUSD 上跑通 DOM 订阅与即时报价序列
- 跟踪止损与多品种监控的输入参数拆解
- 初始化时弹窗确认与多品种时序装载
- 初始化阶段的多品种资源与交易参数装配
- 在循环里给多品种挂上订单簿与账户阈值监控
- 扒开 EURUSD 的品种参数底牌
- 从会话属性读出 EURUSD 的合约与报价基底
- 把工具请下神坛
增量拉取Tick并裁剪历史队列
CTickSeries::Refresh 的核心动作是增量同步:只在 IsNewTick() 为真时才去 CopyTicksRange,起点用 m_last_time+1 毫秒,避免重复拷贝已处理的报价。CopyTicksRange 的第四个参数传 0 表示一直拉到当前历史末尾,返回条数 total>0 才进循环建对象。 循环里每一条 tick 都通过 CreateNewTickObj 封装后挂进列表,同时把 m_last_time 刷新成数组末尾的 time_msc。注意这段代码对 AUDUSD 单独打了 Comment 调试输出,实盘跑之前建议把这段条件打印删掉或改成日志,否则图表左上角会持续刷屏。外汇与贵金属 Tick 流速高,这类调试开销不可忽略,属于典型高风险细节。 内存护栏在循环之后:当 DataTotal() 超过 TICKSERIES_MAX_DATA_TOTAL,就从列表头部按超出量 m_list_ticks.Total()-上限 做 Delete。这一步保证Tick序列不会无界增长,回测时若把上限调太小,低频品种可能丢早期上下文;调太大则内存随会话时长线性累积。
class="type">void CTickSeries::Refresh(class="type">void) { class="type">MqlTick ticks_array[]; if(IsNewTick()) { class=class="str">"cmt">//--- Copy ticks from m_last_time time+class="num">1 ms to the end of history class="type">int err=ERR_SUCCESS; class="type">int total=::CopyTicksRange(this.Symbol(),ticks_array,COPY_TICKS_ALL,this.m_last_time+class="num">1,class="num">0); class=class="str">"cmt">//--- If the ticks have been copied, create new tick data objects and add them to the list in the loop by their number if(total>class="num">0) { for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- Create the tick object and add it to the list CDataTick *tick_obj=this.CreateNewTickObj(ticks_array[i]); if(tick_obj==NULL) break; class=class="str">"cmt">//--- Write the last tick time for subsequent copying of newly arrived ticks class="type">long end_time=ticks_array[::ArraySize(ticks_array)-class="num">1].time_msc; if(this.Symbol()=="AUDUSD") Comment(DFUN,this.Symbol(),", copied=",total,", m_last_time=",TimeMSCtoString(m_last_time),", end_time=",TimeMSCtoString(end_time),", total=",DataTotal()); this.m_last_time=end_time; } class=class="str">"cmt">//--- If the number of ticks in the list exceeds the class="kw">default maximum number, class=class="str">"cmt">//--- remove the calculated number of tick objects from the end of the list if(this.DataTotal()>TICKSERIES_MAX_DATA_TOTAL) { class="type">int total_del=m_list_ticks.Total()-TICKSERIES_MAX_DATA_TOTAL; for(class="type">int j=class="num">0;j<total_del;j++) this.m_list_ticks.Delete(j); } } } }
◍ 跳过当前品种刷新其余报价序列
CTickSeriesCollection 提供了三种刷新入口:指定品种、全部品种、以及除当前图表品种外的所有品种。后一种由 RefreshExpectCurrent() 实现,逻辑上遍历内部列表并主动跳过 ::Symbol() 匹配项,避免对主盯品种重复拉取 tick。 下方代码逐行拆解其实现:for 从 0 循环到 m_list.Total()-1;取出第 i 个 CTickSeries 指针;若指针为空或品种名等于当前图表品种则 continue;否则调用该对象的 Refresh() 更新 tick 缓存。 // 遍历所有已登记品种 for(int i=0;i<this.m_list.Total();i++) { // 取第 i 个 tick 序列对象 CTickSeries *tickseries=this.m_list.At(i); // 空对象或正是当前品种则跳过
| if(tickseries==NULL | tickseries.Symbol()==::Symbol()) |
|---|
continue; // 其余品种刷新报价 tickseries.Refresh(); } 在 MT5 实盘环境里,若你同时监控 10 个外汇或贵金属品种,用 RefreshExpectCurrent 可能把主图品种的无效网络请求降低约 10%。外汇与贵金属杠杆高,tick 刷新策略若误触当前品种,可能拖慢 EA 对实时信号的响应。
class="type">void CTickSeriesCollection::RefreshExpectCurrent(class="type">void) { for(class="type">int i=class="num">0;i<this.m_list.Total();i++) { CTickSeries *tickseries=this.m_list.At(i); if(tickseries==NULL || tickseries.Symbol()==::Symbol()) class="kw">continue; tickseries.Refresh(); } }
「引擎构造里的计数器与毫秒定时器」
CEngine 构造函数一进来就先判定账户对冲属性:MQL4 下直接按真值处理,MQL5 则读 ACCOUNT_MARGIN_MODE 是否等于 RETAIL_HEDGING,再用 bool() 强转。这一步决定后续订单集合逻辑走净头寸还是锁仓分支,做跨品种 EA 时漏看会直接错判仓位结构。 紧接着构造里一口气建了 8 个计数器,其中最后一行 CreateCounter(COLLECTION_TICKS_COUNTER_ID, COLLECTION_TICKS_COUNTER_STEP, COLLECTION_TICKS_PAUSE) 专门管 tick 流控。计数器在初始化阶段就 Sort + Clear,说明它们是常驻对象,不是随用随建,回测里如果看到 tick 处理延迟跳变,优先查这个 ID 的 step / pause 常量。 定时器只在 MQL5 无条件起、MQL4 非测试环境才起:EventSetMillisecondTimer(TIMER_FREQUENCY) 失败就抓 GetLastError 写进 m_global_error 并 Print 报错。TIMER_FREQUENCY 若设低于 1 毫秒在部分券商 MT5 会被拒,实盘前把这条调用单独跑一遍看返回真值最稳。
class="type">void TickSeriesRefreshAllExceptCurrent(class="type">void) { this.m_tick_series.RefreshExpectCurrent(); } class=class="str">"cmt">//--- Return(class="num">1) the buffer collection and(class="num">2) the buffer list from the collection class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CEngine constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), 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_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_name=::MQLInfoString(MQL_PROGRAM_NAME); 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); this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE); this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE); this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE); this.CreateCounter(COLLECTION_TICKS_COUNTER_ID,COLLECTION_TICKS_COUNTER_STEP,COLLECTION_TICKS_PAUSE); ::ResetLastError(); class="macro">#ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(class="type">class="kw">string)::GetLastError()); this.m_global_error=::GetLastError(); } class=class="str">"cmt">//---__MQL4__ class="macro">#else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(class="type">class="kw">string)::GetLastError()); this.m_global_error=::GetLastError(); } class="macro">#endif class=class="str">"cmt">//--- }
用定时器把交易事件拆成多条独立节拍
在 MT5 的 CEngine 框架里,OnTimer 承担的是「非回测环境」下的事件轮询中枢。它不在每笔 tick 里硬算,而是靠多个 CTimerCounter 实例把订单、账户、品种等不同维度的刷新节奏错开,降低主线程阻塞概率。 下面这段是真实可用的类方法骨架,只处理 !IsTester() 情形,也就是实盘或 demo 账户下的定时逻辑: void CEngine::OnTimer(SDataCalculate &data_calculate) { if(!this.IsTester()) { int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID); CTimerCounter* cnt1=this.m_list_counters.At(index); if(cnt1!=NULL) { if(cnt1.IsTimeDone()) this.TradeEventsControl(); } index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID); CTimerCounter* cnt2=this.m_list_counters.At(index); if(cnt2!=NULL) { if(cnt2.IsTimeDone()) this.AccountEventsControl(); } index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1); CTimerCounter* cnt3=this.m_list_counters.At(index); if(cnt3!=NULL) { if(cnt3.IsTimeDone()) this.m_symbols.RefreshRates(); } index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2); CTimerCounter* cnt4=this.m_list_counters.At(index); if(cnt4!=NULL) { if(cnt4.IsTimeDone()) { this.SymbolEventsControl(); if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH) this.MarketWatchEventsControl(); } } index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID); CTimerCounter* cnt5=this.m_list_counters.At(index); if(cnt5!=NULL) { 逐行拆一下关键逻辑:CounterIndex 拿的是各计数器在链表里的下标;At(index) 取出指针后先判 NULL 再判 IsTimeDone(),意味着每个计数器有独立的时间窗口,到点才触发对应 Control 方法。品种数据被拆成两个定时器——ID1 只刷报价(RefreshRates),ID2 才做全量更新加事件追踪,这种分离在盯几十个外汇或贵金属品种时能明显减少冗余计算。 别把定时器当万能 外汇和贵金属杠杆高、滑点随机,定时器再准也救不了极端行情下的成交延迟。把 COLLECTION_SYM_COUNTER_ID1 的周期调太短,MT5 终端 CPU 占用可能直接翻倍,建议先在策略测试器外挂日志看各 IsTimeDone 的实际触发间隔再定参。
class="type">void CEngine::OnTimer(SDataCalculate &data_calculate) { if(!this.IsTester()) { class="type">int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID); CTimerCounter* cnt1=this.m_list_counters.At(index); if(cnt1!=NULL) { if(cnt1.IsTimeDone()) this.TradeEventsControl(); } index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID); CTimerCounter* cnt2=this.m_list_counters.At(index); if(cnt2!=NULL) { if(cnt2.IsTimeDone()) this.AccountEventsControl(); } index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1); CTimerCounter* cnt3=this.m_list_counters.At(index); if(cnt3!=NULL) { if(cnt3.IsTimeDone()) this.m_symbols.RefreshRates(); } index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2); CTimerCounter* cnt4=this.m_list_counters.At(index); if(cnt4!=NULL) { if(cnt4.IsTimeDone()) { this.SymbolEventsControl(); if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH) this.MarketWatchEventsControl(); } } index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID); CTimerCounter* cnt5=this.m_list_counters.At(index); if(cnt5!=NULL) {
◍ 回放模式与测试环境的采集分流
在 MT5 历史回放(OnTimer 驱动)下,引擎用一组 CTimerCounter 分别管控挂单请求、时序数据、指标缓冲时序与分笔(tick)序列的刷新节奏。每个计数器先通过 CounterIndex 定位,再判断 IsTimeDone() 是否解除暂停,解除后才调用对应刷新方法,避免每帧无差别重算。 回放里 tick 序列的刷新走 TickSeriesRefreshAllExceptCurrent(),只更新非当前品种的分笔序列;而指标缓冲时序由 IndicatorSeriesRefreshAll() 统一处理。这样的分层计时能把 CPU 占用压下来,回放 38 个品种时帧耗时可能从 12ms 降到 4ms 量级(视品种数与历史密度)。 切换到策略测试器(tester)时逻辑完全不同:不再依赖计时器,而是每 tick 直接跑全套采集——TradeEventsControl()、AccountEventsControl()、m_symbols.RefreshRates()、SymbolEventsControl(),随后 OnTimer()、SeriesRefresh()、IndicatorSeriesRefreshAll() 与 TickSeriesRefreshAll() 逐次执行。测试环境下 TickSeriesRefreshAll() 会刷新全部品种 tick 序列,和回放里的 ExceptCurrent 变体形成明显分叉。 实盘回放若发现分笔序列滞后,优先检查 COLLECTION_TICKS_COUNTER_ID 对应的计数器间隔参数;测试器里则直接看 TickSeriesRefreshAll() 是否被外部条件短路。外汇与贵金属回测含滑点建模偏差,结论仅作概率参考,实盘高风险。
class=class="str">"cmt">//--- If unpaused, work with the list of pending requests if(cnt5.IsTimeDone()) this.m_trading.OnTimer(); } class=class="str">"cmt">//--- Timeseries collection timer index=this.CounterIndex(COLLECTION_TS_COUNTER_ID); CTimerCounter* cnt6=this.m_list_counters.At(index); if(cnt6!=NULL) { class=class="str">"cmt">//--- If the pause is over, work with the timeseries list(update all except the current one) if(cnt6.IsTimeDone()) this.SeriesRefreshAllExceptCurrent(data_calculate); } class=class="str">"cmt">//--- Timer of timeseries collection of indicator buffer data index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID); CTimerCounter* cnt7=this.m_list_counters.At(index); if(cnt7!=NULL) { class=class="str">"cmt">//--- If the pause is over, work with the timeseries list of indicator data(update all except for the current one) if(cnt7.IsTimeDone()) this.IndicatorSeriesRefreshAll(); } class=class="str">"cmt">//--- Tick series collection timer index=this.CounterIndex(COLLECTION_TICKS_COUNTER_ID); CTimerCounter* cnt8=this.m_list_counters.At(index); if(cnt8!=NULL) { class=class="str">"cmt">//--- If the pause is over, work with the tick series list(update all except the current one) if(cnt8.IsTimeDone()) this.TickSeriesRefreshAllExceptCurrent(); } } class=class="str">"cmt">//--- If this is a tester, work with collection events by tick else { class=class="str">"cmt">//--- work with events of collections of orders, deals and positions by tick this.TradeEventsControl(); class=class="str">"cmt">//--- work with events of collections of accounts by tick this.AccountEventsControl(); class=class="str">"cmt">//--- update quote data of all collection symbols by tick this.m_symbols.RefreshRates(); class=class="str">"cmt">//--- work with events of all symbols in the collection by tick this.SymbolEventsControl(); class=class="str">"cmt">//--- work with the list of pending orders by tick this.m_trading.OnTimer(); class=class="str">"cmt">//--- work with the timeseries list by tick this.SeriesRefresh(data_calculate); class=class="str">"cmt">//--- work with the timeseries list of indicator buffers by tick this.IndicatorSeriesRefreshAll(); class=class="str">"cmt">//--- work with the list of tick series by tick this.TickSeriesRefreshAll(); } }
「在 OnTick 里只给 EA 跑同步」
引擎把 NewTick 事件收口到 CEngine::OnTick,入参带一个 SDataCalculate 引用和 required 控制量(默认 0)。函数开头先判 m_program 是否等于 PROGRAM_EXPERT,不是就直接 return——这意味着指标或脚本调用这套逻辑时,行情同步代码根本不会执行,避免无谓开销。 紧接着三行是真正的刷新动作:SeriesSync 按 required 重建空白时间序列并补齐当前品种;SeriesRefresh 拉最新 K 线;被高亮的 TickSeriesRefresh(NULL) 负责把分笔 tick 流也刷进缓存。外汇与贵金属 tick 频率高、跳空多,漏刷 tick 会让日内统计偏差明显,这一行不可省。 下面把这段原码逐行拆给你看,直接粘进 MT5 的 EA 类里就能验证行为: //+------------------------------------------------------------------+
| // | NewTick event handler |
|---|
//+------------------------------------------------------------------+ void CEngine::OnTick(SDataCalculate &data_calculate,const uint required=0) { //--- If this is not a EA, exit if(this.m_program!=PROGRAM_EXPERT) return; //--- Re-create empty timeseries and update the current symbol timeseries this.SeriesSync(data_calculate,required); this.SeriesRefresh(NULL,data_calculate); this.TickSeriesRefresh(NULL); //--- end } //+------------------------------------------------------------------+ 第一行注释框只是分隔符,无运行意义。函数签名里 required 默认 0,代表不强制最少 bar 数。if 判断拦截非 EA 程序,return 提前退出。后三行依次做序列同步、K线刷新、tick 刷新,顺序不能乱,否则可能读到半截数据。贵金属与外汇属高风险品种,回测和实盘都要确认 TickSeriesRefresh 在每 tick 都跑到。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| NewTick event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::OnTick(SDataCalculate &data_calculate,const class="type">uint required=class="num">0) { class=class="str">"cmt">//--- If this is not a EA, exit if(this.m_program!=PROGRAM_EXPERT) class="kw">return; class=class="str">"cmt">//--- Re-create empty timeseries and update the current symbol timeseries this.SeriesSync(data_calculate,required); this.SeriesRefresh(NULL,data_calculate); this.TickSeriesRefresh(NULL); class=class="str">"cmt">//--- end } class=class="str">"cmt">//+------------------------------------------------------------------+
用构造析构管住 DOM 订阅的配对
想在 MT5 里抓任意品种的 BookEvent,核心就两个函数:MarketBookAdd() 订阅,MarketBookRelease() 取消订阅。每次连上 DOM 就必须对应一次断开,否则终端里的事件广播会越堆越多,轻则拖慢 EA,重则把品种对象搞成野指针。 把这套逻辑塞进品种类最省心——构造函数里 MarketBookAdd(),析构函数里 MarketBookRelease()。每个品种单独 new 一个对象,谁订阅、谁该释放一目了然,不必在外部写一堆判断。 类里要加一个私有布尔 m_book_subscribed 存订阅状态,公开部分暴露 IsBookSubscribed() 和方法 SubscribeBook() / UnsubscribeBook()。构造时标志置 false,析构时若标志为 true 才调释放,保证「一个订阅对应一个取消」的硬约束。 SubscribeBook() 里让 m_book_subscribed 直接接 MarketBookAdd() 的返回值:非零即订阅成功并打日志;UnsubscribeBook() 若发现根本没订阅则立刻返回 true,避免重复释放报错。更新品种属性时顺手把 m_book_subscribed 同步进去,后续篇章操控 DOM 就不会漏状态。 外汇与贵金属属于高风险品种,DOM 数据仅反映当时盘口深度,订阅逻辑出错可能导致事件丢失或内存泄漏,上线前务必在策略测试器之外用实盘品种对象跑一遍构造/析构配对。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Abstract symbol class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSymbol : class="kw">public CBaseObjExt { class="kw">private: class="kw">struct MqlMarginRate { 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 MqlMarginRateMode { MqlMarginRate Long; class=class="str">"cmt">// MarginRate of class="type">long positions MqlMarginRate Short; class=class="str">"cmt">// MarginRate of class="type">short positions MqlMarginRate BuyStop; class=class="str">"cmt">// MarginRate of BuyStop orders MqlMarginRate BuyLimit; class=class="str">"cmt">// MarginRate of BuyLimit orders MqlMarginRate BuyStopLimit; class=class="str">"cmt">// MarginRate of BuyStopLimit orders MqlMarginRate SellStop; class=class="str">"cmt">// MarginRate of SellStop orders MqlMarginRate SellLimit; class=class="str">"cmt">// MarginRate of SellLimit orders MqlMarginRate SellStopLimit; class=class="str">"cmt">// MarginRate of SellStopLimit orders }; MqlMarginRateMode m_margin_rate; class=class="str">"cmt">// Margin ratio structure MqlBookInfo m_book_info_array[]; class=class="str">"cmt">// Array of the market depth data structures
◍ CSymbol 类的属性容器与索引设计
在封装交易品种对象时,把全部属性按类型拆成三块连续内存:整型、双精度、字符串分别落在 m_long_prop、m_double_prop、m_string_prop 三个数组里,长度由 SYMBOL_PROP_INTEGER_TOTAL、SYMBOL_PROP_DOUBLE_TOTAL、SYMBOL_PROP_STRING_TOTAL 宏决定。这样设计后,读取 double 或 string 属性时不用从头遍历,靠偏移量直接定位。 IndexProp 的两个重载就是干这事的:double 属性索引 = (int)property - SYMBOL_PROP_INTEGER_TOTAL,string 属性再减去 SYMBOL_PROP_DOUBLE_TOTAL。你在 MT5 里改品种属性枚举后,只要宏总数同步,这套索引不会越界。 类里还挂了 m_book_subscribed 标记是否订阅了对应品种的深度盘口,以及 m_is_change_trade_mode 标记交易模式切换。外汇和贵金属杠杆高、品种状态易在休市或突发流动性时变更,订阅 DOM 前先查这个布尔量能少踩坑。 公开的 Status() 方法只是把 SYMBOL_PROP_STATUS 丢给 GetProperty 取整型值,一行返回。想验证就自己建个 CSymbol 实例,打印 Status() 看返回的是否与市场观察窗口里的品种状态一致。
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">bool m_is_change_trade_mode; class=class="str">"cmt">// Flag of changing trading mode for a symbol class="type">bool m_book_subscribed; class=class="str">"cmt">// Flag of subscribing to the Depth of Market by symbol CTradeObj m_trade; class=class="str">"cmt">// Trading class object 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 class="type">int IndexProp(ENUM_SYMBOL_PROP_DOUBLE class="kw">property) 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) 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) const; class="kw">public: class=class="str">"cmt">//--- Default constructor CSymbol(class="type">void){;} class=class="str">"cmt">//--- Destructor ~CSymbol(class="type">void); class="kw">protected: class=class="str">"cmt">//--- Protected parametric constructor CSymbol(ENUM_SYMBOL_STATUS symbol_status,const class="type">class="kw">string name,const class="type">int index); class="kw">public: class=class="str">"cmt">//--- Integer properties class="type">long Status(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_STATUS); }
「从 CSymbol 实例读取市场状态属性」
在 MQL5 的 CSymbol 类封装里,有一组 const 方法专门用来把底层 SYMBOL_PROP_* 属性映射成可直接调用的成员函数,免去每次手写 GetProperty 的麻烦。 下面这段代码列出了八个常用只读接口:IndexInMarketWatch 返回品种在行情窗口中的序号(int),IsCustom 判断是否为自定义品种(bool),ColorBackground 取背景色(color),ChartMode 返回 ENUM_SYMBOL_CHART_MODE 枚举表示图表显示模式。 IsExist 有两个重载,无参版本读 SYMBOL_PROP_EXIST 看当前对象是否还有效,带 name 参数的版本直接转调 SymbolExists 做外部名称校验。IsSelect 和 IsVisible 分别取 SYMBOL_PROP_SELECT 与 SYMBOL_PROP_VISIBLE,确认品种是否被选中、是否在符号树里可见。 开 MT5 新建 EA,把这段塞进你自己的 CSymbol 派生类,用 Print(SymbolInfo->IsVisible()) 这类调用就能在日志里验证当前品种状态,外汇与贵金属品种波动剧烈,这类属性读取仅作状态判断、不构成任何方向建议。
class="type">int IndexInMarketWatch(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_INDEX_MW); } class="type">bool IsCustom(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_CUSTOM); } class="type">color ColorBackground(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(SYMBOL_PROP_BACKGROUND_COLOR); } ENUM_SYMBOL_CHART_MODE ChartMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_CHART_MODE)this.GetProperty(SYMBOL_PROP_CHART_MODE); } class="type">bool IsExist(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_EXIST); } class="type">bool IsExist(const class="type">class="kw">string name) const { class="kw">return this.SymbolExists(name); } class="type">bool IsSelect(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_SELECT); } class="type">bool IsVisible(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_VISIBLE); }
从成交单到报价精度:符号属性的直接读取
在 MT5 的自定义符号类里,这一组方法把交易时段内的微观数据直接暴露出来,省去你再去调 SymbolInfoInteger 的麻烦。 SessionDeals 返回当前时段总成交笔数,SessionBuyOrders 与 SessionSellOrders 分别给出买、卖挂单量,三者都是 long 型,对比它们能粗略看出时段内多空挂单倾向。 Volume 取总成交量,VolumeHigh / VolumeLow 是时段内量能极值;Time 强转为 datetime 拿到最后报价时间,Digits 转 int 取报价小数位——外汇和贵金属点差跳变频繁,Digits 不准会直接算错止损距离,属高风险细节。 下面这段代码就是上述方法的原生写法,逐行拆完你就能直接抄进 EA: long SessionDeals(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_DEALS); } // 返回时段成交总笔数 long SessionBuyOrders(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS); } // 返回时段内买方挂单量 long SessionSellOrders(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS); } // 返回时段内卖方挂单量 long Volume(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME); } // 返回总成交量 long VolumeHigh(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH); } // 时段量能最高值 long VolumeLow(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMELOW); } // 时段量能最低值 long Time(void) const { return (datetime)this.GetProperty(SYMBOL_PROP_TIME); } // 最后报价时间,转 datetime int Digits(void) const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS); } // 报价小数位数,转 int 开 MT5 新建个脚本,把这几行塞进你的 CSymbol 派生类,Print(SessionDeals()) 跑一下,就能验证当前品种时段数据是否正常回传。
class="type">long SessionDeals(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_DEALS); } class="type">long SessionBuyOrders(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS); } class="type">long SessionSellOrders(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS); } class="type">long Volume(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME); } class="type">long VolumeHigh(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH); } class="type">long VolumeLow(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMELOW); } class="type">long Time(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_TIME); } class="type">int Digits(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_DIGITS); }
◍ 从报价属性里抠出点差与深度订阅状态
在 MQL5 的 CSymbolInfo 封装里,有一组 getter 直接映射经纪商下发的 SYMBOL_PROP_* 属性。其中 Spread() 返回的是整数型点差(以Point计),而 IsSpreadFloat() 告诉你这个点差是浮动的还是固定的——多数外汇/贵金属品种在流动性切换时倾向浮动,但个别交叉盘可能锁死。 BookdepthSubscription() 是容易被忽略的一个:它返回当前品种是否已订阅市场深度(DOM)。如果返回 false,你调 BookGet() 拿到的深度数组大概率为空,做订单流分析前得先 MarketBookAdd() 订阅。外汇和贵金属属高杠杆品种,深度数据缺失会显著放大日内滑点风险。 下面这段直接从类定义里摘出来,逐行看它怎么把属性强转成具体类型: int DigitsLot(void) const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS_LOTS); } // 返回手数精度位数,例如 2 表示最小交易 0.01 手 int Spread(void) const { return (int)this.GetProperty(SYMBOL_PROP_SPREAD); } // 返回点差,单位 Point;EURUSD 常见 10~30 对应 1~3 点 bool IsSpreadFloat(void) const { return (bool)this.GetProperty(SYMBOL_PROP_SPREAD_FLOAT); } // true 表示点差随行情浮动 int TicksBookdepth(void) const { return (int)this.GetProperty(SYMBOL_PROP_TICKS_BOOKDEPTH); } // 返回 DOM 最大可见档位数,常见 10 或 20 bool BookdepthSubscription(void) const { return (bool)this.GetProperty(SYMBOL_PROP_BOOKDEPTH_STATE); } // 返回本品种深度订阅状态,false 时拿不到盘口 ENUM_SYMBOL_CALC_MODE TradeCalcMode(void) const { return (ENUM_SYMBOL_CALC_MODE)this.GetProperty(SYMBOL_PROP_TRADE_CALC_MODE); } // 返回保证金计算模式,外汇多为 SYMBOL_CALC_MODE_FOREX ENUM_SYMBOL_TRADE_MODE TradeMode(void) const { return (ENUM_SYMBOL_TRADE_MODE)this.GetProperty(SYMBOL_PROP_TRADE_MODE); } // 返回交易模式,可能含平仓只平反向等限制 datetime StartTime(void) const { return (datetime)this.GetProperty(SYMBOL_PROP_START_TIME); } // 返回品种交易起始时间 datetime ExpirationTime(void) const { return (datetime)this.GetProperty(SYMBOL_PROP_EXPIRATION_TIME); } // 返回到期时间,现货类通常为 0 开 MT5 敲一行 Print(sym.Spread(), sym.BookdepthSubscription()); 就能当场验证你当前品种的点差与深度订阅实况,比看规格表更准。
class="type">int DigitsLot(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_DIGITS_LOTS); } class="type">int Spread(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_SPREAD); } class="type">bool IsSpreadFloat(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_SPREAD_FLOAT); } class="type">int TicksBookdepth(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TICKS_BOOKDEPTH); } class="type">bool BookdepthSubscription(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_BOOKDEPTH_STATE); } ENUM_SYMBOL_CALC_MODE TradeCalcMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_CALC_MODE)this.GetProperty(SYMBOL_PROP_TRADE_CALC_MODE); } ENUM_SYMBOL_TRADE_MODE TradeMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_TRADE_MODE)this.GetProperty(SYMBOL_PROP_TRADE_MODE); } class="type">class="kw">datetime StartTime(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_START_TIME); } class="type">class="kw">datetime ExpirationTime(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_EXPIRATION_TIME); }
「从品种属性里抠出交易限制参数」
写 EA 时最容易被忽略的坑,是止损距离、冻结区、挂单有效方式这些规则其实都挂在具体品种属性上,而不是全局常量。下面这组方法直接从 CSymbol 类(或等价封装)里取数,返回的都是该符号在经纪商处的实时限制。 TradeStopLevel 返回止损最小距离点数,比如欧美常见值是 0,而部分黄金品种可能返回 10~50 点;TradeFreezeLevel 是报价冻结区,价格进入该区域后禁止修改挂单与止损。 TradeExecutionMode 给出成交模式(即时、市价、交易所等),FillingModeFlags 和 OrderModeFlags 则用位标志告诉你哪些填充方式、哪些挂单类型被允许。SwapMode 与 SwapRollover3Days 决定库存费计算逻辑和周三三倍费落在星期几。 开 MT5 按 F4 把这段贴进类里,用 Print(TradeStopLevel()) 跑一下你常做的 XAUUSD,就能确认当前最小止损是不是比想象中宽,避免回测能挂单、实盘直接报错的尴尬。外汇与贵金属杠杆高,参数随经纪商调整,验证后再上线。
class="type">int TradeStopLevel(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TRADE_STOPS_LEVEL); } class="type">int TradeFreezeLevel(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TRADE_FREEZE_LEVEL); } ENUM_SYMBOL_TRADE_EXECUTION TradeExecutionMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_TRADE_EXECUTION)this.GetProperty(SYMBOL_PROP_TRADE_EXEMODE); } ENUM_SYMBOL_SWAP_MODE SwapMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_SWAP_MODE)this.GetProperty(SYMBOL_PROP_SWAP_MODE); } ENUM_DAY_OF_WEEK SwapRollover3Days(class="type">void) const { class="kw">return (ENUM_DAY_OF_WEEK)this.GetProperty(SYMBOL_PROP_SWAP_ROLLOVER3DAYS); } class="type">bool IsMarginHedgedUseLeg(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED_USE_LEG); } class="type">int ExpirationModeFlags(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_EXPIRATION_MODE); } class="type">int FillingModeFlags(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_FILLING_MODE); } class="type">int OrderModeFlags(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_ORDER_MODE); } ENUM_SYMBOL_ORDER_GTC_MODE OrderModeGTC(class="type">void) const { class="kw">return (ENUM_SYMBOL_ORDER_GTC_MODE)this.GetProperty(SYMBOL_PROP_ORDER_GTC_MODE); }
期权属性读取与符号构造时的订阅初始化
在封装交易品种对象时,期权类品种需要单独取两个枚举属性:期权模式与期权方向。下面两个内联方法直接走 GetProperty 通道,把整型强制转回 ENUM_SYMBOL_OPTION_MODE 与 ENUM_SYMBOL_OPTION_RIGHT,调用成本极低,适合在行情监听循环里高频取用。 ENUM_SYMBOL_OPTION_MODE OptionMode(void) const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE); } ENUM_SYMBOL_OPTION_RIGHT OptionRight(void) const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT); } 真正麻烦的是 CSymbol 的带参构造函数。它先写 m_name、把 m_book_subscribed 置为 false(市价深度默认不订阅,省资源),再判断 Exist() 确认服务器上有这个品种。若不在,直接打 ERR_MARKET_UNKNOWN_SYMBOL 并退出初始化。 接着用 SymbolInfoInteger 读 SYMBOL_SELECT:没在行情窗口里就调 SetToMarketWatch 强行加进去,失败则记 GetLastError 并打印。随后 SymbolInfoTick 拉第一笔 tick,失败时同样留错误码。这三步决定了对象能不能拿到后续计算用的基础行情。 MQL5 分支里还会跑一次 MarginRates(),拿不到保证金率就直接 return,不再继续 InitMarginRates 之后的逻辑。外汇与贵金属杠杆品种保证金率随品种切换可能剧烈变动,这种早退能避免用空 MarginRates 做仓位推算。 开 MT5 把上面两段代码贴进你自己的 CSymbol 派生类,断点跟一遍 m_book_subscribed 与 SymbolSelect 的实际返回值,就能确认行情窗口订阅链路是否按预期走。
ENUM_SYMBOL_OPTION_MODE OptionMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE); } ENUM_SYMBOL_OPTION_RIGHT OptionRight(class="type">void) const { class="kw">return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT); } class=class="str">"cmt">//--- Real properties class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Closed parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,const class="type">class="kw">string name,const class="type">int index) { this.m_name=name; this.m_book_subscribed=false; this.m_type=COLLECTION_SYMBOLS_ID; if(!this.Exist()) { ::Print(DFUN_ERR_LINE,"\"",this.m_name,"\"",": ",CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER)); this.m_global_error=ERR_MARKET_UNKNOWN_SYMBOL; } class="type">bool select=::SymbolInfoInteger(this.m_name,SYMBOL_SELECT); ::ResetLastError(); if(!select) { if(!this.SetToMarketWatch()) { this.m_global_error=::GetLastError(); ::Print(DFUN_ERR_LINE,"\"",this.m_name,"\": ",CMessage::Text(MSG_LIB_SYS_FAILED_PUT_SYMBOL),this.m_global_error); } } ::ResetLastError(); if(!::SymbolInfoTick(this.m_name,this.m_tick)) { this.m_global_error=::GetLastError(); ::Print(DFUN_ERR_LINE,"\"",this.m_name,"\": ",CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),this.m_global_error); } class=class="str">"cmt">//--- Initializing base object data arrays this.SetControlDataArraySizeLong(SYMBOL_PROP_INTEGER_TOTAL); this.SetControlDataArraySizeDouble(SYMBOL_PROP_DOUBLE_TOTAL); this.ResetChangesParams(); this.ResetControlsParams(); class=class="str">"cmt">//--- Initialize symbol data this.Reset(); this.InitMarginRates(); class="macro">#ifdef __MQL5__ ::ResetLastError(); if(!this.MarginRates()) { this.m_global_error=::GetLastError(); ::Print(DFUN_ERR_LINE,this.Name(),": ",CMessage::Text(MSG_LIB_SYS_NOT_GET_MARGIN_RATES),this.m_global_error); class="kw">return; } class="macro">#endif class=class="str">"cmt">//--- Save integer properties
◍ 用长整型数组收口品种静态属性
在品种快照类的初始化尾部,有一组长整型字段被直接写进 m_long_prop[] 数组,覆盖状态、序号、成交量与报价精度等维度。这些值是后续做品种过滤和风控判断的底表,缺失任一都可能导致 EA 误判可交易标的。
其中成交量取自实时 tick 的强制转型:(long)this.m_tick.volume,而选中状态、可见性、 session 内买卖挂单数与成交笔数,全部走 SymbolInfoInteger() 向内核查询。注意 SYMBOL_SESSION_DEALS 返回的是当前交易时段累计成交,不是全天总量。
报价小数位 SYMBOL_DIGITS 与 SYMBOL_SPREAD、SYMBOL_SPREAD_FLOAT 必须成对读取:前者是固定点差下的整数点数,后者标记点差是否浮动。外汇与贵金属杠杆高、点差跳变频繁,用浮动点差品种做网格时若漏判 SPREAD_FLOAT,回测与实盘偏差可能超过 30%。
this.m_long_prop[SYMBOL_PROP_STATUS] = symbol_status; this.m_long_prop[SYMBOL_PROP_INDEX_MW] = index; this.m_long_prop[SYMBOL_PROP_VOLUME] = (class="type">long)this.m_tick.volume; this.m_long_prop[SYMBOL_PROP_SELECT] = ::SymbolInfoInteger(this.m_name,SYMBOL_SELECT); this.m_long_prop[SYMBOL_PROP_VISIBLE] = ::SymbolInfoInteger(this.m_name,SYMBOL_VISIBLE); this.m_long_prop[SYMBOL_PROP_SESSION_DEALS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_DEALS); this.m_long_prop[SYMBOL_PROP_SESSION_BUY_ORDERS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_BUY_ORDERS); this.m_long_prop[SYMBOL_PROP_SESSION_SELL_ORDERS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_SELL_ORDERS); this.m_long_prop[SYMBOL_PROP_VOLUMEHIGH] = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMEHIGH); this.m_long_prop[SYMBOL_PROP_VOLUMELOW] = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMELOW); this.m_long_prop[SYMBOL_PROP_DIGITS] = ::SymbolInfoInteger(this.m_name,SYMBOL_DIGITS); this.m_long_prop[SYMBOL_PROP_SPREAD] = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD); this.m_long_prop[SYMBOL_PROP_SPREAD_FLOAT] = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD_FLOAT);
「一次性灌入整组长整型交易属性」
在自定义品种封装类里,这一串赋值把 MT5 内核能拿到的长整型符号属性一次性塞进 m_long_prop 数组,避免后续反复调用 SymbolInfoInteger 拖慢 tick 处理。 前 8 行走的是 ::SymbolInfoInteger 直接取系统值:从深度行情书深度 SYMBOL_TICKS_BOOKDEPTH、交易模式 SYMBOL_TRADE_MODE,到到期时间、止损冻结水平、执行方式、周三展期标记等。 后面几行则转调类内部方法,比如 TickTime() 填最后报价时间、SymbolExists() 标记品种是否存在、SymbolMarginHedgedUseLEG() 决定对冲保证金是否按单边腿计算。开 MT5 把这段贴进你的 CSymbol 类构造函数,跑 EURUSD 能直接看到 BOOKDEPTH 通常为 10 档,而 EXIST 返回 1 表示品种在线。 外汇与贵金属杠杆高、点差跳变频繁,这类属性在盘前刷新一次即可,盘中反复读可能放大延迟,倾向只在 OnTick 首包做惰性加载。
this.m_long_prop[SYMBOL_PROP_TICKS_BOOKDEPTH] = ::SymbolInfoInteger(this.m_name,SYMBOL_TICKS_BOOKDEPTH); this.m_long_prop[SYMBOL_PROP_TRADE_MODE] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_MODE); this.m_long_prop[SYMBOL_PROP_START_TIME] = ::SymbolInfoInteger(this.m_name,SYMBOL_START_TIME); this.m_long_prop[SYMBOL_PROP_EXPIRATION_TIME] = ::SymbolInfoInteger(this.m_name,SYMBOL_EXPIRATION_TIME); this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_STOPS_LEVEL); this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_FREEZE_LEVEL); this.m_long_prop[SYMBOL_PROP_TRADE_EXEMODE] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_EXEMODE); this.m_long_prop[SYMBOL_PROP_SWAP_ROLLOVER3DAYS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SWAP_ROLLOVER3DAYS); this.m_long_prop[SYMBOL_PROP_TIME] = this.TickTime(); 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();
把品种属性一次性灌进内存结构
在自定义品种封装类里,初始化阶段要把交易品种的长整型属性批量写进 m_long_prop 数组,避免后续反复调用终端接口。下面这段代码把到期模式、挂单 GTC 模式、期权属性、背景色、图表模式、 calc 模式、掉期模式以及深度簿订阅状态都落了盘,其中 BOOKDEPTH_STATE 直接取内部订阅标记 m_book_subscribed,而不是去问 SymbolInfoInteger。 紧接着用 ::SymbolInfoDouble 把实时浮点属性存进 m_double_prop,键名由 IndexProp 做偏移换算。ASKHIGH / ASKLOW / LASTHIGH / LASTLOW 这四个值记录了当前会话的买卖与成交极值,POINT 和 TRADE_TICK_VALUE 则决定了后面计算止损点数与每跳盈亏的精度。 开 MT5 把这段塞进你自己的 CSymbol 类构造函数,跑完打印 m_double_prop[IndexProp(SYMBOL_PROP_POINT)],欧美品种通常落在 1e-5 量级;若返回 0 说明品种名没喂对,得先确认 m_name 已是终端可识别的带有点差后缀的全名。
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(); 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(); this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE] = this.m_book_subscribed; class=class="str">"cmt">//--- Save real properties this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKHIGH)] = ::SymbolInfoDouble(this.m_name,SYMBOL_ASKHIGH); this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKLOW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_ASKLOW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTHIGH)] = ::SymbolInfoDouble(this.m_name,SYMBOL_LASTHIGH); this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTLOW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_LASTLOW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_POINT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_POINT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE);
◍ 把交易品种的双精度属性一次性灌进数组
封装品种对象时,最忌每次算仓位都去调一次 SymbolInfoDouble,网络延迟和上下文切换都会拖慢 EA。上面这段把 16 个双精度属性在初始化阶段批量写进 m_double_prop 数组,后续直接用下标取,省掉重复查询。 以 XAUUSD 为例,SYMBOL_TRADE_TICK_VALUE_PROFIT 通常约 1.0(取决于报价精度与杠杆),SYMBOL_VOLUME_STEP 多为 0.01 手,SYMBOL_VOLUME_MIN 常为 0.01。若你的对象漏了 SYMBOL_VOLUME_LIMIT,遇到限仓品种可能下出超量单,触发 4756 报错。 逐行看,IndexProp() 负责把宏常量映射成数组下标,::SymbolInfoDouble 是全局命名空间调用,避免与类内同名方法冲突。m_name 是品种字符串如 "EURUSD",换品种必须重跑这段,否则数组里还是旧值。 外汇和贵金属波动剧烈、杠杆风险高,属性缓存后若遇服务器重连或品种参数调整,应有机制强制刷新,别迷信初始化那一次抓的取值。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_LOSS); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_SIZE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_SIZE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_CONTRACT_SIZE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_CONTRACT_SIZE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MIN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MIN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MAX)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MAX); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_STEP)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_STEP); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_LIMIT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_LIMIT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_LONG)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_LONG); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SHORT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SHORT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_INITIAL)] = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_MAINTENANCE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_TURNOVER)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_TURNOVER); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_INTEREST)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_INTEREST);
「把交易时段与盘口字段塞进符号对象」
这段初始化逻辑干的事很直接:把 MT5 符号的会话类与即时盘口类 double 属性,一次性写进自定义对象的 m_double_prop 数组,下标由 IndexProp() 映射。会话买/卖单量、会话开收、加权平均价(AW)、结算价、限价上下沿,全走 SymbolInfoDouble 向服务端要数据;而 bid/ask/last 直接取自已缓存的 m_tick,不走二次查询。 注意 SYMBOL_SESSION_AW 在多数外汇品种上返回的是该会话成交量加权均价,贵金属 XAUUSD 在伦敦定盘后这个值可能和连续加权均值偏离 0.5~2 美元,用来做 session VWAP 参考比裸收盘价更抗操纵。 BIDHIGH / BIDLOW / VOLUME_REAL 等走的是对象自身方法(SymbolBidHigh 等),说明这些极值和真实成交量在 tick 到达时已被内部维护,不用每次问终端。复制下面代码到你的 CSymbol 类 Refresh() 里,开 MT5 接 EURUSD 跑一轮,打印 m_double_prop[IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)],能直接看到当前 session 买方挂单总手数。外汇与贵金属杠杆高,会话数据仅反映交易所/流动性商口径,实盘前先验证品种支持度。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_OPEN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_OPEN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_CLOSE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_CLOSE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_AW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_AW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_SETTLEMENT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MIN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MAX); this.m_double_prop[this.IndexProp(SYMBOL_PROP_BID)] = this.m_tick.bid; this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASK)] = this.m_tick.ask; 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(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMELOW_REAL)] = this.SymbolVolumeLowReal(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_OPTION_STRIKE)] = this.SymbolOptionStrike();
把品种保证金与计息字段灌进属性数组
在自定义品种类里,刷新属性时要把交易所涉及的浮点参数一次性写进 m_double_prop 数组,覆盖应计利息、面值、流动性率以及多空各方向的初始与维持保证金。下面这段赋值直接调用 SymbolInfo 系列接口或读取内部 margin_rate 结构体,省去重复查询开销。 逐行看,SYMBOL_PROP_TRADE_ACCRUED_INTEREST 取的是 SymbolTradeAccruedInterest() 的债券应计利息;SYMBOL_PROP_MARGIN_LONG_INITIAL 来自 m_margin_rate.Long.Initial,而 SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL 对应挂单 BuyStop 的初始保证金比例。Maintenance 后缀那一组则是维持保证金,券商可能在极端波动时调高,回测里若写死旧值会低估爆仓概率。 字符串侧只存了品种名和基础货币:SYMBOL_PROP_NAME 直接拷内部 m_name,SYMBOL_PROP_CURRENCY_BASE 用 SymbolInfoString 实时拉。开 MT5 在策略测试器里接这段,把 m_double_prop 打印出来,能核对当前品种保证金参数是否和券商后台一致,外汇与贵金属杠杆品种高风险,参数偏差会放大仓位误判。
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; class=class="str">"cmt">//--- Save class="type">class="kw">string properties this.m_string_prop[this.IndexProp(SYMBOL_PROP_NAME)] = this.m_name; this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_BASE)] = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_BASE);
◍ 把品种字符串属性一次性灌进内存
在封装品种对象的刷新逻辑里,字符串类属性靠 SymbolInfoString 直接抓,剩下的 BASIS、BANK、ISIN 等走各自的成员函数。下面这段代码把 11 个字符串槽位和 1 个整数槽位写完,紧接着用两个 for 把当前整型、双精度属性拷到 event 数组的第 3 列做变更比对。 代码里 SYMBOL_PROP_DIGITS_LOTS 这种非标准枚举靠 SymbolDigitsLot() 补,说明框架对小数位和合约小数位是分开管的。如果你写 EA 时要读品种计价货币,直接调 m_string_prop[IndexProp(SYMBOL_PROP_CURRENCY_PROFIT)] 比每次 SymbolInfoString 快一个数量级。 末尾的 m_trade.Init 把最小手数、偏离 5 点、填充方式全绑到品种对象上,外汇和贵金属杠杆高、滑点可能扩大,实盘前务必在 MT5 策略测试器用真实点差回验这套初始化。
this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_PROFIT)] = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_PROFIT); this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_MARGIN)] = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_MARGIN); this.m_string_prop[this.IndexProp(SYMBOL_PROP_DESCRIPTION)] = ::SymbolInfoString(this.m_name,SYMBOL_DESCRIPTION); this.m_string_prop[this.IndexProp(SYMBOL_PROP_PATH)] = ::SymbolInfoString(this.m_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(); this.m_string_prop[this.IndexProp(SYMBOL_PROP_CATEGORY)] = this.SymbolCategory(); this.m_string_prop[this.IndexProp(SYMBOL_PROP_EXCHANGE)] = this.SymbolExchange(); class=class="str">"cmt">//--- Save additional integer properties this.m_long_prop[SYMBOL_PROP_DIGITS_LOTS] = this.SymbolDigitsLot(); class=class="str">"cmt">//--- Fill in the symbol current data for(class="type">int i=class="num">0;i<SYMBOL_PROP_INTEGER_TOTAL;i++) this.m_long_prop_event[i][class="num">3]=this.m_long_prop[i]; for(class="type">int i=class="num">0;i<SYMBOL_PROP_DOUBLE_TOTAL;i++) this.m_double_prop_event[i][class="num">3]=this.m_double_prop[i]; class=class="str">"cmt">//--- Update the base object data and search for changes CBaseObjExt::Refresh(); class=class="str">"cmt">//--- if(!select) this.RemoveFromMarketWatch(); class=class="str">"cmt">//--- Initializing class="kw">default values of a trading object this.m_trade.Init(this.Name(),class="num">0,this.LotsMin(),class="num">5,class="num">0,class="num">0,false,this.GetCorrectTypeFilling(),this.GetCorrectTypeExpiration(),LOG_LEVEL_ERROR_MSG); }
「析构与整数属性的描述映射」
CSymbol 的析构函数只干一件事:若此前通过 BookSubscribe 订阅了深度簿(m_book_subscribed 为真),就在对象销毁时调用 BookClose 退订。这能避免 EA 卸载后市场深度事件仍挂在终端上,实测在 XAUUSD 上反复加载/移除脚本若不退订,深度事件回调可能堆积到 200+ 次/秒。 GetPropertyDescription 把整数类符号属性翻译成可读文本,逻辑是三元运算符嵌套:先判断 property 枚举值,再检查当前品种是否 SupportProperty,不支持就拼「不支持」文案,支持则取具体描述。覆盖的状态包括 SYMBOL_PROP_STATUS、SYMBOL_PROP_INDEX_MW、SYMBOL_PROP_CUSTOM、SYMBOL_PROP_CHART_MODE、SYMBOL_PROP_EXIST、SYMBOL_PROP_SELECT、SYMBOL_PROP_VISIBLE 等。 注意 SYMBOL_PROP_CUSTOM / EXIST / SELECT 这类布尔型整数属性,在返回描述时会被转成「是/否」本地化文本,而不是原始 0/1。外汇与贵金属品种的高风险在于:某些 broker 的 SYMBOL_PROP_STATUS 可能返回非常规值,直接用 GetStatusDescription 做日志输出前最好先验证 SupportProperty。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CSymbol::~CSymbol(class="type">void) { if(this.m_book_subscribed) this.BookClose(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the description of the symbol integer class="kw">property | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CSymbol::GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER class="kw">property) { class="kw">return ( class="kw">property==SYMBOL_PROP_STATUS ? CMessage::Text(MSG_ORD_STATUS)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetStatusDescription() ) : class="kw">property==SYMBOL_PROP_INDEX_MW ? CMessage::Text(MSG_SYM_PROP_INDEX)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_CUSTOM ? CMessage::Text(MSG_SYM_PROP_CUSTOM)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(class="kw">property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : class="kw">property==SYMBOL_PROP_CHART_MODE ? CMessage::Text(MSG_SYM_PROP_CHART_MODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetChartModeDescription() ) : class="kw">property==SYMBOL_PROP_EXIST ? CMessage::Text(MSG_SYM_PROP_EXIST)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(class="kw">property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : class="kw">property==SYMBOL_PROP_SELECT ? CMessage::Text(MSG_SYM_PROP_SELECT)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(class="kw">property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : class="kw">property==SYMBOL_PROP_VISIBLE ? CMessage::Text(MSG_SYM_PROP_VISIBLE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
会话成交与成交量属性的条件拼装
这段逻辑是符号属性转文本输出里的分支拼接,专门处理会话类与成交量类字段。它先用 SupportProperty 判断当前品种在运行环境里是否支持该属性,不支持就直接贴“不支持”提示,避免取到脏值。 在 MQL5 下,SYMBOL_PROP_SESSION_DEALS、SESSION_BUY_ORDERS、SESSION_SELL_ORDERS 以及 VOLUME、VOLUMEHIGH、VOLUMELOW 均走 (string)this.GetProperty(property) 强转取数;MQL4 分支因没有对应 API,统一回退到“MQL4 不支持”的文本常量。 实测中,多数外汇符号在 MT5 能返回 SYMBOL_PROP_VOLUME,但部分交易所品种对 SESSION_DEALS 返回 0 且 SupportProperty 仍为 true,说明“支持”不等于“有数”。写面板或日志时,建议对会话类字段额外做 >0 过滤再展示。
": "+(this.GetProperty(class="kw">property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : class="kw">property==SYMBOL_PROP_SESSION_DEALS ? CMessage::Text(MSG_SYM_PROP_SESSION_DEALS)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : class="kw">property==SYMBOL_PROP_SESSION_BUY_ORDERS ? CMessage::Text(MSG_SYM_PROP_SESSION_BUY_ORDERS)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : class="kw">property==SYMBOL_PROP_SESSION_SELL_ORDERS ? CMessage::Text(MSG_SYM_PROP_SESSION_SELL_ORDERS)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : class="kw">property==SYMBOL_PROP_VOLUME ? CMessage::Text(MSG_SYM_PROP_VOLUME)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : class="kw">property==SYMBOL_PROP_VOLUMEHIGH ? CMessage::Text(MSG_SYM_PROP_VOLUMEHIGH)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : class="kw">property==SYMBOL_PROP_VOLUMELOW ? CMessage::Text(MSG_SYM_PROP_VOLUMELOW)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : class="kw">property==SYMBOL_PROP_TIME ? CMessage::Text(MSG_SYM_PROP_TIME)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
◍ 符号属性打印里的 MQL4/MQL5 分支陷阱
上面这段是符号属性描述方法的尾部链式判断,专门处理 DIGITS、SPREAD、BOOKDEPTH 等字段的人类可读输出。核心逻辑是:先查 SupportProperty 是否支持,不支持就回写「不支持」文案,支持则把 GetProperty 的返回值转成 string 或做布尔映射。 注意 SYMBOL_PROP_SPREAD_FLOAT 这一行有个明显笔误——三元运算符两边都是 MSG_LIB_TEXT_YES,也就是说无论该品种是固定点差还是浮动点差,界面上都会显示「是」。如果你在 MT5 里看到点差类型永远为真,先别怀疑券商数据,大概率是抄这段代码时没改后半段。 SYMBOL_PROP_TICKS_BOOKDEPTH 和 SYMBOL_PROP_BOOKDEPTH_STATE 都用 #ifdef __MQL5__ 做了编译隔离:MQL5 下返回真实深度值或开关状态,MQL4 下直接给「不支持」文案。这意味着在 MT4 移植版里,订单簿深度相关属性永远不可见,做贵金属盘口分析时得自己补一层 MarketInfo 兼容。 外汇与贵金属杠杆品种的高风险在于:SPREAD 和 BOOKDEPTH 在极端行情可能瞬间失效,这类属性读取最好加超时与空值兜底,别直接信任 GetProperty 的瞬时返回。
class="kw">property==SYMBOL_PROP_DIGITS ? CMessage::Text(MSG_SYM_PROP_DIGITS)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+((class="type">class="kw">string)this.GetProperty(class="kw">property)) ) : class="kw">property==SYMBOL_PROP_DIGITS_LOTS ? CMessage::Text(MSG_SYM_PROP_DIGITS_LOTS)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+((class="type">class="kw">string)this.GetProperty(class="kw">property)) ) : class="kw">property==SYMBOL_PROP_SPREAD ? CMessage::Text(MSG_SYM_PROP_SPREAD)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+((class="type">class="kw">string)this.GetProperty(class="kw">property)) ) : class="kw">property==SYMBOL_PROP_SPREAD_FLOAT ? CMessage::Text(MSG_SYM_PROP_SPREAD_FLOAT)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(class="kw">property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_YES)) ) : class="kw">property==SYMBOL_PROP_TICKS_BOOKDEPTH ? CMessage::Text(MSG_SYM_PROP_TICKS_BOOKDEPTH)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : class="kw">property==SYMBOL_PROP_BOOKDEPTH_STATE ? CMessage::Text(MSG_SYM_SYMBOLS_MODE_BOOK)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) class="macro">#else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : class="kw">property==SYMBOL_PROP_TRADE_CALC_MODE ? CMessage::Text(MSG_SYM_PROP_TRADE_CALC_MODE)+
「逐属性拼装品种描述串的写法」
在 MT5 的品种封装类里,常用一段嵌套三元运算符把不同 SYMBOL_PROP_* 属性转成可读文本。每段都先判断 SupportProperty,不支持就接「不支持」提示,支持则调各自的 Description 或 GetProperty。 时间类属性(START_TIME / EXPIRATION_TIME)有个细节:GetProperty 返回 0 时打「(空)」,否则要乘 1000 再丢给 TimeMSCtoString,因为底层存的是秒、函数要吃毫秒。 止损冻结级别(TRADE_STOPS_LEVEL / TRADE_FREEZE_LEVEL)直接强转 string 输出整数点值;而交易模式、执行模式、掉期模式则走对应的 Get*Description 方法拿本地化文案。 下面这段节选覆盖了从计算模式到对冲保证金腿的连续分支,复制进 EA 的调试输出函数就能直接看某个品种的真实配置。外汇与贵金属杠杆高,这类属性在跨品种切换时可能突变,实盘前务必逐属性打印核对。
(!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetCalcModeDescription() ) : class="kw">property==SYMBOL_PROP_TRADE_MODE ? CMessage::Text(MSG_SYM_PROP_TRADE_MODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTradeModeDescription() ) : class="kw">property==SYMBOL_PROP_START_TIME ? CMessage::Text(MSG_SYM_PROP_START_TIME)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : (this.GetProperty(class="kw">property)==class="num">0 ? ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+TimeMSCtoString(this.GetProperty(class="kw">property)*class="num">1000)) ) : class="kw">property==SYMBOL_PROP_EXPIRATION_TIME ? CMessage::Text(MSG_SYM_PROP_EXPIRATION_TIME)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : (this.GetProperty(class="kw">property)==class="num">0 ? ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+TimeMSCtoString(this.GetProperty(class="kw">property)*class="num">1000)) ) : class="kw">property==SYMBOL_PROP_TRADE_STOPS_LEVEL ? CMessage::Text(MSG_SYM_PROP_TRADE_STOPS_LEVEL)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_TRADE_FREEZE_LEVEL ? CMessage::Text(MSG_SYM_PROP_TRADE_FREEZE_LEVEL)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_TRADE_EXEMODE ? CMessage::Text(MSG_SYM_PROP_TRADE_EXEMODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTradeExecDescription() ) : class="kw">property==SYMBOL_PROP_SWAP_MODE ? CMessage::Text(MSG_SYM_PROP_SWAP_MODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetSwapModeDescription() ) : class="kw">property==SYMBOL_PROP_SWAP_ROLLOVER3DAYS ? CMessage::Text(MSG_SYM_PROP_SWAP_ROLLOVER3DAYS)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+DayOfWeekDescription(this.SwapRollover3Days()) ) : class="kw">property==SYMBOL_PROP_MARGIN_HEDGED_USE_LEG ? CMessage::Text(MSG_SYM_PROP_MARGIN_HEDGED_USE_LEG)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
按属性类型拼装品种描述串
这段逻辑出现在品种信息类的属性描述方法里,核心是一个嵌套三元表达式:根据传入的 property 枚举值,挑出对应的中文标签,再判断当前品种是否支持该属性,最后拼上具体取值或「不支持」提示。 涉及 SYMBOL_PROP_EXPIRATION_MODE、SYMBOL_PROP_FILLING_MODE、SYMBOL_PROP_ORDER_MODE、SYMBOL_PROP_ORDER_GTC_MODE、SYMBOL_PROP_OPTION_MODE、SYMBOL_PROP_OPTION_RIGHT 等枚举时,分别调用 GetExpirationModeFlagsDescription、GetFillingModeFlagsDescription、GetOrderModeFlagsDescription、GetOrderGTCModeDescription、GetOptionTypeDescription、GetOptionRightDescription 来拿到标志位的人读文本。 背景色属性 SYMBOL_PROP_BACKGROUND_COLOR 在 MQL5 下用 ColorToString 转十六进制串,若取值为 CLR_DEFAULT 或 CLR_NONE 则输出「(空)」;MQL4 分支直接标为不支持。外汇与贵金属品种在 MT5 中多不支持 OPTION 类属性,跑这段代码时大概率走「不支持」分支,属正常现象。 调试时若发现某品种描述缺失,先确认 property 是否落在这串枚举覆盖范围内,否则末尾空串 "" 会静默返回。
": "+(this.GetProperty(class="kw">property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : class="kw">property==SYMBOL_PROP_EXPIRATION_MODE ? CMessage::Text(MSG_SYM_PROP_EXPIRATION_MODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetExpirationModeFlagsDescription() ) : class="kw">property==SYMBOL_PROP_FILLING_MODE ? CMessage::Text(MSG_SYM_PROP_FILLING_MODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetFillingModeFlagsDescription() ) : class="kw">property==SYMBOL_PROP_ORDER_MODE ? CMessage::Text(MSG_SYM_PROP_ORDER_MODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetOrderModeFlagsDescription() ) : class="kw">property==SYMBOL_PROP_ORDER_GTC_MODE ? CMessage::Text(MSG_SYM_PROP_ORDER_GTC_MODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetOrderGTCModeDescription() ) : class="kw">property==SYMBOL_PROP_OPTION_MODE ? CMessage::Text(MSG_SYM_PROP_OPTION_MODE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetOptionTypeDescription() ) : class="kw">property==SYMBOL_PROP_OPTION_RIGHT ? CMessage::Text(MSG_SYM_PROP_OPTION_RIGHT)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetOptionRightDescription() ) : class="kw">property==SYMBOL_PROP_BACKGROUND_COLOR ? CMessage::Text(MSG_SYM_PROP_BACKGROUND_COLOR)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : class="macro">#ifdef __MQL5__(this.GetProperty(class="kw">property)==CLR_DEFAULT || this.GetProperty(class="kw">property)==CLR_NONE ? ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+::ColorToString((class="type">color)this.GetProperty(class="kw">property),true)) class="macro">#else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) class="macro">#endif ) : "" ); }
◍ 深度簿订阅与符号刷新的底层实现
在 MT5 的自定义符号类里,市场深度(DOM)的订阅和释放被拆成了两个明确动作。BookOpen 通过预编译宏区分 MQL5 与 MQL4 环境:MQL5 下调用 MarketBookAdd(name) 尝试订阅,MQL4 直接返回 false,因为后者没有深度簿接口。 订阅成功会把 m_book_subscribed 置真,同时把 SYMBOL_PROP_BOOKDEPTH_STATE 属性同步,并打印一条带符号名的日志;失败则保持原状并返回 false。这一机制意味着你在 EA 初始化阶段若没拿到 true,后续读盘口就是空数据。 BookClose 则先判断订阅标志,未订阅直接返回 true 避免重复释放。MQL5 下用 MarketBookRelease(name) 退订,成功后将标志与状态属性一并清零。实测中,频繁开关图表却忘了调 BookClose,会在终端里堆积无主订阅,占用本地事件队列。 Refresh 是数据更新的总入口:先 RefreshRates 拉最新报价,MQL5 环境再补一次 MarginRates 并捕获错误码;最后重置事件标记与哈希和,重算整数属性。外汇与贵金属杠杆品种在此处若返回全局错误,往往指向品种停盘或经纪商报价权限受限,属高风险场景,需自行核对账户权限。
this.m_book_subscribed=(class="macro">#ifdef __MQL5__ ::MarketBookAdd(this.m_name) class="macro">#else false class="macro">#endif); if(this.m_book_subscribed) { this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]=this.m_book_subscribed; ::Print(CMessage::Text(MSG_SYM_SYMBOLS_BOOK_ADD)+" "+this.m_name); } class="kw">return this.m_book_subscribed; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Close the market depth | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSymbol::BookClose(class="type">void) { class=class="str">"cmt">//--- If the DOM subscription flag is off, subscription is disabled(or not enabled yet). Return &class="macro">#x27;true&class="macro">#x27; if(!this.m_book_subscribed) class="kw">return true; class=class="str">"cmt">//--- Save the result of unsubscribing from the DOM class="type">bool res=( class="macro">#ifdef __MQL5__ ::MarketBookRelease(this.m_name) class="macro">#else true class="macro">#endif ); class=class="str">"cmt">//--- If unsubscribed successfully, reset the DOM subscription flag and write the status to the object class="kw">property if(res) { this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]=this.m_book_subscribed=false; ::Print(CMessage::Text(MSG_SYM_SYMBOLS_BOOK_DEL)+" "+this.m_name); } class=class="str">"cmt">//--- Return the result of unsubscribing from DOM class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update all symbol data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSymbol::Refresh(class="type">void) { class=class="str">"cmt">//--- Update quote data if(!this.RefreshRates()) class="kw">return; class="macro">#ifdef __MQL5__ ::ResetLastError(); if(!this.MarginRates()) { this.m_global_error=::GetLastError(); class="kw">return; } class="macro">#endif class=class="str">"cmt">//--- Initialize event data this.m_is_event=false; this.m_hash_sum=class="num">0; class=class="str">"cmt">//--- Update integer properties this.m_long_prop[SYMBOL_PROP_SELECT] = ::SymbolInfoInteger(this.m_name,SYMBOL_SELECT);
「把交易品种整数属性一次性灌进结构」
在封装交易品种类时,用一组 SymbolInfoInteger 调用把 MT5 内核里的长整型属性批量读入本地数组,比每次临时查询更省开销。下面这段代码覆盖了从可见性、会话挂单到止损冻结位等 13 个关键字段。 SYMBOL_SPREAD 返回的是当前点差点数,例如欧美在亚盘可能只有 1~3 点,美盘开盘前后可能跳到 8~15 点,直接读这个字段就能在 EA 里动态判断手续费环境。SYMBOL_TRADE_STOPS_LEVEL 和 SYMBOL_TRADE_FREEZE_LEVEL 则是 broker 设定的止损最小距离与平仓冻结距离,碰贵金属 XAUUSD 时经常看到 stops level 为 0 但 freeze level 非零,下单前不校验就容易被拒单。 最后一行没有走 SymbolInfoInteger,而是调了自定义的 SymbolBackgroundColor(),说明背景色并非内核整数属性,需要类内部转换。这种混合写法提醒我们:批量初始化时得分清哪些来自 API、哪些来自对象自身计算,否则复盘代码会漏掉隐藏逻辑。
this.m_long_prop[SYMBOL_PROP_VISIBLE] = ::SymbolInfoInteger(this.m_name,SYMBOL_VISIBLE); this.m_long_prop[SYMBOL_PROP_SESSION_DEALS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_DEALS); this.m_long_prop[SYMBOL_PROP_SESSION_BUY_ORDERS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_BUY_ORDERS); this.m_long_prop[SYMBOL_PROP_SESSION_SELL_ORDERS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_SELL_ORDERS); this.m_long_prop[SYMBOL_PROP_VOLUMEHIGH] = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMEHIGH); this.m_long_prop[SYMBOL_PROP_VOLUMELOW] = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMELOW); this.m_long_prop[SYMBOL_PROP_SPREAD] = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD); this.m_long_prop[SYMBOL_PROP_TICKS_BOOKDEPTH] = ::SymbolInfoInteger(this.m_name,SYMBOL_TICKS_BOOKDEPTH); this.m_long_prop[SYMBOL_PROP_START_TIME] = ::SymbolInfoInteger(this.m_name,SYMBOL_START_TIME); this.m_long_prop[SYMBOL_PROP_EXPIRATION_TIME] = ::SymbolInfoInteger(this.m_name,SYMBOL_EXPIRATION_TIME); this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_STOPS_LEVEL); this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_FREEZE_LEVEL); this.m_long_prop[SYMBOL_PROP_BACKGROUND_COLOR] = this.SymbolBackgroundColor();
把实时交易属性刷进符号对象
在自定义品种封装里,深度簿订阅状态先由内部标记同步给属性数组:m_long_prop 里 SYMBOL_PROP_BOOKDEPTHSTATE 直接等于 m_book_subscribed,决定后续能否读到盘口快照。 紧接着是一批实时双精度属性的回填。下面这段用 SymbolInfoDouble 逐个拉取当前品种的真值,写进对应的索引槽位。 注意 SYMBOL_VOLUME_LIMIT 在多数外汇品种上返回 0,代表无单日总量限制;而 SYMBOL_TRADE_TICK_VALUE_PROFIT 与 _LOSS 在交叉盘可能不同,直接影响每点盈亏换算,贵金属尤其要核对。 这些属性每次刷新都依赖 this.m_name 指定的具体品种,若名称错拼,SymbolInfoDouble 会静默返回 0,导致仓位计算偏差。外汇与贵金属杠杆高,参数误读可能引发超额敞口。
this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE] = this.m_book_subscribed; class=class="str">"cmt">//--- Update real properties this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_LOSS); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_SIZE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_SIZE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_CONTRACT_SIZE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_CONTRACT_SIZE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MIN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MIN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MAX)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MAX); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_STEP)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_STEP); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_LIMIT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_LIMIT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_LONG)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_LONG); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SHORT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SHORT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_INITIAL)] = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_MAINTENANCE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_VOLUME);
◍ 把交易时段与成交量属性一次性灌进数组
在封装交易品种对象的初始化流程里,这一段负责把当前品种的会话与真实成交量类属性批量写进内部的 double 数组。注意它混用了两种取值方式:会话类的 10 个字段直接走 SymbolInfoDouble,而带 _REAL 后缀的成交量及债券类字段改调成员函数,说明后者在底层可能做了单位换算或异常兜底。 对外汇和贵金属交易者来说,SYMBOL_SESSION_TURNOVER 与 SYMBOL_SESSION_BUY_ORDERS_VOLUME / SELL_ORDERS_VOLUME 是观察主力时段多空挂单倾斜的原始数据,MT5 里若 broker 不提供则该字段返回 0。 下面这段是原文的核心赋值逻辑,逐行看就是:先算属性索引,再向对应数组下标塞值。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_TURNOVER)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_TURNOVER); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_INTEREST)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_INTEREST); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_OPEN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_OPEN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_CLOSE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_CLOSE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_AW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_AW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_SETTLEMENT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MIN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MAX); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_REAL)] = this.SymbolVolumeReal(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMEHIGH_REAL)] = this.SymbolVolumeHighReal(); 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();
「保证金率与事件快照的填充逻辑」
这段初始化代码把锁仓保证金、多空初始及维护保证金率逐一写进双精度属性数组,覆盖 Long/Short 及挂单类型的 Initial 与 Maintenance 共 16 个字段。 these 字段直接来自 m_margin_rate 结构体,意味着券商对 XAUUSD 这类贵金属的卖 Stop 维护保证金若设为 0.5,数组对应位就是 0.5,开 MT5 用 SymbolInfoDouble 核对能验证一致性。 随后两个 for 循环把整数与双精度属性当前值拷贝到 _event 数组的第 3 列,作为变更比对基线;最后调基类 Refresh 并跑 CheckEvents 捕捉差异。外汇与贵金属保证金参数随券商调整,属高风险变量,回测前务必确认实时值。
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; class=class="str">"cmt">//--- Fill in the symbol current data for(class="type">int i=class="num">0;i<SYMBOL_PROP_INTEGER_TOTAL;i++) this.m_long_prop_event[i][class="num">3]=this.m_long_prop[i]; for(class="type">int i=class="num">0;i<SYMBOL_PROP_DOUBLE_TOTAL;i++) this.m_double_prop_event[i][class="num">3]=this.m_double_prop[i]; class=class="str">"cmt">//--- Update the base object data and search for changes CBaseObjExt::Refresh(); this.CheckEvents(); }
在 EURUSD 上跑通 DOM 订阅与即时报价序列
把上一篇文章里的 EA 拷到 \MQL5\Experts\TestDoEasy\Part62\ 并改名 TestDoEasyPart62.mq5,测试时只勾 EURUSD 和 AUDUSD 两个品种。新增一个输入参数用来决定是否给 EA 操控品种订阅 DOM,这样能在不碰代码逻辑的前提下切换订阅开关。 在 OnInitDoEasy() 的初始化区域,给每个操作品种补一段订阅 DOM 的代码,同时把当前品种的全部属性打印到日志,用来确认新加的 DOM 订阅状态字段确实生效。编译后挂到 EURUSD 图表,预选上述两品种并开启订阅。 启动后日志会返回两条「DOM 订阅已激活」消息,随后列出 All EURUSD 属性,其中能看到一行 DOM 订阅状态的新属性。图表注释里则输出 AUDUSD 即时报价序列类 Refresh() 方法的字符串:新复制的即时报价数量、上次时间,以及列表里即时报价对象总数——这几个数字直接反映序列管理是否正常。 外汇与贵金属属高风险品种,DOM 数据订阅受经纪商深度限制,实测中部分品种可能不返回有效报价序列,需自行在 MT5 验证。
class=class="str">"cmt">//--- input variables input class="type">class="kw">ushort 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">150; class=class="str">"cmt">// StopLoss in points input class="type">uint InpTakeProfit = class="num">150; 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 InpDistancePReq = class="num">50; class=class="str">"cmt">// Distance for Pending Request&class="macro">#x27;s activate(points) input class="type">uint InpBarsDelayPReq = class="num">5; class=class="str">"cmt">// Bars delay for Pending Request&class="macro">#x27;s activate(current timeframe) input class="type">uint InpSlippage = class="num">5; class=class="str">"cmt">// Slippage in points input class="type">uint InpSpreadMultiplier = class="num">1; class=class="str">"cmt">// Spread multiplier for adjusting stop-orders by StopLevel input class="type">uchar InpTotalAttempts = class="num">5; class=class="str">"cmt">// Number of trading attempts sinput class="type">class="kw">double InpWithdrawal = class="num">10; class=class="str">"cmt">// Withdrawal funds(in tester) sinput class="type">uint InpButtShiftX = class="num">0; class=class="str">"cmt">// Buttons X shift sinput 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)
◍ 跟踪止损与多品种监控的输入参数拆解
EA 初始化阶段先读一批 input/sinput 参数,决定跟单逻辑跑在哪些品种和周期上。跟踪步长 InpTrailingStep 设为 20 点,启动偏移 InpTrailingStart 为 0,意味着价格一越过开仓价就允许推损;止损修改阈值 20 点、止盈修改阈值 60 点,构成每轮重算的边界条件。 品种范围由 InpModeUsedSymbols 控制,默认 SYMBOLS_MODE_CURRENT 只盯当前图,若切到 SYMBOLS_MODE_ALL 会触发 SymbolsTotal(false) 统计服务器全部符号,并在对话框提示“最大支持 SYMBOLS_COMMON_TOTAL 个”。实测 EURUSD 这类经纪商常挂 50~80 个符号,全量模式首次建集合可能卡数秒,建议先用逗号分隔的有限列表(如 EURUSD,AUDUSD,EURJPY)验证。 深度行情开关 InpUseBook 标黄,默认 INPUT_YES 会订阅 Order Book,对外汇高频重算有额外开销;周期列表 InpUsedTFs 覆盖 M1 到 MN1 九档,声音报警 InpUseSounds 默认开。把这些值抄进 MT5 输入栏,编译后从日志看集合准备耗时,就能判断该关掉全量模式还是照跑。
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) sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; class=class="str">"cmt">// Mode of used symbols list sinput 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) sinput ENUM_INPUT_YES_NO InpUseBook = INPUT_YES; class=class="str">"cmt">// Use Depth of Market sinput ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_LIST; class=class="str">"cmt">// Mode of used timeframes list sinput class="type">class="kw">string InpUsedTFs = "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; class=class="str">"cmt">// List of used timeframes(comma - separator) sinput class="type">bool InpUseSounds = true; class=class="str">"cmt">// Use sounds class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initializing DoEasy library | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnInitDoEasy() { class=class="str">"cmt">//--- Check if working with the full list is selected used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { class="type">int total=SymbolsTotal(false); class="type">class="kw">string ru_n="\nКоличество символов на сервере "+(class="type">class="kw">string)total+".\nМаксимальное количество: "+(class="type">class="kw">string)SYMBOLS_COMMON_TOTAL+" символов."; class="type">class="kw">string en_n="\nNumber of symbols on server "+(class="type">class="kw">string)total+".\nMaximum number: "+(class="type">class="kw">string)SYMBOLS_COMMON_TOTAL+" symbols."; class="type">class="kw">string caption=TextByLanguage("Внимание!","Attention!"); class="type">class="kw">string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списков коллекций символов и таймсерий может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; class="type">class="kw">string en="Full list mode selected.\nIn this mode, the initial preparation of lists of symbol collections and timeseries can take a class="type">long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
「初始化时弹窗确认与多品种时序装载」
在 EA 初始化阶段,先用 MessageBox 弹窗让用户决定符号作用范围:选 NO 就只盯当前图表品种(used_symbols_mode=SYMBOLS_MODE_CURRENT),选 YES 或其他则按预设列表跑全品种。flags 里挂了 MB_ICONWARNING 和 MB_DEFBUTTON2,默认高亮第二个按钮,降低误触概率。 弹窗过后立即调用 GetTickCount() 记下起点,打印库初始化开始标记,随后 CreateUsedSymbolsArray 按模式填充品种数组,再交给 engine.SetUsedSymbols 建集合。日志会输出实际生效的品种数或当前品种名,MQL5 下还会用 ArrayPrint 把非当前模式的品种清单直接打印出来,方便你核对有没有漏掉目标品种。 时段(timeframe)部分同理:CreateUsedTimeframesArray 读入 InpModeUsedTFs 与 InpUsedTFs,决定只跑当前周期、指定列表还是全周期。Print 输出的 mode 字符串会明确告知当前是单周期还是多周期跑法——这一行是你贴进 MT5 后第一眼该看的诊断信息。 外汇与贵金属波动剧烈、杠杆风险高,多品种多周期扫描会放大滑点与重算开销,上实盘前务必在策略测试器用历史数据验证初始化耗时与品种覆盖是否符合预期。
class="type">class="kw">string message=TextByLanguage(ru,en); class="type">int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); class="type">int mb_res=MessageBox(message,caption,flags); class="kw">switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; break; class="kw">default: break; } class=class="str">"cmt">//--- Set the counter start point to measure the approximate library initialization time class="type">ulong begin=GetTickCount(); Print(TextByLanguage("--- Инициализация библиотеки \"DoEasy\" ---","--- Initializing the \"DoEasy\" library ---")); class=class="str">"cmt">//--- Fill in the array of used symbols CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,InpUsedSymbols,array_used_symbols); class=class="str">"cmt">//--- Set the type of the used symbol list in the symbol collection and fill in the list of symbol timeseries engine.SetUsedSymbols(array_used_symbols); class=class="str">"cmt">//--- Displaying the selected mode of working with the symbol object collection in the journal class="type">class="kw">string num= ( used_symbols_mode==SYMBOLS_MODE_CURRENT ? ": \""+Symbol()+"\"" : TextByLanguage(". Количество используемых символов: ",". The number of symbols used: ")+(class="type">class="kw">string)engine.GetSymbolsCollectionTotal() ); Print(engine.ModeSymbolsListDescription(),num); class=class="str">"cmt">//--- Implement displaying the list of used symbols only for MQL5 - MQL4 has no ArrayPrint() function class="macro">#ifdef __MQL5__ if(InpModeUsedSymbols!=SYMBOLS_MODE_CURRENT) { class="type">class="kw">string array_symbols[]; CArrayObj* list_symbols=engine.GetListAllUsedSymbols(); for(class="type">int i=class="num">0;i<list_symbols.Total();i++) { CSymbol *symbol=list_symbols.At(i); if(symbol==NULL) class="kw">continue; ArrayResize(array_symbols,ArraySize(array_symbols)+class="num">1,SYMBOLS_COMMON_TOTAL); array_symbols[ArraySize(array_symbols)-class="num">1]=symbol.Name(); } ArrayPrint(array_symbols); } class="macro">#endif class=class="str">"cmt">//--- Set used timeframes CreateUsedTimeframesArray(InpModeUsedTFs,InpUsedTFs,array_used_periods); class=class="str">"cmt">//--- Display the selected mode of working with the timeseries object collection class="type">class="kw">string mode= ( InpModeUsedTFs==TIMEFRAMES_MODE_CURRENT ? TextByLanguage("Работа только с текущим таймфреймом: ","Work only with the current Period: ")+TimeframeDescription((ENUM_TIMEFRAMES)Period()) : InpModeUsedTFs==TIMEFRAMES_MODE_LIST ? TextByLanguage("Работа с заданным списком таймфреймов:","Work with a predefined list of Periods:") : TextByLanguage("Работа с полным списком таймфреймов:","Work with the full list of all Periods:") ); Print(mode);
初始化阶段的多品种资源与交易参数装配
EA 在 OnInit 尾部会一次性把多周期、tick 序列和资源文件全部建好。MQL5 下可用 ArrayPrint 把已用周期数组打印到日志,而 MQL4 因为没有该函数需用 #ifdef __MQL5__ 隔离,否则编译直接报错。 SeriesCreateAll 按 array_used_periods 里的周期批量建时序,随后 GetTimeSeriesCollection().PrintShort(false) 会把已声明和已创建的序列描述都打到日志,方便你确认 EURUSD、XAUUSD 等品种是否都拿到了 M1~H1 的数据句柄。 声音与图像资源通过 CreateFile 以 WAV/BMP 类型写入:sound_array_coin_01 到 cash_machine_01 共 9 个音频、img_array_spot_green 与 img_array_spot_red 两个指示灯位图,均用 TextByLanguage 做俄/英双语标签,MT5 终端里能直接看到资源名。 交易侧在 CollectionOnInit 后设定魔术码、同步下单模式(TradingSetAsyncMode(false))、错误重试次数(取 InpTotalAttempts)以及正确的过期与填充类型。注意外汇和贵金属杠杆高,同步下单虽降低竞态但遇报价跳空仍可能滑点扩大。 最后循环 GetListAllUsedSymbols 返回的指针列表,对每品种设受控属性;未显式赋值的项默认 LONG_MAX,代表“不跟踪该属性”,你可在循环体内加一行 Print 确认具体品种数。
class=class="str">"cmt">//--- Implement displaying the list of used timeframes only for MQL5 - MQL4 has no ArrayPrint() function class="macro">#ifdef __MQL5__ if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT) ArrayPrint(array_used_periods); class="macro">#endif class=class="str">"cmt">//--- Create timeseries of all used symbols engine.SeriesCreateAll(array_used_periods); class=class="str">"cmt">//--- Check created timeseries - display descriptions of all created timeseries in the journal class=class="str">"cmt">//--- (true - only created ones, false - created and declared ones) engine.GetTimeSeriesCollection().PrintShort(false); class=class="str">"cmt">// Short descriptions class=class="str">"cmt">//engine.GetTimeSeriesCollection().Print(true); // Full descriptions class=class="str">"cmt">//--- Create tick series of all used symbols engine.GetTickSeriesCollection().CreateTickSeriesAll(); class=class="str">"cmt">//--- Check created tick series - display descriptions of all created tick series in the journal engine.GetTickSeriesCollection().Print(); class=class="str">"cmt">//--- Create resource text files engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки class="num">1","Falling coin class="num">1"),sound_array_coin_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки class="num">2","Falling coin class="num">2"),sound_array_coin_04); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке class="num">1","Button click class="num">1"),sound_array_click_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке class="num">2","Button click class="num">2"),sound_array_click_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке class="num">3","Button click class="num">3"),sound_array_click_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red); class=class="str">"cmt">//--- Pass all existing collections to the main library class engine.CollectionOnInit(); class=class="str">"cmt">//--- Set the class="kw">default magic number for all used symbols engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number)); class=class="str">"cmt">//--- Set synchronous passing of orders for all used symbols engine.TradingSetAsyncMode(false); class=class="str">"cmt">//--- Set the number of trading attempts in case of an error engine.TradingSetTotalTry(InpTotalAttempts); class=class="str">"cmt">//--- Set correct order expiration and filling types to all trading objects engine.TradingSetCorrectTypeExpiration(); engine.TradingSetCorrectTypeFilling(); class=class="str">"cmt">//--- Set standard sounds for trading objects of all used symbols engine.SetSoundsStandart(); class=class="str">"cmt">//--- Set the general flag of using sounds engine.SetUseSounds(InpUseSounds); class=class="str">"cmt">//--- Set the spread multiplier for symbol trading objects in the symbol collection engine.SetSpreadMultiplier(InpSpreadMultiplier); class=class="str">"cmt">//--- Set controlled values for symbols class=class="str">"cmt">//--- Get the list of all collection symbols CArrayObj *list=engine.GetListAllUsedSymbols(); if(list!=NULL && list.Total()!=class="num">0) { class=class="str">"cmt">//--- In a loop by the list, set the necessary values for tracked symbol properties class=class="str">"cmt">//--- By class="kw">default, the LONG_MAX value is set to all properties, which means "Do not track this class="kw">property"
◍ 在循环里给多品种挂上订单簿与账户阈值监控
这段逻辑展示了一个运行时可随时切换的监控装配方式:遍历符号列表,对每个实例决定是否订阅深度簿、是否打印当前品种信息,而不必在初始化阶段一次性写死。 [CODE] //--- 可在程序任意位置开启或关闭(设值小于LONG_MAX即关,设回LONG_MAX即开) for(int i=0;i<list.Total();i++) { CSymbol* symbol=list.At(i); // 取列表中第i个符号对象指针 if(symbol==NULL) // 指针为空则跳过 continue; if(InpUseBook) // 若允许使用订单簿 symbol.BookAdd(); // 为该符号订阅深度市场数据 if(symbol.Name()==Symbol()) // 若遍历到当前图表品种 symbol.Print(); // 打印该符号信息 /* 注释掉的示例:对Bid涨跌、点差涨跌及点差水平设100点/40点控制,此处未启用 */ } [/CODE] 账户层监控紧随其后:取当前账户对象后,对利润增额设 10.0、净值增额设 15.0,并把利润水平线卡在 20.0。这些数值都可在实盘前改小做敏感测试,外汇与贵金属杠杆品种波动剧烈,阈值过宽可能漏掉风控信号。 日志里能看到落地结果:AUDUSD 与 EURUSD 的 H1 时序各请求 1000 根、实际建出 1000 根,服务器上分别有 6325 和 5806 根;当日 tick 历史 AUDUSD 建了 183398 条、EURUSD 建了 148089 条,随后两个品种都打出了"Subscribed to Depth of Market",说明订单簿订阅确实生效。 开 MT5 把 InpUseBook 先置 false 跑一遍,再置 true 对比日志中是否出现订阅行,就能确认你那边的环境是否放行了 DOM 数据。
class=class="str">"cmt">//--- It can be enabled or disabled(by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program for(class="type">int i=class="num">0;i<list.Total();i++) { CSymbol* symbol=list.At(i); if(symbol==NULL) class="kw">continue; if(InpUseBook) symbol.BookAdd(); if(symbol.Name()==Symbol()) symbol.Print(); class=class="str">"cmt">/* class=class="str">"cmt">//--- Set control of the symbol price increase by class="num">100 points symbol.SetControlBidInc(class="num">100000*symbol.Point()); class=class="str">"cmt">//--- Set control of the symbol price decrease by class="num">100 points symbol.SetControlBidDec(class="num">100000*symbol.Point()); class=class="str">"cmt">//--- Set control of the symbol spread increase by class="num">40 points symbol.SetControlSpreadInc(class="num">400); class=class="str">"cmt">//--- Set control of the symbol spread decrease by class="num">40 points symbol.SetControlSpreadDec(class="num">400); class=class="str">"cmt">//--- Set control of the current spread by the value of class="num">40 points symbol.SetControlSpreadLevel(class="num">400); */ } class=class="str">"cmt">//--- Set controlled values for the current account CAccount* account=engine.GetAccountCurrent(); if(account!=NULL) { class=class="str">"cmt">//--- Set control of the profit increase to class="num">10 account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,class="num">10.0); class=class="str">"cmt">//--- Set control of the funds increase to class="num">15 account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,class="num">15.0); class=class="str">"cmt">//--- Set profit control level to class="num">20 account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,class="num">20.0); } class=class="str">"cmt">//--- Get the end of the library initialization time counting and display it in the journal class="type">ulong end=GetTickCount(); Print(TextByLanguage("Время инициализации библиотеки: ","Library initialization time: "),TimeMSCtoString(end-begin,TIME_MINUTES|TIME_SECONDS)); }
「扒开 EURUSD 的品种参数底牌」
在 MT5 里用 SymbolInfoXXX 系列函数,或直接在终端市场报价窗口看 EURUSD 的属性,能挖出一堆决定下单逻辑的参数。这个主流外汇品种在报价窗口的索引是 2,基于 Bid 价生成棒线,点值为 0.00001,报价小数点后保留 5 位,当前浮动点差 2 点。 合约规模固定为 100000 单位,最小开仓手数 0.01、最大 500,步长也是 0.01;执行方式是即时成交,允许市价、限价、止损、止损限价及止盈止损挂单,没有交易限制。 持仓成本这块,周三 triple-day 收 swap,多单隔夜费 -0.70 点、空单 -1.00 点,按点计收。截到的快照里 Bid 1.21665、Ask 1.21667,当日 Bid 高低区间 1.21078–1.21760,会话内成交量和真实成交量均为 0.00,说明那是段休市或零成交的静默期。 外汇和贵金属自带高杠杆与跳空风险,参数只是下单前的地基;真要验证,开 MT5 把同样品种的 DOM 订阅打开(本例已订阅,深度显示上限 10 档),比对下你自己账号下的点差和 swap 是否随经纪商浮动。
从会话属性读出 EURUSD 的合约与报价基底
调用 SymbolInfoSession 系列函数与 SYMBOL 宏后,EURUSD 的会话快照能直接暴露合约尺度与币种结构。某次输出中,开 session 开盘价 1.21371、收盘 1.21413,两者仅差 4.2 点,说明该采样会话波动极窄,适合拿来做微结构验证。 合约层面,每手对冲头寸的合约/保证金规模为 100000.00,即标准一手等于 10 万欧元名义本金;基础币 EUR、盈利币 USD、保证金币 EUR,这种不一致意味着跨币折算会在净值上引入二次汇率风险。外汇与贵金属杠杆品类风险高,参数仅是事实,不预示方向。 会话属性里加权平均价、结算价、最小最大允许价均返回 0.00000,代表当前账户环境未推送这些字段——开 MT5 按 F4 编译下段代码,把 SYMBOL 信息打印出来,比对你的券商是否同样留空。
Open price of the session: class="num">1.21371 Close price of the session: class="num">1.21413 Average weighted price of the session: class="num">0.00000 Settlement price of the current session: class="num">0.00000 Minimum allowable price value for the session: class="num">0.00000 Maximum allowable price value for the session: class="num">0.00000 Size of a contract or margin for one lot of hedged positions: class="num">100000.00 ------ Symbol name: EURUSD Name of the underlaying asset for a derivative symbol: (No) Instrument base currency: "EUR" Profit currency: "USD" Margin funds currency: "EUR" Source of the current quote: (No) Symbol description: "Euro vs US Dollar" Symbol name in ISIN system: (No) Address of the web page containing symbol information: "http:class=class="str">"cmt">//www.google.com/finance?q=EURUSD" Location in the symbol tree: "Forex\EURUSD" Name of the category or sector the symbol belongs to: (No) Name of the exchange in which the security is traded: (No) ================== End of parameter list: "EURUSD" (Euro vs US Dollar) ================== 库初始化时间:class="num">00:class="num">00:class="num">09.953
◍ 把工具请下神坛
这一轮时间序列与价格对象的封装到此收口,当前函数库压缩包 MQL5.zip 体积 3881.55 KB,内含全部已发布的品种周期序列、即时报价集合,以及可供直接加载的测试 EA。 下一步要碰的是市场深度(DOM)操作,作者计划先写抽象请求类,再派生具体订单对象——这部分代码若落地,MT5 用户在盘口博弈贵金属或外汇时,能直接借函数库读薄薄一层挂单厚度。 外汇与贵金属自带高杠杆和高波动,任何函数库都只是把重复劳动自动化,别把它当成预测神器;下载 ZIP 丢进 MT5 的 MQL5 目录,跑一遍测试 EA,比看十篇综述都实在。