
轻松快捷开发 MetaTrader 程序的函数库(第十二部分)。
内容
在开发函数库交易类之前,我们需要创建一些额外的交易相关类。 更具体地说,我们需要交易账户和交易品种的数据。 本文将专门介绍帐户对象。
鉴于帐户数据可能会在交易期间发生变化,因此我们准备将一个帐户对象纳入帐户对象集合。 接着,我们将在帐户里实施事件跟踪。 这可令我们能够检测杠杆,余额,盈利/亏损,净值和账户限制的变化。
Account 对象
交易账户交易量与我在过去的文章中论述过的已创建对象相同。 此对象与之前研究的对象之间的唯一区别是,它不是那种由后继者指定特定对象状态的抽象对象。 帐户对象是一个独立的对象,拥有所有帐户属性。 这些对象会被添加到帐户对象集合中,允许用户按各种参数比较不同帐户的数据。
像往常一样,我们首先开发该类会用到的帐户对象属性的所有必需枚举。
在编辑器帮助里打开 账户属性:
对于 AccountInfoInteger() 函数
ID |
说明 |
属性类型 |
ACCOUNT_LOGIN |
账号 |
long |
ACCOUNT_TRADE_MODE |
交易账户类型 |
|
ACCOUNT_LEVERAGE |
杠杆 |
long |
ACCOUNT_LIMIT_ORDERS |
允许的最大活动挂单数 |
int |
ACCOUNT_MARGIN_SO_MODE |
设置最小可用保证金级别的模式 |
|
ACCOUNT_TRADE_ALLOWED |
当前账户的交易许可 |
bool |
ACCOUNT_TRADE_EXPERT |
EA 的交易许可 |
bool |
ACCOUNT_MARGIN_MODE |
保证金计算模式 |
|
ACCOUNT_CURRENCY_DIGITS |
准确显示交易结果所需的账户货币的小数点位数 |
int |
对于 AccountInfoDouble() 函数
ID |
说明 |
属性类型 |
ACCOUNT_BALANCE |
以存款货币为单位的账户余额 |
double |
ACCOUNT_CREDIT |
以存款货币为单位的信贷额 |
double |
ACCOUNT_PROFIT |
以帐户货币为单位的账户内当前利润 |
double |
ACCOUNT_EQUITY |
以存款货币为单位的净值额 |
double |
ACCOUNT_MARGIN |
以存款货币为单位的保留保证金 |
double |
ACCOUNT_MARGIN_FREE |
以存款货币为单位的开一笔仓位的可用金额 |
double |
ACCOUNT_MARGIN_LEVEL |
账户的保证金 % |
double |
ACCOUNT_MARGIN_SO_CALL |
保证金级别,需要存入账户(追缴保证金)。 取决于 ACCOUNT_MARGIN_SO_MODE,属性既可为 %,亦或以存款货币为单位设置 |
double |
ACCOUNT_MARGIN_SO_SO |
保证金级别,强制亏损平仓(爆仓)。 取决于 ACCOUNT_MARGIN_SO_MODE,属性既可为 %,亦或以存款货币为单位设置 |
double |
ACCOUNT_MARGIN_INITIAL |
在帐户上保留的资金,以确保所有挂单的保证金额 |
double |
ACCOUNT_MARGIN_MAINTENANCE |
账户上保留的资金,以确保所有持仓的最低金额 |
double |
ACCOUNT_ASSETS |
帐户上的当前资产 |
double |
ACCOUNT_LIABILITIES |
账户的当前流动负债 |
double |
ACCOUNT_COMMISSION_BLOCKED |
当前帐户中被冻结佣金的合计 |
double |
对于 AccountInfoString() 函数
ID |
说明 |
属性类型 |
ACCOUNT_NAME |
客户名 |
string |
ACCOUNT_SERVER |
交易服务器名 |
string |
ACCOUNT_CURRENCY |
存款货币 |
string |
ACCOUNT_COMPANY |
服务帐户的公司名称 |
string |
帐户对象会包含所有这些属性,这些属性将在类构造函数中设置。
在函数库的 Defines.mqh 文件里,加入与如上表格所示相对应的 整数型,实数型
和 字符串型账户对象属性。
由于我们已创建了用于处理帐户事件的枚举,因此在先前创建的用于处理帐户事件的数据之前放置帐户属性数据是合理的:
//+------------------------------------------------------------------+ //| Data for working with accounts | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Account integer properties | //+------------------------------------------------------------------+ enum ENUM_ACCOUNT_PROP_INTEGER { ACCOUNT_PROP_LOGIN, // Account number ACCOUNT_PROP_TRADE_MODE, // Trading account type ACCOUNT_PROP_LEVERAGE, // Provided leverage ACCOUNT_PROP_LIMIT_ORDERS, // Maximum allowed number of active pending orders ACCOUNT_PROP_MARGIN_SO_MODE, // Mode of setting the minimum available margin level ACCOUNT_PROP_TRADE_ALLOWED, // Permission to trade for the current account from the server side ACCOUNT_PROP_TRADE_EXPERT, // Permission to trade for an EA from the server side ACCOUNT_PROP_MARGIN_MODE, // Margin calculation mode ACCOUNT_PROP_CURRENCY_DIGITS // Number of digits for an account currency necessary for accurate display of trading results }; #define ACCOUNT_PROP_INTEGER_TOTAL (9) // Total number of account's integer properties #define ACCOUNT_PROP_INTEGER_SKIP (0) // Number of account's integer properties not used in sorting //+------------------------------------------------------------------+ //| Account real properties | //+------------------------------------------------------------------+ enum ENUM_ACCOUNT_PROP_DOUBLE { ACCOUNT_PROP_BALANCE = ACCOUNT_PROP_INTEGER_TOTAL, // Account balance in a deposit currency ACCOUNT_PROP_CREDIT, // Credit in a deposit currency ACCOUNT_PROP_PROFIT, // Current profit on an account in the account currency ACCOUNT_PROP_EQUITY, // Equity on an account in the deposit currency ACCOUNT_PROP_MARGIN, // Reserved margin on an account in a deposit currency ACCOUNT_PROP_MARGIN_FREE, // Free funds available for opening a position in a deposit currency ACCOUNT_PROP_MARGIN_LEVEL, // Margin level on an account in % ACCOUNT_PROP_MARGIN_SO_CALL, // Margin level, at which a deposit to an account is required (Margin Call) ACCOUNT_PROP_MARGIN_SO_SO, // Margin level, at which the most loss-making position is closed (Stop Out) ACCOUNT_PROP_MARGIN_INITIAL, // Funds reserved on an account to ensure a guarantee amount for all pending orders ACCOUNT_PROP_MARGIN_MAINTENANCE, // Funds reserved on an account to ensure a minimum amount for all open positions ACCOUNT_PROP_ASSETS, // Current assets on an account ACCOUNT_PROP_LIABILITIES, // Current liabilities on an account ACCOUNT_PROP_COMMISSION_BLOCKED // Current sum of blocked commissions on an account }; #define ACCOUNT_PROP_DOUBLE_TOTAL (14) // Total number of account's real properties #define ACCOUNT_PROP_DOUBLE_SKIP (0) // Number of account's real properties not used in sorting //+------------------------------------------------------------------+ //| Account string properties | //+------------------------------------------------------------------+ enum ENUM_ACCOUNT_PROP_STRING { ACCOUNT_PROP_NAME = (ACCOUNT_PROP_INTEGER_TOTAL+ACCOUNT_PROP_DOUBLE_TOTAL), // Client name ACCOUNT_PROP_SERVER, // Trade server name ACCOUNT_PROP_CURRENCY, // Deposit currency ACCOUNT_PROP_COMPANY // Name of a company serving an account }; #define ACCOUNT_PROP_STRING_TOTAL (4) // Total number of account's string properties #define ACCOUNT_PROP_STRING_SKIP (0) // Number of account string properties not used in sorting //+------------------------------------------------------------------+ //| Possible account sorting criteria | //+------------------------------------------------------------------+ #define FIRST_ACC_DBL_PROP (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP) #define FIRST_ACC_STR_PROP (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP) enum ENUM_SORT_ACCOUNT_MODE { SORT_BY_ACCOUNT_LOGIN = 0, // Sort by account number SORT_BY_ACCOUNT_TRADE_MODE = 1, // Sort by trading account type SORT_BY_ACCOUNT_LEVERAGE = 2, // Sort by leverage SORT_BY_ACCOUNT_LIMIT_ORDERS = 3, // Sort by maximum acceptable number of existing pending orders SORT_BY_ACCOUNT_MARGIN_SO_MODE = 4, // Sort by mode for setting the minimum acceptable margin level SORT_BY_ACCOUNT_TRADE_ALLOWED = 5, // Sort by permission to trade for the current account SORT_BY_ACCOUNT_TRADE_EXPERT = 6, // Sort by permission to trade for an EA SORT_BY_ACCOUNT_MARGIN_MODE = 7, // Sort by margin calculation mode SORT_BY_ACCOUNT_CURRENCY_DIGITS = 8, // Sort by number of digits for an account currency SORT_BY_ACCOUNT_BALANCE = FIRST_ACC_DBL_PROP, // Sort by an account balance in the deposit currency SORT_BY_ACCOUNT_CREDIT = FIRST_ACC_DBL_PROP+1, // Sort by credit in a deposit currency SORT_BY_ACCOUNT_PROFIT = FIRST_ACC_DBL_PROP+2, // Sort by the current profit on an account in the deposit currency SORT_BY_ACCOUNT_EQUITY = FIRST_ACC_DBL_PROP+3, // Sort by an account equity in the deposit currency SORT_BY_ACCOUNT_MARGIN = FIRST_ACC_DBL_PROP+4, // Served by an account reserved margin in the deposit currency SORT_BY_ACCOUNT_MARGIN_FREE = FIRST_ACC_DBL_PROP+5, // Sort by account free funds available for opening a position in the deposit currency SORT_BY_ACCOUNT_MARGIN_LEVEL = FIRST_ACC_DBL_PROP+6, // Sort by account margin level in % SORT_BY_ACCOUNT_MARGIN_SO_CALL = FIRST_ACC_DBL_PROP+7, // Sort by margin level requiring depositing funds to an account (Margin Call) SORT_BY_ACCOUNT_MARGIN_SO_SO = FIRST_ACC_DBL_PROP+8, // Sort by margin level, at which the most loss-making position is closed (Stop Out) SORT_BY_ACCOUNT_MARGIN_INITIAL = FIRST_ACC_DBL_PROP+9, // Sort by funds reserved on an account to ensure a guarantee amount for all pending orders SORT_BY_ACCOUNT_MARGIN_MAINTENANCE = FIRST_ACC_DBL_PROP+10, // Sort by funds reserved on an account to ensure a minimum amount for all open positions SORT_BY_ACCOUNT_ASSETS = FIRST_ACC_DBL_PROP+11, // Sort by the amount of the current assets on an account SORT_BY_ACCOUNT_LIABILITIES = FIRST_ACC_DBL_PROP+12, // Sort by the current liabilities on an account SORT_BY_ACCOUNT_COMMISSION_BLOCKED = FIRST_ACC_DBL_PROP+13, // Sort by the current amount of blocked commissions on an account SORT_BY_ACCOUNT_NAME = FIRST_ACC_STR_PROP, // Sort by a client name SORT_BY_ACCOUNT_SERVER = FIRST_ACC_STR_PROP+1, // Sort by a trade server name SORT_BY_ACCOUNT_CURRENCY = FIRST_ACC_STR_PROP+2, // Sort by a deposit currency SORT_BY_ACCOUNT_COMPANY = FIRST_ACC_STR_PROP+3 // Sort by a name of a company serving an account }; //+------------------------------------------------------------------+ //| Data for working with account events | //+------------------------------------------------------------------+
此处的所有内容对于我们来说都是熟悉的,所以没有必要详述如何安置枚举和设置未用对象属性和宏替换,以便传递属性数量来准确计算下一个对象属性的初始
枚举常量的地址。所有这些都已在前面的文章中有所阐述,即在函数库论述的第六部分“净持帐户上实现事件处理”章节。
我们于此唯一可讨论的是“保证金计算模式”:
由于 MQL4 中没有 ENUM_ACCOUNT_MARGIN_MODE 枚举,我们需要在 MQL4 中指定它以便进行编译。 我们将它添加到
ToMQL4.mqh 文件的末尾:
//+------------------------------------------------------------------+ //| Margin calculation mode | //+------------------------------------------------------------------+ enum ENUM_ACCOUNT_MARGIN_MODE { ACCOUNT_MARGIN_MODE_RETAIL_NETTING, ACCOUNT_MARGIN_MODE_EXCHANGE, ACCOUNT_MARGIN_MODE_RETAIL_HEDGING }; //+------------------------------------------------------------------+
现在,当准备好所有数据之后,就可以创建一个帐户对象。
在 \MQL5\Include\DoEasy\Objects\ 函数库文件夹中,创建Accounts 子文件夹,并在 Account.mqh 文件中引入新的 CAccount
类。
在新创建的类文件中,添加所有必要方法的声明。
这些方法中的大多数依然是函数库对象的“标准”。 然而,在此有一个小警告:由于这个类并不意味着后代的存在,因此其中没有受保护的类构造函数来接受和设置对象的状态。
因此,帐户对象没有 “status” 属性,其
构造函数不接受任何参数。 同时,我们将保留返回对象支持某个属性标志的虚方法,用于未来可能的类后代:
//+------------------------------------------------------------------+ //| Account.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Object.mqh> #include "..\..\Services\DELib.mqh" //+------------------------------------------------------------------+ //| Account class | //+------------------------------------------------------------------+ class CAccount : public CObject { private: long m_long_prop[ACCOUNT_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[ACCOUNT_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[ACCOUNT_PROP_STRING_TOTAL]; // String properties //--- Return the index of the array the account's (1) double and (2) string properties are actually located at int IndexProp(ENUM_ACCOUNT_PROP_DOUBLE property) const { return(int)property-ACCOUNT_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_ACCOUNT_PROP_STRING property) const { return(int)property-ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_DOUBLE_TOTAL;} public: //--- Constructor CAccount(void); protected: public: //--- Set (1) integer, (2) real and (3) string properties of an account void SetProperty(ENUM_ACCOUNT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_ACCOUNT_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_ACCOUNT_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string properties of an account from the property array long GetProperty(ENUM_ACCOUNT_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_ACCOUNT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_ACCOUNT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Return the flag of calculating MarginCall and StopOut levels in % bool IsPercentsForSOLevels(void) const { return this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT; } //--- Return the flag of supporting the property by the account object virtual bool SupportProperty(ENUM_ACCOUNT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_ACCOUNT_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_ACCOUNT_PROP_STRING property) { return true; } //--- Compare CAccount objects by all possible properties (for sorting the lists by a specified account object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CAccount objects by account properties (to search for equal account objects) bool IsEqual(CAccount* compared_account) const; //+------------------------------------------------------------------+ //| Methods of a simplified access to the account object properties | //+------------------------------------------------------------------+ //--- Return the account's integer properties 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); } 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); } 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); } //--- Return the account's string properties 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); } //+------------------------------------------------------------------+ //| Descriptions of the account object properties | //+------------------------------------------------------------------+ //--- Return the description of the account's (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property); string GetPropertyDescription(ENUM_ACCOUNT_PROP_DOUBLE property); string GetPropertyDescription(ENUM_ACCOUNT_PROP_STRING property); //--- Return a name of a trading account type string TradeModeDescription(void) const; //--- Return the description of the mode for setting the minimum available margin level string MarginSOModeDescription(void) const; //--- Return the description of the margin calculation mode string MarginModeDescription(void) const; //--- Display the description of the account properties in the journal (full_prop=true - all properties, false - supported ones only) void Print(const bool full_prop=false); //--- Display a short account description in the journal void PrintShort(void); //--- }; //+------------------------------------------------------------------+
在类的实体之外编写类构造函数:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccount::CAccount(void) { //--- Save integer properties this.m_long_prop[ACCOUNT_PROP_LOGIN] = ::AccountInfoInteger(ACCOUNT_LOGIN); this.m_long_prop[ACCOUNT_PROP_TRADE_MODE] = ::AccountInfoInteger(ACCOUNT_TRADE_MODE); this.m_long_prop[ACCOUNT_PROP_LEVERAGE] = ::AccountInfoInteger(ACCOUNT_LEVERAGE); this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS] = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE] = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED] = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT] = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT); this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ; this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #else 2 #endif ; //--- Save real properties this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)] = ::AccountInfoDouble(ACCOUNT_BALANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)] = ::AccountInfoDouble(ACCOUNT_CREDIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)] = ::AccountInfoDouble(ACCOUNT_PROFIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)] = ::AccountInfoDouble(ACCOUNT_EQUITY); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)] = ::AccountInfoDouble(ACCOUNT_MARGIN); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)] = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)] = ::AccountInfoDouble(ACCOUNT_ASSETS); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)] = ::AccountInfoDouble(ACCOUNT_LIABILITIES); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED); //--- Save string properties this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)] = ::AccountInfoString(ACCOUNT_NAME); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)] = ::AccountInfoString(ACCOUNT_SERVER); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)] = ::AccountInfoString(ACCOUNT_CURRENCY); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)] = ::AccountInfoString(ACCOUNT_COMPANY); } //+-------------------------------------------------------------------+
此处的一切都很清楚:使用 AccountInfo 函数为每个对象属性分配相应的帐户属性。
对于
MQL4 当中不存在的两个属性,在条件编译指令期间进行选择:我们得到“保证金计算模式”和 MQL5 帐户的“账户货币小数点位数”相应的属性,而对于 MQL4,只需从
ENUM_ACCOUNT_MARGIN_MODE
枚举为第一个属性返回
ACCOUNT_MARGIN_MODE_RETAIL_HEDGING (对冲帐户),以及为第二个属性返回两个小数位。
我们来实现在其集合列表中搜索和排序帐户对象的方法。
该方法与函数库对象中先前描述的相同。
因此,我在此仅提供其清单:
//+-------------------------------------------------------------------+ //|Compare CAccount objects by all possible properties | //+-------------------------------------------------------------------+ int CAccount::Compare(const CObject *node,const int mode=0) const { const CAccount *account_compared=node; //--- compare integer properties of two accounts if(mode<ACCOUNT_PROP_INTEGER_TOTAL) { long value_compared=account_compared.GetProperty((ENUM_ACCOUNT_PROP_INTEGER)mode); long value_current=this.GetProperty((ENUM_ACCOUNT_PROP_INTEGER)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } //--- comparing real properties of two accounts else if(mode<ACCOUNT_PROP_DOUBLE_TOTAL+ACCOUNT_PROP_INTEGER_TOTAL) { double value_compared=account_compared.GetProperty((ENUM_ACCOUNT_PROP_DOUBLE)mode); double value_current=this.GetProperty((ENUM_ACCOUNT_PROP_DOUBLE)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } //--- comparing string properties of two accounts else if(mode<ACCOUNT_PROP_DOUBLE_TOTAL+ACCOUNT_PROP_INTEGER_TOTAL+ACCOUNT_PROP_STRING_TOTAL) { string value_compared=account_compared.GetProperty((ENUM_ACCOUNT_PROP_STRING)mode); string value_current=this.GetProperty((ENUM_ACCOUNT_PROP_STRING)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } return 0; } //+------------------------------------------------------------------+
若要比较两个帐户对象,我们需要比较它们的未变更属性,以便确定这些对象是否属于不同的帐户。 要提供帐号(登录),用户名和公司名称以便准确识别。 在帐户对象的比较方法中,这些是要检查并比较的属性:
//+------------------------------------------------------------------+ //| Compare CAccount objects by account properties | //+------------------------------------------------------------------+ bool CAccount::IsEqual(CAccount *compared_account) const { if(this.GetProperty(ACCOUNT_PROP_COMPANY)!=compared_account.GetProperty(ACCOUNT_PROP_COMPANY) || this.GetProperty(ACCOUNT_PROP_LOGIN)!=compared_account.GetProperty(ACCOUNT_PROP_LOGIN) || this.GetProperty(ACCOUNT_PROP_NAME)!=compared_account.GetProperty(ACCOUNT_PROP_NAME) ) return false; return true; } //+------------------------------------------------------------------+
指向比较对象的指针会传递给方法,其中两个对象的三个属性(公司名称,帐号和客户端名称均要被检查)。
如果任何对象属性不相等,则它们属于不同的帐户 —
返回 false。
所有三次比较成功通过后,
返回
true — 对象相等。
其它类方法 作为“服务”,不需要展示,因为它们的清单里包含所有必要的信息。 此外,我们在函数库论述的前面部分分析过类似的方法:
//+------------------------------------------------------------------+ //| Display account properties in the journal | //+------------------------------------------------------------------+ void CAccount::Print(const bool full_prop=false) { ::Print("============= ",TextByLanguage("Начало списка параметров аккаунта","Beginning of Account parameter list")," =================="); int beg=0, end=ACCOUNT_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_ACCOUNT_PROP_INTEGER prop=(ENUM_ACCOUNT_PROP_INTEGER)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=ACCOUNT_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_ACCOUNT_PROP_DOUBLE prop=(ENUM_ACCOUNT_PROP_DOUBLE)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=ACCOUNT_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_ACCOUNT_PROP_STRING prop=(ENUM_ACCOUNT_PROP_STRING)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("================== ",TextByLanguage("Конец списка параметров аккаунта","End of Account parameter list")," ==================\n"); } //+------------------------------------------------------------------+ //| Display the brief account description in the journal | //+------------------------------------------------------------------+ void CAccount::PrintShort(void) { string mode=(this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? ", Hedge" : this.MarginMode()==ACCOUNT_MARGIN_MODE_EXCHANGE ? ", Exhange" : ""); string names=TextByLanguage("Счёт ","Account ")+(string)this.Login()+": "+this.Name()+" ("+this.Company()+" "; string values=DoubleToString(this.Balance(),(int)this.CurrencyDigits())+" "+this.Currency()+", 1:"+(string)+this.Leverage()+mode+", "+this.TradeModeDescription()+")"; ::Print(names,values); } //+------------------------------------------------------------------+ //| Display a description of an account integer property | //+------------------------------------------------------------------+ string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property) { return ( property==ACCOUNT_PROP_LOGIN ? TextByLanguage("Номер счёта","Account number")+": "+(string)this.GetProperty(property) : property==ACCOUNT_PROP_TRADE_MODE ? TextByLanguage("Тип торгового счета","Account trade mode")+": "+this.TradeModeDescription() : property==ACCOUNT_PROP_LEVERAGE ? TextByLanguage("Размер предоставленного плеча","Account leverage")+": "+(string)this.GetProperty(property) : property==ACCOUNT_PROP_LIMIT_ORDERS ? TextByLanguage("Максимально допустимое количество действующих отложенных ордеров","Maximum allowed number of active pending orders")+": "+ (string)this.GetProperty(property) : property==ACCOUNT_PROP_MARGIN_SO_MODE ? TextByLanguage("Режим задания минимально допустимого уровня залоговых средств","Mode for setting the minimal allowed margin")+": "+ this.MarginSOModeDescription() : property==ACCOUNT_PROP_TRADE_ALLOWED ? TextByLanguage("Разрешенность торговли для текущего счета","Allowed trade for the current account")+": "+ (this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) : property==ACCOUNT_PROP_TRADE_EXPERT ? TextByLanguage("Разрешенность торговли для эксперта","Allowed trade for an Expert Advisor")+": "+ (this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) : property==ACCOUNT_PROP_MARGIN_MODE ? TextByLanguage("Режим расчета маржи","Margin calculation mode")+": "+ this.MarginModeDescription() : property==ACCOUNT_PROP_CURRENCY_DIGITS ? TextByLanguage("Количество знаков после запятой для валюты счета","Number of decimal places in account currency")+": "+ (string)this.GetProperty(property) : "" ); } //+------------------------------------------------------------------+ //| Return a description of an account real property | //+------------------------------------------------------------------+ string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_DOUBLE property) { return ( property==ACCOUNT_PROP_BALANCE ? TextByLanguage("Баланс счета","Account balance")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency() : property==ACCOUNT_PROP_CREDIT ? TextByLanguage("Предоставленный кредит","Account credit")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency() : property==ACCOUNT_PROP_PROFIT ? TextByLanguage("Текущая прибыль на счете","Current profit of an account")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency() : property==ACCOUNT_PROP_EQUITY ? TextByLanguage("Собственные средства на счете","Account equity")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency() : property==ACCOUNT_PROP_MARGIN ? TextByLanguage("Зарезервированные залоговые средства на счете","Account margin used in deposit currency")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency() : property==ACCOUNT_PROP_MARGIN_FREE ? TextByLanguage("Свободные средства на счете, доступные для открытия позиции","Account free margin")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits()) : property==ACCOUNT_PROP_MARGIN_LEVEL ? TextByLanguage("Уровень залоговых средств на счете в процентах","Account margin level in percentage")+": "+ ::DoubleToString(this.GetProperty(property),1)+"%" : property==ACCOUNT_PROP_MARGIN_SO_CALL ? TextByLanguage("Уровень залоговых средств для наступления Margin Call","Margin call level")+": "+ ::DoubleToString(this.GetProperty(property),(this.IsPercentsForSOLevels() ? 1 : (int)this.CurrencyDigits()))+ (this.IsPercentsForSOLevels() ? "%" : this.Currency()) : property==ACCOUNT_PROP_MARGIN_SO_SO ? TextByLanguage("Уровень залоговых средств для наступления Stop Out","Margin stop out level")+": "+ ::DoubleToString(this.GetProperty(property),(this.IsPercentsForSOLevels() ? 1 : (int)this.CurrencyDigits()))+ (this.IsPercentsForSOLevels() ? "%" : this.Currency()) : property==ACCOUNT_PROP_MARGIN_INITIAL ? TextByLanguage("Зарезервированные средства для обеспечения гарантийной суммы по всем отложенным ордерам","Amount reserved on account to cover margin of all pending orders ")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits()) : property==ACCOUNT_PROP_MARGIN_MAINTENANCE ? TextByLanguage("Зарезервированные средства для обеспечения минимальной суммы по всем открытым позициям","Min equity reserved on account to cover min amount of all open positions")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits()) : property==ACCOUNT_PROP_ASSETS ? TextByLanguage("Текущий размер активов на счёте","Current account assets")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits()) : property==ACCOUNT_PROP_LIABILITIES ? TextByLanguage("Текущий размер обязательств на счёте","Current liabilities on account")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits()) : property==ACCOUNT_PROP_COMMISSION_BLOCKED ? TextByLanguage("Сумма заблокированных комиссий по счёту","Currently blocked commission amount on account")+": "+ ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits()) : "" ); } //+------------------------------------------------------------------+ //| Return a description of an account string property | //+------------------------------------------------------------------+ string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_STRING property) { return ( property==ACCOUNT_PROP_NAME ? TextByLanguage("Имя клиента","Client name")+": \""+this.GetProperty(property)+"\"" : property==ACCOUNT_PROP_SERVER ? TextByLanguage("Имя торгового сервера","Trade server name")+": \""+this.GetProperty(property)+"\"" : property==ACCOUNT_PROP_CURRENCY ? TextByLanguage("Валюта депозита","Account currency")+": \""+this.GetProperty(property)+"\"" : property==ACCOUNT_PROP_COMPANY ? TextByLanguage("Имя компании, обслуживающей счет","Name of company that serves account")+": \""+this.GetProperty(property)+"\"" : "" ); } //+------------------------------------------------------------------+ //| Return a trading account type name | //+------------------------------------------------------------------+ string CAccount::TradeModeDescription(void) const { return ( this.TradeMode()==ACCOUNT_TRADE_MODE_DEMO ? TextByLanguage("Демонстрационный счёт","Demo account") : this.TradeMode()==ACCOUNT_TRADE_MODE_CONTEST ? TextByLanguage("Конкурсный счёт","Contest account") : this.TradeMode()==ACCOUNT_TRADE_MODE_REAL ? TextByLanguage("Реальный счёт","Real account") : TextByLanguage("Неизвестный тип счёта","Unknown account type") ); } //+------------------------------------------------------------------+ //| Return a description of a mode for setting | //| minimum available margin level | //+------------------------------------------------------------------+ string CAccount::MarginSOModeDescription(void) const { return ( this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT ? TextByLanguage("Уровень задается в процентах","Account stop out mode in percentage") : TextByLanguage("Уровень задается в деньгах","Account stop out mode in money") ); } //+------------------------------------------------------------------+ //| Return a description of a margin calculation mode | //+------------------------------------------------------------------+ string CAccount::MarginModeDescription(void) const { return ( this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_NETTING ? TextByLanguage("Внебиржевой рынок в режиме \"Неттинг\"","Netting mode") : this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? TextByLanguage("Внебиржевой рынок в режиме \"Хеджинг\"","Hedging mode") : TextByLanguage("Биржевой рынок","Exchange markets mode") ); } //+------------------------------------------------------------------+
可在文后附加的文件中查找帐户类的完整清单。 我们来测试该类。
测试帐户对象
若要检查该类是否正确接收帐户数据,临时将类文件包含到函数库的主对象— CEngine 类:
//+------------------------------------------------------------------+ //| Engine.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Collections\HistoryCollection.mqh" #include "Collections\MarketCollection.mqh" #include "Collections\EventsCollection.mqh" #include "Services\TimerCounter.mqh" #include "Objects\Accounts\Account.mqh" //+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine : public CObject {
包含帐户对象类文件后,程序可以看到该对象。
为了类测试目的,请从上一篇文章 — \MQL5\Experts\TestDoEasy\Part11\TestDoEasyPart11.mq5 中获取 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part12 文件夹中,并命名为 TestDoEasyPart12_1.mq5。
若要包含并测试帐户对象,在 EA 的 OnInit() 处理程序里加入代码(在初始化期间执行检查):
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal, //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- Check and remove remaining EA graphical objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- Set trailing activation button status ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- Set CTrade trading class parameters #ifdef __MQL5__ trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); #endif //--- Fast check of the account object CAccount* acc=new CAccount(); if(acc!=NULL) { acc.PrintShort(); acc.Print(); delete acc; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
在此创建帐户对象,如果成功,则在日志中显示简要帐户数据,以及帐户参数的完整清单。 完成后 删除帐户对象。
在任意品种的图表上启动 EA,并查看智能系统日志:
所有帐户数据都正确显示。
账户对象集合
由于在帐户变更时会重新初始化所有 EA,因此首先调用析构函数,然后调用类构造函数。 因此,EA 在帐户变更之前会丢失先前存在的帐户对象。 为了维护帐户集合,我们需要记住终端先前所连接帐户的数据。
为此,在帐户集合类析构函数中会将当前帐户对象的数据保存到文件之中,并在类构造函数中从文件加载数据。 因此,基于函数库的程序在操作期间会将终端所连接的所有帐户的数据填充到帐户集合中。
由于文件存储在所有客户终端的公共文件夹中,所以每个启动的终端均可看到电脑连接的所有帐户,前提是终端中工作的程序都要基于函数库。
新的帐户集合类将能够根据不同的参数比较所有现有帐户的数据。
若要将帐户对象保存到文件,我们需要在 CAccount 类中创建将数据保存到文件的方法。
所有创建的对象都继承自 CObject
— 标准库的基础对象。 该类还含有用来将对象保存和加载到文件中的虚拟方法:
//+------------------------------------------------------------------+ //| Class CObject. | //| Purpose: Base class for storing elements. | //+------------------------------------------------------------------+ class CObject { private: CObject *m_prev; // previous item of list CObject *m_next; // next item of list public: CObject(void): m_prev(NULL),m_next(NULL) { } ~CObject(void) { } //--- methods to access protected data CObject *Prev(void) const { return(m_prev); } void Prev(CObject *node) { m_prev=node; } CObject *Next(void) const { return(m_next); } void Next(CObject *node) { m_next=node; } //--- methods for working with files virtual bool Save(const int file_handle) { return(true); } virtual bool Load(const int file_handle) { return(true); } //--- method of identifying the object virtual int Type(void) const { return(0); } //--- method of comparing the objects virtual int Compare(const CObject *node,const int mode=0) const { return(0); } }; //+------------------------------------------------------------------+
这些方法什么都不做,我们会在需要它们的后代类中重新定义它们(CAccount 类)。
若要将所有帐户对象属性保存到文件,我们将用到一个简单的结构,并将其保存到文件中。 然而,对象字段包含若干行,这意味着这不是 POD 结构。 因此,在将对象保存到结构字段固定大小的 uchar 数组时,需转换对象的所有字符串型属性。 在这种情况下,我们能够利用 FileWriteArray() 函数将帐户对象属性上的所有数据按结构保存到文件。
若要创建存储函数库文件和固定大小 uchar 数组的目录,请在 Defines.mqh 文件中创建宏替换:
#define DIRECTORY ("DoEasy\\") // Library directory for placing class object folders #define UCHAR_ARRAY_SIZE (64) // Size of uchar arrays for storing string properties
由于注释行的长度限制为 64 个字符,因此会为数组创建相应的大小。 此外,我们可能需要将订单对象保存到文件中,若小于 64 个字符的长度可能会不合适。 事实证明,应为帐户字符串型属性分配更大的字符串大小。 如果测试显示尺寸不足以存储该帐户服务公司的名称,则可以随时增加。
在 CAccount 类的私有部分中创建用于保存帐户对象属性的结构和存取文件的类成员变量:
//+------------------------------------------------------------------+ //| Account class | //+------------------------------------------------------------------+ class CAccount : public CObject { private: struct SData { //--- Account integer properties long login; // ACCOUNT_LOGIN (Account number) int trade_mode; // ACCOUNT_TRADE_MODE (Trading account type) long leverage; // ACCOUNT_LEVERAGE (Leverage) int limit_orders; // ACCOUNT_LIMIT_ORDERS (Maximum allowed number of active pending orders) int margin_so_mode; // ACCOUNT_MARGIN_SO_MODE (Mode of setting the minimum available margin level) bool trade_allowed; // ACCOUNT_TRADE_ALLOWED (Permission to trade for the current account from the server side) bool trade_expert; // ACCOUNT_TRADE_EXPERT (Permission to trade for an EA from the server side) int margin_mode; // ACCOUNT_MARGIN_MODE (Margin calculation mode) int currency_digits; // ACCOUNT_CURRENCY_DIGITS (Number of digits for an account currency) //--- Account real properties double balance; // ACCOUNT_BALANCE (Account balance in the deposit currency) double credit; // ACCOUNT_CREDIT (Credit in the deposit currency) double profit; // ACCOUNT_PROFIT (Current profit on an account in the deposit currency) double equity; // ACCOUNT_EQUITY (Equity on an account in the deposit currency) double margin; // ACCOUNT_MARGIN (Reserved margin on an account in the deposit currency) double margin_free; // ACCOUNT_MARGIN_FREE (Free funds available for opening a position in the deposit currency) double margin_level; // ACCOUNT_MARGIN_LEVEL (Margin level on an account in %) double margin_so_call; // ACCOUNT_MARGIN_SO_CALL (Margin Call level) double margin_so_so; // ACCOUNT_MARGIN_SO_SO (StopOut level) double margin_initial; // ACCOUNT_MARGIN_INITIAL (Funds reserved on an account to ensure a guarantee amount for all pending orders) double margin_maintenance; // ACCOUNT_MARGIN_MAINTENANCE (Funds reserved on an account to ensure a minimum amount for all open positions) double assets; // ACCOUNT_ASSETS (Current assets on an account) double liabilities; // ACCOUNT_LIABILITIES (Current liabilities on an account) double comission_blocked; // ACCOUNT_COMMISSION_BLOCKED (Current sum of blocked commissions on an account) //--- Account string properties uchar name[UCHAR_ARRAY_SIZE]; // ACCOUNT_NAME (Client name) uchar server[UCHAR_ARRAY_SIZE]; // ACCOUNT_SERVER (Trade server name) uchar currency[UCHAR_ARRAY_SIZE]; // ACCOUNT_CURRENCY (Deposit currency) uchar company[UCHAR_ARRAY_SIZE]; // ACCOUNT_COMPANY (Name of a company serving an account) }; SData m_struct_obj; // Account object structure uchar m_uchar_array[]; // uchar array of the account object structure //--- Object properties long m_long_prop[ACCOUNT_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[ACCOUNT_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[ACCOUNT_PROP_STRING_TOTAL]; // String properties //--- Return the array of the index the account (1) double and (2) string properties are actually located at int IndexProp(ENUM_ACCOUNT_PROP_DOUBLE property) const { return(int)property-ACCOUNT_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_ACCOUNT_PROP_STRING property) const { return(int)property-ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_DOUBLE_TOTAL; } protected: //--- Create (1) the account object structure and (2) the account object from the structure bool ObjectToStruct(void); void StructToObject(void); public:根据清单,在 CAccount 类的受保护部分中另外声明了两个方法 — 依据对象属性字段创建结构的方法,以及逆向的依据结构创建帐户对象的方法。
第一种方法用于将帐户对象写入文件,而第二种方法用于从文件中读取。
我们在类的实体之外编写方法:
//+------------------------------------------------------------------+ //| Create the account object structure | //+------------------------------------------------------------------+ bool CAccount::ObjectToStruct(void) { //--- Save the integer properties this.m_struct_obj.login=this.Login(); this.m_struct_obj.trade_mode=this.TradeMode(); this.m_struct_obj.leverage=this.Leverage(); this.m_struct_obj.limit_orders=(int)this.LimitOrders(); this.m_struct_obj.margin_so_mode=this.MarginSOMode(); this.m_struct_obj.trade_allowed=this.TradeAllowed(); this.m_struct_obj.trade_expert=this.TradeExpert(); this.m_struct_obj.margin_mode=this.MarginMode(); this.m_struct_obj.currency_digits=(int)this.CurrencyDigits(); //--- Save the real properties this.m_struct_obj.balance=this.Balance(); this.m_struct_obj.credit=this.Credit(); this.m_struct_obj.profit=this.Profit(); this.m_struct_obj.equity=this.Equity(); this.m_struct_obj.margin=this.Margin(); this.m_struct_obj.margin_free=this.MarginFree(); this.m_struct_obj.margin_level=this.MarginLevel(); this.m_struct_obj.margin_so_call=this.MarginSOCall(); this.m_struct_obj.margin_so_so=this.MarginSOSO(); this.m_struct_obj.margin_initial=this.MarginInitial(); this.m_struct_obj.margin_maintenance=this.MarginMaintenance(); this.m_struct_obj.assets=this.Assets(); this.m_struct_obj.liabilities=this.Liabilities(); this.m_struct_obj.comission_blocked=this.ComissionBlocked(); //--- Save the string properties ::StringToCharArray(this.Name(),this.m_struct_obj.name); ::StringToCharArray(this.Server(),this.m_struct_obj.server); ::StringToCharArray(this.Currency(),this.m_struct_obj.currency); ::StringToCharArray(this.Company(),this.m_struct_obj.company); //--- Saving the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { ::Print(DFUN,TextByLanguage("Не удалось сохранить структуру объекта в uchar-массив, ошибка ","Failed to save object structure to uchar array, error "),(string)::GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
正如我们在清单中所看到的,所有整数型和实数型对象属性都保存在同名的结构字段中。 若要保存字符串型属性,将字符串转换为
uchar 数组,并将其保存在相应的结构字段中。
保存对象属性之后,整个结构将保存到
uchar 数组里,然后保存到文件中。
//+------------------------------------------------------------------+ //| Create the account object from the structure | //+------------------------------------------------------------------+ void CAccount::StructToObject(void) { //--- Save integer properties this.m_long_prop[ACCOUNT_PROP_LOGIN] = this.m_struct_obj.login; this.m_long_prop[ACCOUNT_PROP_TRADE_MODE] = this.m_struct_obj.trade_mode; this.m_long_prop[ACCOUNT_PROP_LEVERAGE] = this.m_struct_obj.leverage; this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS] = this.m_struct_obj.limit_orders; this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE] = this.m_struct_obj.margin_so_mode; this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED] = this.m_struct_obj.trade_allowed; this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT] = this.m_struct_obj.trade_expert; this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE] = this.m_struct_obj.margin_mode; this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS] = this.m_struct_obj.currency_digits; //--- Save real properties this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)] = this.m_struct_obj.balance; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)] = this.m_struct_obj.credit; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)] = this.m_struct_obj.profit; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)] = this.m_struct_obj.equity; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)] = this.m_struct_obj.margin; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)] = this.m_struct_obj.margin_free; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)] = this.m_struct_obj.margin_level; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = this.m_struct_obj.margin_so_call; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)] = this.m_struct_obj.margin_so_so; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)] = this.m_struct_obj.margin_initial; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=this.m_struct_obj.margin_maintenance; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)] = this.m_struct_obj.assets; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)] = this.m_struct_obj.liabilities; this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=this.m_struct_obj.comission_blocked; //--- Save string properties this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)] = ::CharArrayToString(this.m_struct_obj.name); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)] = ::CharArrayToString(this.m_struct_obj.server); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)] = ::CharArrayToString(this.m_struct_obj.currency); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)] = ::CharArrayToString(this.m_struct_obj.company); } //+------------------------------------------------------------------+
将结构字段逆向转换为帐户对象属性的方法几乎与上面讨论的第一个相同。
此处,通过将结构的
uchar 数组转换为字符串来获取对象帐户字符串型属性。
在 CAccount 类的公有部分中,声明 Save() 和 Load() 虚拟方法:
public: //--- Constructor CAccount(void); //--- Set (1) integer, (2) real and (3) string account properties void SetProperty(ENUM_ACCOUNT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_ACCOUNT_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_ACCOUNT_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string account properties from the properties array long GetProperty(ENUM_ACCOUNT_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_ACCOUNT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_ACCOUNT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Return the flag of the MarginCall and StopOut levels calculation in % bool IsPercentsForSOLevels(void) const { return this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT; } //--- Return the flag of the order supporting the property virtual bool SupportProperty(ENUM_ACCOUNT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_ACCOUNT_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_ACCOUNT_PROP_STRING property) { return true; } //--- Compare CAccount objects with one another by all possible properties (for sorting the lists by a specified account object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CAccount objects by account properties (to search for equal account objects) bool IsEqual(CAccount* compared_account) const; //--- (1) Save the account object to the file, (2), download the account object from the file virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); //+------------------------------------------------------------------+ //| Methods of simplified access to the account object properties | //+------------------------------------------------------------------+
我们编写将帐户对象保存到文件,并从文件中加载的方法:
//+------------------------------------------------------------------+ //| Save the account object to the file | //+------------------------------------------------------------------+ bool CAccount::Save(const int file_handle) { if(!this.ObjectToStruct()) { Print(DFUN,TextByLanguage("Не удалось создать структуру объекта.","Could not create object structure")); return false; } if(::FileWriteArray(file_handle,this.m_uchar_array)==0) { Print(DFUN,TextByLanguage("Не удалось записать uchar-массив в файл.","Could not write uchar array to file")); return false; } return true; } //+------------------------------------------------------------------+
其中:
- 已按写入模式打开的文件句柄传递给该方法,
- 将所有对象字段保存到 POD 结构,
- 该方法将 POD 结构写入所接收到句柄的文件
从文件加载对象数据的方法:
//+------------------------------------------------------------------+ //| Download the account object from the file | //+------------------------------------------------------------------+ bool CAccount::Load(const int file_handle) { if(::FileReadArray(file_handle,this.m_uchar_array)==0) { Print(DFUN,TextByLanguage("Не удалось загрузить uchar-массив из файла.","Could not load uchar array from file")); return false; } if(!::CharArrayToStruct(this.m_struct_obj,this.m_uchar_array)) { Print(DFUN,TextByLanguage("Не удалось создать структуру объекта из uchar-массива.","Could not create object structure from uchar array")); return false; } this.StructToObject(); return true; } //+------------------------------------------------------------------+
其中:
- 将之前已打开供读取的文件句柄传递给该方法
- 从 文件 加载数据至 uchar 数组
- 将数组数据保存到 POD 结构
- 将 POD 结构数据写入对象字段
帐户集合的操作将按以下方式安排:当启动执行程序时,检查当前帐户,创建含有当前帐户数据的帐户对象,并将其置入帐户集合列表中。 接着,我们将查看包含以前保存有帐户文件的文件夹。 如果它包含文件,我们将逐一阅读它们,检查是否与当前帐户保持一致性,并将它们放入帐户集合列表。 创建列表后,定时检查当前帐户的状态,并记录发生的变化(如果有)。
对于某些变化,我们将创建事件,并将其发送到程序,以便控制帐户参数的变更。 例如,杠杆的突然变化是一个非常现实且令人不快的事件,应该及时通知用户和他/她的程序。
由于我们需要在定时器中工作,以及创建一个新的集合列表,我们将在 Defines.mqh 文件里为它们创建带有定时器参数和列表 ID 的宏替换。 此外,请确保修改先前为订单、成交和仓位集合计时器创建的宏替换的名称(将 “ORD” 添加到其名称,以区分属于不同集合计时器的宏替换)。 设置更新帐户数据的暂停为 一秒。 我认为,这足以跟踪变化,并减少系统负荷:
//+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ //--- Describe the function with the error line number #define DFUN_ERR_LINE (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ") #define DFUN (__FUNCTION__+": ") // "Function description" #define COUNTRY_LANG ("Russian") // Country language #define END_TIME (D'31.12.3000 23:59:59') // End date for requesting account history data #define TIMER_FREQUENCY (16) // Minimal frequency of the library timer in milliseconds //--- Parameters of orders and deals collection timer #define COLLECTION_ORD_PAUSE (250) // Orders and deals collection timer pause in milliseconds #define COLLECTION_ORD_COUNTER_STEP (16) // Increment of the orders and deals collection timer counter #define COLLECTION_ORD_COUNTER_ID (1) // Orders and deals collection timer counter ID //--- Parameters of the account collection timer #define COLLECTION_ACC_PAUSE (1000) // Account collection timer pause in milliseconds #define COLLECTION_ACC_COUNTER_STEP (16) // Account timer counter increment #define COLLECTION_ACC_COUNTER_ID (2) // Account timer counter ID //--- Collection list IDs #define COLLECTION_HISTORY_ID (0x7778+1) // Historical collection list ID #define COLLECTION_MARKET_ID (0x7778+2) // Market collection list ID #define COLLECTION_EVENTS_ID (0x7778+3) // Event collection list ID #define COLLECTION_ACCOUNT_ID (0x7778+4) // Account collection list ID //--- Data parameters for file operations #define DIRECTORY ("DoEasy\\") // Library directory for storing object folders #define UCHAR_ARRAY_SIZE (64) // Size of uchar arrays for storing string properties
在 CEngine 类的文本里,将 COLLECTION_PAUSE, COLLECTION_COUNTER_STEP 和 COLLECTION_COUNTER_ID 替换为相应的新宏替换名: COLLECTION_ORD_PAUSE,COLLECTION_ORD_COUNTER_STEP 和 COLLECTION_ORD_COUNTER_ID。
由于我们创建了帐户集合,因此这意味着能够比较多个帐户对象的属性。 为此,为 CSelect 类中的帐户集合添加选择和排序方法,以便选择符合条件的对象。 该类已在函数库论述的第三部分里有所阐述。
打开已在 \MQL5\Include\DoEasy\Services 函数库服务类文件夹中选择的 Select.mqh 文件,将含有帐户类的文件与其关联,并添加新的帐户对象操作方法:
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Arrays\ArrayObj.mqh> #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\Event.mqh" #include "..\Objects\Accounts\Account.mqh" //+------------------------------------------------------------------+ //| Storage list | //+------------------------------------------------------------------+ CArrayObj ListStorage; // Storage object for storing sorted collection lists //+------------------------------------------------------------------+ //| Class for sorting objects meeting the criterion | //+------------------------------------------------------------------+ class CSelect { private: //--- Two values comparison method template<typename T> static bool CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode); public: //+------------------------------------------------------------------+ //| Methods of working with orders | //+------------------------------------------------------------------+ //--- Return the list of orders with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the order index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property); static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property); static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property); //--- Return the order index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property); static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property); static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with events | //+------------------------------------------------------------------+ //--- Return the list of events with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the event index with the maximum value of the event's (1) integer, (2) real and (3) string properties static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property); static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property); static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property); //--- Return the event index with the minimum value of the event's (1) integer, (2) real and (3) string properties static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property); static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property); static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with accounts | //+------------------------------------------------------------------+ //--- Return the list of accounts with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the event index with the maximum value of the event's (1) integer, (2) real and (3) string properties static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property); static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property); static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property); //--- Return the event index with the minimum value of the event's (1) integer, (2) real and (3) string properties static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property); static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property); static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property); }; //+------------------------------------------------------------------+
在类的实体之外实现声明的方法:
//+------------------------------------------------------------------+ //| Methods of working with account lists | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the list of accounts with one integer | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); int total=list_source.Total(); for(int i=0; i<total; i++) { CAccount *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; long obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of accounts with one real | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CAccount *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; double obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of accounts with one string | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CAccount *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; string obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the account index in the list | //| with the maximum integer property value | //+------------------------------------------------------------------+ int CSelect::FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CAccount *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CAccount *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); long obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the account index in the list | //| with the maximum real property value | //+------------------------------------------------------------------+ int CSelect::FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CAccount *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CAccount *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); double obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the account index in the list | //| with the maximum string property value | //+------------------------------------------------------------------+ int CSelect::FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CAccount *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CAccount *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); string obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the account index in the list | //| with the minimum integer property value | //+------------------------------------------------------------------+ int CSelect::FindAccountMin(CArrayObj* list_source,ENUM_ACCOUNT_PROP_INTEGER property) { int index=0; CAccount* min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++){ CAccount* obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); long obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the account index in the list | //| with the minimum real property value | //+------------------------------------------------------------------+ int CSelect::FindAccountMin(CArrayObj* list_source,ENUM_ACCOUNT_PROP_DOUBLE property) { int index=0; CAccount* min_obj=NULL; int total=list_source.Total(); if(total== 0) return WRONG_VALUE; for(int i=1; i<total; i++){ CAccount* obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); double obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the account index in the list | //| with the minimum string property value | //+------------------------------------------------------------------+ int CSelect::FindAccountMin(CArrayObj* list_source,ENUM_ACCOUNT_PROP_STRING property) { int index=0; CAccount* min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++){ CAccount* obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); string obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+
这些方法的操作已在函数库论述的第三部分进行了研究,因此我们不必在此赘述。 如有必要,您可以随时返回到必要的文章。
我们来创建一个帐户集合类的工作片段。
在 MQL5\Include\DoEasy\Collections\ 函数库文件中,创建新的 AccountsCollection.mqh 类文件,包含必要的类文件,并立即用标准方法填写它:
//+------------------------------------------------------------------+ //| AccountsCollection.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Accounts\Account.mqh" //+------------------------------------------------------------------+ //| Account class | //+------------------------------------------------------------------+ class CAccountsCollection : public CListObj { private: CListObj m_list_accounts; // List of account objects public: //--- Return the full event collection list "as is" CArrayObj *GetList(void) { return &this.m_list_accounts; } //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode); } //--- Constructor CAccountsCollection(); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccountsCollection::CAccountsCollection(void) { this.m_list_accounts.Clear(); this.m_list_accounts.Sort(SORT_BY_ACCOUNT_LOGIN); this.m_list_accounts.Type(COLLECTION_ACCOUNT_ID); } //+------------------------------------------------------------------+
类构造函数中的这段简短代码用来准备存储帐户对象的列表:
- 该列表已被清理,
- 该列表设置为按帐号排序,且
- 已为该列表分配了帐户集合列表 ID 。
帐户集合类的操作分配如下:当程序附加到品种图表时,我们可以访问单个帐户的当前数据。 我们能够跟踪其属性的变化,并响应它们的变化。 其余帐户只能在程序中“跟踪” — 它们连接到新帐户时的最后状态。 所以,帐户连接列表将包含我们所连接的所有帐户的对象,但我们只能跟踪当前帐户的变化。 此外,我们将能够按任意属性比较所有账户的数据。
若要跟踪重要的帐户属性,要用到哈希控件 — 将当前时间内所有帐户属性的总和,与上一次检查期间获得的总和进行比较。 一旦总和发生变化后,我们会检查确切的变化内容,并设置相应的变更标记。 接着,当跟踪帐户事件(重要帐户属性的变化)时,该标志表示我们需要检查所有跟踪的属性,并将有关已变更属性的事件发送给程序。
我们来添加所有必要的变量和类方法,然后分析它们:
//+------------------------------------------------------------------+ //| AccountsCollection.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Accounts\Account.mqh" //+------------------------------------------------------------------+ //| Account collection | //+------------------------------------------------------------------+ class CAccountsCollection : public CListObj { private: struct MqlDataAccount { double hash_sum; // Account data hash sum //--- Account integer properties long login; // ACCOUNT_LOGIN (Account number) long leverage; // ACCOUNT_LEVERAGE (Leverage) int limit_orders; // ACCOUNT_LIMIT_ORDERS (Maximum allowed number of active pending orders) bool trade_allowed; // ACCOUNT_TRADE_ALLOWED (Permission to trade for the current account from the server side) bool trade_expert; // ACCOUNT_TRADE_EXPERT (Permission to trade for an EA from the server side) //--- Account real properties double balance; // ACCOUNT_BALANCE (Account balance in a deposit currency) double credit; // ACCOUNT_CREDIT (Credit in a deposit currency) double profit; // ACCOUNT_PROFIT (Current profit on an account in the account currency) double equity; // ACCOUNT_EQUITY (Equity on an account in the deposit currency) double margin; // ACCOUNT_MARGIN (Reserved margin on an account in a deposit currency) double margin_free; // ACCOUNT_MARGIN_FREE (Free funds available for opening a position in a deposit currency) double margin_level; // ACCOUNT_MARGIN_LEVEL (Margin level on an account in %) double margin_so_call; // ACCOUNT_MARGIN_SO_CALL (Margin Call) double margin_so_so; // ACCOUNT_MARGIN_SO_SO (Stop Out) double margin_initial; // ACCOUNT_MARGIN_INITIAL (Funds reserved on an account to ensure a guarantee amount for all pending orders) double margin_maintenance; // ACCOUNT_MARGIN_MAINTENANCE (Funds reserved on an account to ensure a minimum amount for all open positions) double assets; // ACCOUNT_ASSETS (Current assets on an account) double liabilities; // ACCOUNT_LIABILITIES (Current liabilities on an account) double comission_blocked; // ACCOUNT_COMMISSION_BLOCKED (Current sum of blocked commissions on an account) }; MqlDataAccount m_struct_curr_account; // Account current data MqlDataAccount m_struct_prev_account; // Account previous data CListObj m_list_accounts; // Account object list string m_folder_name; // Name of a folder account objects are stored in int m_index_current; // Index of an account object featuring the current account data bool m_is_account_event; // Event flag in the account data //--- Write the current account data to the account object properties void SetAccountsParams(CAccount* account); //--- Save the current data status values of the current account as previous ones void SavePrevValues(void) { this.m_struct_prev_account=this.m_struct_curr_account; } //--- Check the account object presence in the collection list bool IsPresent(CAccount* account); //--- Find and return the account object index with the current account data int Index(void); public: //--- Return the full account collection list "as is" CArrayObj *GetList(void) { return &this.m_list_accounts; } //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);} CArrayObj *GetList(ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);} CArrayObj *GetList(ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);} //--- Return the (1) current account object index, (2) occurred event flag in the account data int IndexCurrentAccount(void) const { return this.m_index_current; } bool IsAccountEvent(void) const { return this.m_is_account_event; } //--- Constructor, destructor CAccountsCollection(); ~CAccountsCollection(); //--- Add the account object to the list bool AddToList(CAccount* account); //--- (1) Save account objects from the list to the files //--- (2) Save account objects from the files to the list bool SaveObjects(void); bool LoadObjects(void); //--- Update the current account data void Refresh(void); }; //+------------------------------------------------------------------+
类的私有部分包括 MqlDataAccount结构,用于存储重要的帐户属性。 它用来存储所有跟踪的帐户对象属性。 我们有两个拥有结构类型的变量:第一个 存储当前帐户数据,而另一个存储以前的数据。 结构中唯一保持不变的属性是存储帐号的 login(登录)。 该字段值在第一次启动时定义。 如果“登录”字段中的结构为零,则是第一次启动,且当前帐户状态应作为前一个帐户状态加以保存,以便进行后续比较。 在结构的哈希字段中,设置所有结构字段的值的总和,并将其与“先前”帐户状态结构中设置的值进行比较。 如果检测到这两个字段的值之间不匹配,则认为检测到帐户对象属性的改变。
由于帐户集合列表里存储不同帐户的数据(基于函数库的程序操作期间所连接的所有帐户和当前帐户),而我们无法通过读取文件来跟踪列表中保存的帐户数据,我们需要知道列表中帐户对象的确切索引,这是我们要跟踪的当前帐户对象。
此索引用于获取帐户对象,并定时检查其属性的状态。 我们还有
类成员变量,它将用作变更帐户对象属性的标志,和文件夹地址变量,我们要将类对象存储到该值指示的函数库目录。
相同的私有部分包含四种方法。 我们来看看它们的实现。
将当前帐户的数据写入帐户对象属性的方法:
//+------------------------------------------------------------------+ //| Write the current account data to the account object properties | //+------------------------------------------------------------------+ void CAccountsCollection::SetAccountsParams(CAccount *account) { if(account==NULL) return; //--- Account number this.m_struct_curr_account.login=account.Login(); //--- Leverage account.SetProperty(ACCOUNT_PROP_LEVERAGE,::AccountInfoInteger(ACCOUNT_LEVERAGE)); this.m_struct_curr_account.leverage=account.Leverage(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.leverage; //--- Maximum allowed number of active pending orders account.SetProperty(ACCOUNT_PROP_LIMIT_ORDERS,::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS)); this.m_struct_curr_account.limit_orders=(int)account.LimitOrders(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.limit_orders; //--- Permission to trade for the current account from the server side account.SetProperty(ACCOUNT_PROP_TRADE_ALLOWED,::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)); this.m_struct_curr_account.trade_allowed=account.TradeAllowed(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.trade_allowed; //--- Permission to trade for an EA from the server side account.SetProperty(ACCOUNT_PROP_TRADE_EXPERT,::AccountInfoInteger(ACCOUNT_TRADE_EXPERT)); this.m_struct_curr_account.trade_expert=account.TradeExpert(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.trade_expert; //--- Account balance in a deposit currency account.SetProperty(ACCOUNT_PROP_BALANCE,::AccountInfoDouble(ACCOUNT_BALANCE)); this.m_struct_curr_account.balance=account.Balance(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.balance; //--- Credit in a deposit currency account.SetProperty(ACCOUNT_PROP_CREDIT,::AccountInfoDouble(ACCOUNT_CREDIT)); this.m_struct_curr_account.credit=account.Credit(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.credit; //--- Current profit on an account in the account currency account.SetProperty(ACCOUNT_PROP_PROFIT,::AccountInfoDouble(ACCOUNT_PROFIT)); this.m_struct_curr_account.profit=account.Profit(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.profit; //--- Equity on an account in the deposit currency account.SetProperty(ACCOUNT_PROP_EQUITY,::AccountInfoDouble(ACCOUNT_EQUITY)); this.m_struct_curr_account.equity=account.Equity(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.equity; //--- Reserved margin on an account in a deposit currency account.SetProperty(ACCOUNT_PROP_MARGIN,::AccountInfoDouble(ACCOUNT_MARGIN)); this.m_struct_curr_account.margin=account.Margin(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin; //--- Free funds available for opening a position in a deposit currency account.SetProperty(ACCOUNT_PROP_MARGIN_FREE,::AccountInfoDouble(ACCOUNT_MARGIN_FREE)); this.m_struct_curr_account.margin_free=account.MarginFree(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_free; //--- Margin level on an account in % account.SetProperty(ACCOUNT_PROP_MARGIN_LEVEL,::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)); this.m_struct_curr_account.margin_level=account.MarginLevel(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_level; //--- Margin Call level account.SetProperty(ACCOUNT_PROP_MARGIN_SO_CALL,::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL)); this.m_struct_curr_account.margin_so_call=account.MarginSOCall(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_so_call; //--- StopOut level account.SetProperty(ACCOUNT_PROP_MARGIN_SO_SO,::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO)); this.m_struct_curr_account.margin_so_so=account.MarginSOSO(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_so_so; //--- Funds reserved on an account to ensure a guarantee amount for all pending orders account.SetProperty(ACCOUNT_PROP_MARGIN_INITIAL,::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL)); this.m_struct_curr_account.margin_initial=account.MarginInitial(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_initial; //--- Funds reserved on an account to ensure a minimum amount for all open positions account.SetProperty(ACCOUNT_PROP_MARGIN_MAINTENANCE,::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE)); this.m_struct_curr_account.margin_maintenance=account.MarginMaintenance(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_maintenance; //--- Current assets on an account account.SetProperty(ACCOUNT_PROP_ASSETS,::AccountInfoDouble(ACCOUNT_ASSETS)); this.m_struct_curr_account.assets=account.Assets(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.assets; //--- Current liabilities on an account account.SetProperty(ACCOUNT_PROP_LIABILITIES,::AccountInfoDouble(ACCOUNT_LIABILITIES)); this.m_struct_curr_account.liabilities=account.Liabilities(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.liabilities; //--- Current sum of blocked commissions on an account account.SetProperty(ACCOUNT_PROP_COMMISSION_BLOCKED,::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED)); this.m_struct_curr_account.comission_blocked=account.ComissionBlocked(); this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.comission_blocked; } //+------------------------------------------------------------------+
我们看看杠杆变化的一个例子:
方法接收指向帐户对象的指针,将当前帐户数据填写到帐户对象字段,和以帐户当前状态为特征的结构字段。
接下来,将每个获取的属性的
值添加到哈希值字段。
用于将当前帐户状态的结构保存到先前状态结构的 SavePrevValues()方法只是将当前状态结构复制到先前的状态结构。
检查集合列表中是否存在帐户对象的方法:
//+------------------------------------------------------------------+ //| Check the presence of the account object in the collection list | //+------------------------------------------------------------------+ bool CAccountsCollection::IsPresent(CAccount *account) { int total=this.m_list_accounts.Total(); if(total==0) return false; for(int i=0;i<total;i++) { CAccount* check=this.m_list_accounts.At(i); if(check==NULL) continue; if(check.IsEqual(account)) return true; } return false; } //+------------------------------------------------------------------+
接收指向帐户对象指针的方法,其数据在集合列表中应该能找到。 当创建帐户对象类时,可利用我之前论述过的按帐号、客户和公司名称进行搜索的 IsEqual()方法。
利用帐户对象列表(在循环中)从列表中获取对象,并将其数据与传递给方法的对象数据进行比较。
如果数据匹配,则返回 true。
否则,返回 false,如果在循环完成时没有找到相等的对象。
该方法返回当前帐户数据在帐户对象列表中的索引:
//+------------------------------------------------------------------+ //| Return the account object index with the current account data | //+------------------------------------------------------------------+ int CAccountsCollection::Index(void) { int total=this.m_list_accounts.Total(); if(total==0) return WRONG_VALUE; for(int i=0;i<total;i++) { CAccount* account=this.m_list_accounts.At(i); if(account==NULL) continue; if(account.Login()==::AccountInfoInteger(ACCOUNT_LOGIN) && account.Company()==::AccountInfoString(ACCOUNT_COMPANY) && account.Name()==::AccountInfoString(ACCOUNT_NAME) ) return i; } return WRONG_VALUE; } //+------------------------------------------------------------------+
利用帐户对象列表(在循环中)获取对象,并将其帐户数据(登录,客户和公司名称)与启动程序时的帐户数据进行比较。
如果循环中找到匹配值,则返回该索引。 循环结束后,如果未找到当前帐户数据的对象,则返回 -1。
类的公有部分中添加了以下方法:
该方法返回所保存的当前帐户对象索引的变量值,该方法返回帐户属性已发生变化的标志。
此外,还有一个
类析构函数(用于将列表中的所有帐户保存到文件中),将帐户对象添加到集合列表的方法,操作文件写入/读取对象的方法,和更新当前帐户对象数据的方法:
public: //--- Return the full account collection list "as is" CArrayObj *GetList(void) { return &this.m_list_accounts; } //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);} CArrayObj *GetList(ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);} CArrayObj *GetList(ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);} //--- Return the (1) current object account index, (2) flag of an occurred event in the account data int IndexCurrentAccount(void) const { return this.m_index_current; } bool IsAccountEvent(void) const { return this.m_is_account_event; } //--- Constructor, destructor CAccountsCollection(); ~CAccountsCollection(); //--- Add the account object to the list bool AddToList(CAccount* account); //--- (1) Save account objects from the list to the files //--- (2) Save account objects from the files to the list bool SaveObjects(void); bool LoadObjects(void); //--- Update the current account data void Refresh(void); }; //+------------------------------------------------------------------+
我们来研究一下这些方法。
函数库文件将保存在终端的 Files\DoEasy\ 文件夹中,每个类均拥有文件夹(如果类需要保存文件)。 还有 m_folder_name 类成员变量,用于设置存储帐户对象的文件夹名称。 在类构造函数的初始化列表时,即刻以帐户属性发生变化标志来初始化:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccountsCollection::CAccountsCollection(void) : m_folder_name(DIRECTORY+"Accounts"),m_is_account_event(false) { this.m_list_accounts.Clear(); this.m_list_accounts.Sort(SORT_BY_ACCOUNT_LOGIN); this.m_list_accounts.Type(COLLECTION_ACCOUNT_ID); ::ZeroMemory(this.m_struct_prev_account); //--- Create the folder for storing account files ::ResetLastError(); if(!::FolderCreate(this.m_folder_name,FILE_COMMON)) Print(DFUN,TextByLanguage("Не удалось создать папку хранения файлов. Ошибка ","Could not create file storage folder. Error "),::GetLastError()); //--- Create the current account object and add it to the list CAccount* account=new CAccount(); if(account!=NULL) { if(!this.AddToList(account)) { Print(DFUN_ERR_LINE,TextByLanguage("Ошибка. Не удалось добавить текущий объект-аккаунт в список-коллекцию.","Error. Failed to add current account object to collection list.")); delete account; } else account.PrintShort(); } else Print(DFUN,TextByLanguage("Ошибка. Не удалось создать объект-аккаунт с данными текущего счёта.","Error. Failed to create an account object with current account data.")); //--- Download account objects from the files to the collection this.LoadObjects(); //--- Save the current account index this.m_index_current=this.Index(); } //+------------------------------------------------------------------+
接着,在类构造函数中,利用当前帐户的先前数据重置结构,并且创建用于存储类文件的文件夹,该文件夹位于
“公共_数据_文件夹” 下该类的 \Files\DoEasy\Accounts。
然后创建含有当前帐户数据的帐户对象,并利用 AddToList()
方法将其添加到帐户集合列表中。 如果没有对象添加到列表中,则向日志发送相应的消息,否则将显示帐户属性的简明消息(登录,客户名称,公司名称,帐户余额,杠杆,若非净持账户则为帐户类型)。
下一步是将帐户对象上传到集合列表。 存储帐户对象的这些文件位于存储类对象的文件夹中。
最后一步是查找当前帐户数据的对象索引,并赋值给
m_index_current,该变量值会由IndexCurrentAccount() 方法返回给程序进一步处理。
在类析构函数中调用该方法保存集合列表里的所有对象至相应文件:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CAccountsCollection::~CAccountsCollection(void) { //--- Save account objects from the list to the files this.SaveObjects(); } //+------------------------------------------------------------------+
将帐户对象添加到集合列表的方法:
//+------------------------------------------------------------------+ //| Add the account object to the list | //+------------------------------------------------------------------+ bool CAccountsCollection::AddToList(CAccount *account) { if(account==NULL) return false; if(!this.IsPresent(account)) return this.m_list_accounts.Add(account); return false; } //+------------------------------------------------------------------+
方法接收指向帐户对象的指针,然后利用 IsPresent()
方法检查集合列表中是否存在此类对象。 如果还没有这个对象,会将其添加到集合列表中,并返回添加结果。
该方法将帐户对象从集合列表保存到文件:
//+------------------------------------------------------------------+ //| Save account objects from the list to the files | //+------------------------------------------------------------------+ bool CAccountsCollection::SaveObjects(void) { bool res=true; int total=this.m_list_accounts.Total(); if(total==0) return false; for(int i=0;i<total;i++) { CAccount* account=this.m_list_accounts.At(i); if(account==NULL) continue; string file_name=this.m_folder_name+"\\"+account.Server()+" "+(string)account.Login()+".bin"; if(::FileIsExist(file_name,FILE_COMMON)) ::FileDelete(file_name,FILE_COMMON); ::ResetLastError(); int handle=::FileOpen(file_name,FILE_WRITE|FILE_BIN|FILE_COMMON); if(handle==INVALID_HANDLE) { ::Print(DFUN,TextByLanguage("Не удалось открыть для записи файл ","Could not open file for writing: "),file_name,TextByLanguage(". Ошибка ",". Error "),(string)::GetLastError()); return false; } res &=account.Save(handle); ::FileClose(handle); } return res; } //+------------------------------------------------------------------+
使用集合列表(在循环中) 从列表中获取帐户对象,并创建文件名,其中包含帐户对象的文件夹路径、服务器名称和登录名(帐号),扩展名为 “.bin”。 如果帐户对象文件夹中存在此类文件,则删除该文件,并打开一个新文件以变写入。 打开的文件句柄传递给我之前描述的 CAccount 类的 Save() 虚方法,返回的集合列表中内所有帐户对象写入文件的结果会赋值给 res 变量。 对象保存完毕后,为写入打开的文件被关闭。
将帐户对象从文件加载到集合列表的方法:
//+------------------------------------------------------------------+ //| Download account objects from the files to the list | //+------------------------------------------------------------------+ bool CAccountsCollection::LoadObjects(void) { bool res=true; string name=""; long handle_search=::FileFindFirst(this.m_folder_name+"\\*",name,FILE_COMMON); if(handle_search!=INVALID_HANDLE) { do { string file_name=this.m_folder_name+"\\"+name; ::ResetLastError(); int handle_file=::FileOpen(m_folder_name+"\\"+name,FILE_BIN|FILE_READ|FILE_COMMON); if(handle_file!=INVALID_HANDLE) { CAccount* account=new CAccount(); if(account!=NULL) { if(!account.Load(handle_file)) { delete account; ::FileClose(handle_file); res &=false; continue; } if(this.IsPresent(account)) { delete account; ::FileClose(handle_file); res &=false; continue; } if(!this.AddToList(account)) { delete account; res &=false; } } } ::FileClose(handle_file); } while(::FileFindNext(handle_search,name)); ::FileFindClose(handle_search); } return res; } //+------------------------------------------------------------------+
首先,在存储函数库帐户对象文件的文件夹中查找第一个文件。 接下来,打开另一个检测到的文件,并在
do-while 循环中读取,创建一个新的帐户对象,利用
CAccount 类当中的 Load() 虚方法从文件里加载数据。 如果没有此类对象(具有相同的帐户数据),则将对象添加到列表。
如果从文件里加载数据至对象,或将对象添加到列表时出现任何错误,请确保删除此新对象(以避免内存泄漏)并关闭文件。
循环完成后,从文件加载数据至帐户对象,并将它们放入集合列表的结果将被返回。
更新当前帐户对象数据的方法:
//+------------------------------------------------------------------+ //| Update the current account data | //+------------------------------------------------------------------+ void CAccountsCollection::Refresh(void) { if(this.m_index_current==WRONG_VALUE) return; CAccount* account=this.m_list_accounts.At(this.m_index_current); if(account==NULL) return; ::ZeroMemory(this.m_struct_curr_account); this.m_is_account_event=false; this.SetAccountsParams(account); //--- First launch if(!this.m_struct_prev_account.login) { this.SavePrevValues(); } //--- If the account hash changed if(this.m_struct_curr_account.hash_sum!=this.m_struct_prev_account.hash_sum) { this.m_is_account_event=true; this.SavePrevValues(); } } //+------------------------------------------------------------------+
在此,我们首先要做的事情就是检查包含当前帐户数据的帐户对象的索引的有效性。 如果出于某种原因未能成功,退出方法。
接下来,
通过列表中的索引获取当前帐户数据的帐户对象,重置当前帐户的数据结构,重置帐户对象属性已变化的标志,并调用设置帐户对象属性的方法。
相同的方法将最近(新读取的)属性复制到当前帐户数据结构,以便稍后与帐户先前状态进行比较,并检测变更。
接下来,定义帐户先前状态的数据结构中哪些数据需要设置。 如果登录字段为零,则表示结构从未填写过,于是则为首次启动。 因此,我们只需用含有当前状态结构的数据填充先前状态数据的结构。
接下来,将当前状态哈希值与之前的状态哈希值进行比较来检验哈希值的变化。 如果有变更,设置帐户属性已发生变更事件的标志,将当前状态保存为先前状态,用于后续比较。
稍后,实现跟踪重要帐户状态变更,并发送有关此程序重要变更的事件消息。
由于整个类的工作是从含糊库基础对象(CEngine 类)执行的,因此请转至 Engine.mqh 文件并添加必要的功能。
首先,包含账户集合文件:
//+------------------------------------------------------------------+ //| Engine.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Collections\HistoryCollection.mqh" #include "Collections\MarketCollection.mqh" #include "Collections\EventsCollection.mqh" #include "Collections\AccountsCollection.mqh" #include "Services\TimerCounter.mqh" //+------------------------------------------------------------------+
在类的私有部分中,创建帐户集合对象,并添加操控帐户集合的方法:
//+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine : public CObject { private: CHistoryCollection m_history; // Collection of historical orders and deals CMarketCollection m_market; // Collection of market orders and deals CEventsCollection m_events; // Event collection CAccountsCollection m_accounts; // Account collection CArrayObj m_list_counters; // List of timer counters bool m_first_start; // First launch flag bool m_is_hedge; // Hedge account flag bool m_is_tester; // Flag of working in the tester bool m_is_market_trade_event; // Account trading event flag bool m_is_history_trade_event; // Account history trading event flag ENUM_TRADE_EVENT m_last_trade_event; // Account last trading event //--- Return the counter index by id int CounterIndex(const int id) const; //--- Return (1) the first launch flag, (2) the flag presence in a trading event bool IsFirstStart(void); //--- Working with (1) order, deal and position, as well as (2) account events void TradeEventsControl(void); void AccountEventsControl(void); //--- Return the last (1) market pending order, (2) market order, (3) last position, (4) position by ticket COrder* GetLastMarketPending(void); COrder* GetLastMarketOrder(void); COrder* GetLastPosition(void); COrder* GetPosition(const ulong ticket); //--- Return the last (1) removed pending order, (2) historical market order, (3) historical order (market or pending) by its ticket COrder* GetLastHistoryPending(void); COrder* GetLastHistoryOrder(void); COrder* GetHistoryOrder(const ulong ticket); //--- Return the (1) first and the (2) last historical market orders from the list of all position orders, (3) the last deal COrder* GetFirstOrderPosition(const ulong position_id); COrder* GetLastOrderPosition(const ulong position_id); COrder* GetLastDeal(void); public:
在类构造函数中,创建一个新的定时计数器,用于处理帐户集合:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true),m_last_trade_event(TRADE_EVENT_NO_EVENT) { 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.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); ::ResetLastError(); #ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError()); } //---__MQL4__ #else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError()); } #endif } //+------------------------------------------------------------------+
我们在函数库论述的第三部分中讨论了定时器及其计数器。
在类的 OnTimer() 处理程序中,添加帐户集合计时器:
//+------------------------------------------------------------------+ //| CEngine timer | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Timer of historical orders, deals, market orders and positions collections int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- If this is not a tester if(!this.IsTester()) { //--- If unpaused, work with the order, deal and position collections events if(counter.IsTimeDone()) this.TradeEventsControl(); } //--- If this is a tester, work with collection events by tick else this.TradeEventsControl(); } } //--- Account collection timer index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- If this is not a tester if(!this.IsTester()) { //--- If unpaused, work with the account collections if(counter.IsTimeDone()) this.AccountEventsControl(); } //--- If this is a tester, work with collection events by tick else this.AccountEventsControl(); } } } //+------------------------------------------------------------------+
帐户集合定时器的工作方式类似于函数库论述第三部分中讨论的订单、成交和仓位集合定时器(在专门开发函数库基础对象的章节 — CEngine
类)。 与订单、成交和仓位集合定时器的唯一区别是调用另一个集合事件处理方法 —AccountEventsControl()。
我们来添加检查当前帐户属性变更的方法:
//+------------------------------------------------------------------+ //| Check the account events | //+------------------------------------------------------------------+ void CEngine::AccountEventsControl(void) { this.m_accounts.Refresh(); } //+------------------------------------------------------------------+
该方法只调用 CAccountsCollection 类的 Refresh() 方法。
在 CEngine 类的公有部分中,编写两个方法,将事件和帐户集合列表返回给程序。
这令我们可直接从程序访问集合列表:
public: //--- Return the list of market (1) positions, (2) pending orders and (3) market orders CArrayObj* GetListMarketPosition(void); CArrayObj* GetListMarketPendings(void); CArrayObj* GetListMarketOrders(void); //--- Return the list of historical (1) orders, (2) removed pending orders, (3) deals, (4) all position market orders by its id CArrayObj* GetListHistoryOrders(void); CArrayObj* GetListHistoryPendings(void); CArrayObj* GetListDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- Return the account list CArrayObj* GetListAllAccounts(void) { return this.m_accounts.GetList(); } //--- Return the event list CArrayObj* GetListAllEvents(void) { return this.m_events.GetList(); } //--- Reset the last trading event void ResetLastTradeEvent(void) { this.m_events.ResetLastTradeEvent(); } //--- Return the (1) last trading event, (2) hedge account flag, (3) flag of working in the tester ENUM_TRADE_EVENT LastTradeEvent(void) const { return this.m_last_trade_event; } bool IsHedge(void) const { return this.m_is_hedge; } bool IsTester(void) const { return this.m_is_tester; } //--- Create the timer counter void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- Timer void OnTimer(void); //--- Constructor/destructor CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
测试帐户集合类的一切都已准备就绪。 在我们开始测试之前,我们来修改 CEventsCollection::Refresh 事件集合类。 加入检查列表的代码 233,以便消除偶发的新、旧事件一起发送到程序所导致的激活:
//--- If the event is in the account history if(is_history_event) { //--- If the number of historical orders increased (MQL5, MQL4) if(new_history_orders>0 && new_market_pendings<0) { //--- Receive the list of removed pending orders only
我还修复了为 MetaTrader 4(DELib.mqh 文件)编写 MQL4 交易函数时相当低级的错误。 该问题是在 MQL4 中,OrderSend() 函数返回订单票证而非布尔值。 显然,我正在开始忘记 MQL4 :)
我们来研究以下示例:
检查 MQL4 函数操作结果如下(这对于 MQL5 是正确的):
if( ! OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue))
我通过实现正确检查修复了 MQL4 错误:
if(OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue)==WRONG_VALUE)
对于测试器来说这不是什么大问题,但仍然是一个错误。
我会很快介绍完整的交易类,因此这些函数将从函数库中剔除。
测试帐户集合
我们利用已开发的 TestDoEasyPart12_1.mq5 EA,并在同一文件夹 \MQL5\Experts\TestDoEasy 中以 TestDoEasyPart12_2.mq5 为名保存。
在 EA 输入中,引入变量来切换日志中显示的现有帐户数据的样貌 — brief(简要,默认为 false)或 full(全部, true):
//--- input variables input ulong InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 50; // StopLoss in points input uint InpTakeProfit = 50; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 0; // Slippage in points input double InpWithdrawal = 10; // Withdrawal funds (in tester) input uint InpButtShiftX = 40; // Buttons X shift input uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) input bool InpFullProperties = false;// Show full accounts properties //--- global variables
将以下代码添加到 OnInit() 处理程序:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- Check and remove remaining EA graphical objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- Set trailing activation button status ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- Set CTrade trading class parameters #ifdef __MQL5__ trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); #endif //--- Fast check of the account object CArrayObj* list=engine.GetListAllAccounts(); if(list!=NULL) { int total=list.Total(); if(total>0) Print("\n",TextByLanguage("=========== Список сохранённых аккаунтов ===========","=========== List of saved accounts ===========")); for(int i=0;i<total;i++) { CAccount* account=list.At(i); if(account==NULL) continue; Sleep(100); if(InpFullProperties) account.Print(); else account.PrintShort(); } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
此处我们利用 CEngine 类的 GetListAllAccounts() 方法获取帐户集合列表。 我们从一个循环中获取每个后续对象,该循环在日志中显示其属性,具体取决于输入值 — 简要记录或帐户对象属性的完整清单。
启动 EA 并选择简要记录,查看它在日记中显示的内容(显示全部帐户属性 = false):
现在选择全部清单 — 按 F7 并在参数窗口中将 "Show full accounts properties 显示全部帐户属性” 设置为 “true”:
现在,日志中将显示每个现有帐户的完整属性清单。
请注意,若要将帐户写入文件,您需要连接到第一个帐户,再重新连接到第二个帐户,然后转到第三个帐户,依此类推。 换言之,先前的帐户数据在每次连接到新帐户时才会写入文件。
下一步是什么?
在下一篇文章中,我们将跟踪一些帐户属性变更的重要事件,并开始处理品种对象及其集合。
文后附有当前版本含糊库的所有文件,以及测试 EA 文件,供您测试和下载。
请在评论中留下您的问题、意见和建议。
系列中的前几篇文章:
第一部分 概念,数据管理。
第二部分
历史订单和成交集合。
第三部分 在场订单和持仓集合,安排搜索。
第四部分
交易事件, 概念。
第五部分 交易事件类和集合。 将事件发送至程序。
第六部分
净持帐户事件。
第七部分 StopLimit 挂单激活事件,为订单和持仓修改事件准备功能。
第八部分
订单和持仓修改事件。
第九部分 与 MQL4 的兼容性 - 准备数据。
第十部分
与 MQL4 的兼容性 - 开仓和激活挂单事件。
第十一部分 与 MQL4 的兼容性 - 平仓事件。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/6952
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.




