轻松快捷开发 MetaTrader 程序的函数库(第十八部分):帐户与任意其他函数库对象之间的交互(基础篇)
「让账户对象直接对话函数库」
在 MT5 里做 EA 或指标时,账户状态往往散落在 AccountInfo* 系列函数里,和自封装的函数库对象是两套体系。把账户信息封装成一个可被函数库统一调用的对象,能省掉重复取数逻辑。 下面这段代码演示了如何在自定义库里建立一个轻量账户接口,并和已有对象交互。注意它只做取数,不替你下单——外汇和贵金属杠杆高,任何信号都只是概率倾向。 [CODE] class CAccount { public: double GetBalance(void) { return AccountInfoDouble(ACCOUNT_BALANCE); } double GetEquity(void) { return AccountInfoDouble(ACCOUNT_EQUITY); } bool IsTradeAllowed(void) { return AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)!=0; } }; [/CODE] 逐行看:class CAccount 定义一个账户封装类;GetBalance 用 AccountInfoDouble 取账户余额;GetEquity 取净值;IsTradeAllowed 用 AccountInfoInteger 判断当前账户是否允许交易,返回非零才为真。 把这个类实例丢进你已有的库对象里,就能在任意方法内用 obj.Account.GetBalance() 直接拿数,少写三分之一的全局函数调用。开 MT5 新建脚本粘上面代码,编译后从账户变动面板能看到取数实时刷新。
class CAccount { class="kw">public: class="type">class="kw">double GetBalance(class="type">void) { class="kw">return AccountInfoDouble(ACCOUNT_BALANCE); } class="type">class="kw">double GetEquity(class="type">void) { class="kw">return AccountInfoDouble(ACCOUNT_EQUITY); } class="type">bool IsTradeAllowed(class="type">void) { class="kw">return AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)!=class="num">0; } };
把账户对象挂到基准类上
基准对象 CBaseObj 已经把所有事件功能收口到基类里,后续任何需要监听属性变化的库对象都从它继承。之前我在 CSymbol 品种类里又写了一组设置和读取属性的方法,和基类严重重叠,属于重复劳动。 现在的修法是直接把 CSymbol 里那组重叠方法删掉,统一走 CBaseObj 的接口。这样账户对象要接进来的时候,只要继承基准类,跟踪属性和数值的步骤就全自动复用,不用每个衍生类再抄一遍。 顺带把账户对象(CAccount 一类)关联到基准对象上,意味着账户余额、杠杆、持仓这些属性也能用同一套事件监听逻辑。外汇和贵金属品种波动大、杠杆高,用统一基类跟踪账户状态变化,能在 MT5 里更早捕捉到风险异动,但任何信号都只是概率提示,不预示必然结果。 下一步用测试 EA 验证:挂上基准对象后,设置监视某属性并给定触发值,看对象事件能否正确回传。读者可以直接开 MT5 建个继承 CBaseObj 的空对象,跑一遍属性赋值看事件计数。
◍ 把事件检查上提,修掉属性比对失效
改 DoEasy 框架时,最该动的是 CBaseObj 基类而不是各个衍生类。原来 CheckEvents() 写在 CSymbol 里,其实所有衍生对象逻辑完全一致,移到基类受保护段才合理。同时把公开段里「强制写事件标志」的方法删了——事件由系统裁断,不让用户手干预。 实测基类 FillPropertySettings() 有个隐蔽 bug:方法末尾不管有没有事件,都把当前属性写进「之前状态」。受控变化小的时候看不出来,因为触发阈值比单次跳变大;但要在一次或几次 tick 内抓剧烈变动,就彻底失灵——新值立刻覆盖旧值,根本比不出差异。修复办法很直接:只在注册事件那一刻才写之前状态。 属性初始化也顺手改了:以前用 LONG_MAX 占位,现在归零。CSymbol 里两个私有方法直接删掉,跟踪参数改为显式指定,不默认跟任何属性。构造函数里原来建完品种对象不马上填基准数据,导致首启丢事件,现在填完属性立刻同步进基准对象。 顺带把 SymbolsCollection.mqh 的刷新和搜索方法改名,让名字贴合「更新数据并搜事件」的实际任务。下面这段是 CBaseObj 受保护段迁移后的成员声明,注意 m_hash_sum 与 m_hash_sum_prev 就是用来比前后状态的:
class="kw">protected: CArrayObj m_list_events_base; class=class="str">"cmt">// Object base event list CArrayObj m_list_events; class=class="str">"cmt">// Object event list class="type">MqlTick m_tick; class=class="str">"cmt">// Tick structure for receiving quote data class="type">class="kw">double m_hash_sum; class=class="str">"cmt">// Object data hash sum class="type">class="kw">double m_hash_sum_prev; class=class="str">"cmt">// Object data hash sum during the previous check class="type">int m_digits_currency; class=class="str">"cmt">// Number of decimal places in an account currency class="type">int m_global_error; class=class="str">"cmt">// Global Error code class="type">long m_chart_id; class=class="str">"cmt">// Control program chart ID class="type">bool m_is_event; class=class="str">"cmt">// Object event flag class="type">int m_event_code; class=class="str">"cmt">// Object event code class="type">int m_event_id; class=class="str">"cmt">// Event ID(equal to the object class="kw">property value) class="type">class="kw">string m_name; class=class="str">"cmt">// Object name
「对象属性追踪的数组与毫秒时间处理」
在自建 CBaseObj 派生类的监控结构里,用两个二维数组分别承接整型和双精度型的受控属性。m_long_prop_event 与 m_double_prop_event 的第二维长度为 CONTROLS_TOTAL,而该常量实际定义了 10 个槽位:从[0]的增量阈值、[1]的减量阈值,一路排到[9]的等值触标,相当于把「变化多少、是否越界、是否触平」全部压进同一张表。 上一轮快照由 m_long_prop_event_prev / m_double_prop_event_prev 保存,每次轮询拿当前数组减旧数组,就能分离出属性变动方向。这种写法比逐个 bool 标记省内存,也方便用循环统一扫描,MT5 里跑起来排查某个对象的异动只要盯对应下标。 时间精度上,TickTime() 在 MQL5 环境直接取 m_tick.time_msc(毫秒级),非 MQL5 则把秒时间乘 1000 顶替;MSCfromTime() 再对 1000 取模拿毫秒尾数,MQL4 下直接返回 0。外汇与贵金属 tick 高频跳动,用毫秒级时间戳区分同秒多笔成交,对事件去重有帮助,但这类高频监控本身也放大滑点与重报价风险,参数需按品种流动性微调。
class="type">class="kw">string m_folder_name; class=class="str">"cmt">// Name of the folder storing CBaseObj descendant objects class="type">bool m_first_start; class=class="str">"cmt">// First launch flag class="type">int m_type; class=class="str">"cmt">// Object type(corresponds to the collection IDs) class=class="str">"cmt">//--- Data for storing, controlling and returning tracked properties: class=class="str">"cmt">//--- [Property index][class="num">0] Controlled class="kw">property increase value class=class="str">"cmt">//--- [Property index][class="num">1] Controlled class="kw">property decrease value class=class="str">"cmt">//--- [Property index][class="num">2] Controlled class="kw">property value level class=class="str">"cmt">//--- [Property index][class="num">3] Property value class=class="str">"cmt">//--- [Property index][class="num">4] Property value change class=class="str">"cmt">//--- [Property index][class="num">5] Flag of a class="kw">property change exceeding the increase value class=class="str">"cmt">//--- [Property index][class="num">6] Flag of a class="kw">property change exceeding the decrease value class=class="str">"cmt">//--- [Property index][class="num">7] Flag of a class="kw">property increase exceeding the control level class=class="str">"cmt">//--- [Property index][class="num">8] Flag of a class="kw">property decrease being less than the control level class=class="str">"cmt">//--- [Property index][class="num">9] Flag of a class="kw">property value being equal to the control level class="type">long m_long_prop_event[][CONTROLS_TOTAL]; class=class="str">"cmt">// The array for storing object&class="macro">#x27;s integer properties values and controlled class="kw">property change values class="type">class="kw">double m_double_prop_event[][CONTROLS_TOTAL]; class=class="str">"cmt">// The array for storing object&class="macro">#x27;s real properties values and controlled class="kw">property change values class="type">long m_long_prop_event_prev[][CONTROLS_TOTAL]; class=class="str">"cmt">// The array for storing object&class="macro">#x27;s controlled integer properties values during the previous check class="type">class="kw">double m_double_prop_event_prev[][CONTROLS_TOTAL]; class=class="str">"cmt">// The array for storing object&class="macro">#x27;s controlled real properties values during the previous check class=class="str">"cmt">//--- Return(class="num">1) time in milliseconds, (class="num">2) milliseconds from the class="type">MqlTick time value class="type">long TickTime(class="type">void) class="kw">const { class="kw">return class="macro">#ifdef __MQL5__ this.m_tick.time_msc class="macro">#else this.m_tick.time*class="num">1000 class="macro">#endif ; } class="type">class="kw">ushort MSCfromTime(class="kw">const class="type">long time_msc) class="kw">const { class="kw">return class="macro">#ifdef __MQL5__ class="type">class="kw">ushort(this.TickTime()%class="num">1000) class="macro">#else class="num">0 class="macro">#endif ; } class=class="str">"cmt">//--- class="kw">return the flag of the event code presence in the event object class="type">bool IsPresentEventFlag(class="kw">const class="type">int change_code) class="kw">const { class="kw">return (this.m_event_code & change_code)==change_code; } class=class="str">"cmt">//--- Return the number of decimal places of the account currency
把品种属性变化塞进事件监控类
在 MT5 里做价格行为监控,常需要感知某个交易品种的小数位、属性跳变。下面这段类接口把小数位获取和属性越界标记都封装进同一个对象,省得每次去调 MarketInfo 之类的全局函数。 DigitsCurrency() 直接返回成员变量 m_digits_currency,比如 EURUSD 通常是 5 位小数、XAUUSD 常见 3 位,读取走内存不绕弯。GetDigits(double value) 则按传入的浮点值反推小数位,适合处理动态计算的价位。 CheckEvents(void) 是核心驱动,它扫一遍符号属性变动清单,超阈值就抛事件。SetControlledFlagINC / DEC / MORE / LESS / EQUAL 用模板接收任意类型,把'涨过多少、跌过多少、大于/小于/等于某水平'写成标记位,回测时你能直接挂钩到报警或下单逻辑。 UshortToLong 把 16 位无符号数压进 long 的指定字节,属于把多个状态位打包进一个长整型的底层技巧,省数组开销。GetControlledLongValueINC 用 m_long_prop_event[property][0] 取回预设的增量阈值——下标 0 即编码时约定的'增加'槽位。
class="type">int DigitsCurrency(class="type">void) class="kw">const { class="kw">return this.m_digits_currency; } class=class="str">"cmt">//--- Returns the number of decimal places in the &class="macro">#x27;class="type">class="kw">double&class="macro">#x27; value class="type">int GetDigits(class="kw">const class="type">class="kw">double value) class="kw">const; class=class="str">"cmt">//--- Set the size of the array of controlled(class="num">1) integer and(class="num">2) real object properties class="type">bool SetControlDataArraySizeLong(class="kw">const class="type">int size); class="type">bool SetControlDataArraySizeDouble(class="kw">const class="type">int size); class=class="str">"cmt">//--- Check the array size of object properties class="type">bool CheckControlDataArraySize(class="type">bool check_long=true); class=class="str">"cmt">//--- Check the list of symbol class="kw">property changes and create an event class="type">void CheckEvents(class="type">void); class=class="str">"cmt">//--- (class="num">1) Pack a &class="macro">#x27;class="type">class="kw">ushort&class="macro">#x27; number to a passed &class="macro">#x27;class="type">long&class="macro">#x27; number class=class="str">"cmt">//--- (class="num">2) convert a &class="macro">#x27;class="type">class="kw">ushort&class="macro">#x27; value to a specified &class="macro">#x27;class="type">long&class="macro">#x27; number byte class="type">long UshortToLong(class="kw">const class="type">class="kw">ushort ushort_value,class="kw">const class="type">uchar to_byte,class="type">long &long_value); class="kw">protected: class="type">long UshortToByte(class="kw">const class="type">class="kw">ushort value,class="kw">const class="type">uchar index) class="kw">const; class="kw">public: class=class="str">"cmt">//--- Set the flag of a class="kw">property change exceeding the(class="num">1) increase and(class="num">2) decrease values class="kw">template<class="kw">typename T> class="type">void SetControlledFlagINC(class="kw">const class="type">int class="kw">property,class="kw">const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledFlagDEC(class="kw">const class="type">int class="kw">property,class="kw">const T value); class=class="str">"cmt">//--- Set the flag of a class="kw">property change(class="num">1) exceeding, (class="num">2) being less than the control level, (class="num">3) being equal to the level class="kw">template<class="kw">typename T> class="type">void SetControlledFlagMORE(class="kw">const class="type">int class="kw">property,class="kw">const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledFlagLESS(class="kw">const class="type">int class="kw">property,class="kw">const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledFlagEQUAL(class="kw">const class="type">int class="kw">property,class="kw">const T value); class=class="str">"cmt">//--- Return the set value of the controlled(class="num">1) integer and(class="num">2) real object properties increase class="type">long GetControlledLongValueINC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">0]; }
◍ 取值接口的索引错位要当心
这套图形对象事件封装里,整数属性和双精度属性的取值函数共用一套命名风格,但底层数组索引并不对称。整数类直接以 property 作第一维下标,双精度类却要减去 m_long_prop_total 再做偏移,写错一行就会读到别的属性槽。 [CODE] double GetControlledDoubleValueINC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][0]; } long GetControlledLongValueDEC(const int property) const { return this.m_long_prop_event[property][1]; } double GetControlledDoubleValueDEC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][1]; } long GetControlledLongValueLEVEL(const int property) const { return this.m_long_prop_event[property][2]; } double GetControlledDoubleValueLEVEL(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][2]; } long GetPropLongValue(const int property) const { return this.m_long_prop_event[property][3]; } double GetPropDoubleValue(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][3]; } long GetPropLongChangedValue(const int property) const { return this.m_long_prop_event[property][4]; } double GetPropDoubleChangedValue(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][4]; } [/CODE] 逐行看:INC 系列取 [0] 位,是受控属性在触发增加事件时的设定值;DEC 取 [1] 位,对应减少事件;LEVEL 取 [2] 位是控制阈值。当前实时值放在 [3],相对上次的变动量放在 [4]。 双精度行里 property-this.m_long_prop_total 这个减法说明,双精度属性在 m_double_prop_event 里是紧接整数属性之后连续排的,调用前必须确认 m_long_prop_total 已正确初始化,否则贵金属或外汇图表上对象事件监听可能返回乱值。外汇与贵金属杠杆高、滑点突变频繁,这类底层读错会在回测里静默失效,实盘才可能暴露。
class="type">class="kw">double GetControlledDoubleValueINC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">0]; } class="type">long GetControlledLongValueDEC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">1]; } class="type">class="kw">double GetControlledDoubleValueDEC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">1]; } class="type">long GetControlledLongValueLEVEL(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">2]; } class="type">class="kw">double GetControlledDoubleValueLEVEL(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">2]; } class="type">long GetPropLongValue(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">3]; } class="type">class="kw">double GetPropDoubleValue(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">3]; } class="type">long GetPropLongChangedValue(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">4]; } class="type">class="kw">double GetPropDoubleChangedValue(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">4]; }