Asesor Experto multiplataforma: las clases CExpertAdvisor y CExpertAdvisor
Enrico Lambino | 12 diciembre, 2017
Índice
- Introducción
- Clase del asesor comercial
- Inicialización
- Determinando una nueva barra
- El manejador OnTick
- Contenedor de asesores expertos
- Almacenamiento de datos
- Ejemplos
- Observaciones finales
- Conclusión
Introducción
En los ejmplos de las anteriores partes del artículo de la serie (1, 2, 3, 4, 5, 6, 7, 8, 9) los componentes de los asesores comerciales estaban distribuidos de forma caótica por el principal archivo de encabezado del asesor. Las responsables de ello eran las funciones de usuario. Vamos a finalizar el trabajo de creación de las clases CExpertAdvisor y CExpertsAdvisors. Con su ayuda se organiza una interacción más armoniosa entre los componentes individuales del experto. Asimismo, analizaremos algunas cuestiones comunes en el uso de los asesores expertos: la carga y el almacenamiento de datos cambiantes y la detección de nuevas barras.
Clase del asesor comercial
La clase CExpertAdvisorBase se muestra en el código más abajo. Hasta el momento, la mayoría de diferencias entre MQL4 y MQL5 se suavizaban mediante el procesamiento con otros objetos de clase. Estos han sido descritos en los anteriores artículos de la serie.
class CExpertAdvisorBase : public CObject { protected: //--- parámetros de comercio bool m_active; string m_name; int m_distance; double m_distance_factor_long; double m_distance_factor_short; bool m_on_tick_process; //--- parámetros de la señal bool m_every_tick; bool m_one_trade_per_candle; datetime m_last_trade_time; string m_symbol_name; int m_period; bool m_position_reverse; //--- objetos de las señales CSignals *m_signals; //--- objetos comerciales CAccountInfo m_account; CSymbolManager m_symbol_man; COrderManager m_order_man; //--- objetos de tiempo de comercio CTimes *m_times; //--- vela CCandleManager m_candle_man; //--- evento CEventAggregator *m_event_man; //--- contenedor CObject *m_container; public: CExpertAdvisorBase(void); ~CExpertAdvisorBase(void); virtual int Type(void) const {return CLASS_TYPE_EXPERT;} //--- inicialización bool AddEventAggregator(CEventAggregator*); bool AddMoneys(CMoneys*); bool AddSignal(CSignals*); bool AddStops(CStops*); bool AddSymbol(const string); bool AddTimes(CTimes*); virtual bool Init(const string,const int,const int,const bool,const bool,const bool); virtual bool InitAccount(void); virtual bool InitCandleManager(void); virtual bool InitEventAggregator(void); virtual bool InitComponents(void); virtual bool InitSignals(void); virtual bool InitTimes(void); virtual bool InitOrderManager(void); virtual bool Validate(void) const; //--- contenedor void SetContainer(CObject*); CObject *GetContainer(void); //--- activación y desactivación bool Active(void) const; void Active(const bool); //--- métodos de colocación y obtención string Name(void) const; void Name(const string); int Distance(void) const; void Distance(const int); double DistanceFactorLong(void) const; void DistanceFactorLong(const double); double DistanceFactorShort(void) const; void DistanceFactorShort(const double); string SymbolName(void) const; void SymbolName(const string); //--- punteros a los objetos CAccountInfo *AccountInfo(void); CStop *MainStop(void); CMoneys *Moneys(void); COrders *Orders(void); COrders *OrdersHistory(void); CStops *Stops(void); CSignals *Signals(void); CTimes *Times(void); //--- administrador de órdenes string Comment(void) const; void Comment(const string); bool EnableTrade(void) const; void EnableTrade(bool); bool EnableLong(void) const; void EnableLong(bool); bool EnableShort(void) const; void EnableShort(bool); int Expiration(void) const; void Expiration(const int); double LotSize(void) const; void LotSize(const double); int MaxOrdersHistory(void) const; void MaxOrdersHistory(const int); int Magic(void) const; void Magic(const int); uint MaxTrades(void) const; void MaxTrades(const int); int MaxOrders(void) const; void MaxOrders(const int); int OrdersTotal(void) const; int OrdersHistoryTotal(void) const; int TradesTotal(void) const; //--- administrador de señales int Period(void) const; void Period(const int); bool EveryTick(void) const; void EveryTick(const bool); bool OneTradePerCandle(void) const; void OneTradePerCandle(const bool); bool PositionReverse(void) const; void PositionReverse(const bool); //--- velas adicionales void AddCandle(const string,const int); //--- detección de una nueva barra void DetectNewBars(void); //-- evento virtual bool OnTick(void); virtual void OnChartEvent(const int,const long&,const double&,const string&); virtual void OnTimer(void); virtual void OnTrade(void); virtual void OnDeinit(const int,const int); //--- restauración virtual bool Save(const int); virtual bool Load(const int); protected: //--- administrador de velas virtual bool IsNewBar(const string,const int); //--- administrador de órdenes virtual void ManageOrders(void); virtual void ManageOrdersHistory(void); virtual void OnTradeTransaction(COrder*) {} virtual datetime Time(const int); virtual bool TradeOpen(const string,const ENUM_ORDER_TYPE,double,bool); //--- administrador de símbolos virtual bool RefreshRates(void); //--- desinicialización void DeinitAccount(void); void DeinitCandle(void); void DeinitSignals(void); void DeinitSymbol(void); void DeinitTimes(void); };
La mayoría de los métodos declarados en esta clase cumplen el papel de envoltorio de los métodos de sus componentes. Los métodos clave de la clase se describen más abajo
Inicialización
En la fase de desinicialización del asesor, en primer lugar hay que crear los objetos necesarios para que la estrategia comercial funcione (money-management, señales, etcétera) e integrarlos con el ejemplar СExpertAdvisor, que también se debe crear en OnInit. Como resultado, al iniciar en el asesor cualquier función, necesitaremos una sola línea de código, que llamará el manejador correspondiente o el método del ejemplar CExpertAdvisor. Es algo muy parecido al método de uso de CExpert de la Biblioteca Estándar MQL5.
Después de crear el ejemplar CExpertAdvisor, se llama su método Init. Aquí tenemos el código de este método:
bool CExpertAdvisorBase::Init(string symbol,int period,int magic,bool every_tick=true,bool one_trade_per_candle=true,bool position_reverse=true) { m_symbol_name=symbol; CSymbolInfo *instrument; if((instrument=new CSymbolInfo)==NULL) return false; if(symbol==NULL) symbol=Symbol(); if(!instrument.Name(symbol)) return false; instrument.Refresh(); m_symbol_man.Add(instrument); m_symbol_man.SetPrimary(m_symbol_name); m_period=(ENUM_TIMEFRAMES)period; m_every_tick=every_tick; m_order_man.Magic(magic); m_position_reverse=position_reverse; m_one_trade_per_candle=one_trade_per_candle; CCandle *candle=new CCandle(); candle.Init(instrument,m_period); m_candle_man.Add(candle); Magic(magic); return false; }
Aquí creamos los ejemplares de la mayoría de los componentes, que con frecuencia se encuentran en las estrategias comerciales. Asimismo, aquí se incluye el símbolo o el instrumento a usar (debe ser transformado en un tipo de objeto) y el periodo/marco temporal por defecto. Aquí se indican las normas:
- sobre si deben ejecutarse las tareas esenciales de la estrategia en cada tick o solo en el tick inicial de cada vela;
- sobre si es necesario indicar un límite máximo - una transacción por vela (para prevenir las entradas múltiples en el transcurso de una vela);
- sobre si debe virar la posición al recibir una señal opuesta (cerrarse la transacción actual y abrirse una nueva basada en la nueva señal).
Al final de la función OnInit, el ejemplar CExpertAdvisor debe llamar su método InitComponents. El fragmento de código que vemos más abajo demuestra el método indicado para CExpertBase:
bool CExpertAdvisorBase::InitComponents(void) { if(!InitSignals()) { Print(__FUNCTION__+": error in signal initialization"); return false; } if(!InitTimes()) { Print(__FUNCTION__+": error in time initialization"); return false; } if(!InitOrderManager()) { Print(__FUNCTION__+": error in order manager initialization"); return false; } if(!InitCandleManager()) { Print(__FUNCTION__+": error in candle manager initialization"); return false; } if(!InitEventAggregator()) { Print(__FUNCTION__+": error in event aggregator initialization"); return false; } return true; }
En este método se llaman los métodos Init de todos los componentes del ejemplar del asesor. De una forma análoga, en este método se llaman los métodos Validate para cada componente, para comprobar si superarán sus ajustes la validación.
Determinando una nueva barra
Algunas estrategias comerciales funcionan en el primer tick de la nueva vela. Hay muchas formas de implementar esta función. Una de ellas es comparar la hora y el precio de apertura de la vela actual con su estado anterior. Este método se implementa en la clase CCandle. El siguiente fragmento de código muestra la declaración de la clase CCandleBase, básica para CCandle:
class CCandleBase : public CObject { protected: bool m_new; bool m_wait_for_new; bool m_trade_processed; int m_period; bool m_active; MqlRates m_last; CSymbolInfo *m_symbol; CEventAggregator *m_event_man; CObject *m_container; public: CCandleBase(void); ~CCandleBase(void); virtual int Type(void) const {return(CLASS_TYPE_CANDLE);} virtual bool Init(CSymbolInfo*,const int); virtual bool Init(CEventAggregator*); CObject *GetContainer(void); void SetContainer(CObject*); //--- métodos de colocación y obtención void Active(bool); bool Active(void) const; datetime LastTime(void) const; double LastOpen(void) const; double LastHigh(void) const; double LastLow(void) const; double LastClose(void) const; string SymbolName(void) const; int Timeframe(void) const; void WaitForNew(bool); bool WaitForNew(void) const; //--- procesamiento virtual bool TradeProcessed(void) const; virtual void TradeProcessed(bool); virtual void Check(void); virtual void IsNewCandle(bool); virtual bool IsNewCandle(void) const; virtual bool Compare(MqlRates &) const; //--- restauración virtual bool Save(const int); virtual bool Load(const int); };
La comprobación de la aparición de una nueva vela en el gráfico se lleva a cabo mediante Check:
CCandleBase::Check(void) { if(!Active()) return; IsNewCandle(false); MqlRates rates[]; if(CopyRates(m_symbol.Name(),(ENUM_TIMEFRAMES)m_period,1,1,rates)==-1) return; if(Compare(rates[0])) { IsNewCandle(true); TradeProcessed(false); m_last=rates[0]; } }
Al comprobar la nueva barra, el ejemplar del asesor debe llamar a este método en cada tick. Después, el programador puede ampliar CCxpertAdvisor de tal forma que al aparecer una nueva vela, pueda realizar diferentes tareas en el gráfico.
Como se muestra en el código más arriba, la comparación real de la hora y el precio de la barra se realiza con el método Compare de esta clase, como se muestra en el siguiente código:
bool CCandleBase::Compare(MqlRates &rates) const { return (m_last.time!=rates.time || (m_last.open/m_symbol.TickSize())!=(rates.open/m_symbol.TickSize()) || (!m_wait_for_new && m_last.time==0)); }
Este método de comprobación de una nueva barra depende de tres condiciones. El cumplimiento de al menos una de ellas garantiza el resultado true, lo que indica que una nueva vela ha aparecido en el gráfico:
- La última hora registrada no es igual a la hora de apertura de la barra actual.
- El último precio de apertura registrado no coincide con el precio de apertura de la barra actual.
- La última hora de apertura registrada es cero, y el nuevo tick no debe ser el primer tick de esta vela.
Las dos primeras condiciones implican una comparación directa del valor de la barra actual con el registro anterior. La tercera condición se aplica solo al primer tick, que será registrado por el experto. En el momento en que el asesor ha sido cargado en el gráfico, no dispone aún del registro anterior (valores de hora y precio de apertura). De esta forma, la última hora anotada será igual a cero. Algunos tráders analizan esta barra como nueva en sus asesores. Otros prefieren que el experto espere a que aparezca una nueva barra tras la inicialización del asesor.
Al igual que sucede con los otros tipos de clases que hemos descrito anteriormente, la clase CCandle también debe tener su contenedor, CCandleManager. En el código de más abajo se muestra la declaración de la clase СCandleManagerBase:
class CCandleManagerBase : public CArrayObj { protected: bool m_active; CSymbolManager *m_symbol_man; CEventAggregator *m_event_man; CObject *m_container; public: CCandleManagerBase(void); ~CCandleManagerBase(void); virtual int Type(void) const {return(CLASS_TYPE_CANDLE_MANAGER);} virtual bool Init(CSymbolManager*,CEventAggregator*); virtual bool Add(const string,const int); CObject *GetContainer(void); void SetContainer(CObject *container); bool Active(void) const; void Active(bool active); virtual void Check(void) const; virtual bool IsNewCandle(const string,const int) const; virtual CCandle *Get(const string,const int) const; virtual bool TradeProcessed(const string,const int) const; virtual void TradeProcessed(const string,const int,const bool) const; //--- recovery virtual bool Save(const int); virtual bool Load(const int); };
El ejemplar de la clase СCandle se crea usando como base el nombre del instrumento y el marco temporal. La presencia de CCandleManager hace más sencillo para el asesor monitorear varios gráficos del instrumento indicado, por ejemplo, podemos comprobar la aparición de una nueva vela en EURUSD M15 y EURUSD H1 en el mismo asesor. Los ejemplares de CCandle con símbolo y marco temporal idénticos están de más, por lo que es recomandable evitarlos. Durante la búsqueda de un ejemplar específico, CCandle solo tiene que llamar al método apropiado, que está en CCandleManager, y especificar el símbolo y el marco temporal. A su vez, CCandleManager, encontrará el correspondiente ejemplar de CCandle y llamará al método destinado a él.
Además de la comprobación de una nueva vela, CCandle y CCandleManager cumplen con otra función: comprueban si el asesor ha abierto una nueva transacción del símbolo y marco temporal dados. Es posible comprobar las transacciones recientes solo por el símbolo, sin tener en cuenta el marco temporal. Para implementar este comportamiento, el propio ejemplar de CExpertAdvisor debe establecer/resetear la bandera responsable de ello. El interrumpor para ambas clases se puede implementar con el uso del método TradeProcessed.
Al gestionar las velas, los métodos TradeProcessed (tanto para establecer como para recibir) funcionan solo para encontrar el ejemplar solicitado de CCandle y aplicar el valor correspondiente:
bool CCandleManagerBase::TradeProcessed(const string symbol,const int timeframe) const { CCandle *candle=Get(symbol,timeframe); if(CheckPointer(candle)) return candle.TradeProcessed(); return false; }
CCandle asigna un nuevo valor a uno de sus miembros de clase, m_trade_processed. Los responsables de ello son los métodos mostrados más abajo:
bool CCandleBase::TradeProcessed(void) const { return m_trade_processed; } CCandleBase::TradeProcessed(bool value) { m_trade_processed=value; }
El manejador OnTick
El método OnTick de la clase CExpertAdvisor se usa con mayor frecuencia en esta clase. Precisamente él ejecuta la mayor parte de las acciones. La principal operación de este método se muestra en el diagrama:
El proceso comienza con la conmutación de la bandera en el asesor. Esto previene el procesamiento repetido del tick. El método OnTick, en condiciones ideales, se llama solo dentro de la función del evento OnTick, pero también puede ser llamado por otros medios, por ejemplo, con OnChartEvent. En ausencia de esta bandera, el método OnTick de la clase puede ser llamado cuando el procesamiento del tick anterior aún no ha terminado. Entonces el tick puede ser procesado más de una vez, y si en él se genera una transacción, esta también puede ser duplicada.
La actualización de los datos es necesaria para que el asesor tenga acceso a los datos más recientes y no tenga que pasar al procesamiento del tick anterior. Si el asesor no logra actualizar los datos, resetea la bandera de procesamiento del tick, finaliza el funcionamiento del método y comienza a esperar un nuevo tick.
El siguiente paso es buscar nuevas barras y comprobar las señales comerciales. Esta comprobación se realiza por defecto en cada tick. Sin embargo, podemos ampliar este método de tal forma que realice esta comprobación solo cuando se haya registrado la llegada de una nueva señal (para acelerar el tiempo de procesamiento, especialmente durante los back-tests y la optimización).
Asimismo, en la clase se encuentra el miembro de la clase m_position_reverse, responsable del viraje de la posición en la dirección opuesta a la señal recibida. Este viraje se realiza aquí solo para neutralizar la posición actual. En MetaTrader 4 y en el modo de cobertura de MetaTrader 5 el miembro de la clase procesa la salida de las transacciones opuestas a la señal recibida (las transacciones cuya dirección se corresponde con la dirección de la señal actual, no se cierran). En el sistema de compensación de MetaTrader 5 puede haber solo una posición. De esta forma, el asesor abrirá una posición nueva con un volumen análogo a la transacción actual, pero en dirección opuesta.
La señal comercial se procesa básicamente usando m_signals, pero hay otros factores que pueden prevenir la apertura de una nueva transacción por parte del asesor, como por ejemplo, el comercio solo en una nueva barra o los filtros temporales. El experto obtine la posibilidad de abrir una nueva operación solo cuando se cumplen todas las condiciones.
Al final del procesamiento del tick, el asesor pondrá la bandera de tick en false, y después de ello, se le permitirá procesar otro tick.
Contenedor de asesores expertos
De forma análoga a los otros objetos de clase descritos en las anteriores partes del artículo, la clase CExpertAdvisor también tiene su propio contenedor, CExpertAdvisors. En el fragmento de código que vemos a continuación se muestra la declaración de su clase básica, CExpertAdvisorsBase:
class CExpertAdvisorsBase : public CArrayObj { protected: bool m_active; int m_uninit_reason; CObject *m_container; public: CExpertAdvisorsBase(void); ~CExpertAdvisorsBase(void); virtual int Type(void) const {return CLASS_TYPE_EXPERTS;} virtual int UninitializeReason(void) const {return m_uninit_reason;} //--- métodos de establecimiento y obtención void SetContainer(CObject *container); CObject *GetContainer(void); bool Active(void) const; void Active(const bool); int OrdersTotal(void) const; int OrdersHistoryTotal(void) const; int TradesTotal(void) const; //--- inicialización virtual bool Validate(void) const; virtual bool InitComponents(void) const; //--- evento virtual void OnTick(void); virtual void OnChartEvent(const int,const long&,const double&,const string&); virtual void OnTimer(void); virtual void OnTrade(void); virtual void OnDeinit(const int,const int); //--- restauración virtual bool CreateElement(const int); virtual bool Save(const int); virtual bool Load(const int); };
Este contenedor refleja principalmente los métodos públicos de la clase. Un ejemplo de ello es el manejador OnTick. El método simplemente itera por cada ejemplar de СExpertAdvisor para llamar su método OnTick:
void CExpertAdvisorsBase::OnTick(void) { if(!Active()) return; for(int i=0;i<Total();i++) { CExpertAdvisor *e=At(i); e.OnTick(); } }
La presencia de este contenedor da la posibilidad de guardar multitud de ejemplares de CExpertAdvisor. Se trata, posiblemente, del único método para iniciar varios asesores en un gráfico. Solo tendrá que inicializar varios ejemplares de CExpertAdvisor, guardar sus punteros a un contenedor CExpertAdvisors, y después usar el método OnTick del contenedor, para que los métodos OnTick de cada ejemplar CExpertAdvisor se activen. Lo mismo se puede hacer para cada ejemplar de la clase CExpert de la Biblioteca estándar MQL5, usando la clase CArrayObj o sus herederos.
Almacenamiento de datos
Algunos datos que se usan en los ejemplares CExpertAdvisor, se guardan solo en la memoria de la computadora. Normalmente, los datos necesarios se guardan en la plataforma, y el asesor los recibe desde ella con la ayuda de la función de llamada. Sin embargo, para los datos que se crean dinámicamente mientras el asesor está en marcha, esto no es así. Cuando el experto inicia el evento OnDeinit, el asesor elimina todos los objetos y de esta forma pierde sus datos.
OnDeinit puede ser iniciado en varios casos: al cerrar la plataforma completa (MetaTrader 4 o MetaTrader 5), al eliminar el experto del gráfico o cuando sucede la nueva compilación del asesor tras cambiar su código. La lista completa de los posibles eventos que pueden causar la desinicialización se puede ver utilizando UninitializeReason. Cuando el asesor no tiene acceso a estos datos, es muy probable que se comporte como si se hubiese cargado por primera vez en el gráfico.
La mayoría de los datos volátiles en la clase CExpertAdvisor puede encontrarse en uno de sus miembros, que es una instancia COrderManager. Aquí se crean los ejemplares COrder y COrderStop (y sus descendientes), al tiempo que el experto cumple con su trabajo habitual. Puesto que dichos ejemplares se crean de forma dinámica usando OnTick, estos no se restablecen cuando el asesor se inicializa de nuevo. Por eso, en el asesor debe implementarse un método de guardado y extracción de estos datos cambiantes. Uno de los métodos de implementación de esta acción consiste en usar un heredero de la clase CFileBin, CExpertFile. En el código descrito más abajo se muestra la declaración de su clase básica, CExpertFileBase.
class CExpertFileBase : public CFileBin { public: CExpertFileBase(void); ~CExpertFileBase(void); void Handle(const int handle) { m_handle=handle; }; uint WriteBool(const bool value); bool ReadBool(bool &value); };
Aquí expandimos CFileBin, para declarar de forma explícita los métodos de registro y cálculo de los datos del tipo bool.
Al final del archivo de esta clase declaramos un ejemplar de la clase CExpertFile. Este ejemplar se usará todo el tiempo para el funcionamiento del asesor, si este debe guardar y cargar los datos cambiantes. Como alternativa, podemos simplemente confiar en los métodos Save y Load, heredados de CObject, y procesar el guardado y la carga de forma habitual. Pero puede ser un trabajo muy meticuloso. Podremos ahorrar muchas fuerzas y líneas de código si usamos solo CFile (y sus herederos).
//CExpertFileBase class definition //+------------------------------------------------------------------+ #ifdef __MQL5__ #include "..\..\MQL5\File\ExpertFile.mqh" #else #include "..\..\MQL4\File\ExpertFile.mqh" #endif //+------------------------------------------------------------------+ CExpertFile file; //+------------------------------------------------------------------+
El gestor de órdenes guarda los datos cambiantes con el método Save:
bool COrderManagerBase::Save(const int handle) { if(handle==INVALID_HANDLE) return false; file.WriteDouble(m_lotsize); file.WriteString(m_comment); file.WriteInteger(m_expiration); file.WriteInteger(m_history_count); file.WriteInteger(m_max_orders_history); file.WriteBool(m_trade_allowed); file.WriteBool(m_long_allowed); file.WriteBool(m_short_allowed); file.WriteInteger(m_max_orders); file.WriteInteger(m_max_trades); file.WriteObject(GetPointer(m_orders)); file.WriteObject(GetPointer(m_orders_history)); return true; }
La mayor parte de estos datos son tipos primitivos, excepto los dos últimos, que son los contenedores de las órdenes actuales y las históricas. Para estos datos, se usa el método WriteObject de la clase CFileBin, que simplemente llama el método Save del objeto que hay que escribir. En el código de más abajo se muestra el métod Save de la clase COrderBase:
bool COrderBase::Save(const int handle) { if(handle==INVALID_HANDLE) return false; file.WriteBool(m_initialized); file.WriteBool(m_closed); file.WriteBool(m_suspend); file.WriteInteger(m_magic); file.WriteDouble(m_price); file.WriteLong(m_ticket); file.WriteEnum(m_type); file.WriteDouble(m_volume); file.WriteDouble(m_volume_initial); file.WriteString(m_symbol); file.WriteObject(GetPointer(m_order_stops)); return true; }
Como podemos ver aquí, el proceso simplemente se repite al guardar los objetos. Los tipos de datos primitivos se guardan en un archivo, como siempre. Para los datos de tipo complejo, el método Save del objeto se llama a través del método WriteObject de la clase CFileBin.
En los casos en los que tenemos ante nosotros varios ejemplares de CExpertAdvisor, el contenedor CExpertAdvisors también será capaz de guardar datos:
bool CExpertAdvisorsBase::Save(const int handle) { if(handle!=INVALID_HANDLE) { for(int i=0;i<Total();i++) { CExpertAdvisor *e=At(i); if(!e.Save(handle)) return false; } } return true; }
Los métodos Save se llaman para cada ejemplar de CExpertAdvisor. El único manejador de archivo significa que para cada archivo de un asesor experto debe haber solo un archivo de guardado. Cada ejemplar de CExpertAdvisor podría tener su propio archivo de guardado, pero esto supondría un enfoque más complicado.
La carga de datos es la parte más complicada. Al ejecutar el guardado, los valores de ciertos miembros de la clase se anotan en un archivo. Al cargar estos datos, los ejemplares del objeto deberán ser restablecidos, y, si fuera posible, en el mismo estado que tenían antes del guardado. El código siguiente muestra el método Load del gestor de órdenes.
bool COrderManagerBase::Load(const int handle) { if(handle==INVALID_HANDLE) return false; if(!file.ReadDouble(m_lotsize)) return false; if(!file.ReadString(m_comment)) return false; if(!file.ReadInteger(m_expiration)) return false; if(!file.ReadInteger(m_history_count)) return false; if(!file.ReadInteger(m_max_orders_history)) return false; if(!file.ReadBool(m_trade_allowed)) return false; if(!file.ReadBool(m_long_allowed)) return false; if(!file.ReadBool(m_short_allowed)) return false; if(!file.ReadInteger(m_max_orders)) return false; if(!file.ReadInteger(m_max_trades)) return false; if(!file.ReadObject(GetPointer(m_orders))) return false; if(!file.ReadObject(GetPointer(m_orders_history))) return false; for(int i=0;i<m_orders.Total();i++) { COrder *order=m_orders.At(i); if(!CheckPointer(order)) continue; COrderStops *orderstops=order.OrderStops(); if(!CheckPointer(orderstops)) continue; for(int j=0;j<orderstops.Total();j++) { COrderStop *orderstop=orderstops.At(j); if(!CheckPointer(orderstop)) continue; for(int k=0;k<m_stops.Total();k++) { CStop *stop=m_stops.At(k); if(!CheckPointer(stop)) continue; orderstop.Order(order); if(StringCompare(orderstop.StopName(),stop.Name())==0) { orderstop.Stop(stop); orderstop.Recreate(); } } } } return true; }
Como podemos ver, el código del método en COrderManager es más complejo, a diferencia del método Load análogo en la clase CExpertAdvisor. El motivo es que, a diferencia del gestor de órdenes, los ejemplares de CExpertAdvisor se crean a través de OnInit, así que le contenedor solo necesita llamar los métodos Load para cada ejmplar CExpertAdvisor, en lugar de usar el método ReadObject de la clase CFileBin.
Los ejemplares de la clase que no hayan sido creado durante la ejecución de OnInit, deberán irse creando a medida que el asesor se cargue de nuevo. Esto se logra expandiendo el método CreateElement de la clase CArrayObj. El objeto no puede crearse a sí mismo, por lo que debe ser creado por su objeto primario o contenedor, o incluso desde el archivo de encabezamiento del código fuente. Podemos ver un ejemplo en el método ampliado de CreateElement, que se encuentra en COrdersBase. En esta clase, el contendor es COrders (heredero de la clase COrdersBase), y el objeto creado tiene el tipo COrder:
bool COrdersBase::CreateElement(const int index) { COrder*order=new COrder(); if(!CheckPointer(order)) return(false); order.SetContainer(GetPointer(this)); if(!Reserve(1)) return(false); m_data[index]=order; m_sort_mode=-1; return CheckPointer(m_data[index]); }
Aquí, aparte del elemento de creación, también establecemos su objeto ancestro o contenedor, para distinguir si pertenece a la lista de transacciones activas (miembro m_orders de la clase COrderManagerBase) o históricas (miembro m_orders_history de la clase COrderManagerBase).
Ejemplos
Los ejemplos №№ 1 — 4 de este artículo son versiones cambiadas de los cuatro ejemplos del artículo anterior (ver Asesor Experto Multiplataforma: Stops Personalizados, Ausencia de Pérdidas y Trailing). Vamos a echar un vistazo a un ejemplo más complejo, expert_custom_trail_ha_ma.mqh, que es una variante modificada de custom_trail_ha_ma.mqh.
Antes de la función OnInit, declaramos los siguientes ejemplares de objetos globales:
COrderManager *order_manager; CSymbolManager *symbol_manager; CSymbolInfo *symbol_info; CSignals *signals; CMoneys *money_manager; CTimes *time_filters;
Lo sustituimos por el ejemplar CExpert. Algunos de los ejemplares enumerados más arriba se encuentran en el propio CExpetAdvisor (por ejemplo, COrderManager), el resto deben ser creados por el método OnInit (contenedores de las clases):
CExpertAdvisors experts;
Al principio del método creamos el ejemplar CExpertAdvisor. Asimismo, llamamos el método Init con los ajustes básicos:
int OnInit() { //--- CExpertAdvisor *expert=new CExpertAdvisor(); expert.Init(Symbol(),Period(),12345,true,true,true); //--- other code //--- return(INIT_SUCCEEDED); }
Ya no es necesario crear CSymbolInfo / CSymbolManager, puesto que los ejemplares de la clase CExpertAdvisor pueden crear ellos mismos los ejemplares de estas clases.
La función definida por el usuario también debe ser eliminada, nuestro experto ya no la necesita.
También eliminamos del código la declaración global de los contenedores, puesto que ellos deben ser declarados en OnInit. Un ejemplo de ello es el contenedor de filtros temporales (CTimeFilters), que se encuentra en la función OnInit:
CTimes *time_filters=new CTimes();
Los punteros a los contenedores, que antes fueron añadidos al gestor de órdenes, ahora, en lugar de ello, se añaden al ejemplar de CExpertAdvisor. Todos los demás contenedores que no hayan sido añadidos al gestor de órdenes, también deberán ser añadidos al ejemplar CExpertAdvisor. Precisamente COrderManager guarda estos punteros. El ejemplar de CExpertAdvisor crea solo los métodos de envoltorio.
Después de ello, añadimos el ejemplar de CExpertAdvisor al ejemplar CExpertAdvisors. A continuación, llamamos el método InitComponents del ejemplar CExpertAdvisors. Esto garantiza la inicialización de todos los ejemplares de CExpertAdvisor y sus componentes.
int OnInit() { //--- //--- otro código experts.Add(GetPointer(expert)); if(!experts.InitComponents()) return(INIT_FAILED); //--- otro código //--- return(INIT_SUCCEEDED); }
Y al fin, insertamos el código que posibilita la nueva carga del experto, si su trabajo ha sido interrumpido.
int OnInit() { //--- //--- otro código file.Open(savefile,FILE_READ); if(!experts.Load(file.Handle())) return(INIT_FAILED); file.Close(); //--- return(INIT_SUCCEEDED); }
Si el asesor no se carga desde el archivo, retorna INIT_FAILED. Sin embargo, si no está el archivo de guardado (y se genera INVALID_HANDLE), el asesor no tendrá error de inicialización, puesto que los métodos Load de las clases CExpertAdvisors y CExpertAdvisor retornan true después de recibir un manejador no válido. Este enfoque conlleva un determinado riesgo, pero es muy poco probable que el archivo guardado sea abierto por otro programa. Simplemente, asegúrese de que cada ejemplar del asesor que esté iniciado en el gráfico tenga un archivo de guardado aparte (como sucede con el número mágico de la orden).
El quinto ejemplo no lo hemos tomado del artículo anterior. En lugar de ello, los cuatro asesores de este artículo se han combinado en un experto dentro de él. En él se usa una versión ligeramente modificada de la función OnInit para cada uno de estos asesores, que se declara como "definida por el usuario". Su valor devuelto tiene el tipo CExpertAdvisor*. Si no se ha logrado crear el experto, se retorna NULL en lugar de INIT_SUCCEEDED. El fragmento de código representado más abajo muestra la función actualizada OnInit del archivo de encabezamiento del experto combinado:
int OnInit() { //--- CExpertAdvisor *expert1=expert_breakeven_ha_ma(); CExpertAdvisor *expert2=expert_trail_ha_ma(); CExpertAdvisor *expert3=expert_custom_stop_ha_ma(); CExpertAdvisor *expert4=expert_custom_trail_ha_ma(); if (!CheckPointer(expert1)) return INIT_FAILED; if (!CheckPointer(expert2)) return INIT_FAILED; if (!CheckPointer(expert3)) return INIT_FAILED; if (!CheckPointer(expert4)) return INIT_FAILED; experts.Add(GetPointer(expert1)); experts.Add(GetPointer(expert2)); experts.Add(GetPointer(expert3)); experts.Add(GetPointer(expert4)); if(!experts.InitComponents()) return(INIT_FAILED); file.Open(savefile,FILE_READ); if(!experts.Load(file.Handle())) return(INIT_FAILED); file.Close(); //--- return(INIT_SUCCEEDED); }
El experto comienza el trabajo con la inicialización de cada ejemplar de CExpertAdvisor. A continuación, pasa a la comprobación de cada uno de los punteros a CExpertAdvisor. Si el puntero no es dinámico, la función retorna INIT_FAILED, y la inicialización no tendrá éxito. Si cada uno de los ejemplares supera la comprobación de los punteros, estos punteros se guardan en el ejemplar CExpertAdvisors. El ejemplar CExpertAdvisors (el contenedor, no el ejemplar del asesor) iniciliaza a continuación sus componentes y, en caso necesario, carga los datos anteriores.
El asesor usa la función definida por el usuario para crear el ejemplar СExpertAdvisor. Más abajo se muestra la función usada para crear el ejemplar del cuarto asesor:
CExpertAdvisor *expert_custom_trail_ha_ma() { CExpertAdvisor *expert=new CExpertAdvisor(); expert.Init(Symbol(),Period(),magic4,true,true,true); CMoneys *money_manager=new CMoneys(); CMoney *money_fixed=new CMoneyFixedLot(0.05); CMoney *money_ff=new CMoneyFixedFractional(5); CMoney *money_ratio=new CMoneyFixedRatio(0,0.1,1000); CMoney *money_riskperpoint=new CMoneyFixedRiskPerPoint(0.1); CMoney *money_risk=new CMoneyFixedRisk(100); money_manager.Add(money_fixed); money_manager.Add(money_ff); money_manager.Add(money_ratio); money_manager.Add(money_riskperpoint); money_manager.Add(money_risk); expert.AddMoneys(GetPointer(money_manager)); CTimes *time_filters=new CTimes(); if(time_range_enabled && time_range_end>0 && time_range_end>time_range_start) { CTimeRange *timerange=new CTimeRange(time_range_start,time_range_end); time_filters.Add(GetPointer(timerange)); } if(time_days_enabled) { CTimeDays *timedays=new CTimeDays(sunday_enabled,monday_enabled,tuesday_enabled,wednesday_enabled,thursday_enabled,friday_enabled,saturday_enabled); time_filters.Add(GetPointer(timedays)); } if(timer_enabled) { CTimer *timer=new CTimer(timer_minutes*60); timer.TimeStart(TimeCurrent()); time_filters.Add(GetPointer(timer)); } switch(time_intraday_set) { case INTRADAY_SET_1: { CTimeFilter *timefilter=new CTimeFilter(time_intraday_gmt,intraday1_hour_start,intraday1_hour_end,intraday1_minute_start,intraday1_minute_end); time_filters.Add(timefilter); break; } case INTRADAY_SET_2: { CTimeFilter *timefilter=new CTimeFilter(0,0,0); timefilter.Reverse(true); CTimeFilter *sub1 = new CTimeFilter(time_intraday_gmt,intraday2_hour1_start,intraday2_hour1_end,intraday2_minute1_start,intraday2_minute1_end); CTimeFilter *sub2 = new CTimeFilter(time_intraday_gmt,intraday2_hour2_start,intraday2_hour2_end,intraday2_minute2_start,intraday2_minute2_end); timefilter.AddFilter(sub1); timefilter.AddFilter(sub2); time_filters.Add(timefilter); break; } default: break; } expert.AddTimes(GetPointer(time_filters)); CStops *stops=new CStops(); CCustomStop *main=new CCustomStop("main"); main.StopType(stop_type_main); main.VolumeType(VOLUME_TYPE_PERCENT_TOTAL); main.Main(true); //main.StopLoss(stop_loss); //main.TakeProfit(take_profit); stops.Add(GetPointer(main)); CTrails *trails=new CTrails(); CCustomTrail *trail=new CCustomTrail(); trails.Add(trail); main.Add(trails); expert.AddStops(GetPointer(stops)); MqlParam params[1]; params[0].type=TYPE_STRING; #ifdef __MQL5__ params[0].string_value="Examples\\Heiken_Ashi"; #else params[0].string_value="Heiken Ashi"; #endif SignalHA *signal_ha=new SignalHA(Symbol(),0,1,params,signal_bar); SignalMA *signal_ma=new SignalMA(Symbol(),(ENUM_TIMEFRAMES) Period(),maperiod,0,mamethod,maapplied,signal_bar); CSignals *signals=new CSignals(); signals.Add(GetPointer(signal_ha)); signals.Add(GetPointer(signal_ma)); expert.AddSignal(GetPointer(signals)); //--- return expert; }
Como podemos ver, el código se parace mucho a la función OnInit del archivo de encabezamiento del asesor original (expert_custom_trail_ha_ma.mqh). Otras funciones definidas por el usuario se organizan de forma semejante.
Observaciones finales
Antes de concluir esta serie de artículos, voy a hablar un poco a los lectores que quieran utilizar la biblioteca acerca de los factores que contribuyen a su desarrollo.
Hasta la fecha, la biblioteca que se presenta en el anexo a este artículo tiene más de 10.000 líneas de código (incluyendo los comentarios). A pesar de esto, todavía no se ha completado. Para sacar el máximo provecho de MQL4 y MQL5, aún hay queda mucho trabajo.
Empecé a trabajar en este proyecto antes de que en MetaTrader 5 estuviese disponible el modo de cobertura. Esto influyó mucho en el desarrollo posterior de la biblioteca. Como resultado, la biblioteca tiende a adoptar las convenciones usadas en MetaTrader 4, más que las de MetaTrader 5. Además, me he encontrado con una serie de problemas de compatibilidad en algunas versiones lanzadas en los últimos años, lo que ha implicado la introducción de ciertos ajustes en el código (y como consecuencia de ello, se ha retrasado la publicación de algunas partes de esta serie). Al momento de escribir estas líneas, el lanzamiento de actualizaciones para ambas plataformas se ha convertido en algo cada vez más raro y estable. Se espera que esta tendencia aumente. Sin embargo, la biblioteca deberá perfeccionarse para futuras actualizaciones de las plataformas que podrían causar incompatibilidad.
La biblioteca utiliza los datos almacenados en la memoria para monitorear sus propias operaciones. Esto se ha convertido en la razón por la que un experto creado con su uso, depende en gran medida del almacenamiento y carga de los datos para permitir la recuperación del trabajo después de posibles interrupciones y fallos. El posterior trabajo sobre esta biblioteca (así como sobre cualquier producto multiplataforma) deberá estar orientado a la implementación independiente (o casi independiente), como la implementación de la Biblioteca Estándar MQL5.
Y una última observación: la biblioteca creada en esta serie de artículos no debe ser considerada como una solución permanente. Se la puede utilizar para una transición más suave de MetaTrader 4 a MetaTrader 5. La incompatibilidad entre MQL4 y MQL5 puede ser un serio obstáculo para los tráders que tengan la intención de pasar a la nueva plataforma. Como resultado, el código fuente en MQL4 escrito para sus asesores debe ser rediseñado para que sea compatible con el compilador MQL5. La biblioteca creada en este artículo se presenta como un medio para desplegar el asesor en una nueva plataforma con algunos ajustes en el código fuente, o ninguno en absoluto. Esto puede ayudar al tráder a la hora de decidir si va a seguir utilizando MetaTrader 4 o va a dar el salto a MetaTrader 5. Si finalmente decide cambiar, el uso de esta biblioteca puede reducir al mínimo el número de cambios, permitiendo que el tráder pueda utilizar el asesor de la forma acostumbrada. Pero, en el caso de seguir usando por el momento la plataforma antigua, el tráder tendrá todavía la oportunidad de cambiar rápidamente a MetaTrader 5 en el momento en el que los desarrolladores dejen de dar soporte a MetaTrader 4.
Conclusión
En este artículo se han desarrollado los objetos de la clase CExpertAdvisor y CExpertAdvisors, que se usan para integrar todos los componentes del asesor comercial multiplataforma analizados en los anteriores artículos. El artículo estudia cómo se crean estas dos clases y cómo se relacionan con los demás componentes del asesor comercial multiplataforma. Asimismo, se presentan varias soluciones a los problemas que surgen habitualmente al trabajar con asesores: la detección de una nueva barra, y el guardado y la carga de los datos modificados.
Programas utilizados en el artículo
# | Nombre |
Tipo |
Descripción de los parámetros |
---|---|---|---|
1 |
expert_breakeven_ha_ma.mqh |
Archivo de encabezamiento |
Archivo de encabezamiento principal utilizado en el primer ejemplo |
2. |
expert_breakeven_ha_ma.mq4 | Asesor Experto |
Archivo fuente principal utilizado para la versión MQL4 en el primer ejemplo |
3. |
expert_breakeven_ha_ma.mq5 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL5 en el primer ejemplo |
4. |
expert_trail_ha_ma.mqh | Archivo de encabezamiento | Archivo de encabezamiento principal utilizado en el segundo ejemplo |
5. |
expert_trail_ha_ma.mq4 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL4 en el segundo ejemplo |
6. |
expert_trail_ha_ma.mq5 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL5 en el segundo ejemplo |
7. |
expert_custom_stop_ha_ma.mqh | Archivo de encabezamiento | Archivo de encabezamiento principal utilizado en el tercer ejemplo |
8. |
expert_custom_stop_ha_ma.mq4 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL4 en el tercer ejemplo |
9. |
expert_custom_stop_ha_ma.mq5 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL5 en el tercer ejemplo |
10. |
expert_custom_trail_ha_ma.mqh | Archivo de encabezamiento | Archivo de encabezamiento principal utilizado en el cuarto ejemplo |
11. |
expert_custom_trail_ha_ma.mq4 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL4 en el cuarto ejemplo |
12. |
expert_custom_trail_ha_ma.mq5 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL5 en el cuarto ejemplo |
13. |
combined.mqh | Archivo de encabezamiento | Archivo de encabezamiento principal utilizado en el quinto ejemplo |
13. |
combined.mq4 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL4 en el quinto ejemplo |
15. |
combined.mq5 | Asesor Experto | Archivo fuente principal utilizado para la versión MQL5 en el quinto ejemplo |
Archivos de las clases creadas en el artículo
# |
Nombre |
Tipo |
Descripción de los parámetros |
---|---|---|---|
1 | MQLx\Base\Expert\ExperAdvisorsBase | Archivo de encabezamiento |
CExpertAdvisors (contenedor CExpertAdvisor, clase básica) |
2. |
MQLx\MQL4\Expert\ExperAdvisors | Archivo de encabezamiento | CExpertAdvisors (versión MQL4) |
3. |
MQLx\MQL5\Expert\ExperAdvisors | Archivo de encabezamiento |
CExpertAdvisors (versión MQL5) |
4. |
MQLx\Base\Expert\ExperAdvisorBase | Archivo de encabezamiento |
CExpertAdvisor (clase básica) |
5. |
MQLx\MQL4\Expert\ExperAdvisor | Archivo de encabezamiento |
CExpertAdvisor (versión MQL4) |
6. |
MQLx\MQL5\Expert\ExperAdvisor | Archivo de encabezamiento |
CExpertAdvisor (versión MQL5) |
7. |
MQLx\Base\Candle\CandleManagerBase | Archivo de encabezamiento | CCandleManager (contenedor CCandle, clase básica) |
8. |
MQLx\MQL4\Candle\CandleManager | Archivo de encabezamiento | CCandleManager (versión MQL4) |
9. |
MQLx\MQL5\Candle\CandleManager | Archivo de encabezamiento | CCandleManager (versión MQL5) |
10. |
MQLx\Base\Candle\CandleBase | Archivo de encabezamiento | CCandle (clase básica) |
11. |
MQLx\MQL4\Candle\Candle | Archivo de encabezamiento | CCandle (versión MQL4) |
12. |
MQLx\MQL5\Candle\Candle | Archivo de encabezamiento |
CCandle (versión MQL5) |
13. |
MQLx\Base\File\ExpertFileBase | Archivo de encabezamiento | CExpertFile (clase básica) |
13. |
MQLx\MQL4\File\ExpertFile | Archivo de encabezamiento | CExpertFile(versión MQL4) |
15. |
MQLx\MQL5\File\ExpertFile | Archivo de encabezamiento | CExpertFile(versión MQL5) |