轻松快捷开发 MetaTrader 程序的函数库(第十八部分):帐户与任意其他函数库对象之间的交互(基础篇)
📘

轻松快捷开发 MetaTrader 程序的函数库(第十八部分):帐户与任意其他函数库对象之间的交互(基础篇)

第 1/3 篇

「让账户对象直接对话函数库」

在 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 新建脚本粘上面代码,编译后从账户变动面板能看到取数实时刷新。

MQL5 / C++
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 就是用来比前后状态的:

MQL5 / C++
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 高频跳动,用毫秒级时间戳区分同秒多笔成交,对事件去重有帮助,但这类高频监控本身也放大滑点与重报价风险,参数需按品种流动性微调。

MQL5 / C++
  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 即编码时约定的'增加'槽位。

MQL5 / C++
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 已正确初始化,否则贵金属或外汇图表上对象事件监听可能返回乱值。外汇与贵金属杠杆高、滑点突变频繁,这类底层读错会在回测里静默失效,实盘才可能暴露。

MQL5 / C++
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]; }

常见问题

把账户对象挂到基准类上,让它作为公共成员被其他对象直接读取,避免每层都手动传引用。
事件检查逻辑没上提导致比对时机错乱,把属性检查上提到统一入口修掉失效即可。
小布可接管这类对象事件监控,自动捕获属性变化并推送提醒,你只看结论不用自己写轮询。
用动态数组存变更记录,时间戳取毫秒级避免同秒覆盖,轮询间隔小于最小变动频率。
接口内部索引从0和从1混用导致错位,统一基准类里的索引定义并对外封装转换层。