Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XII): Implementando la clase de objeto "cuenta" y la colección de objetos de cuenta
Contenido
- El objeto de cuenta
- Poniendo a prueba el objeto de cuenta
- La colección de objetos de cuenta
- Poniendo a prueba la colección de cuentas
- ¿Qué es lo próximo?
Antes de comenzar a crear las clases comerciales de la biblioteca, vamos a preparar varias clases necesarias para el trading y relacionadas íntimamente con él, en concreto, necesitaremos los datos de la cuenta (cuenta comercial) y de los símbolos con los que comerciamos. En el presente artículo, vamos a ocuparnos del objeto de cuenta.
Dado que los datos de una cuenta pueden cambiar directamente mientras comerciamos, vamos a crear una clase de cuenta, y después, una colección de objetos de cuenta. A continuación, implementaremos el seguimiento de eventos en la cuenta. Esto nos permitirá monitorear a tiempo el cambio del apalancamiento crediticio preestablecido en la cuenta, monitorear los cambios en el saldo, el beneficio/pérdidas, la suma de los propios fondos y los datos referentes a las limitaciones de la cuenta comercial.
El objeto de cuenta
El objeto de cuenta (cuenta comercial) es idéntico a los objetos que hemos creado y descrito en anteriores artículos. La única diferencia de este objeto respecto a los anteriores, consiste en que no será un objeto abstracto con herederos que precisarán el estado de un objeto concreto. El objeto de cuenta será un objeto independiente, con todas las propiedades de la cuenta. A continuación, estos objetos se añadirán a la colección de objetos de cuenta, lo que permitirá comparar los datos de diferentes cuentas según diversos parámetros.
Comenzaremos como siempre, creando todas las enumeraciones necesarias de las propiedades del objeto de cuenta para trabajar con la clase.
Abrimos en la guía del editor
las propiedades
de la cuenta:
Para la función AccountInfoInteger()
Identificador |
Descripción |
Tipo de propiedad |
ACCOUNT_LOGIN |
Número de cuenta |
long |
ACCOUNT_TRADE_MODE |
Tipo de cuenta comercial |
|
ACCOUNT_LEVERAGE |
Tamaño del apalancamiento proporcionado |
long |
ACCOUNT_LIMIT_ORDERS |
Número máximo permitido de órdenes pendientes activas |
int |
ACCOUNT_MARGIN_SO_MODE |
Modo de definición del nivel mínimo permitido de fondos de margen |
|
ACCOUNT_TRADE_ALLOWED |
Permiso de comercio para la cuenta actual |
bool |
ACCOUNT_TRADE_EXPERT |
Permiso de comercio para el experto |
bool |
ACCOUNT_MARGIN_MODE |
Modo de cálculo del margen |
|
ACCOUNT_CURRENCY_DIGITS |
Número de dígitos decimales para la divisa de la cuenta, necesarios para representar con precisión los resultados comerciales |
int |
Para la función AccountInfoDouble()
Identificador |
Descripción |
Tipo de propiedad |
ACCOUNT_BALANCE |
Saldo de la cuenta en la divisa del depósito |
double |
ACCOUNT_CREDIT |
Suma del crédito ofrecido en la divisa del depósito |
double |
ACCOUNT_PROFIT |
Suma del beneficio actual en la cuenta en la divisa del depósito |
double |
ACCOUNT_EQUITY |
Valor de los fondos propios en la cuenta en la divisa del depósito |
double |
ACCOUNT_MARGIN |
Suma de los fondos de margen reservados en la cuenta en la divisa del depósito |
double |
ACCOUNT_MARGIN_FREE |
Suma de fondos libres en la cuenta en la divisa del depósito disponibles para la apertura de una posición |
double |
ACCOUNT_MARGIN_LEVEL |
Nivel de fondos de margen en la cuenta, en tanto por ciento |
double |
ACCOUNT_MARGIN_SO_CALL |
Nivel de fondos de margen con el que se requerirá realizar un ingreso en la cuenta (Margin Call). Dependiendo del ACCOUNT_MARGIN_SO_MODE esteblecido en tanto por ciento, o bien en la divisa del depósito |
double |
ACCOUNT_MARGIN_SO_SO |
Nivel de fondos de margen con el que sucederá el cierre forzoso de la posición menos rentable (Stop Out). Dependiendo del ACCOUNT_MARGIN_SO_MODE esteblecido en tanto por ciento, o bien en la divisa del depósito |
double |
ACCOUNT_MARGIN_INITIAL |
Cantidad de los fondos reservados en la cuenta para proporcionar la suma de garantía de todas las órdenes pendientes |
double |
ACCOUNT_MARGIN_MAINTENANCE |
Cantidad de los fondos reservados en la cuenta para proporcionar la suma mínima de todas las posiciones abiertas |
double |
ACCOUNT_ASSETS |
Cantidad actual de activos en la cuenta |
double |
ACCOUNT_LIABILITIES |
Cantidad actual de obligaciones en la cuenta |
double |
ACCOUNT_COMMISSION_BLOCKED |
Suma actual de comisiones bloqueadas de la cuenta |
double |
Para la función AccountInfoString()
Identificador |
Descripción |
Tipo de propiedad |
ACCOUNT_NAME |
Nombre del cliente |
string |
ACCOUNT_SERVER |
Nombre del servidor comercial |
string |
ACCOUNT_CURRENCY |
Divisa del depósito |
string |
ACCOUNT_COMPANY |
Nombre de la compañía que da servicio a la cuenta |
string |
El objeto de cuenta se implementará con todas estas propiedades, y estas se establcerán en el constructor de la clase.
Añadimos en el archivo Defines.mqh de la biblioteca las propiedades de
tipo entero, de tipo real y de
tipo string del objeto de cuenta, correspondientes a los recuadros de propiedades de la cuenta mostrados más arriba.
Dado
que anteriormente hemos creado enumeraciones para trabajar con los eventos de la cuenta, lo lógico será ubicar los datos sobre las
propiedades de la cuenta antes de los
datos para trabajar con los eventos de la cuenta que ya hemos creado:
//+------------------------------------------------------------------+ //| 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 | //+------------------------------------------------------------------+
Aquí ya debería sonarnos todo de los artículos anteriores, por eso, no vamos a perder tiempo en analizar la organización de las
enumeraciones, la definción de las propiedades del objeto no utilizadas y las macrosustituciones para indicar el número de
propiedades omitidas para calcular la dirección exacta de la constante inicial de la enumeración del
siguiente tipo de las propiedades de la orden: ya hemos estudiado todo esto en uno de los anteriores artículos,
en concreto, en el sexto artículo de la descripción de la biblioteca, en el apartado "
Implementando el procesamiento de eventos en una cuenta de compensación".
El único punto en el que vamos a detenernos es la propiedad "Modo
de cálculo del margen":
Dado que la enumeración ENUM_ACCOUNT_MARGIN_MODE no existe en MQL4, para realizar la
compilación en MQL4 deberemos indicar esta enumeración. Para ello, la añadimos al final del archivo
ToMQL4.mqh:
//+------------------------------------------------------------------+ //| Margin calculation mode | //+------------------------------------------------------------------+ enum ENUM_ACCOUNT_MARGIN_MODE { ACCOUNT_MARGIN_MODE_RETAIL_NETTING, ACCOUNT_MARGIN_MODE_EXCHANGE, ACCOUNT_MARGIN_MODE_RETAIL_HEDGING }; //+------------------------------------------------------------------+
Ahora que todos los datos están preparados, podemos crear el objeto de cuenta.
En la carpeta de la biblioteca \MQL5\Include\DoEasy\Objects\, creamos la subcarpeta Accounts, y en esta
nueva carpeta, creamos la nueva clase
CAccount en el archivo Account.mqh.
En el archivo de la clase nuevamente creado, añadimos de inmediato las declaraciones de todos los métodos necesarios.
Para
los objetos de la biblioteca, la mayoría de estos métodos ya son "estándar". Aquí, eso sí, existe un pequeño "pero": dado que esta clase no
presupone la presencia de herederos, en ella no hay constructor protegido de clase que reciba y establezca el estado del objeto.
Precisamente por eso, el objeto de cuenta no dispone de la propiedad "estado", y su
constructor no acepta ningún argumento. Al mismo tiempo, vamos a dejar los
métodos virtuales que retornan las banderas de soporte por parte del objeto de esta u otra propiedad, para los posibles herederos de
la clase en el futuro:
//+------------------------------------------------------------------+ //| 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); //--- }; //+------------------------------------------------------------------+
Implementamos el constructor de la clase fuera del cuerpo de la misma:
//+------------------------------------------------------------------+ //| 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); } //+-------------------------------------------------------------------+
Aquí todo está bastante claro: a cada propiedad del objeto se le asigna la propiedad correspondiente de la cuenta con la ayuda de las
funciones AccountInfo.
Para las dos propiedades ausentes en MQL4,
hemos implementado la selección con la ayuda de las directivas de la compilación condicional: obtenemos las propiedades
correspondientes para las propiedades
"modo de cálculo del margen" y "número de dígitos decimales para la divisa de la cuenta" para MQL5, mientras
que para
MQL4, simplemente retornamos para la primera propiedad ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
(cuenta de cobertura) de la enumeración ENUM_ACCOUNT_MARGIN_MODE,
y para el segundo,
2 dígitos decimales.
Implementamos el método para la búsqueda y la clasificación de los objetos de cuenta en su lista de colección.
El método es idéntico al
que analizamos anteriormente con los mismos métodos en los objetos de la
biblioteca, por eso, aquí solo vamos a ver su listado:
//+-------------------------------------------------------------------+ //|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; } //+------------------------------------------------------------------+
Para comparar dos objetos de cuenta, necesitamos comparar sus propiedades inmutables, para determinar la pertenencia de estos objetos a diferentes cuentas. Para identificar con precisión la cuenta, disponemos de su número (login), el nombre del usuario y el nombre de la compañía. Precisamente estas propiedades de las cuentas comparadas serán las que comprobemos en los objetos de cuenta, en el método de comparación de los dos objetos de cuenta:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Transmitimos al método el puntero al objeto comparado y
comprobamos las tres propiedades de los objetos:
el nombre de la compañía, el
número de cuenta y el nombre del cliente. Si cualquiera de
las propiedades de los objetos no coincide, significará que estos objetos pertenecen a cuentas diferentes, por lo que
retornamos false.
Después de superar las tres comparaciones,
retornamos true,
los objetos son iguales entre sí.
Los demás métodos de clase son "de servicio" y no requieren de análisis aparte, su lógica se comprende perfectamente a partir del listado; además, ya hemos estudiado métodos semejantes en más de una ocasión en anteriores partes de la descripción de la biblioteca:
//+------------------------------------------------------------------+ //| 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") ); } //+------------------------------------------------------------------+
Él lector podrá ver el listado completo de la clase de cuenta en los archivos adjuntos al final del artículo. Vamos a poner a prueba el
funcionamiento de la clase.
Poniendo a prueba el objeto de cuenta
Para comprobar el funcionamiento de la clase, es decir, para ver si la función de obtención de los datos de la cuenta opera como es debido, incluimos temporalmente el archivo de la clase en el objeto principal de la biblioteca, en la clase 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 {
Después de incluir el archivo en la clase del objeto de cuenta, nuestro programa verá este objeto, que es precisamente lo que necesitamos ahora.
Para la simulación, vamos a tomar el asesor de prueba del artículo anterior \MQL5\Experts\TestDoEasy\Part11\TestDoEasyPart11.mq5 y guardarlo con el nombre TestDoEasyPart12_1.mq5 en la carpeta \MQL5\Experts\TestDoEasy\Part12.
Para conectar y poner a prueba el objeto de cuenta, vamos a añadir unas líneas al manejador OnInit() del asesor (lo comprobaremos durante la inicialización):
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Aquí: creamos el objeto de cuenta y, si lo hemos creado con éxito, mostramos primero en el diario una breve entrada sobre la cuenta, y a continuación, la lista completa de parámetros de la cuenta. Una vez finalizado el proceso, eliminamos el objeto de cuenta.
Iniciamos el asesor en el gráfico de cualquier instrumento y echamos un vistazo al diario "Expertos":
Todos los datos sobre la cuenta se muestran correctamente.
La colección de objetos de cuenta
Dado que, al cambiar de cuenta, todos los asesores se reinicializan, se llaman primero los destructores, y después los constructores de
clases, y de esta forma, el asesor pierde al objeto de cuenta pasado, que existía antes de cambiar de cuenta. Y nosotros necesitamos para
la colección de cuentas recordar los datos de las cuentas pasadas a las que se ha conectado el terminal. Para ello, guardaremos en un
archivo en el destructor de clase, en la colección de cuentas, los datos del objeto de cuenta (cuenta) actual, mientras que cargaremos
los datos de los archivos en el constructor de la clase. De esta forma, la colección se rellenará con los datos sobre todas las cuentas a
las que se ha conectado el terminal durante el funcionamiento del programa, usando como base nuestra biblioteca. Y puesto que los
archivos se guardarán en la carpeta común de todos los terminales de cliente, cada terminal iniciado - si en él funciona un programa
basado en la biblioteca - verá todas las cuentas a las que se ha conectado el terminal en la computadora.
En la nueva clase de la colección de cuentas tendremos la posibilidad de comparar los datos de toda la información disponible sobre
las cuentas según diversos parámetros de las mismas.
Para guardar el objeto de cuenta en un archivo, necesitaremos crear en la clase CAccount un método para guardar los datos en un archivo.
En
nuestro caso, todos los objetos han sido heredados de
CObject, el objeto básico de la biblioteca
estándar. Y en esta clase ya se prevén los métodos virtuales de
guardado y carga del objeto en un archivo:
//+------------------------------------------------------------------+ //| 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); } }; //+------------------------------------------------------------------+
Los métodos no hacen nada, por lo que debemos redefinirlos en nuestras clases herederas, en los lugares en los que sean necesarios, más concretamente, en la clase CAccount.
Para guardar un objeto de cuenta y todas sus propiedades en un archivo, usaremos una sencilla estructura, que guardaremos en un archivo. Pero los campos del objeto contienen líneas, por lo que ya no se trata de una estructura POD. Esto significa que, al guardar las propiedades de tipo string del objeto en los campos de la estructura, deberemos convertirlas todas en matrices uchar con tamaño fijo, y entonces podremos guardar todos los datos sobre las propiedades del objeto de cuenta como una estructura con la función FileWriteArray().
Para definir el directorio de guardado de los archivos de la biblioteca y el tamaño fijo de las matrices uchar, vamos a crear una macrosustitución en el archivo 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
Puesto que la longitud de la línea de comentarios está limitada a 64 caracteres, vamos a establecer precisamente ese tamaño para las matrices. Hemos partido precisamente del tamaño de la línea de cometarios de la orden, ya que más tarde podría ocurrir que necesitáramos guardar los objetos de las órdenes en archivos, y una longitud inferior a 64 caracteres no nos convendría. Es totalmente posibe que las propiedades de tipo string dispongan de un tamaño superior para la línea. Si la simulación muestra que el tamaño es insuficiente para guardar los nombres de las compañías que dan servicio a la cuenta, siempre podremos aumentarlo.
Vamos a crear en la sección privada de la clase CAccount la estructura necesaria para guardar las propiedades del objeto de cuenta y las variables de miembro de clase para trabajar con archivos:
//+------------------------------------------------------------------+ //| 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:Gracias al listado, podemos ver que en la sección protegida de la clase CAccount se declaran de forma adicional dos métodos: uno para crear estructuras a partir de los campos de las propiedades de un objeto, y otro para el método inverso, para crear un objeto de cuenta a partir de una estructura.
El primer método se usará para registrar un objeto de cuenta en un archivo, y el segundo, para leerlo desde este.
Vamos a implementar estos métodos fuera del cuerpo de la clase:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Como podemos ver por el listado, todas las porpiedades de tipo entero y real se guardan en los campos de estructura homónimos. En cambio, para
guardar las propiedades de tipo string, vamos a utilizar
la conversión de líneas en matrices uchar,
guardando estas en el campo correspondiente.
Una vez finalizado el guardado de las propiedades del objeto, la
estructura al completo
se guarda en una matriz uchar, que posteriormente
guardaremos en un archivo.
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
El método para convertir de forma inversa los campos de la estructura en propiedades del objeto de cuenta es prácticamente idéntico al
primero, que hemos visto más arriba.
Aquí, obtenemos las propiedades de tipo string del objeto de cuenta convirtiendo
en líneas las matrices uchar de la estructura.
Declaramos en la sección pública de la clase CAccount los métodos virtuales Save() y 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 | //+------------------------------------------------------------------+
Escribimos los métodos para guardar un objeto de cuenta en un archivo y cargarlo desde un archivo:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Aquí:
- transmitimos al método el manejador del archivo ya abierto para el registro,
- guardamos todos los campos del objeto en una estructura POD,
- anotamos la estructura POD en el archivo cuyo manejador
hemos obenido en el método
Método para cargar los datos del objeto desde un archivo:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Aquí:
- transmitimos al método el manejador del archivo abierto anteriormente para la lectura
- cargamos en la matriz uchar los datos del archivo
- guardamos los datos de la matriz en una estructura POD
- anotamos en los campos del objeto los datos de la estructura POD
Bien, vamos a organizar el trabajo de la colección de cuentas de la forma siguiente: al iniciar el programa para su ejecución,
comprobaremos la cuenta actual, crearemos un objeto de cuenta con los datos de la cuenta actual y lo ubicaremos en la lista de
colección de cuentas. A continuación, analizaremos la carpeta con los archivos de las cuentas anteriormente guardadas, y si
existen archivos en ella, los leeremos por turno, comprobaremos si coinciden con la cuenta actual y los ubicaremos en la lista de
colección de cuentas. Después de que la lista haya sido creada, comprobaremos en el temporizador el estado de los datos de la cuenta
actual, y si han cambiado, registraremos las modificaciones ocurridas.
Para algunos cambios, crearemos en consecuencia eventos que enviaremos al programa para controlar los cambios de los
parámetros de la cuenta. Por ejemplo, el cambio repentino del apalancamiento ofrecido supone un cambio perceptible y muy
desagradable, sobre el cual debemos notificar a su debido tiempo al usuario y su programa.
Dado que necesitaremos trabajar en el temporizador, así como una nueva lista de colección, vamos a crear para ellos una macrosustitución con los parámetros del temporizador y el identificador de la lista en el archivo Defines.mqh, cambiando de paso los nombres de las macrosistituciones anteriormente creadas para el temporizador de la colección de órdenes, transacciones y posiciones (añadimos a su denominación " ORD", para distinguir la pertenencia de las macrosustituciones al temporizador de esta u otra colección). Establecemos una pausa de un segundo para actualizar los datos de la cuenta, esto resultará suficiente para monitorear los cambios, además de no suponer una carga considerable para el sistema:
//+------------------------------------------------------------------+ //| 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
En el texto de la clase CEngine, sustituimos COLLECTION_PAUSE, COLLECTION_COUNTER_STEP y COLLECTION_COUNTER_ID por las nuevas denominaciones de las macrosustituciones que les corresponden: COLLECTION_ORD_PAUSE, COLLECTION_ORD_COUNTER_STEP y COLLECTION_ORD_COUNTER_ID.
Ya que estamos creando una colección de cuentas, esto presupone la posibilidad de comparar las propiedades de varios objetos de cuenta. Para ello, vamos a necesitar añadir los métodos de selección y filtrado a las colecciones de cuentas, en la clase para la selección de objetos que cumplan con el criterio, CSelect, descrita en la tercera parte de la descripción de la biblioteca.
Abrimos el archivo Select.mqh, ubicado en la carpeta de clases de servicio de la biblioteca \MQL5\Include\DoEasy\Services, incluimos en el mismo el archivo con la clase de cuenta y añadimos los nuevos métodos para trabajar con los objetos de cuenta:
//+------------------------------------------------------------------+ //| 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); }; //+------------------------------------------------------------------+
Añadimos la implementación de los métodos declarados fuera del cuerpo de la clase:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
El funcionamiento de los métodos se ha analizado en la tercera parte de la descripción de la biblioteca, así que no vamos a perder el
tiempo describiéndolos aquí. Si el lector lo desea, siempre puede
refrescar la memoria con la información correspondiente.
Creamos una plantilla para la clase de colección de cuentas.
En la carpeta de la biblioteca MQL5\Include\DoEasy\Collections\, creamos el nuevo archivo de clase AccountsCollection.mqh, incluimos en el mismo los archivos de clase necesarios para su funcionamiento y lo rellenamos directamente con los métodos que ya suponen un estándar para esta biblioteca:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
En esta composición mínima, preparamos en el constructor de la clase la lista en la que se guardarán los objetos de cuenta:
- limpiamos la lista,
- establecemos la clasificación según el número de cuenta
y
- asignamos a la lista el identificador de la lista de colección de cuentas.
El funcionamiento de la clase de colección de cuentas se organizará de la sigueinte forma: durante el funcionamiento del programa fijado al gráfico del símbolo, tendremos acceso a los datos actuales de solo una cuenta; podremos monitorear los cambios de sus propiedades y reaccionar a sus cambios. Las demás cuentas solo podremos "observarlas" en el programa: su último estado en el momento de conexión a una nueva cuenta. Por eso, en la lista de colección de cuentas se encontrarán los objetos de todas las cuentas a las que nos hemos conectado alguna vez, pero solo podremos monitorear los cambios en el estado para la cuenta actual. En este caso, además, tendremos la posibilidad de comparar todas las cuentas de las que disponemos según cualquiera de sus propiedades.
Para monitorear los cambios de las propiedades significativas, usaremos el control de la suma hash: la comparación de la suma de todas las propiedades de la cuenta con la suma en la anterior comprobación. En cuanto la suma cambie, comprobaremos qué ha sido precisamente lo que ha cambiado y pondremos la bandera con el cambio ocurrido. Después, cuando monitoreemos los eventos de la cuenta (cambios significativos de las propiedades de la cuenta), esta bandera nos indicará que debemos comprobar todas las propiedades controlables y enviar al programa los eventos sobre las propiedades cambiadas.
Vamos a añadir directamente todas las variables y métodos de clase necesarios, ya los analizaremos más tarde:
//+------------------------------------------------------------------+ //| 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); }; //+------------------------------------------------------------------+
En la sección privada de la clase, hemos creado la estructura MqlDataAccount, para guardar las propiedades significativas de la cuenta. En ella se registrarán todas las propiedades monitoreadas del objeto de cuenta. Tenemos dos variables con el tipo de esta estructura: una para guardar los datos actuales de la cuenta, y otra para guardar los datos pasados. La única propiedad que es insustituible en la estructura es el login, donde se guarda el número de la cuenta. Usaremos el valor de este campo para determinar el primer inicio: si en el campo login de la estructura hay un cero, esto significará que se trata del primer inicio, y será necesario guardar el estado actual de la cuenta como pasado, para compararlos posteriormente. En el campo de la suma hash de la estructura, registraremos la suma de los valores de todos los campos de la estructura, y compararemos este valor con el valor registrado en la estructura del "pasado" estado de la cuenta. Cuando detectemos una diferencia en los valores de estos campos en las dos estructuras, consideraremos que ha ocurrido un cambio en las propiedades del objeto de cuenta.
Dado que en la lista de colección de cuentas se guardarán los datos tanto de cuentas diferentes (todas aquellas a las que nos hemos
conectado) durante el funcionamiento del programa basado en la biblioteca, como de la cuenta actual, a la que estamos conectados en
este momento, y que los datos de las cuentas guardados en la lista no se pueden monitorear con la lectura desde el archivo, vamos a
necesitar conocer con exactitud
el índice del objeto de cuenta en la lista que constituye el objeto de la cuenta
actual, y que debemos monitorear. Según este índice, obtendremos el objeto de cuenta y comprobaremos el estado de sus
propiedades en el temporizador. En ese mismo lugar, tenemos
la variable de miembro de clase que será la bandera de cambio de las propiedades
del objeto de cuenta, así como la variable en la que se registrará
la dirección de la carpeta en el directorio de la biblioteca donde guardaremos los objetos de esta clase.
En esa misma sección privada, tendremos ubicados cuatro métodos. Vamos a ver su implementación.
Método para registrar los datos de la cuenta actual en las propiedades del objeto de cuenta:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Vamos a analizar un ejemplo de actualización del valor del apalancamiento ofrecido:
transmitimos
al método el puntero al objeto de cuenta y registramos los datos de la cuenta
actual en los campos del objeto de cuenta y los campo de la estructura
con los datos del estado actual de la cuenta. A continuación, añadimos a la suma hash el
valor de cada propiedad obtenida.
El método para el guardado de la estructura del estado actual de la cuenta en la estructura del estado anterior SavePrevValues() simplemente copia la estructura del estado actual en la estructura del estado pasado.
Método de comprobación de la presencia del objeto de cuenta en la lista de colección:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Transmitimos al método el puntero al objeto de cuenta
cuyos datos debemos encontrar en la lista de colección. La búsqueda se realiza según los datos del número de cuenta, el nombre del
cliente y la compañía, con la ayuda del método
IsEqual(), que ya analizamos anteriormente aquí al crear la clase del objeto de cuenta.
Solo hay que obtener
en el ciclo por la lista de objetos de cuenta un objeto de la lista
y compararlo con los datos del objeto transmitido al método.
Si los datos coinciden, retornamos true.
De lo contrario, si al finalizar el ciclo no se han encontrado objetos iguales, retornamos
false.
Método que retorna el índice del objeto de cuenta en la lista con los datos de la cuenta actual:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Obtenemos en el ciclo por la lista de objetos de cuenta
un objeto y comparamos sus datos de cuenta (login, nombre del
cliente y la compañía) con los datos de la cuenta en la que está funcionando el programa.
si coinciden, se retorna el índice del ciclo. Al finalizar el
ciclo,
si no se ha localizado el objeto con los datos de la cuenta actual, retornamos -1.
Hemos añadido en la sección pública de la clase los siguientes métodos:
Un método que retorna el valor de la variable que guarda el índice del objeto
de cuenta con los datos de la cuenta actual, y otro método que
retorna la bandera de cambio ocurrido en las propiedades de la cuenta. Hemos añadido un
destructor de clase (en él guardaremos en archivos todas las cuentas disponibles en la lista), un
método que añade un objeto de cuenta a la lista de colección, varios
métodos para el guardado y la carga de objetos en una archivo/desde un archivo y un
método de actualización de los datos de la cuenta actual en el objeto de cuenta actual:
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); }; //+------------------------------------------------------------------+
Echamos un vistazo a estos métodos.
Vamos a guardar los archivos de la biblioteca en el directorio del terminal Files\DoEasy\, y en él, nuestras carpetas para cada clase (si la clase necesita de guardado de archivos). Para establecer las denominaciones de la carpeta de los objetos de cuenta, hemos declarado la variable de miembro de clase m_folder_name. La inicializamos directamente en la lista de inicialización del constructor de la clase, y también la variable de bandera del cambio ocurrido en las propiedades de la cuenta:
//+------------------------------------------------------------------+ //| 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(); } //+------------------------------------------------------------------+
A continuación, reseteamos en el constructor de la clase la estructura con los
datos anteriores de la cuenta actual, y luego creamos una carpeta para
guardar los archivos de la clase, que para esta clase tendrá la ruta "Carpeta_general_de_datos"\Files\DoEasy\Accounts.
Después,
creamos un objeto de cuenta con los datos actuales de la cuenta,
y con la ayuda del método
AddToList(), lo añadimos a la lista de colección de cuentas. Si no hemos logrado añadir el objeto a la lista, se mostrará el mensaje
correspondiente en el diario, de lo contrario, en el diario se mostrará un mensaje con las propiedades abreviadas de la cuenta: login,
nombre del cliente, nombre de la compañía, saldo de la cuenta, tamaño del apalancamiento ofrecido y tipo de cuenta (si no es de
compensación).
El siguiente paso consiste en cargar en la lista de colección los
objetos de cuenta cuyos archivos de guardado se encuentran en la carpeta de guardado de objetos de esta clase.
Como último
paso,
buscamos el índice del objeto con los datos de la cuenta actual y se lo asignamos a la
variable
m_index_current, cuyo valor es retornado por el método IndexCurrentAccount() para ser usado en los
programas.
Llamamos en el destructor de la clase al método de guardado de todos los objetos de la lista de colección en los archivos que les corresponden:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CAccountsCollection::~CAccountsCollection(void) { //--- Save account objects from the list to the files this.SaveObjects(); } //+------------------------------------------------------------------+
Método de adición de un objeto de cuenta a una lista de colección:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Transmitimos al método el puntero al objeto de cuenta; a
continuación,
con la ayuda del método IsPresent(), comprobamos la presencia de este objeto en
la lista de colección, y si tal objeto no existe, el objeto es
añadido a la lista de colección, retornando el resultado de su adición.
Método para guardar los objetos de cuenta de la lista de colección en archivos:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Obtenemos en el ciclo por la lista de colección un objeto de cuenta de la lista, y luego creamos un nombre de archivo que conste de la ruta a la carpeta de los objetos de cuenta, el nombre del servidor y el login (número de cuenta) con la extensión ".bin". Si ese archivo ya existe en la carpeta de objetos de cuenta, el archivo será eliminado y se abrirá uno nuevo para el registro. Transmitimos el manejador del archivo abierto al método virtual Save() de la clase CAccount, que ya hemos analizado anteriormente, y añadimos el resultado del guardado del archivo a la variable res, que retorna desde el método el resultado del registro en el archivo de todo los objetos de cuenta de la lista de colección. Después de guardar el objeto, el archivo abierto para el registro será cerrado.
Método de cargado de los objetos de cuenta desde los archivos a la lista de colección:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Primero, encontramos el primer archivo en la carpeta de
guardado de archivos de los objetos de cuenta de la biblioteca; a continuación,
abrimos en el ciclo do-while el siguiente archivo encontrado para la lectura,
creamos un nuevo objeto de cuenta, cargamos
en este los datos del archivo con la ayuda del método virtual
Load() de la clase CAccount y, si ese objeto (con los mismos datos de cuenta) no existe en la lista, añadimos
el objeto a la lista. Al darse cualquier situación errónea, ya sea al
cargar los datos en un objeto desde un archivo o al añadir un objeto a la lista, deberemos necesariamente eliminar este nuevo objeto
(para evitar pérdidas de memoria) y cerrar el archivo abierto.
Al finalizar el funcionamiento del ciclo, se retorna el
resultado de la carga de datos a los objetos de cuenta desde los archivos y la colocación de estos objetos en la lista de colección.
Método para actualizar los datos del objeto de cuenta actual:
//+------------------------------------------------------------------+ //| 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(); } } //+------------------------------------------------------------------+
Aquí: lo primero que hacemos es comprobar si el índice del objeto de
cuenta con los datos de la cuenta actual es correcto, y si por algún motivo no ha sido obtenido, salimos
del método. A continuación, obtenemos de la lista el objeto de cuenta con
los datos de la cuenta actual según su índice en la lista, reseteamos la
estructura de datos de la cuenta actual, reseteamos la bandera de cambio de las propiedades del objeto de cuenta y llamamos
al método de establecimiento de las propiedades del objeto de cuenta. Este mismo método copiará en la estructura de datos de la
cuenta actual las últimas propiedades (recién calculadas), para compararlas posteriormente con el anterior estado de la cuenta y
determinar sus cambios.
A continuación, echamos un vistazo a ver qué hay escrito en la estructura de datos del estado anterior de la cuenta, y si
el campo del login es igual a cero, significará que la estructura no ha sido rellenada una sola vez. Por consiguiente, solo
tenemos que
rellenar la estructura que tiene los datos del estado anterior con los datos del
estado actual.
Acto seguido, comprobamos los cambios en la suma hash,
comparando la suma hash del estado actual con la suma hash del estado pasado. Si hay cambios, establecemos
la bandera de evento ocurrido de cambio de las propiedades de la cuenta y guardamos
el estado actual como pasado, para compararlos posteriormente.
Más tarde, implementaremos el seguimiento de cambios
importantes en el estado de la cuenta y el envío de mensajes de evento sobre estos cambios importantes al programa.
Dado que todo el trabajo con esta clase se realizará, como siempre, desde el objeto básico de la biblioteca, la clase CEngine, vamos a pasar al archivo Engine.mqh y añadir la funcionalidad necesaria.
En primer lugar, incluimos el archivo de la colección de cuentas:
//+------------------------------------------------------------------+ //| 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" //+------------------------------------------------------------------+
En la sección privada de la clase, creamos el objeto de colección de
cuentas y añadimos el método para trabajar con la colección de cuentas:
//+------------------------------------------------------------------+ //| 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:
Creamos en el constructor de la clase un nuevo contador del
temporizador para trabajar con la colección de cuentas:
//+------------------------------------------------------------------+ //| 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 } //+------------------------------------------------------------------+
Ya analizamos el funcionamiento de los temporizadores y sus contadores en la
tercera parte de la descricpión de la biblioteca.
Añadimos al manejador OnTimer() de la clase el temporizador de
la colección de cuentas:
//+------------------------------------------------------------------+ //| 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(); } } } //+------------------------------------------------------------------+
El temporizador de la colección de cuentas funciona de forma idéntica al temporizador de la colección de órdenes, transacciones y
posiciones, y ya lo analizamos en la tercera parte de la descripción de la biblioteca, al hablar sobre la creación del objeto básico de la
biblioteca, la clase
CEngine. La única diferencia con respecto al temporizador de la
colección de órdenes, transacciones y posiciones es la llamada de otro método de procesamiento de eventos de colección, la llamada del
método AccountEventsControl().
Vamos a añadir el método de comprobación del cambio en las propiedades de la cuenta actual:
//+------------------------------------------------------------------+ //| Check the account events | //+------------------------------------------------------------------+ void CEngine::AccountEventsControl(void) { this.m_accounts.Refresh(); } //+------------------------------------------------------------------+
Este método simplemente llama al método Refresh() de la clase CAccountsCollection.
Vamos a escribir en la sección pública de la clase CEngine dos métodos que retornan al programa las listas de la colección de eventos
y cuentas, lo que nos dará la posibilidad de recurrir a
estas listas de colección desde nuestros programas:
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(); }; //+------------------------------------------------------------------+
Ya está todo listo para proceder a la simulación de la clase de colección de cuentas. Pero antes de ello, vamos a introducir alguna corrección en el funcionamiento del método CEventsCollection::Refresh de la clase de colección de eventos. Vamos a añadir a la línea 233 del listado una comprobación sin la cual, en ciertos casos, se han dado activaciones erróneas al determinar eventos, enviando a veces al programa un evento antiguo y olvidado junto con uno nuevo:
//--- 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
Asimismo, hemos corregido errores "infantiles" cometidos al escribir las funciones comerciales para MQL4, para trabajar en el simulador de MetaTrader4 en el archivo DELib.mqh. De alguna forma, perdimos de vista que las funciones OrderSend() retornan a MQL4 el ticket de la orden, y no un valor boleano; por lo visto, resulta fácil olvidar MQL4 :)
Como ejemplo:
La comprobación del resultado del funcionamiento de
la función para MQL4 era así (para MQL5 es correcto):
if( ! OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue))
Lo hemos corregido por las comprobaciones correctas para MQL4:
if(OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue)==WRONG_VALUE)
En principio, esto no resultaba crítico para el simulador, pero si que era totalmente incorrecto.
Pronto
dispondremos de clases comerciales completamente funcionales, y estas funciones serán eliminadas de la biblioteca.
Poniendo a prueba la colección de cuentas
Para la simulación, vamos a tomar el asesor de este artículo (creado anteriormente), TestDoEasyPart12_1.mq5, y lo vamos a guardar con el
nuevo nombre
TestDoEasyPart12_2.mq5, en la misma carpeta \MQL5\Experts\TestDoEasy.
Introducimos en los parámetros de entrada del asesor una variable para alternar el tipo de información sobre las cuentas disponibles imprimida en el diario: o bien breve (false por defecto), o bien completa (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
Y añadimos el código en el manejador 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); } //+------------------------------------------------------------------+
Aquí: obtenemos la lista de colección de cuentas con la ayuda del método GetListAllAccounts() de la clase CEngine. Obtenemos de ella cada objeto sucesivo en el ciclo por la lista e imprimimos sus propiedades en el diario, dependiendo del valor de la variable de entrada: o bien una entrada breve, o bien la lista completa de propiedades del objeto de cuenta.
Iniciamos el asesor en el gráfico y miramos lo que nos muestra en el diario al seleccionar la entrada breve (en los ajustes Show full accounts properties = false):
Ahora, seleccionamos la muestra de las propiedades completas, pulsamos F7, y en la ventana de ajuste de los
parámetros para Show full accounts properties, indicamos true:
Ahora, en el diario se mostrará la lista completa de propiedades de cada una de las cuentas disponibles.
Queremos destacar que, para conseguir que las cuentas sean registradas en un archivo, deberemos conectarnos a la primera cuenta, después
volver a conectarnos a la segunda, y después a la siguiente, etc. Es decir, con cada conexión a una cuenta nueva, los datos de la cuenta
anterior se registrarán en un archivo.
¿Qué es lo próximo?
En el siguiente artículo, implementaremos el seguimiento de algunos eventos importantes de cambio en las propiedades de la cuenta, y
comenzaremos a trabajar con los objetos de símbolo y sus colecciones.
Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. El lector podrá
descargar y poner a prueba todo por sí mismo.
Si tiene cualquier duda, observación o sugerencia, podrá formularla en los comentarios al artículo.
Artículos de esta serie:
Parte 1. Concepto, organización de datos.
Parte
2. Colección de órdenes y transacciones históricas.
Parte 3. Colección de órdenes
y posiciones de mercado, organización de la búsqueda.
Parte 4. Eventos
comerciales. Concepto.
Parte 5. Clases y concepto de los eventos comerciales.
Envío de eventos al programa.
Parte 6. Eventos en las cuentas de compensación.
Parte 7. Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para los
eventos de modificación de órdenes y posiciones.
Parte 8. Eventos de modificación
de órdenes y posiciones.
Parte 9. Compatibilidad con MQL4 - Preparando los datos.
Parte 10. Compatibilidad con MQL4 - Eventos de apertura de posición y activación de órdenes
pendientes.
Parte 11. Compatibilidad con MQL4 - Eventos de cierre de posición.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/6952
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso