轻松快捷开发 MetaTrader 程序的函数库(第十二部分)。·进阶篇
「账户属性枚举与封装类的底层结构」
在 MT5 的账户信息封装里,排序标识 SORT_BY_ACCOUNT_COMPANY 被定义为 FIRST_ACC_STR_PROP+3,说明它归属于字符串类账户属性数组,偏移量为 3,对应经纪商公司名称的排序键。 保证金计算模式被收进 ENUM_ACCOUNT_MARGIN_MODE 枚举,只有三种取值:RETAIL_NETTING(零售锁仓对冲外的净仓模式)、EXCHANGE(交易所模式)、RETAIL_HEDGING(零售对冲模式)。不同模式直接决定开仓占用保证金的算法,外汇与贵金属品种在高杠杆下差异会被放大,属于高风险参数。 CAccount 类把账户属性按类型拆成三块定长数组:m_long_prop 存整数属性,长度为 ACCOUNT_PROP_INTEGER_TOTAL;m_double_prop 存实数属性,长度为 ACCOUNT_PROP_DOUBLE_TOTAL;m_string_prop 存字符串属性,长度为 ACCOUNT_PROP_STRING_TOTAL。这种分桶写法让你在 EA 里取账户数据时不用来回调 AccountInfoInteger 系列函数,直接读数组下标更快。 开 MT5 切到 MetaEditor,把下面这段结构对照 Account.mqh 头文件看,重点确认你本地枚举常量值是否一致,再决定自己的封装要不要复用同套索引。
enum ENUM_ACCOUNT_MARGIN_MODE { ACCOUNT_MARGIN_MODE_RETAIL_NETTING, ACCOUNT_MARGIN_MODE_EXCHANGE, ACCOUNT_MARGIN_MODE_RETAIL_HEDGING }; class CAccount : class="kw">public CObject { class="kw">private: class="type">long m_long_prop[ACCOUNT_PROP_INTEGER_TOTAL]; class=class="str">"cmt">// Integer properties class="type">class="kw">double m_double_prop[ACCOUNT_PROP_DOUBLE_TOTAL]; class=class="str">"cmt">// Real properties class="type">class="kw">string m_string_prop[ACCOUNT_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties };
◍ 账户属性读写与索引偏移的处理
在 MT5 里账户信息分三类:整数、双精度、字符串,分别由 ENUM_ACCOUNT_PROP_INTEGER / DOUBLE / STRING 枚举标识。由于三类枚举各自从 0 开始编号,存进数组时必须做偏移换算,否则双精度和字符串会被写到整数区的越界位置。 IndexProp 两个重载就是干这个的:DOUBLE 类减去 ACCOUNT_PROP_INTEGER_TOTAL 得到在双精度数组里的下标;STRING 类再减去 ACCOUNT_PROP_DOUBLE_TOTAL,落到字符串数组。实测在标准账户下 ACCOUNT_PROP_INTEGER_TOTAL 通常为 70 上下,具体值可用 Print(ACCOUNT_PROP_INTEGER_TOTAL) 在 MT5 里确认。 SetProperty / GetProperty 成对出现,写入时整数直接用原枚举值做下标,双精度和字符串则先过 IndexProp 映射。这样上层调用不用关心底层数组怎么排,统一传枚举即可。 IsPercentsForSOLevels 读了 MarginSOMode,若等于 ACCOUNT_STOPOUT_MODE_PERCENT 就说明券商的强平/ MarginCall 水平是按百分比给的,否则是按保证金金额。外汇和贵金属杠杆高,强平逻辑按百分比还是金额直接影响风控阈值,开户前最好用这段代码探一下自家券商返回值。
class="type">int IndexProp(ENUM_ACCOUNT_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-ACCOUNT_PROP_INTEGER_TOTAL; } class="type">int IndexProp(ENUM_ACCOUNT_PROP_STRING class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_DOUBLE_TOTAL; } class="kw">public: class=class="str">"cmt">//--- Constructor CAccount(class="type">void); class="kw">protected: class="kw">public: class=class="str">"cmt">//--- Set(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties of an account class="type">void SetProperty(ENUM_ACCOUNT_PROP_INTEGER class="kw">property,class="type">long value) { this.m_long_prop[class="kw">property]=value; } class="type">void SetProperty(ENUM_ACCOUNT_PROP_DOUBLE class="kw">property,class="type">class="kw">double value) { this.m_double_prop[this.IndexProp(class="kw">property)]=value; } class="type">void SetProperty(ENUM_ACCOUNT_PROP_STRING class="kw">property,class="type">class="kw">string value) { this.m_string_prop[this.IndexProp(class="kw">property)]=value; } class=class="str">"cmt">//--- Return(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties of an account from the class="kw">property array class="type">long GetProperty(ENUM_ACCOUNT_PROP_INTEGER class="kw">property) class="kw">const { class="kw">return this.m_long_prop[class="kw">property]; } class="type">class="kw">double GetProperty(ENUM_ACCOUNT_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)]; } class="type">class="kw">string GetProperty(ENUM_ACCOUNT_PROP_STRING class="kw">property) class="kw">const { class="kw">return this.m_string_prop[this.IndexProp(class="kw">property)]; } class=class="str">"cmt">//--- Return the flag of calculating MarginCall and StopOut levels in % class="type">bool IsPercentsForSOLevels(class="type">void) class="kw">const { class="kw">return this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT; } class=class="str">"cmt">//--- Return the flag of supporting the class="kw">property by the account object class="kw">virtual class="type">bool SupportProperty(ENUM_ACCOUNT_PROP_INTEGER class="kw">property) { class="kw">return true; }
账户对象的属性接管与快捷读取
在自定义账户类里,先把 double 和 string 两类账户属性声明为全部支持,等于告诉框架这套对象能透传任何浮点与字符串字段,不会在属性访问时漏接。 virtual bool SupportProperty(ENUM_ACCOUNT_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_ACCOUNT_PROP_STRING property) { return true; } 比较逻辑分两层:Compare() 按指定 mode 对所有属性排序用,IsEqual() 只判断两个账户对象是否等价,用于列表查重。 virtual int Compare(const CObject *node,const int mode=0) const; bool IsEqual(CAccount* compared_account) const; 下面这组内联方法把常用整数属性包成一句话读取:TradeMode() 取交易模式,MarginSOMode() 取爆仓模式,MarginMode() 取保证金计算模式,Login() 取账号 ID(long 型),Leverage() 取杠杆倍数,LimitOrders() 取挂单上限。 ENUM_ACCOUNT_TRADE_MODE TradeMode(void) const { return (ENUM_ACCOUNT_TRADE_MODE)this.GetProperty(ACCOUNT_PROP_TRADE_MODE); } ENUM_ACCOUNT_STOPOUT_MODE MarginSOMode(void) const { return (ENUM_ACCOUNT_STOPOUT_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_SO_MODE); } ENUM_ACCOUNT_MARGIN_MODE MarginMode(void) const { return (ENUM_ACCOUNT_MARGIN_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_MODE); } long Login(void) const { return this.GetProperty(ACCOUNT_PROP_LOGIN); } long Leverage(void) const { return this.GetProperty(ACCOUNT_PROP_LEVERAGE); } long LimitOrders(void) const { return this.GetProperty(ACCOUNT_PROP_LIMIT_ORDERS); } 开 MT5 自建 EA 时,直接抄这组封装,调用 Account.Leverage() 比每次写 AccountInfoInteger(ACCOUNT_LEVERAGE) 少敲一半字符;外汇和贵金属杠杆波动大、强平风险高,读到的杠杆值仅作风控参考,不代表实际可承受仓位。
class="kw">virtual class="type">bool SupportProperty(ENUM_ACCOUNT_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_ACCOUNT_PROP_STRING class="kw">property) { class="kw">return true; } class="kw">virtual class="type">int Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const; class="type">bool IsEqual(CAccount* compared_account) class="kw">const; ENUM_ACCOUNT_TRADE_MODE TradeMode(class="type">void) class="kw">const { class="kw">return (ENUM_ACCOUNT_TRADE_MODE)this.GetProperty(ACCOUNT_PROP_TRADE_MODE); } ENUM_ACCOUNT_STOPOUT_MODE MarginSOMode(class="type">void) class="kw">const { class="kw">return (ENUM_ACCOUNT_STOPOUT_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_SO_MODE); } ENUM_ACCOUNT_MARGIN_MODE MarginMode(class="type">void) class="kw">const { class="kw">return (ENUM_ACCOUNT_MARGIN_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_MODE); } class="type">long Login(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_LOGIN); } class="type">long Leverage(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_LEVERAGE); } class="type">long LimitOrders(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_LIMIT_ORDERS); }
「账户属性读取的封装方法」
在 MT5 的账户对象封装里,交易权限与小数位这类整型属性,和余额、净值这类浮点属性是分开成组返回的。下面这段代码把 GetProperty 按属性枚举做了薄封装,调用方不用记具体枚举值,直接读语义化方法名即可。
[CODE]
long TradeAllowed(void) const { return this.GetProperty(ACCOUNT_PROP_TRADE_ALLOWED); }
long TradeExpert(void) const { return this.GetProperty(ACCOUNT_PROP_TRADE_EXPERT); }
long CurrencyDigits(void) const { return this.GetProperty(ACCOUNT_PROP_CURRENCY_DIGITS); }
//--- Return the account's real properties
double Balance(void) const { return this.GetProperty(ACCOUNT_PROP_BALANCE); }
double Credit(void) const { return this.GetProperty(ACCOUNT_PROP_CREDIT); }
double Profit(void) const { return this.GetProperty(ACCOUNT_PROP_PROFIT); }
double Equity(void) const { return this.GetProperty(ACCOUNT_PROP_EQUITY); }
double Margin(void) const { return this.GetProperty(ACCOUNT_PROP_MARGIN); }
[/CODE]
逐行看:前三个 long 返回的是账户开关与精度——TradeAllowed 取是否允许交易,TradeExpert 取是否允许 EA 交易,CurrencyDigits 取账户币种小数位(多数外汇账户为 2)。
后面五个 double 才是真金白银的实时数值:Balance 是静态余额,Equity 随持仓浮亏浮盈变动,Margin 是已用保证金;Credit 和 Profit 在多数零售账户里常为 0,但桥接账户可能非零。
开 MT5 按 F4 把这段塞进你的 CAccount 类,EA 里就能写 if(Account.TradeAllowed() && Account.Margin() < 1000.0) 去做前置风控。外汇与贵金属杠杆高,保证金占用突变可能直接触发强平,这类检查建议放在每笔下单前。
class="type">long TradeAllowed(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_TRADE_ALLOWED); } class="type">long TradeExpert(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_TRADE_EXPERT); } class="type">long CurrencyDigits(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_CURRENCY_DIGITS); } class=class="str">"cmt">//--- Return the account&class="macro">#x27;s real properties class="type">class="kw">double Balance(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_BALANCE); } class="type">class="kw">double Credit(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_CREDIT); } class="type">class="kw">double Profit(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_PROFIT); } class="type">class="kw">double Equity(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_EQUITY); } class="type">class="kw">double Margin(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_MARGIN); }
◍ 账户保证金与负债的只读封装
在 MT5 的账户类封装里,这组方法把交易账户的关键数值统一走 GetProperty 只读接口,避免外部直接改底层字段。MarginFree 返回空闲保证金,MarginLevel 返回保证金水平百分比,两者是爆仓风控最该盯的实时量。 MarginSOCall 与 MarginSOSO 分别对应预警线和平仓线的阈值,由券商在账户属性里下发;MarginInitial 与 MarginMaintenance 则是开仓初始保证金和持仓维护保证金,价差直接吃掉短线刷单的利润空间。 Assets 和 Liabilities 给出总资产与负债,ComissionBlocked 是已被冻结的佣金。实盘里若 MarginLevel 掉到 100 以下,贵金属这类高杠杆品种触发强平的概率会明显抬升,外汇同样属于高风险,别只看点值算盈利。 下面这段是原始方法声明,逐行看就是每个 double 函数 const 限定、void 入参、内部一行 return this.GetProperty(对应枚举): double MarginFree(void) const { return this.GetProperty(ACCOUNT_PROP_MARGIN_FREE); } double MarginLevel(void) const { return this.GetProperty(ACCOUNT_PROP_MARGIN_LEVEL); } double MarginSOCall(void) const { return this.GetProperty(ACCOUNT_PROP_MARGIN_SO_CALL); } double MarginSOSO(void) const { return this.GetProperty(ACCOUNT_PROP_MARGIN_SO_SO); } double MarginInitial(void) const { return this.GetProperty(ACCOUNT_PROP_MARGIN_INITIAL); } double MarginMaintenance(void) const { return this.GetProperty(ACCOUNT_PROP_MARGIN_MAINTENANCE); } double Assets(void) const { return this.GetProperty(ACCOUNT_PROP_ASSETS); } double Liabilities(void) const { return this.GetProperty(ACCOUNT_PROP_LIABILITIES); } double ComissionBlocked(void) const { return this.GetProperty(ACCOUNT_PROP_COMMISSION_BLOCKED); } 开 MT5 用 MetaEditor 搜这些枚举,能在账户监听 EA 里直接复用,不用自己再写一套解析。
class="type">class="kw">double MarginFree(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_MARGIN_FREE); } class="type">class="kw">double MarginLevel(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_MARGIN_LEVEL); } class="type">class="kw">double MarginSOCall(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_MARGIN_SO_CALL); } class="type">class="kw">double MarginSOSO(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_MARGIN_SO_SO); } class="type">class="kw">double MarginInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_MARGIN_INITIAL); } class="type">class="kw">double MarginMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_MARGIN_MAINTENANCE); } class="type">class="kw">double Assets(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_ASSETS); } class="type">class="kw">double Liabilities(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_LIABILITIES); } class="type">class="kw">double ComissionBlocked(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_COMMISSION_BLOCKED); }
账户对象的属性读取与日志输出
在封装账户对象时,把基础字段做成只读访问器是最省事的做法。下面这组方法直接返回账户名、服务器、结算币种和经纪商名称,底层都走同一个 GetProperty 字符串属性接口。 string Name(void) const { return this.GetProperty(ACCOUNT_PROP_NAME); } string Server(void) const { return this.GetProperty(ACCOUNT_PROP_SERVER); } string Currency(void) const { return this.GetProperty(ACCOUNT_PROP_CURRENCY); } string Company(void) const { return this.GetProperty(ACCOUNT_PROP_COMPANY); } 除了四个字符串字段,类里还声明了三组 GetPropertyDescription 重载,分别接收整型、双精度和字符串类型的枚举,用来把账户属性翻译成可读说明。交易模式、止损预扣保证金模式、保证金计算模式也各有独立描述方法,方便在面板或日志里直接展示中文含义。 void Print(const bool full_prop=false) 负责把账户属性打印到 MT5 专家日志;传 true 输出全部属性,false 只输出当前账户支持的那些。PrintShort 则只丢一句简版描述,调试时少刷屏。外汇与贵金属交易杠杆高,账户属性读错可能导致仓位计算偏差,实盘前建议在策略测试器里先跑一遍 Print 核对。
class="type">class="kw">string Name(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_NAME); } class="type">class="kw">string Server(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_SERVER); } class="type">class="kw">string Currency(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_CURRENCY); } class="type">class="kw">string Company(class="type">void) class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_COMPANY); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Descriptions of the account object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return the description of the account&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties class="type">class="kw">string GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_ACCOUNT_PROP_DOUBLE class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_ACCOUNT_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Return a name of a trading account type class="type">class="kw">string TradeModeDescription(class="type">void) class="kw">const; class=class="str">"cmt">//--- Return the description of the mode for setting the minimum available margin level class="type">class="kw">string MarginSOModeDescription(class="type">void) class="kw">const; class=class="str">"cmt">//--- Return the description of the margin calculation mode class="type">class="kw">string MarginModeDescription(class="type">void) class="kw">const; class=class="str">"cmt">//--- Display the description of the account properties in the journal(full_prop=true - all properties, class="kw">false - supported ones only) class="type">void Print(class="kw">const class="type">bool full_prop=class="kw">false); class=class="str">"cmt">//--- Display a class="type">class="kw">short account description in the journal class="type">void PrintShort(class="type">void); class=class="str">"cmt">//--- };