DoEasy 函数库中的其他类(第六十六部分):MQL5.com 信号集合类·进阶篇
(2/3)· 手动翻信号库挑订阅目标太慢?用集合类一次拉全、按盈利排序、顺手算清 DOM 买卖量
「把日志与盘口快照塞进类里」
做 MT5 工具时,统一消息出口能少踩很多坑。下面这段把日志打印收口到 CMessage::ToLog,两个重载分别处理「只给消息 ID」和「带来源标识」的场景,避免满屏 Print 分不清是谁报的错。 静态方法 GetError 直接返回 CMessage::m_global_error,相当于给全局错误码留了个只读窗口,调试 EA 初始化失败时用得上。注意 ERR_USER_ERROR_FIRST 这个边界:消息 ID 大于它减 1 时才不附加 Retcode 文本,否则会在日志里拼上错误描述。 盘口快照类 CMBookSnapshot 从 CBaseObj 派生,私有成员把一次 DOM 抓取的关键字段全收了:m_symbol、m_time、m_digits 是上下文,m_volume_buy / m_volume_sell 是整型买卖量,而 m_volume_buy_real / m_volume_sell_real 用 double 存高精度版本——实际跑起来,前者在常规品种上和 real 版可能一致,遇到小数手数经纪商时 real 才会拉开差距。
class="kw">static class="type">bool ToFTP(const class="type">class="kw">string filename,const class="type">class="kw">string ftp_path=NULL); class="kw">static class="type">int GetError(class="type">void) { class="kw">return CMessage::m_global_error; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display a message in the journal by a message ID | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMessage::ToLog(const class="type">int msg_id,const class="type">bool code=false) { CMessage::GetTextByID(msg_id); ::Print(m_text,(!code || msg_id>ERR_USER_ERROR_FIRST-class="num">1 ? "" : " "+CMessage::Retcode(msg_id))); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display a message in the journal by a message ID | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMessage::ToLog(const class="type">class="kw">string source,const class="type">int msg_id,const class="type">bool code=false) { CMessage::GetTextByID(msg_id); ::Print)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "DOM snapshot" class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CMBookSnapshot : class="kw">public CBaseObj { class="kw">private: class="type">class="kw">string m_symbol; class=class="str">"cmt">// Symbol class="type">long m_time; class=class="str">"cmt">// Snapshot time class="type">int m_digits; class=class="str">"cmt">// Symbol&class="macro">#x27;s Digits class="type">long m_volume_buy; class=class="str">"cmt">// DOM buy volume class="type">long m_volume_sell; class=class="str">"cmt">// DOM sell volume class="type">class="kw">double m_volume_buy_real; class=class="str">"cmt">// DOM buy volume with an increased accuracy class="type">class="kw">double m_volume_sell_real; class=class="str">"cmt">// DOM sell volume with an increased accuracy CArrayObj m_list; class=class="str">"cmt">// List of DOM order objects class="kw">public:
static bool ToFTP(...):静态方法,把文件推到 FTP,路径可缺省为 NULL。static int GetError(void):返回类内全局错误变量,零成本查错。void CMessage::ToLog(const int msg_id,const bool code=false):按 ID 取文本后 Print;code 为 true 且 ID 在用户错误区之前才追加 Retcode。void CMessage::ToLog(const string source,...):多接一个 source 参数,打印时前缀来源,便于多模块共用日志。class CMBookSnapshot : public CBaseObj:盘口快照类,私有段把符号、时间、精度、买卖量(整型与 double 高精度)、订单对象列表一次性封住。
开 MT5 建个 EA 把 CMBookSnapshot 接上 MarketBookGet,抓几次 XAUUSD 的 DOM,对比 m_volume_buy 与 m_volume_buy_real 在 0.01 手步进下是否出现小数差异,外汇与贵金属杠杆高,盘口结构随时变,验证时请控仓。
class="kw">static class="type">bool ToFTP(const class="type">class="kw">string filename,const class="type">class="kw">string ftp_path=NULL); class="kw">static class="type">int GetError(class="type">void) { class="kw">return CMessage::m_global_error; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display a message in the journal by a message ID | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMessage::ToLog(const class="type">int msg_id,const class="type">bool code=false) { CMessage::GetTextByID(msg_id); ::Print(m_text,(!code || msg_id>ERR_USER_ERROR_FIRST-class="num">1 ? "" : " "+CMessage::Retcode(msg_id))); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display a message in the journal by a message ID | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMessage::ToLog(const class="type">class="kw">string source,const class="type">int msg_id,const class="type">bool code=false) { CMessage::GetTextByID(msg_id); ::Print)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "DOM snapshot" class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CMBookSnapshot : class="kw">public CBaseObj { class="kw">private: class="type">class="kw">string m_symbol; class=class="str">"cmt">// Symbol class="type">long m_time; class=class="str">"cmt">// Snapshot time class="type">int m_digits; class=class="str">"cmt">// Symbol&class="macro">#x27;s Digits class="type">long m_volume_buy; class=class="str">"cmt">// DOM buy volume class="type">long m_volume_sell; class=class="str">"cmt">// DOM sell volume class="type">class="kw">double m_volume_buy_real; class=class="str">"cmt">// DOM buy volume with an increased accuracy class="type">class="kw">double m_volume_sell_real; class=class="str">"cmt">// DOM sell volume with an increased accuracy CArrayObj m_list; class=class="str">"cmt">// List of DOM order objects class="kw">public:
◍ DOM 快照对象的属性读取接口
在 MT5 里做深度盘口(DOM)分析,先得把某一时刻的盘口快照封装成对象。下面这组方法提供了对快照基础属性的简化访问:设置品种、快照时间,以及把时间批量写入各挂单。 读取接口里最常用的是买卖体量。VolumeBuy / VolumeSell 返回 long 类型的整手量,VolumeBuyReal / VolumeSellReal 返回 double 类型的真实精度体量——后者对黄金这类小数手数品种更有参考价值。 另外 VolumeBuyDescription / VolumeSellDescription 两个方法返回买卖量的文本描述,方便直接塞进面板或日志。外汇与贵金属 DOM 数据跳动极快,用这类封装读取时需注意高风险,快照只代表某一毫秒的可能状态。 代码逐行拆解: // 设置品种:若传入空或 NULL 则默认用当前图表品种
| void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL | symbol=="" ? ::Symbol() : symbol); } |
|---|
// 设置快照时间(毫秒) void SetTime(const long time_msc) { this.m_time=time_msc; } // 将时间批量写入所有 DOM 挂单(定义见别处) void SetTimeToOrders(const long time_msc); // 取品种名 string Symbol(void) const { return this.m_symbol; } // 取品种小数点位数 int Digits(void) const { return this.m_digits; } // 取快照时间 long Time(void) const { return this.m_time; } // 买盘整手量 long VolumeBuy(void) const { return this.m_volume_buy; } // 卖盘整手量 long VolumeSell(void) const { return this.m_volume_sell; } // 买盘真实精度量 double VolumeBuyReal(void) const { return this.m_volume_buy_real; } // 卖盘真实精度量 double VolumeSellReal(void) const { return this.m_volume_sell_real; } // 买量描述文本(定义见别处) string VolumeBuyDescription(void); // 卖量描述文本(定义见别处) string VolumeSellDescription(void);
class="type">void SetSymbol(const class="type">class="kw">string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } class="type">void SetTime(const class="type">long time_msc) { this.m_time=time_msc; } class="type">void SetTimeToOrders(const class="type">long time_msc); class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.m_symbol; } class="type">int Digits(class="type">void) const { class="kw">return this.m_digits; } class="type">long Time(class="type">void) const { class="kw">return this.m_time; } class="type">long VolumeBuy(class="type">void) const { class="kw">return this.m_volume_buy; } class="type">long VolumeSell(class="type">void) const { class="kw">return this.m_volume_sell; } class="type">class="kw">double VolumeBuyReal(class="type">void) const { class="kw">return this.m_volume_buy_real; } class="type">class="kw">double VolumeSellReal(class="type">void) const { class="kw">return this.m_volume_sell_real; } class="type">class="kw">string VolumeBuyDescription(class="type">void); class="type">class="kw">string VolumeSellDescription(class="type">void);
DOM 快照里四类挂单的归类与累计
在自定义 DOM 类里,每次拿到一份深度快照,第一步是清掉旧列表并把买卖总量归零:m_volume_buy、m_volume_sell 以及带小数精度的 _real 版本都置为 0,避免上一份快照的累计值污染当前帧。 随后用 ArraySize(book_array) 拿到本帧订单总数,逐个判断 book_array[i].type。限价买、限价卖、市价买、市价卖分别对应 CMarketBookBuy / CMarketBookSell / CMarketBookBuyMarket / CMarketBookSellMarket 四个子类对象;类型不在四类中则直接跳过,不进列表。 每个订单对象写入前先打上快照时间 m_time,再按价格做 InsertSort 插入排序。插不进列表的内存对象立刻 delete,防止泄漏;插成功的,按类型把 Volume() 和 VolumeReal() 累加进对应的总量字段。 这样一轮跑完,m_volume_buy 与 m_volume_sell 就是当前帧限价单的累计手数,_real 后缀是真实成交量精度版本。外汇和贵金属属高风险品种,DOM 数据仅反映当时盘口,后续失衡可能快速反转,不能直接当作方向确认。
class=class="str">"cmt">//--- Set a symbol this.SetSymbol(symbol); class=class="str">"cmt">//--- Clear the list this.m_list.Clear(); class=class="str">"cmt">//--- In the loop by the structure array class="type">int total=::ArraySize(book_array); this.m_volume_buy=this.m_volume_sell=class="num">0; this.m_volume_buy_real=this.m_volume_sell_real=class="num">0; for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- Create order objects of the current DOM snapshot depending on the order type CMarketBookOrd *mbook_ord=NULL; class="kw">switch(book_array[i].type) { case BOOK_TYPE_BUY : mbook_ord=new CMarketBookBuy(this.m_symbol,book_array[i]); class="kw">break; case BOOK_TYPE_SELL : mbook_ord=new CMarketBookSell(this.m_symbol,book_array[i]); class="kw">break; case BOOK_TYPE_BUY_MARKET : mbook_ord=new CMarketBookBuyMarket(this.m_symbol,book_array[i]); class="kw">break; case BOOK_TYPE_SELL_MARKET : mbook_ord=new CMarketBookSellMarket(this.m_symbol,book_array[i]); class="kw">break; class="kw">default: class="kw">break; } if(mbook_ord==NULL) class="kw">continue; class=class="str">"cmt">//--- Set the DOM snapshot time for the order mbook_ord.SetTime(this.m_time); class=class="str">"cmt">//--- Set the sorted list flag for the list(by the price value) and add the current order object to it class=class="str">"cmt">//--- If failed to add the object to the DOM order list, remove the order object this.m_list.Sort(SORT_BY_MBOOK_ORD_PRICE); if(!this.m_list.InsertSort(mbook_ord)) class="kw">delete mbook_ord; class=class="str">"cmt">//--- If the order object is successfully added to the DOM order list, supplement the total snapshot volumes else { class="kw">switch(mbook_ord.TypeOrd()) { case BOOK_TYPE_BUY : this.m_volume_buy+=mbook_ord.Volume(); this.m_volume_buy_real+=mbook_ord.VolumeReal(); class="kw">break; case BOOK_TYPE_SELL : this.m_volume_sell+=mbook_ord.Volume(); this.m_volume_sell_real+=mbook_ord.VolumeReal(); class="kw">break; case BOOK_TYPE_BUY_MARKET :
「买卖盘口快照的日志打印实现」
在深度快照类里,PrintShort 用一行日志把当前买盘与卖盘体量甩进 MT5 终端:若真实成交量 VolumeBuyReal() 大于 0 就取两位小数输出,否则退回整型 VolumeBuy()。 Print 方法则更重一些——先拼出买卖体量描述,再按价格对挂单链表做 SORT_BY_MBOOK_ORD_PRICE 排序,从尾到头逐条打印每个价位挂单的 Header()。 从代码可见,买盘体量在 BOOK_TYPE_BUY_MARKET 与 BOOK_TYPE_SELL_MARKET 两个分支里都累加到 m_volume_buy,说明市价卖单也视作主动买入压力,这是读盘口时要留意的口径。 开 MT5 把这段挂到 EA 里,跑黄金 XAUUSD 的盘口回调,能在日志里直接看到某一毫秒的买卖体量差,外汇与贵金属杠杆高,盘口瞬变风险大,数据仅作概率参考。
class="type">void CMBookSnapshot::PrintShort(class="type">void) { class="type">class="kw">string vol_buy="Buy vol: "+(this.VolumeBuyReal()>class="num">0 ? ::DoubleToString(this.VolumeBuyReal(),class="num">2) : (class="type">class="kw">string)this.VolumeBuy()); class="type">class="kw">string vol_sell="Sell vol: "+(this.VolumeSellReal()>class="num">0 ? ::DoubleToString(this.VolumeSellReal(),class="num">2) : (class="type">class="kw">string)this.VolumeSell()); ::Print(this.Header()," ",vol_buy,", ",vol_sell," ("+TimeMSCtoString(this.m_time),")"); } class="type">void CMBookSnapshot::Print(class="type">void) { class="type">class="kw">string vol_buy=CMessage::Text(MSG_MBOOK_SNAP_VOLUME_BUY)+": "+(this.VolumeBuyReal()>class="num">0 ? ::DoubleToString(this.VolumeBuyReal(),class="num">2) : (class="type">class="kw">string)this.VolumeBuy()); class="type">class="kw">string vol_sell=CMessage::Text(MSG_MBOOK_SNAP_VOLUME_SELL)+": "+(this.VolumeSellReal()>class="num">0 ? ::DoubleToString(this.VolumeSellReal(),class="num">2) : (class="type">class="kw">string)this.VolumeSell()); ::Print(this.Header(),": ",vol_buy,", ",vol_sell," ("+TimeMSCtoString(this.m_time),"):"); this.m_list.Sort(SORT_BY_MBOOK_ORD_PRICE); for(class="type">int i=this.m_list.Total()-class="num">1;i>WRONG_VALUE;i--) { CMarketBookOrd *ord=this.m_list.At(i); if(ord==NULL) class="kw">continue; ::Print("- ",ord.Header()); } }
◍ DOM 快照里的买卖量描述与列表筛选
在深度行情(DOM)快照类里,买量和卖量的文本输出走的是两套分支:若实时买量 VolumeBuyReal() 大于 0,就格式化为两位小数返回;否则直接把整型 VolumeBuy() 强转成字符串。卖量 VolumeSellDescription() 逻辑完全对称,只是换成了 VolumeSellReal() 与 VolumeSell()。 CMBookSeriesCollection 对外暴露了 GetObject() 和 GetList() 两个入口,前者回传自身指针,后者回传内部 m_list 对象数组指针。基于 m_list,又重载了三组 GetList(),分别按整数、实数、字符串属性配合比较模式(默认 EQUAL)做筛选,底层统一调 CSelect::ByMBookProperty()。 实际排查盘口异常时,可直接用 DataTotal() 拿当前列表里的 DOM 系列数量,再用带属性的 GetList() 抓出满足条件的子集,最后调 Print() 把属性打到终端日志。外汇与贵金属 DOM 受流动性影响大,盘口数据瞬变,验证时务必在 MT5 真实 tick 环境跑,高风险品种尤需谨慎。
class="type">class="kw">string CMBookSnapshot::VolumeBuyDescription(class="type">void) { class="kw">return(CMessage::Text(MSG_MBOOK_SNAP_VOLUME_BUY)+": "+(this.VolumeBuyReal()>class="num">0 ? ::DoubleToString(this.VolumeBuyReal(),class="num">2) : (class="type">class="kw">string)this.VolumeBuy())); } class="type">class="kw">string CMBookSnapshot::VolumeSellDescription(class="type">void) { class="kw">return(CMessage::Text(MSG_MBOOK_SNAP_VOLUME_SELL)+": "+(this.VolumeSellReal()>class="num">0 ? ::DoubleToString(this.VolumeSellReal(),class="num">2) : (class="type">class="kw">string)this.VolumeSell())); } class="kw">public: CMBookSeriesCollection *GetObject(class="type">void) { class="kw">return &this; } CArrayObj *GetList(class="type">void) { class="kw">return &this.m_list; } class=class="str">"cmt">//--- Return the list by selected(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting the compared criterion CArrayObj *GetList(ENUM_MBOOK_ORD_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode=EQUAL) { class="kw">return CSelect::ByMBookProperty(this.GetList(),class="kw">property,value,mode); } CArrayObj *GetList(ENUM_MBOOK_ORD_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode=EQUAL) { class="kw">return CSelect::ByMBookProperty(this.GetList(),class="kw">property,value,mode); } CArrayObj *GetList(ENUM_MBOOK_ORD_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode=EQUAL) { class="kw">return CSelect::ByMBookProperty(this.GetList(),class="kw">property,value,mode); } class="type">int DataTotal(class="type">void) const { class="kw">return this.m_list.Total(); } class="type">void Print(const class="type">bool full_prop=false);
正文
<span style="background-color:rgb(255, 242, 153);"><span class="keyword">virtual</span> <span class="keyword">void</span> PrintShort(<span class="keyword">const</span> <span class="keyword">bool</span> dash=<span class="macro">false</span>);</span> <span class="comment">//--- Return the object short name</span> <span class="keyword">virtual</span> <span class="keyword">string</span> Header(<span class="keyword">const</span> <span class="keyword">bool</span> shrt=<span class="macro">false</span>); <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | Display a short description of the object in the journal | </span> |
|---|
<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">void</span> CMQLSignal::PrintShort(<span style="background-color:rgb(255, 242, 153);"><span class="keyword">const</span> <span class="keyword">bool</span> dash=<span class="macro">false</span></span>) { ::<span class="functions">Print</span> ( <span style="background-color:rgb(255, 242, 153);">(dash ? <span class="string">"- "</span> : <span class="string">""</span>)</span>,<span class="keyword">this</span>.Header(<span class="macro">true</span>), <span class="