
Recetas MQL5 - procesamiento de eventos personalizados del gráfico
Introducción
Este artículo es la continuación lógica del artículo Recetas de MQL5 - procesamiento de eventos típicos del gráfico. En el material actual propongo al lector examinar la metodología de trabajo con los eventos personalizados del gráfico. Se presentarán ejemplos de creación y procesamiento de los eventos personalizados. Y con esto, además, se usará un instrumental orientado a objetos.
Querría destacar que el tema de los eventos personalizados es muy amplio. Se trata de un caso en el que el trabajo del programador y el desarrollador se conjuga con la artisticidad y la creatividad.
1. Eventos personalizados del gráfico
Como reza el propio nombre, es el usuario el que introduce ese tipo de eventos. El programador mismo decide, qué o cuál tarea o bloque de programa dotar con forma de evento. El procesador MQL5 permite crear sus propios eventos, lo que aumenta la flexibilidad del propio lenguaje al realizar algoritmos complejos.
El evento personalizado es el segundo tipo posible de evento de gráfico. El primero sería el evento típico. Y aunque en la Documentación no existe un término como "evento típico de gráfico", propongo de todas formas utilizarlo para referirnos a los primeros 10 tipos de eventos del gráfico.
El desarrollador propone para procesar todos los eventos del gráfico 1 la enumeración ENUM_CHART_EVENT.
De acuerdo con la Documentación, existen 65536 identificadores de eventos personalizados. El primer y último identificador de eventos personalizados se establecen con los valores claros CHARTEVENT_CUSTOM y CHARTEVENT_CUSTOM_LAST, que en expresión numérica serían igual a 1000 y 66534 respectivamente (fig.1).
Fig.1 Primer y último identificador de los eventos personalizados
Si el autor se lleva bien con la aritmética, entonces, contando el primer identificador y el último, obtenemos en total: 66534-1000+1=65535.
Antes de utilizar los eventos personalizados, hay que inventarlos. En este sentido, el desarrollador es el inspirador de la idea y el autor de la concepción del evento, que luego se realizará en forma de algoritmo para el futuro asesor. No estaría mal disponer de una cierta clasificación de los eventos personalizados. Este método cognitivo permite, si no deshacerse, al menos sí reducir el nivel de indeterminación, aumentando el orden en el transcurso del razonamiento.
Propongo estudiar este criterio de evento personalizado como una fuente. Por ejemplo, el desarrollador sergeev ha lanzado una idea sobre un prototipo de robot comercial. Divide todos los eventos en 3 grupos (fig.2).
Fig.2 Grupos de fuentes de eventos personalizados
Entonces, de acuerdo con esta idea general, hay que procesar los eventos personalizados partiendo de la pertenencia de grupo.
Vamos a probar, para comenzar, a "crear" algo sencillo. Tomemos el primer grupo, los eventos de indicador. Los eventos que pueden entrar en este grupo son: la creación y eliminación de un indicador, la obtención de una señal de apertura, la obtención de una señal de cierre. El segundo grupo lo constituyen los eventos de cambio de estado de las órdenes y posiciones. Vamos a suponer que en nuestro ejemplo, este grupo incluya: apertura de posición y cierre de posición. Todo es muy simple. Y, el que quizá sea el grupo más complicado para la formalización es el de los eventos externos.
Tomemos 2 eventos: pausa y actualización del comercio de manera manual.
Fig.3 Fuentes de los eventos personalizados
El método deductivo (de lo general a lo concreto) permite exponer al detalle el esquema primario (fig.3). Precisamente, conforme a este esquema crearemos más tarde los tipos de eventos en la clase correspondiente (recuadro1).
Recuadro1 Eventos personalizados
Puede que el recuadro aún no "tire" como concepción de evento, pero es un comienzo. Mostraré un enfoque más. Como es bien sabido, el modelo de sistema de comercio abstracto consta de tres subsistemas, los módulos básicos (fig.4).
Fig.4 Modelo de sistema comercial abstracto
Entonces, los eventos personalizados en base al criterio "fuente" pueden ser clasificados como eventos que surgen en:
- el subsistema de señal;
- el subsistema de acompañamiento de posición;
- el subsistema de gestión de capital.
El último puede incluir, por ejemplo, eventos tales como: alcance del nivel de pérdida permisible, el aumento del volumen comercial en el momento establecido, el aumento del tanto por ciento del límite de pérdidas, etc.
2. Procesador y el generador ChartEvent
Voy a seleccionar varias líneas del procesador y el generador de evento del gráfico. En lo que respecta al procesamiento del evento personalizado del gráfico, el principio es análogo al procesamiento del evento típico del gráfico.
El procesador, la función OnChartEvent(), adopta como parámetros cuatro constantes. Los más probable es que con ayuda de tal mecanismo, el procesador haya realizado la idea de la identificación del evento y la obtención de información adicional sobre él. A mi parecer, es un mecanismo de programación muy cómodo y compacto.
La función EventChartCustom() genera los eventos personalizados del gráfico. Además se puede crear un evento tanto para "su propio" gráfico, como para uno "ajeno". Quizá el artículo más intersante sobre la idea de los gráficos propios y ajenos sea La implementación del modo multidivisa en MetaTrader 5.
Percibo cierta disonancia en el hecho de que el identificador de evento pertenezca al tipo ushort en el generador, mientras que el en procesador pertenece al tipo int. Seguramente, lo más lógico habría sido utilizar también en el procesador el tipo de datos ushort.
3. Clase del evento personalizado
Bien, como ya he hecho notar, el autor del asesor debe preocuparse él mismo sobre la concepción de evento. Probemos a trabajar con los eventos mostrados en el recuadro 1. En primer lugar, hay que aclararse con la clase base del evento personalizado CEventBase y sus descendientes (fig.5).
Fig.5 Jerarquía de las clases de evento
La clase más básica tiene el aspecto siguiente:
//+------------------------------------------------------------------+ //| Class CEventBase. | //| Purpose: base class for a custom event | //| Derives from class CObject. | //+------------------------------------------------------------------+ class CEventBase : public CObject { protected: ENUM_EVENT_TYPE m_type; ushort m_id; SEventData m_data; public: void CEventBase(void) { this.m_id=0; this.m_type=EVENT_TYPE_NULL; }; void ~CEventBase(void){}; //-- bool Generate(const ushort _event_id,const SEventData &_data, const bool _is_custom=true); ushort GetId(void) {return this.m_id;}; private: virtual bool Validate(void) {return true;}; };
El tipo de evento se establece con la enumeración ENUM_EVENT_TYPE:
//+------------------------------------------------------------------+ //| A custom event type enumeration | //+------------------------------------------------------------------+ enum ENUM_EVENT_TYPE { EVENT_TYPE_NULL=0, // no event //--- EVENT_TYPE_INDICATOR=1, // indicator event EVENT_TYPE_ORDER=2, // order event EVENT_TYPE_EXTERNAL=3, // external event };
Además, los miembros-datos incluyen el identificador de evento y la estructura de datos.
El método Generate() de la clase básica CEventBase se ocupa de la generación del evento. El método GetId() retorna eventos id, y el método virtual Validate() comprobará el valor del identificador de evento. Al principio había incluido en los componentes de la clase el método de procesamiento de evento. Pero después entendí que cada evento es único, y que con el método abstracto aquí no podrías arreglártelas. Así que delegué esta tarea en los miembros de la clase-procesador de eventos personalizados CEventProcessor.
4. Clase de procesador de eventos personalizados
Se supone que la clase CEventProcessor generará y procesará los 8 eventos presentados. Los miembros-datos de la clase tienen el aspecto siguiente:
//+------------------------------------------------------------------+ //| Class CEventProcessor. | //| Purpose: base class for an event processor EA | //+------------------------------------------------------------------+ class CEventProcessor { //+----------------------------Data members--------------------------+ protected: ulong m_magic; //--- flags bool m_is_init; bool m_is_trade; //--- CEventBase *m_ptr_event; //--- CTrade m_trade; //--- CiMA m_fast_ema; CiMA m_slow_ema; //--- CButton m_button; bool m_button_state; //+------------------------------------------------------------------+ };
La lista de atributos es bastante variada: aquí hay etiquetas de inicialización y de comercio. La primera no permite al asesor comerciar si no ha comenzado con éxito. La segunda comprueba la autorización del usuario para comerciar.
Asimismo, tenemos el índice del objeto del tipo CEventBase, que precisamente trabaja con eventos de diferentes tipos, con ayuda del polimorfismo. El objeto de clase comercial CTrade proporciona acceso a las operaciones comerciales.
Un par de objetos del tipo CiMA facilitan el procesamiento de los datos de los indicadores. Para simplificar el ejemplo, he tomado 2 movings. Ellos captarán la señal comercial. Además, tenemos un representante de clase "Botón". Posibilitará la activación/desactivación "manual" del asesor.
Los métodos de clase los he dividido según el principio de "módulo-procedimiento-función-macro":
//+------------------------------------------------------------------+ //| Class CEventProcessor. | //| Purpose: base class for an event processor EA | //+------------------------------------------------------------------+ class CEventProcessor { //+-------------------------------Methods----------------------------+ public: //--- constructor/destructor void CEventProcessor(const ulong _magic); void ~CEventProcessor(void); //--- Modules //--- event generating bool Start(void); void Finish(void); void Main(void); //--- event processing void ProcessEvent(const ushort _event_id,const SEventData &_data); private: //--- Procedures void Close(void); void Open(void); //--- Functions ENUM_ORDER_TYPE CheckCloseSignal(const ENUM_ORDER_TYPE _close_sig); ENUM_ORDER_TYPE CheckOpenSignal(const ENUM_ORDER_TYPE _open_sig); bool GetIndicatorData(double &_fast_vals[],double &_slow_vals[]); //--- Macros void ResetEvent(void); bool ButtonStop(void); bool ButtonResume(void); };
Entre los módulos, hay tres que solo generan eventos: el de inicio — Start(), el final — Finish(), y el principal — Main(). Y el cuarto módulo, ProcessEvent(), es tanto procesador de eventos, como generador.
4.1 Módulo de incio
Se supone que el módulo será llamado en el procesador OnInit().
//+------------------------------------------------------------------+ //| Start module | //+------------------------------------------------------------------+ bool CEventProcessor::Start(void) { //--- create an indicator event object this.m_ptr_event=new CIndicatorEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; //--- generate CHARTEVENT_CUSTOM+1 event if(this.m_ptr_event.Generate(1,data)) //--- create a button if(this.m_button.Create(0,"Start_stop_btn",0,25,25,150,50)) if(this.ButtonStop()) { this.m_button_state=false; return true; } } //--- return false; }
En el módulo se crea un índice al objeto del evento de indicador. Después se genera el evento "Creación de un indicador". Y al finalizar se crea un botón. Su estado se pasa al modo "Stop". Esto significa que la próxima vez que se pulse el botón, interrumpirá el funcionamiento del asesor.
En la determinación del método está también implicada la estructura SEventData. Se trata de un container sencillo para los parámetros transmitidos al generador del evento personalizado. Aquí solo se rellenará un campo de la estructura, de tipo long. Guardamos en él el mágico del asesor.
4.2 Módulo final
Se supone que el módulo será llamado en el procesador OnDeinit().
//+------------------------------------------------------------------+ //| Finish module | //+------------------------------------------------------------------+ void CEventProcessor::Finish(void) { //--- reset the event object this.ResetEvent(); //--- create an indicator event object this.m_ptr_event=new CIndicatorEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; //--- generate CHARTEVENT_CUSTOM+2 event bool is_generated=this.m_ptr_event.Generate(2,data,false); //--- process CHARTEVENT_CUSTOM+2 event if(is_generated) this.ProcessEvent(CHARTEVENT_CUSTOM+2,data); } }
En él se descarta el anterior índice de evento y se genera el evento "Eliminación del indicador". Destaco el siguiente detalle: si en el procesador OnDeinit() se genera un evento personalizado, entonces obtendremos un error de hora de ejecución 4001 (error interno inesperado). Por eso, para este método, la generación y procesamiento del evento se ejecutan en los límites del método sin llamar OnChartEvent().
Mediante la estructura SEventData, asimismo, solo guardamos el mágico del asesor.
4.3 Módulo principal
Se supone que el módulo será llamado en el procesador OnTick().
//+------------------------------------------------------------------+ //| Main module | //+------------------------------------------------------------------+ void CEventProcessor::Main(void) { //--- a new bar object static CisNewBar newBar; //--- if initialized if(this.m_is_init) //--- if not paused if(this.m_is_trade) //--- if a new bar if(newBar.isNewBar()) { //--- close module this.Close(); //--- open module this.Open(); } }
En este módulo se llaman los procedimientos Open() y Close(). El primero puede generar el evento "Obtención de la señal de apertura", y el segundo, "Obtención de la señal de cierre". La versión actual del módulo funciona totalmente al aparecer una nueva barra. La clase para la búsqueda de una nueva barra ha sido determinada por Konstantín Gruzdev.
4.4 Módulo de procesamiento de eventos
Se supone que el módulo será llamado en el procesador OnChartEvent(). Por su volumen de código, y también por su carga funcional, este módulo es el más grande.
//+------------------------------------------------------------------+ //| Process event module | //+------------------------------------------------------------------+ void CEventProcessor::ProcessEvent(const ushort _event_id,const SEventData &_data) { //--- check event id if(_event_id==CHARTEVENT_OBJECT_CLICK) { //--- button click if(StringCompare(_data.sparam,this.m_button.Name())==0) { //--- button state bool button_curr_state=this.m_button.Pressed(); //--- to stop if(button_curr_state && !this.m_button_state) { if(this.ButtonResume()) { this.m_button_state=true; //--- reset the event object this.ResetEvent(); //--- create an external event object this.m_ptr_event=new CExternalEvent(); //--- if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)TimeCurrent(); //--- generate CHARTEVENT_CUSTOM+7 event ushort curr_id=7; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } //--- to resume else if(!button_curr_state && this.m_button_state) { if(this.ButtonStop()) { this.m_button_state=false; //--- reset the event object this.ResetEvent(); //--- create an external event object this.m_ptr_event=new CExternalEvent(); //--- if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)TimeCurrent(); //--- generate CHARTEVENT_CUSTOM+8 event ushort curr_id=8; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } } } //--- user event else if(_event_id>CHARTEVENT_CUSTOM) { long magic=_data.lparam; ushort curr_event_id=this.m_ptr_event.GetId(); //--- check magic if(magic==this.m_magic) //--- check id if(curr_event_id==_event_id) { //--- process the definite user event switch(_event_id) { //--- 1) indicator creation case CHARTEVENT_CUSTOM+1: { //--- create a fast ema if(this.m_fast_ema.Create(_Symbol,_Period,21,0,MODE_EMA,PRICE_CLOSE)) if(this.m_slow_ema.Create(_Symbol,_Period,55,0,MODE_EMA,PRICE_CLOSE)) if(this.m_fast_ema.Handle()!=INVALID_HANDLE) if(this.m_slow_ema.Handle()!=INVALID_HANDLE) { this.m_trade.SetExpertMagicNumber(this.m_magic); this.m_trade.SetDeviationInPoints(InpSlippage); //--- this.m_is_init=true; } //--- break; } //--- 2) indicator deletion case CHARTEVENT_CUSTOM+2: { //---release indicators bool is_slow_released=IndicatorRelease(this.m_fast_ema.Handle()); bool is_fast_released=IndicatorRelease(this.m_slow_ema.Handle()); if(!(is_slow_released && is_fast_released)) { //--- to log? if(InpIsLogging) Print("Failed to release the indicators!"); } //--- reset the event object this.ResetEvent(); //--- break; } //--- 3) check open signal case CHARTEVENT_CUSTOM+3: { MqlTick last_tick; if(SymbolInfoTick(_Symbol,last_tick)) { //--- signal type ENUM_ORDER_TYPE open_ord_type=(ENUM_ORDER_TYPE)_data.dparam; //--- double open_pr,sl_pr,tp_pr,coeff; open_pr=sl_pr=tp_pr=coeff=0.; //--- if(open_ord_type==ORDER_TYPE_BUY) { open_pr=last_tick.ask; coeff=1.; } else if(open_ord_type==ORDER_TYPE_SELL) { open_pr=last_tick.bid; coeff=-1.; } sl_pr=open_pr-coeff*InpStopLoss*_Point; tp_pr=open_pr+coeff*InpStopLoss*_Point; //--- to normalize prices open_pr=NormalizeDouble(open_pr,_Digits); sl_pr=NormalizeDouble(sl_pr,_Digits); tp_pr=NormalizeDouble(tp_pr,_Digits); //--- open the position if(!this.m_trade.PositionOpen(_Symbol,open_ord_type,InpTradeLot,open_pr, sl_pr,tp_pr)) { //--- to log? if(InpIsLogging) Print("Failed to open the position: "+_Symbol); } else { //--- pause Sleep(InpTradePause); //--- reset the event object this.ResetEvent(); //--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)this.m_trade.ResultDeal(); //--- generate CHARTEVENT_CUSTOM+5 event ushort curr_id=5; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } //--- break; } //--- 4) check close signal case CHARTEVENT_CUSTOM+4: { if(!this.m_trade.PositionClose(_Symbol)) { //--- to log? if(InpIsLogging) Print("Failed to close the position: "+_Symbol); } else { //--- pause Sleep(InpTradePause); //--- reset the event object this.ResetEvent(); //--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)this.m_trade.ResultDeal(); //--- generate CHARTEVENT_CUSTOM+6 event ushort curr_id=6; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } //--- break; } //--- 5) position opening case CHARTEVENT_CUSTOM+5: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_IN) { //--- to log? if(InpIsLogging) { Print("\nNew position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } //--- 6) position closing case CHARTEVENT_CUSTOM+6: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_OUT) { //--- to log? if(InpIsLogging) { Print("\nClosed position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } //--- 7) stop trading case CHARTEVENT_CUSTOM+7: { datetime stop_time=(datetime)_data.dparam; //--- this.m_is_trade=false; //--- to log? if(InpIsLogging) PrintFormat("Expert trading is stopped at: %s", TimeToString(stop_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- break; } //--- 8) resume trading case CHARTEVENT_CUSTOM+8: { datetime resume_time=(datetime)_data.dparam; this.m_is_trade=true; //--- to log? if(InpIsLogging) PrintFormat("Expert trading is resumed at: %s", TimeToString(resume_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- break; } } } } }
Consta de dos partes. La primera se ocupa de procesar los eventos relacionados con el click sobre el objeto "Botón". Si el click tiene lugar, entonces se generará un evento personalizado externo, al que un poco después prestará servicio el propio procesador.
La segunda parte deberá reaccionar a los eventos personalizados generados. En ella hay 2 bloques, donde, después del procesamiento del evento correspondiente, tiene lugar la creación de uno nuevo. El primer bloque presta servicio al evento "Obetención de la señal de apertura". Si se procesa con éxito, genera un nuevo evento de orden "Apertura de posición". El segundo bloque presta servicio al evento "Obetención de la señal de cierre". Si la señal es procesada, entonces aparece el evento "Cierre de posición".
En calidad de ejemplo de uso de la clase CEventProcessor pondré al asesor CustomEventProcessor.mq5. En él todo se ha hecho de manera que se puedan crear eventos y reaccionar a los mismos. El uso de la POO ha permitido escribir de forma bastante compacta el archivo final del código fuente. En el archivo se muestra el código del asesor.
No pienso que siempre sea necesario recurrir al mecanismo del evento personalizado. Seguramente se puedan conformar de otra manera ciertas cosas pequeñas, de poca importancia y que no estén relacionadas con eventos para la estrategia.
Conclusión
En este artículo he tratado de demostrar los principios del trabajo con los eventos personalizados en el entorno MQL5. Espero que el material del artículo provoque el interés no solo de los programadores principiantes.
Me gustaría hacer notar, con cierta alegría, que el lenguaje MQL5 continúa desarrollándose. Es posible que dentro de muy poco aparezcan plantillas de clases, y que algún día también surjan índices para las funciones. Entonces podremos programar un delegado que indique el método de un objeto arbitrario.
Es cómodo distribuir los archivos de origen del archivo en la carpeta de proyectos. En mi caso es la carpeta MQL5\Projects\ChartUserEvent.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1163





- 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