Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte IV): Eventos comerciales
Artyom Trishkin | 13 junio, 2019
Contenido
- Los eventos comerciales y su transmisión al programa
- Ponemos a prueba el procesamiento de los eventos comerciales
- ¿Qué es lo próximo?
Los eventos comerciales y su transmisión al programa
Si regresamos al asesor de prueba creado al final de la tercera parte, podremos ver que nuestra biblioteca sabe determinar los eventos comerciales que suceden en la cuenta, pero... Debemos dividir con precisión todos los eventos sucedidos según sus tipos, para enviarlos a un programa que use esta biblioteca para su trabajo.
Para ello, escribiremos métodos: un método que defina los eventos, y un método que defina los tipos de evento.
Vamos a pensar qué eventos comerciales debemos identificar:
- una orden pendiente puede ser colocada,
- una orden pendiente puede ser eliminada,
- una orden pendiente puede ser activada, generando con ello una posición,
- una orden pendiente puede ser parcialmente activada, generando con ello una posición,
- una posición puede ser abierta,
- una posición puede ser cerrada,
- una posición puede ser parcialmente abierta,
- una posición puede ser parcialmente cerrada,
- una posición puede ser cerrada por otra opuesta,
- una posición puede ser parcialmente cerrada por otra opuesta,
- una cuenta puede ser recargada,
- de la cuenta pueden retirarse fondos,
- en la cuenta puede suceder alguna operación de balance
eventos que no monitoreamos por el momento: - una orden pendiente puede ser modificada (cambiar el precio de colocación, añadir/eliminar/modificar niveles de StopLoss y TakeProfit)
- una orden posición puede ser modificada (añadir/eliminar/modificar niveles de StopLoss y TakeProfit)
Partiendo de lo enumerado, debemos decidir cómo identificar de forma unívoca uno u otro evento. Lo mejor es dividir de inmediato la decisión según el tipo de cuenta:
Cobertura:
- ha aumentado el número de órdenes pendientes: indica la adición de una orden pendiente (evento en el entorno de mercado)
- ha disminuido el número de órdenes pendientes:
- ha aumentado el número de posiciones: indica la activación de una orden pendiente (evento en el entorno de mercado y el entorno histórico)
- no ha aumentado el número de posiciones: indica la eliminación de una orden pendiente (evento en el entorno de mercado)
- no ha disminuido el número de órdenes pendientes:
- ha aumentado el número de posiciones: indica la apertura de una nueva posición (evento en el entorno de mercado y el entorno histórico)
- ha disminuido el número de posiciones: indica el cierre de una posición (evento en el entorno de mercado y el entorno histórico)
- no ha cambiado el número de posiciones, no ha disminuido el volumen: indica el cierre parcial de una posición (evento en el entorno histórico)
Compensación:
- ha aumentado el número de órdenes pendientes: indica la adición de una orden pendiente
- ha disminuido el número de órdenes pendientes:
- ha aumentado el número de posiciones: indica la activación de una orden pendiente
- no ha aumentado el número de posiciones, pero ha aumentado el tiempo de cambio de posición y no ha cambiado el volumen: indica la activación de una orden pendiente y, como consecuencia,el incremento de una posición
- ha disminuido el número de posiciones: indica el cierre de una posición
- no ha disminuido el número de órdenes pendientes:
- ha aumentado el número de posiciones: indica la apertura de una nueva posición
- ha disminuido el número de posiciones: indica el cierre de una posición
- no ha aumentado el número de posiciones, pero ha aumentado el tiempo de cambio de posición y ha aumentado el volumen: indica la adición de volumen a una posición
- no ha aumentado el número de posiciones, pero ha aumentado el tiempo de cambio de posición y ha disminuido el volumen: indica el cierre parcial de una posición
Para identificar los eventos comerciales, deberemos saber en qué cuenta funciona el programa. Vamos a añadir a la sección privada de la clase CEngine la bandera del tipo de cuenta "cobertura", determinando el tipo de cuenta en el constructor de clase, y anotando asimismo el resultado en esta variable de bandera:
//+------------------------------------------------------------------+ //| 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 CArrayObj m_list_counters; // List of timer counters bool m_first_start; // First launch flag bool m_is_hedge; // Hedge account flag //--- Return the counter index by id int CounterIndex(const int id) const; //--- Return the first launch flag bool IsFirstStart(void); public: //--- Create the timer counter void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- Timer void OnTimer(void); CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true) { ::EventSetMillisecondTimer(TIMER_FREQUENCY); this.m_list_counters.Sort(); this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE); this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); } //+------------------------------------------------------------------+
Durante la primera ejcución, en el momento de construcción del objeto de clase, deberemos determinar de inmediato en su constructor el tipo de cuenta en la que está iniciado el programa. Por consiguiente, también los métodos de definición de eventos comerciales se atribuirán o bien a la cuenta de cobertura, o bien a la de compensación.
Después de definir el evento comercial sucedido, deberemos guardar el código de este evento, que permanecerá constante hasta el siguiente evento. De esta forma, el programa podrá saber en cualquier momento qué evento ha sucedido en último lugar en la cuenta. El código del evento constará de un conjunto de banderas. Cada bandera describirá un evento concreto. Por ejemplo, ha tenido lugar un evento de posición, este evento se puede dividir en un cierto subconjunto que caracterizará con mayor precisión el cierre de posición:
- cerrada por completo
- cerrada parcialmente
- cerrada por una posición opuesta
- cerrada por stop loss
- cerrada por take profit
- etcétera.
Todos estos rasgos pueden ser propios del evento "la posición está cerrada", y esto significa que el código del evento debe contener todos estos datos. Para construir un evento usando banderas, crearemos dos nuevas enumeraciones en el archivo Defines.mqh de la carpeta raíz de la biblioteca: banderas de eventos comerciales y posibles eventos comerciales en la cuenta, que vamos a monitorear posteriormente:
//+------------------------------------------------------------------+ //| List of trading event flags on the account | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT_FLAGS { TRADE_EVENT_FLAG_NO_EVENT = 0, // No event TRADE_EVENT_FLAG_ORDER_PLASED = 1, // Pending order placed TRADE_EVENT_FLAG_ORDER_REMOVED = 2, // Pending order removed TRADE_EVENT_FLAG_ORDER_ACTIVATED = 4, // Pending order activated by price TRADE_EVENT_FLAG_POSITION_OPENED = 8, // Position opened TRADE_EVENT_FLAG_POSITION_CLOSED = 16, // Position closed TRADE_EVENT_FLAG_ACCOUNT_BALANCE = 32, // Balance operation (clarified by a deal type) TRADE_EVENT_FLAG_PARTIAL = 64, // Partial execution TRADE_EVENT_FLAG_BY_POS = 128, // Executed by opposite position TRADE_EVENT_FLAG_SL = 256, // Executed by StopLoss TRADE_EVENT_FLAG_TP = 512 // Executed by TakeProfit }; //+------------------------------------------------------------------+ //| List of possible trading events on the account | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT { TRADE_EVENT_NO_EVENT, // No trading event TRADE_EVENT_PENDING_ORDER_PLASED, // Pending order placed TRADE_EVENT_PENDING_ORDER_REMOVED, // Pending order removed //--- enumeration members matching the ENUM_DEAL_TYPE enumeration members TRADE_EVENT_ACCOUNT_CREDIT, // Charging credit TRADE_EVENT_ACCOUNT_CHARGE, // Additional charges TRADE_EVENT_ACCOUNT_CORRECTION, // Correcting entry TRADE_EVENT_ACCOUNT_BONUS, // Charging bonuses TRADE_EVENT_ACCOUNT_COMISSION, // Additional commissions TRADE_EVENT_ACCOUNT_COMISSION_DAILY, // Commission charged at the end of a day TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY, // Commission charged at the end of a month TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY, // Agent commission charged at the end of a trading day TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY, // Agent commission charged at the end of a month TRADE_EVENT_ACCOUNT_INTEREST, // Accrual of interest on free funds TRADE_EVENT_BUY_CANCELLED, // Canceled buy deal TRADE_EVENT_SELL_CANCELLED, // Canceled sell deal TRADE_EVENT_DIVIDENT, // Accrual of dividends TRADE_EVENT_DIVIDENT_FRANKED, // Accrual of franked dividend TRADE_EVENT_TAX, // Tax accrual //--- members of enumeration related to the DEAL_TYPE_BALANCE deal type from the ENUM_DEAL_TYPE enumeration TRADE_EVENT_ACCOUNT_BALANCE_REFILL, // Replenishing account balance TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL, // Withdrawing funds from an account //--- TRADE_EVENT_PENDING_ORDER_ACTIVATED, // Pending order activated by price TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL, // Pending order partially activated by price TRADE_EVENT_POSITION_OPENED, // Position opened TRADE_EVENT_POSITION_OPENED_PARTIAL, // Position opened partially TRADE_EVENT_POSITION_CLOSED, // Position closed TRADE_EVENT_POSITION_CLOSED_PARTIAL, // Position closed partially TRADE_EVENT_POSITION_CLOSED_BY_POS, // Position closed by an opposite one TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS, // Position partially closed by an opposite one TRADE_EVENT_POSITION_CLOSED_BY_SL, // Position closed by StopLoss TRADE_EVENT_POSITION_CLOSED_BY_TP, // Position closed by TakeProfit TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL, // Position closed partially by StopLoss TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP, // Position closed partially by TakeProfit TRADE_EVENT_POSITION_REVERSED, // Position reversal (netting) TRADE_EVENT_POSITION_VOLUME_ADD // Added volume to position (netting) }; //+------------------------------------------------------------------+
Aquí debemos aclarar la enumeración ENUM_TRADE_EVENT.
Puesto que algunos eventos comerciales pueden suceder sin implicación por parte del programa o del tráder (por ejemplo, el cobro de comisiones, tasas, bonificaciones, etc.), en relación con ello, vamos a tomar esos datos en MQL5 del tipo de transacción: de la enumeración ENUM_DEAL_TYPE. Y para que resulte más sencillo monitorear después un evento, deberemos hacer que el valor de la enumeración de nuestros eventos coincida con el valor de ENUM_DEAL_TYPE.
Las operaciones de balance las dividiremos en dos eventos: recarga del saldo de la cuenta y retirada de fondos de la cuenta. Los demás eventos de la enumeración del tipo de transacción (excepto la compra y la venta (DEAL_TYPE_BUY y DEAL_TYPE_SELL), que no pertenecen a tales operaciones de balance), comenzando por DEAL_TYPE_CREDIT, los hemos hecho con el mismo valor que en la enumeración ENUM_DEAL_TYPE.
Perfeccionando la clase de colección de las órdenes y posiciones de mercado.
Para que podamos obtener las listas de órdenes y posiciones de mercado necesarias de la colección (como se hizo con la colección de órdenes y transacciones histórcias en la Parte 2 y la Parte 3 de la descripción de la biblioteca), vamos a añadir a la clase CMarketCollection en la sección privada una orden de muestra para realizar la búsqueda según las propiedades de la orden establecidas; en la sección pública, añadimos los métodos de obtención de la lista completa de órdenes y posiciones, la lista de órdenes y posiciones elegidas según el intervalo temporal indicado y las listas que retornan las órdenes y posiciones elegidas según un criterio conocido de las propiedades de tipo entero, real y string de una orden o posición:
//+------------------------------------------------------------------+ //| Collection of market orders and positions | //+------------------------------------------------------------------+ class CMarketCollection { private: struct MqlDataCollection { long hash_sum_acc; // Hash sum of all orders and positions on the account int total_pending; // Number of pending orders on the account int total_positions; // Number of positions on the account double total_volumes; // Total volume of orders and positions on the account }; MqlDataCollection m_struct_curr_market; // Current data on market orders and positions on the account MqlDataCollection m_struct_prev_market; // Previous data on market orders and positions on the account CArrayObj m_list_all_orders; // List of pending orders and positions on the account COrder m_order_instance; // Order object for searching by property bool m_is_trade_event; // Trading event flag bool m_is_change_volume; // Total volume change flag double m_change_volume_value; // Total volume change value int m_new_positions; // Number of new positions int m_new_pendings; // Number of new pending orders //--- Save the current values of the account data status as previous ones void SavePrevValues(void) { this.m_struct_prev_market=this.m_struct_curr_market; } public: //--- Return the list of all pending orders and open positions CArrayObj* GetList(void) { return &m_list_all_orders; } //--- Return the list of orders and positions with an open time from begin_time to end_time CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Return the list of orders and positions by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj* GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } //--- Return the number of (1) new pending orders, (2) new positions, (3) occurred trading event flag, (4) changed volume int NewOrders(void) const { return this.m_new_pendings; } int NewPosition(void) const { return this.m_new_positions; } bool IsTradeEvent(void) const { return this.m_is_trade_event; } double ChangedVolumeValue(void) const { return this.m_change_volume_value; } //--- Constructor CMarketCollection(void); //--- Update the list of pending orders and positions void Refresh(void); }; //+------------------------------------------------------------------+
Implementamos fuera del cuerpo de la clase el método de selección de órdenes y posiciones según el tiempo:
//+------------------------------------------------------------------------+ //| Select market orders or positions from the collection with the time | //| within the range from begin_time to end_time | //+------------------------------------------------------------------------+ CArrayObj* CMarketCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0) { CArrayObj* list=new CArrayObj(); if(list==NULL) { ::Print(DFUN,TextByLanguage("Ошибка создания временного списка","Error creating temporary list")); return NULL; } datetime begin=begin_time,end=(end_time==0 ? END_TIME : end_time); list.FreeMode(false); ListStorage.Add(list); m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,begin); int index_begin=m_list_all_orders.SearchGreatOrEqual(&m_order_instance); if(index_begin==WRONG_VALUE) return list; m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,end); int index_end=m_list_all_orders.SearchLessOrEqual(&m_order_instance); if(index_end==WRONG_VALUE) return list; for(int i=index_begin; i<=index_end; i++) list.Add(m_list_all_orders.At(i)); return list; } //+------------------------------------------------------------------+
El método es prácticamente idéntico al método de selección por tiempo de la colección de órdenes y transacciones históricas que analizamos en la Parte 3, podemos releer su funcionamiento una vez más en el apartado correspondiente del tercer artículo. La diferencia de este método con respecto al método de clase de la colección histórica consiste solo en que aquí no elegimos el tiempo según el cual se seleccionarán las órdenes: las órdenes y posiciones de mercado solo tienen una hora, la hora de apertura.
Asimismo, modificamos el constructor de clase de la colección de órdenes y transacciones históricas. El asunto es que en el sistema de órdenes de MQL5 no existe el concepto de hora de cierre: todas las órdenes y transacciones se ubican en listas de acuerdo con su hora de colocación (la hora de apertura, según la clasificación del sistema de órdenes de MQL4). Para ello, modificamos la línea que indica la dirección de clasificación en la lista de colección de órdenes y transacciones históricas en el constructor de clase CHistoryCollection:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CHistoryCollection::CHistoryCollection(void) : m_index_deal(0),m_delta_deal(0),m_index_order(0),m_delta_order(0),m_is_trade_event(false) { this.m_list_all_orders.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN #else SORT_BY_ORDER_TIME_CLOSE #endif ); this.m_list_all_orders.Clear(); } //+------------------------------------------------------------------+
Ahora, en MQL5, todas las órdenes y transacciones de la colección de órdenes y transacciones históricas se clasificarán por defecto según su hora de colocación, mientras que en MQL4, se clasificarán según la hora de cierre registrada en las propiedades de la orden.
Debemos tener en cuenta otra peculiaridad del sistema de órdenes de MQL5. Al cerrar una posición con otra opuesta, se coloca una orden de cierre especial con el tipo ORDER_TYPE_CLOSE_BY, y al cerrar con una orden stop, se coloca una orden de mercado de cierre de posición.
Para que se puedan tener en cuenta las órdenes de mercado, hemos tenido que añadir otra propiedad a los métodos de obtención y retorno de las propiedades de tipo entero de la orden básica (usando como ejemplo la obtención y el retorno del número mágico de la orden):
//+------------------------------------------------------------------+ //| Return the magic number | //+------------------------------------------------------------------+ long COrder::OrderMagicNumber() const { #ifdef __MQL4__ return ::OrderMagicNumber(); #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetInteger(POSITION_MAGIC); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_MAGIC); break; case ORDER_STATUS_DEAL : res=::HistoryDealGetInteger(m_ticket,DEAL_MAGIC); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_MAGIC); break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
Hemos implementado estos cambios (o los métodos que le correspondan lógicamente) en todos los métodos de obtención y retorno de las propiedades de tipo entero de la orden básica, allá donde realmente se deba tener en cuenta este estado. Y ya que ese estado existe, para guardar este tipo de órdenes, crearemos la nueva clase de orden de mercado CMarketOrder en la carpeta Objects de la biblioteca. La clase es totalmente idéntica al resto de objetos de las órdenes y transacciones de mercado e históricas que hemos creado anteriormente, por eso, solo vamos a mostrar parte de su lista:
//+------------------------------------------------------------------+ //| MarketOrder.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 "Order.mqh" //+------------------------------------------------------------------+ //| Market order | //+------------------------------------------------------------------+ class CMarketOrder : public COrder { public: //--- Constructor CMarketOrder(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_ORDER,ticket) {} //--- Supported order properties (1) real, (2) integer virtual bool SupportProperty(ENUM_ORDER_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_ORDER_PROP_INTEGER property); }; //+------------------------------------------------------------------+ //| Return 'true' if the order supports the passed | //| integer property, otherwise, return 'false' | //+------------------------------------------------------------------+ bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_INTEGER property) { if(property==ORDER_PROP_TIME_EXP || property==ORDER_PROP_DEAL_ENTRY || property==ORDER_PROP_TIME_UPDATE || property==ORDER_PROP_TIME_UPDATE_MSC || property==ORDER_PROP_PROFIT_PT || property==ORDER_PROP_TIME_CLOSE || property==ORDER_PROP_TIME_CLOSE_MSC || property==ORDER_PROP_TICKET_FROM || property==ORDER_PROP_TICKET_TO ) return false; return true; } //+------------------------------------------------------------------+ //| Return 'true' if the order supports the passed | //| real property, otherwise, return 'false' | //+------------------------------------------------------------------+ bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property) { if(property==ORDER_PROP_PROFIT || property==ORDER_PROP_PROFIT_FULL || property==ORDER_PROP_SWAP || property==ORDER_PROP_COMMISSION || property==ORDER_PROP_PRICE_CLOSE || property==ORDER_PROP_SL || property==ORDER_PROP_TP || property==ORDER_PROP_PRICE_STOP_LIMIT ) return false; return true; } //+------------------------------------------------------------------+
En el archivo Defines.mqh de la biblioteca, escribimos el nuevo estado de la orden de mercado:
//+------------------------------------------------------------------+ //| Abstract order type (status) | //+------------------------------------------------------------------+ enum ENUM_ORDER_STATUS { ORDER_STATUS_MARKET_PENDING, // Market pending order ORDER_STATUS_MARKET_ORDER, // Market order ORDER_STATUS_MARKET_POSITION, // Market position ORDER_STATUS_HISTORY_ORDER, // Historical market order ORDER_STATUS_HISTORY_PENDING, // Removed pending order ORDER_STATUS_BALANCE, // Balance operation ORDER_STATUS_CREDIT, // Credit operation ORDER_STATUS_DEAL, // Deal ORDER_STATUS_UNKNOWN // Unknown status }; //+------------------------------------------------------------------+
Ahora, en la clase CMarketCollection, es necesario comprobar el tipo de orden en el método de actualización de la lista de órdenes y posiciones de mercado Refresh(), en el bloque de adición de órdenes a la lista, y a continuación, dependiendo del tipo, añadir un objeto de orden de mercado, o bien un objeto de orden pendiente a la lista de colección:
//+------------------------------------------------------------------+ //| Update the order list | //+------------------------------------------------------------------+ void CMarketCollection::Refresh(void) { ::ZeroMemory(this.m_struct_curr_market); this.m_is_trade_event=false; this.m_is_change_volume=false; this.m_new_pendings=0; this.m_new_positions=0; this.m_change_volume_value=0; m_list_all_orders.Clear(); #ifdef __MQL4__ int total=::OrdersTotal(); for(int i=0; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS)) continue; long ticket=::OrderTicket(); ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType(); if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL) { CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; if(this.m_list_all_orders.InsertSort(position)) { this.m_struct_market.hash_sum_acc+=ticket; this.m_struct_market.total_volumes+=::OrderLots(); this.m_struct_market.total_positions++; } else { ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list")); delete position; } } else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; if(this.m_list_all_orders.InsertSort(order)) { this.m_struct_market.hash_sum_acc+=ticket; this.m_struct_market.total_volumes+=::OrderLots(); this.m_struct_market.total_pending++; } else { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list")); delete order; } } } //--- MQ5 #else //--- Positions int total_positions=::PositionsTotal(); for(int i=0; i<total_positions; i++) { ulong ticket=::PositionGetTicket(i); if(ticket==0) continue; CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; if(this.m_list_all_orders.InsertSort(position)) { this.m_struct_curr_market.hash_sum_acc+=(long)::PositionGetInteger(POSITION_TIME_UPDATE_MSC); this.m_struct_curr_market.total_volumes+=::PositionGetDouble(POSITION_VOLUME); this.m_struct_curr_market.total_positions++; } else { ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list")); delete position; } } //--- Orders int total_orders=::OrdersTotal(); for(int i=0; i<total_orders; i++) { ulong ticket=::OrderGetTicket(i); if(ticket==0) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE); if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL) { CMarketOrder *order=new CMarketOrder(ticket); if(order==NULL) continue; if(this.m_list_all_orders.InsertSort(order)) { this.m_struct_curr_market.hash_sum_acc+=(long)ticket; this.m_struct_curr_market.total_market++; } else { ::Print(DFUN,TextByLanguage("Не удалось добавить маркет-ордер в список","Failed to add market order to list")); delete order; } } else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; if(this.m_list_all_orders.InsertSort(order)) { this.m_struct_curr_market.hash_sum_acc+=(long)ticket; this.m_struct_curr_market.total_volumes+=::OrderGetDouble(ORDER_VOLUME_INITIAL); this.m_struct_curr_market.total_pending++; } else { ::Print(DFUN,TextByLanguage("Не удалось добавить отложенный ордер в список","Failed to add pending order to list")); delete order; } } } #endif //--- First launch if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE) { this.SavePrevValues(); } //--- If the hash sum of all orders and positions changed if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc) { this.m_new_market=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market; this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending; this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions; this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4); this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false); this.m_is_trade_event=true; this.SavePrevValues(); } } //+------------------------------------------------------------------+
Para que podamos tener en cuenta la órdenes de cierre con el tipo ORDER_TYPE_CLOSE_BY, en la clase CHistoryCollection, en el método de actualización de la lista de órdenes y transacciones históricas Refresh(), en el bloque de definición del tipo de órdenes añadimos también este tipo de orden, para que dichas órdenes entren en la colección. Sin ello, el objeto básico de la biblioteca CEngine no podrá determinar el evento de cierre de una posición con otra opuesta:
//+------------------------------------------------------------------+ //| Update the list of orders and deals | //+------------------------------------------------------------------+ void CHistoryCollection::Refresh(void) { #ifdef __MQL4__ int total=::OrdersHistoryTotal(),i=m_index_order; for(; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType(); //--- Closed positions and balance/credit operations if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP) { CHistoryOrder *order=new CHistoryOrder(::OrderTicket()); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list")); delete order; } } else { //--- Removed pending orders CHistoryPending *order=new CHistoryPending(::OrderTicket()); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list")); delete order; } } } //--- int delta_order=i-m_index_order; this.m_index_order=i; this.m_delta_order=delta_order; this.m_is_trade_event=(this.m_delta_order!=0 ? true : false); //--- __MQL5__ #else if(!::HistorySelect(0,END_TIME)) return; //--- Orders int total_orders=::HistoryOrdersTotal(),i=m_index_order; for(; i<total_orders; i++) { ulong order_ticket=::HistoryOrderGetTicket(i); if(order_ticket==0) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE); if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY) { CHistoryOrder *order=new CHistoryOrder(order_ticket); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list")); delete order; } } else { CHistoryPending *order=new CHistoryPending(order_ticket); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list")); delete order; } } } //--- save the index of the last added order and the difference as compared to the previous check int delta_order=i-this.m_index_order; this.m_index_order=i; this.m_delta_order=delta_order; //--- Deals int total_deals=::HistoryDealsTotal(),j=m_index_deal; for(; j<total_deals; j++) { ulong deal_ticket=::HistoryDealGetTicket(j); if(deal_ticket==0) continue; CHistoryDeal *deal=new CHistoryDeal(deal_ticket); if(deal==NULL) continue; this.m_list_all_orders.InsertSort(deal); } //---save the index of the last added deal and the difference as compared to the previous check int delta_deal=j-this.m_index_deal; this.m_index_deal=j; this.m_delta_deal=delta_deal; //--- Set the new event flag in history this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal); #endif } //+------------------------------------------------------------------+
Adelantándonos un poco: durante la simulación del funcionamiento de la clase CEngine, para determinar los eventos sucedidos en la cuenta, se han detectado y corregido ciertos detalles en los métodos de servicio. No tiene sentido enumerar aquí las numerosas aunque pequeñas correcciones realizadas, ya que estas no han influido en el rendimiento de nuestro trabajo, y su descripción solo nos distrairá de la creación de la importante funcionalidad de la biblioteca. Todos los cambios ya han sido introducidos en el listado de clases, y el lector podrá verlos por sí mismo en los archivos de la biblioteca adjuntos al final del artículo.
Vamos a continuar trabajando en la definición de eventos.
Después de depurar el funcionamiento de los eventos comerciales, todos los eventos sucedidos se empaquetarán en una variable-miembro de clase en forma de conjunto de banderas, y después se creará un método que lea la información de esta variable para descomponer su valor en componentes que caractericen un evento concreto.
Añadimos a la sección privada de la clase CEngine la variable-miembro de clase para guardar el código del evento comercial ylos métodos de comprobación del evento comercial para las cuentas con el tipo de cobertura y compensación, y los métodos que retonan los objetos de orden necesarios.
Añadimos en la sección pública los métodos que retornan las listas de posiciones de mercado y órdenes pendientes, así como las listas de órdenes de mercado y transacciones históricas, además del método que retorna el código del evento comercial de la variable m_trade_event_code y el método que retorna la bandera de la cuenta con el tipo de cobertura.
//+------------------------------------------------------------------+ //| 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 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_market_trade_event; // Account trading event flag bool m_is_history_trade_event; // Account history trading event flag int m_trade_event_code; // Account trading event status code //--- Return the counter index by id int CounterIndex(const int id) const; //--- Return the first launch flag bool IsFirstStart(void); //--- Working with (1) hedging and (2) netting collections void WorkWithHedgeCollections(void); void WorkWithNettoCollections(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 market order 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: //--- 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* GetListHistoryDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- Return the (1) trading event code and (2) hedge account flag int TradeEventCode(void) const { return this.m_trade_event_code; } bool IsHedge(void) const { return this.m_is_hedge; } //--- Create the timer account void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- Timer void OnTimer(void); //--- Constructor/destructor CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
Inicializamos el código de evento comercial en la lista de inicialización del constructor de clase.
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT) { ::EventSetMillisecondTimer(TIMER_FREQUENCY); this.m_list_counters.Sort(); this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE); this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); } //+------------------------------------------------------------------+
Implementamos los métodos declarados fuera del cuerpo de la clase:
//+------------------------------------------------------------------+ //| Return the list of market positions | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListMarketPosition(void) { CArrayObj* list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL); return list; } //+------------------------------------------------------------------+ //| Return the list of market pending orders | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListMarketPendings(void) { CArrayObj* list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL); return list; } //+------------------------------------------------------------------+ //| Return the list of market orders | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListMarketOrders(void) { CArrayObj* list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL); return list; } //+------------------------------------------------------------------+ //| Return the list of historical orders | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListHistoryOrders(void) { CArrayObj* list=this.m_history.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL); return list; } //+------------------------------------------------------------------+ //| Return the list of removed pending orders | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListHistoryPendings(void) { CArrayObj* list=this.m_history.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL); return list; } //+------------------------------------------------------------------+ //| Return the list of deals | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListHistoryDeals(void) { CArrayObj* list=this.m_history.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL); return list; } //+------------------------------------------------------------------+ //| Return the list of all position orders | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id) { CArrayObj* list=this.GetListHistoryOrders(); list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL); return list; } //+------------------------------------------------------------------+ //| Return the last position | //+------------------------------------------------------------------+ COrder* CEngine::GetLastPosition(void) { CArrayObj* list=this.GetListMarketPosition(); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return position by ticket | //+------------------------------------------------------------------+ COrder* CEngine::GetPosition(const ulong ticket) { CArrayObj* list=this.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TICKET); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return the last deal | //+------------------------------------------------------------------+ COrder* CEngine::GetLastDeal(void) { CArrayObj* list=this.GetListHistoryDeals(); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return the last market pending order | //+------------------------------------------------------------------+ COrder* CEngine::GetLastMarketPending(void) { CArrayObj* list=this.GetListMarketPendings(); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return the last historical pending order | //+------------------------------------------------------------------+ COrder* CEngine::GetLastHistoryPending(void) { CArrayObj* list=this.GetListHistoryPendings(); if(list==NULL) return NULL; list.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN_MSC #else SORT_BY_ORDER_TIME_CLOSE_MSC #endif); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return the last market order | //+------------------------------------------------------------------+ COrder* CEngine::GetLastMarketOrder(void) { CArrayObj* list=this.GetListMarketOrders(); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return the last historical market order | //+------------------------------------------------------------------+ COrder* CEngine::GetLastHistoryOrder(void) { CArrayObj* list=this.GetListHistoryOrders(); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return historical market order by its ticket | //+------------------------------------------------------------------+ COrder* CEngine::GetHistoryOrder(const ulong ticket) { CArrayObj* list=this.GetListHistoryOrders(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TICKET); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return the first historical market order | //| from the list of all position orders | //+------------------------------------------------------------------+ COrder* CEngine::GetFirstOrderPosition(const ulong position_id) { CArrayObj* list=this.GetListAllOrdersByPosID(position_id); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TIME_OPEN); COrder* order=list.At(0); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+ //| Return the last historical market order | //| from the list of all position orders | //+------------------------------------------------------------------+ COrder* CEngine::GetLastOrderPosition(const ulong position_id) { CArrayObj* list=this.GetListAllOrdersByPosID(position_id); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TIME_OPEN); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+
Vamos a ver cómo se obtienen las listas en el ejemplo:
//+------------------------------------------------------------------+ //| Return the list of market positions | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListMarketPosition(void) { CArrayObj* list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL); return list; } //+------------------------------------------------------------------+
Todo resulta bastante cómodo y sencillo: primero obtenemos la lista completa de posiciones de la colección de órdenes y posiciones de mercado con la ayuda del método GetList(), y después seleccionamos de ella las órdenes con el estado de "posición" con la ayuda del método de selección de órdenes según la propiedad indicada de la clase CSelect, caracterizada en el tercer artículo de la descripción de la biblioteca. Y retornamos la lista obtenida.
La lista puede resultar vacía (NULL), por ello, debemos comprobar el resultado retornado por este método en el programa que realiza la llamada.
Vamos a analizar la obtención de la orden necesaria en el ejemplo:
//+------------------------------------------------------------------+ //| Return the last position | //+------------------------------------------------------------------+ COrder* CEngine::GetLastPosition(void) { CArrayObj* list=this.GetListMarketPosition(); if(list==NULL) return NULL; list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); COrder* order=list.At(list.Total()-1); return(order!=NULL ? order : NULL); } //+------------------------------------------------------------------+
En primer lugar, obtenemos la lista solo de las posiciones con el método GetListMarketPosition(); que ya vimos anteriormente. Si la lista está vacía, retornamos NULL. A continuación, clasificamos la lista según la hora de apertura en milesegundos (puesto que queremos ontener la última posición abierta, será necesario que la lista haya sido clasificada por tiempo) y seleccionamos de esta lista la última orden. Como resultado, retornamos la orden obtenida de la lista al programa que ha realizado la llamada.
El resultado de la búsqueda de la orden retornado por el método puede estar vacío (la orden no ha sido encontrada) y tener el valor NULL, por ello, es necesario comprobar el resultado respecto a la presencia de NULL antes de acceder a él.
COmo podemos ver, todo es rápido, fácil y simple. De esta forma, podemos crear cualquier método para obtener cualquier dato a partir de cualquier lista de colección que tengamos o creemos en el futuro, lo que nos da una gran flexibilidad a la hora de usar tales listas.
Los métodos de obtención de listas se encuentran en la sección pública de la clase, lo que permite obtener cualquier dato de las listas en nuestros programas usando como ejemplo los métodos que acabamos de analizar.
Los métodos de obtención de las órdenes necesarias están ocultos en la sección privada: solo se requieren en la clase CEngine para necesidades internas, concretamente, para obtener los datos de las últimas órdenes, transacciones y posiciones; en nuestros programas podemos crear nuestras propias funciones para obtener órdenes concretas según las propiedades establecidas, acabamos de ver ejemplos sobre ello.
No obstante, para obtener fácilmente cualquier dato de las colecciones, crearemos finalmente un amplio conjunto de funciones para el usuario final.
Pero esto será más tarde, en los artículos finales de la descripción del trabajo con las colecciones de órdenes.
Ahora vamos a implementar el método de comprobación de eventos comerciales.
Por ahora, solo hemos escrito la implementación para las cuentas con el tipo de cobertura para MQL5. A continuación, escribiremos los métodos de comprobación de los eventos comerciales para las cuentas de compensación en MQL5, y después para MQL4. Conviene tener en cuenta que para simular el funcionamiento del método, se ha escrito en él la muestra de lo resultados de las comprobaciones y los eventos en el diario. Esa funcionalidad la quitaremos más adelante, ya que se ocupará de ella otro método que se creará después de la depuración del método de comprobación de eventos comerciales en la cuenta.
//+------------------------------------------------------------------+ //| Check trading events (hedging) | //+------------------------------------------------------------------+ void CEngine::WorkWithHedgeCollections(void) { //--- Initialize the trading events code and flag this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT; this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Update the lists this.m_market.Refresh(); this.m_history.Refresh(); //--- Actions during the first launch if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Check the market state dynamics and account history this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- #ifdef __MQL4__ #else // MQL5 //--- If an event is only in market orders and positions if(this.m_is_market_trade_event && !this.m_is_history_trade_event) { Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account")); //--- If the number of pending orders increased if(this.m_market.NewPendingOrders()>0) { //--- Add the flag for installing a pending order this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED; string text=TextByLanguage("Установлен отложенный ордер: ","Pending order placed: "); //--- Take the last market pending order COrder* order=this.GetLastMarketPending(); if(order!=NULL) { //--- add the order ticket to the message text+=order.TypeDescription()+" #"+(string)order.Ticket(); } //--- Add the message to the journal Print(DFUN,text); } //--- If the number of market orders increased if(this.m_market.NewMarketOrders()>0) { //--- do not add the event flag //--- ... string text=TextByLanguage("Выставлен маркет-ордер: ","Market order placed: "); //--- Take the last market order COrder* order=this.GetLastMarketOrder(); if(order!=NULL) { //--- add the order ticket to the message text+=order.TypeDescription()+" #"+(string)order.Ticket(); } //--- Add the message to the journal Print(DFUN,text); } } //--- If an event is only in historical orders and deals else if(this.m_is_history_trade_event && !this.m_is_market_trade_event) { Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history")); //--- If a new deal appeared if(this.m_history.NewDeals()>0) { //--- Add the account balance event flag this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE; string text=TextByLanguage("Новая сделка: ","New deal: "); //--- Take the last deal COrder* deal=this.GetLastDeal(); if(deal!=NULL) { //--- add its description to the text text+=deal.TypeDescription(); //--- if the deal is a balance operation if((ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE)==DEAL_TYPE_BALANCE) { //--- check the deal profit and add the event (adding or withdrawing funds) to the message text+=(deal.Profit()>0 ? TextByLanguage(": Пополнение счёта: ",": Account Recharge: ") : TextByLanguage(": Вывод средств: ",": Withdrawal: "))+::DoubleToString(deal.Profit(),(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS)); } } //--- Display the message in the journal Print(DFUN,text); } } //--- If the events are in market and historical orders and positions else if(this.m_is_market_trade_event && this.m_is_history_trade_event) { Print(DFUN,TextByLanguage("Новые торговые события на счёте и в истории счёта","New trading events on account and in account history")); //--- If the number of pending orders decreased and no new deals appeared if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0) { //--- Add the flag for removing a pending order this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED; string text=TextByLanguage("Удалён отложенный ордер: ","Removed pending order: "); //--- Take the last historical pending order COrder* order=this.GetLastHistoryPending(); if(order!=NULL) { //--- add the ticket to the appropriate message text+=order.TypeDescription()+" #"+(string)order.Ticket(); } //--- Display the message in the journal Print(DFUN,text); } //--- If there is a new deal and a new historical order if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0) { //--- Take the last deal COrder* deal=this.GetLastDeal(); if(deal!=NULL) { //--- In case of a market entry deal if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN) { //--- Add the position opening flag this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED; string text=TextByLanguage("Открыта позиция: ","Position opened: "); //--- If the number of pending orders decreased if(this.m_market.NewPendingOrders()<0) { //--- Add the pending order activation flag this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED; text=TextByLanguage("Сработал отложенный ордер: ","Pending order activated: "); } //--- Take the order's ticket from the order ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(order_ticket); if(order!=NULL) { //--- If the current order volume exceeds zero if(order.VolumeCurrent()>0) { //--- Add the partial execution flag this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; text=TextByLanguage("Частично открыта позиция: ","Position partially open: "); } //--- add the order direction to the message text+=order.DirectionDescription(); } //--- add the position ticket from the deal to the message and display the message in the journal text+=" #"+(string)deal.PositionID(); Print(DFUN,text); } //--- In case of a market exit deal if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT) { //--- Add the position closing flag this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED; string text=TextByLanguage("Закрыта позиция: ","Position closed: "); //--- Take the deal's order ticket from the deal ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(order_ticket); if(order!=NULL) { //--- If the deal's position is still present in the market COrder* pos=this.GetPosition(deal.PositionID()); if(pos!=NULL) { //--- Add the partial execution flag this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; text=TextByLanguage("Частично закрыта позиция: ","Partially closed position: "); } //--- Otherwise, if the position is fully closed else { //--- If the order has the flag for closing by StopLoss if(order.IsCloseByStopLoss()) { //--- Add the flag for closing by StopLoss this.m_trade_event_code+=TRADE_EVENT_FLAG_SL; text=TextByLanguage("Позиция закрыта по StopLoss: ","Position closed by StopLoss: "); } //--- If the order has the flag for closing by TakeProfit if(order.IsCloseByTakeProfit()) { //--- Add the the flag for closing by TakeProfit this.m_trade_event_code+=TRADE_EVENT_FLAG_TP; text=TextByLanguage("Позиция закрыта по TakeProfit: ","Position closed by TakeProfit: "); } } //--- add reverse order direction to the message: //--- closing Buy order for Sell position and closing Sell order for Buy position, //--- therefore, reverse the order direction for correct description of a closed position text+=(order.DirectionDescription()=="Sell" ? "Buy " : "Sell "); } //--- add the ticket of the deal's position and display the message in the journal text+="#"+(string)deal.PositionID(); Print(DFUN,text); } //--- When closing by an opposite position if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY) { //--- Add the position closing flag this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED; //--- Add the flag for closing by an opposite position this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS; string text=TextByLanguage("Позиция закрыта встречной: ","Position closed by opposite position: "); //--- Take the deal order ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(ticket_from); if(order!=NULL) { //--- add reverse order direction to the message: //--- closing Buy order for Sell position and closing Sell order for Buy position, //--- therefore, reverse the order direction for correct description of a closed position text+=(order.DirectionDescription()=="Sell" ? "Buy" : "Sell"); text+=" #"+(string)order.PositionID()+TextByLanguage(" закрыта позицией #"," closed by position #")+(string)order.PositionByID(); //--- If the order position is still present in the market COrder* pos=this.GetPosition(order.PositionID()); if(pos!=NULL) { //--- Add the partial execution flag this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; text+=TextByLanguage(" частично"," partially"); } } //--- Display the message in the journal Print(DFUN,text); } //--- end of the last deal processing block } } } #endif } //+------------------------------------------------------------------+
El método es sencillo, pero bastante amplio. Por eso, hemos escrito directamente en el código todas las comprobaciones realizadas y las acciones que les corresponden: a nuestro juicio, así resulta más fácil ver qué se hace y cómo se hace dentro del método. Debemos decir que en el método solo se ha implementado por el momento la comprobación para la cuenta de cobertura de MQL5, y, en esencia, todo se reduce a la comprobación del número de órdenes y transacciones aparecidas de nuevo o bien en la historia de la cuenta, o bien en el mercado. Para introducir los datos en el diario, estos se toman de la última posición, de la última transacción, orden u orden de la última transacción: se ha hecho de esta forma solo para mostrar los datos en el diario, para controlar que la ejecución del código sea correcta, y como consecuencia, se elimine y sustituya del código con un método para el uso en los programas utilizados en la biblioteca.
Ponemos a prueba el procesamiento de los eventos comerciales
Vamos a crear un asesor de prueba para comprobar el funcionamiento del método de definición de eventos comerciales en la cuenta.
Para que sea posible gestionar de alguna forma la aparición de nuevos eventos, crearemos un conjunto de botones cuya pulsación llevará a las acciones que necesitemos.
El conjunto de acciones necesarias y sus botones correspondientes será el siguiente:
- Abrir una posición Buy
- Colocar una orden pendiente BuyLimit
- Colocar una orden pendiente BuyStop
- Colocar una orden pendiente BuyStopLimit
- Cerrar una posición Buy
- Cerrar la mitad de la posición Buy
- Cerrar una posición Buy con una posición Sell opuesta
- Abrir una posición Sell
- Colocar una orden pendiente SellLimit
- Colocar una orden pendiente SellStop
- Colocar una orden pendiente SellStopLimit
- Cerrar una posición Sell
- Cerrar la mitad de la posición Sell
- Cerrar una posición Sell con una posición Buy opuesta
- Cerrar todas las posiciones
- Retirar fondos de la cuenta
El conjunto de parámetros de entrada será el siguiente:
- Magic number - número mágico
- Lots - volumen de las posiciones abiertas
- StopLoss in points - stoploss en puntos
- TakeProfit in points - takeprofit en puntos
- Pending orders distance (points) - distancia de colocación de órdenes pendientes en puntos
- StopLimit orders distance (points) - distancia de colocación de una orden Limit cuando el precio alcanza el precio de colocación de la orden StopLimit
Aquí debemos realizar una aclaración: la orden StopLimit se coloca como orden stop a una distancia del precio indicada por el valor Pending orders distance.
En cuanto el precio alcance la orden colocada y se active, será ya a partir de este precio donde se colocará una orden límite a una distancia del precio indicada por el valor StopLimit orders distance. - Slippage in points - magnitud del deslizamiento en puntos
- Withdrawal funds (in tester) - suma de los fondos retirados de la cuenta en el simulador
Para calcular los precios de colocación de las órdenes con respecto al nivel de StopLevel, las órdenes stop y el volumen de las posiciones, necesitaremos las funciones de cálculo de los valores correctos. En esta etapa, mientras no tenemos clases comerciales y clases de símbolos, vamos a añadir simplemente las funciones a la biblioteca de funciones de servicio en el archivo DELib.mqh:
//+------------------------------------------------------------------+ //| Return the minimum symbol lot | //+------------------------------------------------------------------+ double MinimumLots(const string symbol_name) { return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN); } //+------------------------------------------------------------------+ //| Return the maximum symbol lot | //+------------------------------------------------------------------+ double MaximumLots(const string symbol_name) { return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX); } //+------------------------------------------------------------------+ //| Return the symbol lot change step | //+------------------------------------------------------------------+ double StepLots(const string symbol_name) { return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_STEP); } //+------------------------------------------------------------------+ //| Return the normalized lot | //+------------------------------------------------------------------+ double NormalizeLot(const string symbol_name, double order_lots) { double ml=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN); double mx=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX); double ln=NormalizeDouble(order_lots,int(ceil(fabs(log(ml)/log(10))))); return(ln<ml ? ml : ln>mx ? mx : ln); } //+------------------------------------------------------------------+ //| Return correct StopLoss relative to StopLevel | //+------------------------------------------------------------------+ double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const int spread_multiplier=2) { if(stop_loss==0) return 0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT); double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set); return (order_type==ORDER_TYPE_BUY || order_type==ORDER_TYPE_BUY_LIMIT || order_type==ORDER_TYPE_BUY_STOP #ifdef __MQL5__ || order_type==ORDER_TYPE_BUY_STOP_LIMIT #endif ? NormalizeDouble(fmin(price-lv*pt,stop_loss),dg) : NormalizeDouble(fmax(price+lv*pt,stop_loss),dg) ); } //+------------------------------------------------------------------+ //| Return correct StopLoss relative to StopLevel | //+------------------------------------------------------------------+ double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int stop_loss,const int spread_multiplier=2) { if(stop_loss==0) return 0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT); double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set); return (order_type==ORDER_TYPE_BUY || order_type==ORDER_TYPE_BUY_LIMIT || order_type==ORDER_TYPE_BUY_STOP #ifdef __MQL5__ || order_type==ORDER_TYPE_BUY_STOP_LIMIT #endif ? NormalizeDouble(fmin(price-lv*pt,price-stop_loss*pt),dg) : NormalizeDouble(fmax(price+lv*pt,price+stop_loss*pt),dg) ); } //+------------------------------------------------------------------+ //| Return correct TakeProfit relative to StopLevel | //+------------------------------------------------------------------+ double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const int spread_multiplier=2) { if(take_profit==0) return 0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT); double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set); return (order_type==ORDER_TYPE_BUY || order_type==ORDER_TYPE_BUY_LIMIT || order_type==ORDER_TYPE_BUY_STOP #ifdef __MQL5__ || order_type==ORDER_TYPE_BUY_STOP_LIMIT #endif ? NormalizeDouble(fmax(price+lv*pt,take_profit),dg) : NormalizeDouble(fmin(price-lv*pt,take_profit),dg) ); } //+------------------------------------------------------------------+ //| Return correct TakeProfit relative to StopLevel | //+------------------------------------------------------------------+ double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int take_profit,const int spread_multiplier=2) { if(take_profit==0) return 0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT); double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set); return (order_type==ORDER_TYPE_BUY || order_type==ORDER_TYPE_BUY_LIMIT || order_type==ORDER_TYPE_BUY_STOP #ifdef __MQL5__ || order_type==ORDER_TYPE_BUY_STOP_LIMIT #endif ? ::NormalizeDouble(::fmax(price+lv*pt,price+take_profit*pt),dg) : ::NormalizeDouble(::fmin(price-lv*pt,price-take_profit*pt),dg) ); } //+------------------------------------------------------------------+ //| Return the correct order placement price | //| relative to StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch(order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); default : Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+ //| Return the correct order placement price | //| relative to StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch(order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); default : Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+ //| Check the stop level in points relative to StopLevel | //+------------------------------------------------------------------+ bool CheckStopLevel(const string symbol_name,const int stop_in_points,const int spread_multiplier) { return(stop_in_points>=StopLevel(symbol_name,spread_multiplier)); } //+------------------------------------------------------------------+ //| Return StopLevel in points | //+------------------------------------------------------------------+ int StopLevel(const string symbol_name,const int spread_multiplier) { int spread=(int)SymbolInfoInteger(symbol_name,SYMBOL_SPREAD); int stop_level=(int)SymbolInfoInteger(symbol_name,SYMBOL_TRADE_STOPS_LEVEL); return(stop_level==0 ? spread*spread_multiplier : stop_level); } //+------------------------------------------------------------------+
Las funciones simplemente calculan las magnitudes correctas de tal forma que no violen las limitaciones establecidas en el servidor. Debemos notar que, aparte del símbolo, a la función de cálculo del nivel de StopLevel se le transmite el multiplicador de spread. Esto se ha hecho porque si el nivel de StopLevel se ha etsblecido como igual a cero en el servidor, no significa que sea igual a cero, sino que es flotante, y para el cálculo del nivel StopLevel debemos usar una magnitud de spread multiplicada por cierto número (normalmente 2, pero también puede ser 3); precisamente este multiplicador transmitimos a la función, permitiendo que este sea establecido rigurosamente, o bien que sea calculado.
Para hacer rápidamente el asesor, no vamos a escribir nuestras propias funciones comerciales, sino que vamos a usar clases comerciales preparadas de la biblioteca estándar, más concretamente, la clase para ejecutar operaciones comerciales CTrade.
Para crear los botones en el gráfico y trabajar comn ellos, vamos a crear una enumeración cuyos miembros servirán para establecer los nombres de los botones, sus rótulos y valores para comprobar la pulsación de un botón concreto.
Creamos en la carpeta del terminal MQL5\Experts\TestDoEasy\Part04\ un nuevo asesor con el nombre TestDoEasy04.mqh (al crearlo, en el Wizard de MQL marcamos los manejadores de eventos necesarios, es decir, OnTimer y OnChartEvent):
Después de crear la plantilla del asesor con el Wizard MQL, incluimos de inmediato en el mismo nuestra biblioteca y la clase comercial de la biblioteca estándar, y también añadimos los parámetros de entrada:
//+------------------------------------------------------------------+ //| TestDoEasyPart04.mq5 | //| 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" //--- includes #include <DoEasy\Engine.mqh> #include <Trade\Trade.mqh> //--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_CLOSE_ALL, BUTT_PROFIT_WITHDRAWAL }; #define TOTAL_BUTT (16) //--- structures struct SDataButt { string name; string text; }; //--- 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) //--- global variables CEngine engine; CTrade trade; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; //+------------------------------------------------------------------+
Aquí incluimos el objeto principal de la biblioteca CEngine y la clase comercial CTrade.
A continuación, creamos una enumeración, indicando todos los botones necesarios.
El orden de los miembros de la enumeración es importante, puesto que establece el orden secuencial en el que se crean los botones y su ubicación en el gráfico.
A continuación, declaramos la estructura para guardar el nombre del objeto gráfico del botón y el texto que se escribirá en el botón.
En el bloque de variables de entrada, escribimos todas las variables de parámetro del asesor descritas más arriba, y en el bloque de variables globales del asesor, declaramos el objeto de biblioteca, el objeto de la clase comercial, la matriz de estructuras de los botones y las variables a las que se les asignarán los valores de los parámetros de entrada en el manejador OnInit():
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Check account type if(!engine.IsHedge()) { Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge")); return INIT_FAILED; } //--- set 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; //--- create buttons if(!CreateButtons()) return INIT_FAILED; //--- trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_ERRORS); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
En el manejador OnInit(), en primer lugar comprobamos el tipo de cuenta y, si no es es cobertura, lo anunciamos y salimos del progrma con error.
A continuación, establecemos el prefijo de los nombres de los objetos (para que el asesor pueda reconocer sus objetos según este prefijo) y rellenamos en el ciclo la matriz de estructuras con los datos de los botones según el número de botones.
El nombre del objeto-botón se indica como el prefijo+la representación de línea de la enumeración ENUM_BUTTONS que se corresponda con el índice del ciclo,
mientras que el texto del botón se compone con el método de conversión de la representación de línea de la enumeración que se corresponde con el índice del ciclo, con la ayuda de la función EnumToButtText().
Después, se calcula el lote de posiciones abiertas y óredenes a colocar. Dado que nuestro caso contempla el cierre de la mitad de una posición, debemos tener en cuenta que el lote de la posición abierta debe ser como mínimo el doble del lote mínimo. Por eso, el lote máximo se toma de dos casos:
1) el introducido en los parámetros de entrada, 2) el lote mínimo multiplicado por dos en la línea fmax(InpLots,MinimumLots(Symbol())*2.0), el valor del lote obtenido se normaliza y se asigna a la variable global lot. En conclusión: si el lote introducido por el usuario en los parámetros de entrada resulta menor que el doble del lote mínimo, se usará el doble del lote mínimo; en el caso contrario, se usará el lote introducido por el usuario.
A continuación, el resto de parámetros de entrada se asignan las variables globales correspondientes y se llama la función CreateButtons() para crear los botones de la matriz de estructuras, que ya ha sido rellenada con los datos de los botones en el paso anterior. Si no hemos logrado crear ningún botón de todos los necesarios para ButtonCreate(), se emite un mensaje sobre el error de creación del botón, y el programa se finaliza con error de inicialización.
Finalmente, se inicializa la clase CTrade:
//--- trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_ERRORS); //---
- Se establece la desviación en puntos,
- se establece el número mágico,
- se establece el tipo de orden de ejecución de acuerdo con los ajustes del símbolo actual,
- se establece el modo de cálculo del margen de acuerdo con los ajustes de la cuenta actual y
- se establece el nivel de registro de los mensajes, para mostrar en el diario solo mensajes sobre errores
(en el simulador se activa automáticamente el modo de registro completo).
En el manejador OnDeinit(), escribimos la eliminación de todos los botones según el prefijo de los nombres de los objetos:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- delete objects ObjectsDeleteAll(0,prefix); } //+------------------------------------------------------------------+
Ahora vamos a decidir cómo proceder con respecto al orden de inicio del temporizador de la biblioteca y el manejador de los eventos del asesor.
- Si el asesor no ha sido iniciado en el simulador, iniciaremos el temporizador de la biblioteca desde el temporizador del asesor; entre tanto, trabajaremos con el manejador de eventos en el modo estándar.
- Si el asesor ha sido iniciado en el simulador, iniciaremos el temporizador de la biblioteca desde el manejador OnTick() del asesor, y monitorearemos el evento de pulsación de botones allí mismo, en OnTick(), realizando un seguimiento del estado de los botones.
Manejadores OnTick(), OnTimer() y OnChartEvent() del asesor:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- if(MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); int total=ObjectsTotal(0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { if(!MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(MQLInfoInteger(MQL_TESTER)) return; if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0) { PressButtonEvents(sparam); } } //+------------------------------------------------------------------+
En OnTick()
- Se comprueba dónde ha sido iniciado el asesor, y si ha sido iniciado en el simulador, se llama el manejador OnTimer() de la biblioteca.
- A continuación, en el ciclo por todos los objetos del gráfico actual comprobamos el nombre del objeto, y si coincide con el nombre de alguno de los botones, se llama el manejador de pulsación de dicho botón.
En OnTimer()
- Se comprueba dónde ha sido iniciado el asesor, y si ha sido iniciado en el simulador, se llama el manejador OnTimer() de la biblioteca.
En OnChartEvent()
- Se comprueba dónde ha sido iniciado el asesor, y si ha sido iniciado en el simulador, salimos del procesador.
- Después se comprueba el identificador del evento, y si se trata del evento de clic sobre un objeto gráfico, y el nombre del objeto contiene el texto de pertenencia a los botones, se llama el manejador de pulsación de dicho botón.
Función CreateButtons():
//+------------------------------------------------------------------+ //| Create the buttons panel | //+------------------------------------------------------------------+ bool CreateButtons(void) { int h=18,w=84,offset=10; int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/2)+h+1; int x=cx,y=cy; int shift=0; for(int i=0;i<TOTAL_BUTT;i++) { x=x+(i==7 ? w+2 : 0); if(i==TOTAL_BUTT-2) x=cx; y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text); return false; } } ChartRedraw(0); return true; } //+------------------------------------------------------------------+
En esta función, todo se reduce al cálculo de coordenadas y el color del botón en el ciclo según el número de miembros de la enumeración ENUM_BUTTONS . Las coordenadas y el color se calculan dependiendo del índice del ciclo que indica el número del miembro de la enumeración ENUM_BUTTONS. Después de calcular las cooredenas x e y, se llama la función de creación del botón con los valores de las coordenadas y el color calculados en este ciclo.
Función EnumToButtText():
//+------------------------------------------------------------------+ //| Convert enumeration into the button text | //+------------------------------------------------------------------+ string EnumToButtText(const ENUM_BUTTONS member) { string txt=StringSubstr(EnumToString(member),5); StringToLower(txt); StringReplace(txt,"buy","Buy"); StringReplace(txt,"sell","Sell"); StringReplace(txt,"_limit"," Limit"); StringReplace(txt,"_stop"," Stop"); StringReplace(txt,"close_","Close "); StringReplace(txt,"2"," 1/2"); StringReplace(txt,"_by_"," by "); StringReplace(txt,"profit_","Profit "); return txt; } //+------------------------------------------------------------------+
Aquí todo es sencillo: la función obtiene el miembro de la enumeración, lo transforma en línea eliminando el texto innecesario, después se transforman todos los símbolos de la línea obtenida en string (minúsculas), y a continuación se sustituyen todas las entradas inadecuadas por las necesarias.
Como resultado, se retorna la línea transformada en texto de la enumeración de entrada.
Las funciones de creación de botones, su colocación y la obtención de su estado:
//+------------------------------------------------------------------+ //| Create the button | //+------------------------------------------------------------------+ bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8) { if(ObjectFind(0,name)<0) { if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) { Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); return false; } ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,w); ObjectSetInteger(0,name,OBJPROP_YSIZE,h); ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size); ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetString(0,name,OBJPROP_TEXT,text); ObjectSetInteger(0,name,OBJPROP_COLOR,clr); ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n"); ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray); return true; } return false; } //+------------------------------------------------------------------+ //| Return the button status | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| Set the button status | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); } //+------------------------------------------------------------------+
Aquí todo es muy sencillo y claro, por eso no requiere de aclaraciones.
Función para manipular la pulsación de botones:
//+------------------------------------------------------------------+ //| Handling buttons pressing | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- Get correct StopLoss and TakeProfit prices relative to StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Open Buy position trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp); } //--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- Set BuyLimit order trade.BuyLimit(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Get the correct order placement price relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit); //--- Set BuyStop order trade.BuyStop(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Get the correct BuyStop order placement price relative to StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Calculate BuyLimit order price relative to BuyStop level considering StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit); //--- Set BuyStopLimit order trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- If the BUTT_SELL button is pressed: Open Sell position else if(button==EnumToString(BUTT_SELL)) { //--- Get correct StopLoss and TakeProfit prices relative to StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit); //--- Open Sell position trade.Sell(lot,Symbol(),0,sl,tp); } //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Get correct order price relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit); //--- Set SellLimit order trade.SellLimit(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Get the correct price of placing an order relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit); //--- Set SellStop order trade.SellStop(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Get the correct SellStop order price relative to StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Calculate SellLimit order price relative to SellStop level considering StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop); //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit); //--- Set the SellStopLimit order trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the Buy position index with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Get the Buy position ticket and close the position by the ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_CLOSE_BUY2 button is closed: Close the half of the Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY2)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the Buy position index with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Calculate the closed volume and close the half of the Buy position by the ticket trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0)); } } } //--- If the BUTT_CLOSE_BUY_BY_SELL button is pressed: Close Buy with the maximum profit by the opposite Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)) { //--- Get the list of all open positions CArrayObj* list_buy=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); //--- Get the list of all open positions CArrayObj* list_sell=engine.GetListMarketPosition(); //--- Select Sell positions only from the list list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE) { //--- Select Buy position with the maximum profit COrder* position_buy=list_buy.At(index_buy); //--- Select Sell position with the maximum profit COrder* position_sell=list_sell.At(index_sell); if(position_buy!=NULL && position_sell!=NULL) { //--- Close Buy position by the opposite Sell position trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket()); } } } //--- If the BUTT_CLOSE_SELL button is pressed: Close Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL)) { //--- Get the list of all opened positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Get the Sell position ticket and close the position by the ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_CLOSE_SELL2 button is pressed: Close the half of the Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL2)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Calculate the closed volume and close the half of the Sell position by the ticket trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0)); } } } //--- If the BUTT_CLOSE_SELL_BY_BUY button is pressed: Close Sell with the maximum profit by the opposite Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)) { //--- Get the list of all open positions CArrayObj* list_sell=engine.GetListMarketPosition(); //--- Get only Sell positions from the list list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); //--- Get the list of all open positions CArrayObj* list_buy=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE) { //--- Select the Sell position with the maximum profit COrder* position_sell=list_sell.At(index_sell); //--- Select the Buy position with the maximum profit COrder* position_buy=list_buy.At(index_buy); if(position_sell!=NULL && position_buy!=NULL) { //--- Close the Sell position by the opposite Buy position trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket()); } } } //--- If the BUTT_CLOSE_ALL button is pressed: Close all positions starting with the one with the least profit else if(button==EnumToString(BUTT_CLOSE_ALL)) { //--- Receive the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); if(list!=NULL) { //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); int total=list.Total(); //--- In the loop from the position with the least profit for(int i=0;i<total;i++) { COrder* position=list.At(i); if(position==NULL) continue; //--- close each position by its ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_PROFIT_WITHDRAWAL button is pressed: Withdraw the funds from the account if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- If the program is launched in the tester if(MQLInfoInteger(MQL_TESTER)) { //--- Emulate funds withdrawal TesterWithdrawal(withdrawal); } } //--- Wait for 1/10 of a second Sleep(100); //--- "Unpress" the button and redraw the chart ButtonState(button_name,false); ChartRedraw(); } } //+------------------------------------------------------------------+
La función es bastante grande, pero su contenido es bastante sencillo: transmitimos a la función el nombre del objeto-botón que transformamos en identificador de línea. A continuación, comprobamos el estado del botón, y si está pulsado, comprobamos el identificador de línea. También se ejecuta la ramificación if-else correspondiente, donde se calculan todos los niveles con corrección, para no violar la limitación del nivel StolLevel, y se ejecuta el método correspondiente de la clase comercial.
Todas las correcciones se detallan directamente en el código.
Para el asesor de prueba, hemos hecho las comprobaciones mínimas necesarias, no vamos a hacer el resto de comprobaciones importantes para la cuenta real: aquí y ahora para nosotros es importante comprobar el funcionamiento de la biblioteca, y no hacer un asesor para trabajar con él en cuentas reales.
Aquí tenemos el listado completo del asesor de prueba:
//+------------------------------------------------------------------+ //| TestDoEasyPart04.mq5 | //| 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" //--- includes #include <DoEasy\Engine.mqh> #include <Trade\Trade.mqh> //--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_CLOSE_ALL, BUTT_PROFIT_WITHDRAWAL }; #define TOTAL_BUTT (16) //--- structures struct SDataButt { string name; string text; }; //--- 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) //--- global variables CEngine engine; CTrade trade; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Check account type if(!engine.IsHedge()) { Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge")); return INIT_FAILED; } //--- set 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; //--- create buttons if(!CreateButtons()) return INIT_FAILED; //--- set trading parameters trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- delete objects ObjectsDeleteAll(0,prefix); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- if(MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); int total=ObjectsTotal(0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } if(engine.TradeEventCode()!=TRADE_EVENT_FLAG_NO_EVENT) { Print(DFUN,EnumToString((ENUM_TRADE_EVENT_FLAGS)engine.TradeEventCode())); } engine.TradeEventCode(); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { if(!MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(MQLInfoInteger(MQL_TESTER)) return; if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0) { PressButtonEvents(sparam); } } //+------------------------------------------------------------------+ //| Create the buttons panel | //+------------------------------------------------------------------+ bool CreateButtons(void) { int h=18,w=84,offset=10; int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/2)+h+1; int x=cx,y=cy; int shift=0; for(int i=0;i<TOTAL_BUTT;i++) { x=x+(i==7 ? w+2 : 0); if(i==TOTAL_BUTT-2) x=cx; y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text); return false; } } ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Create the button | //+------------------------------------------------------------------+ bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8) { if(ObjectFind(0,name)<0) { if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) { Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); return false; } ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,w); ObjectSetInteger(0,name,OBJPROP_YSIZE,h); ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size); ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetString(0,name,OBJPROP_TEXT,text); ObjectSetInteger(0,name,OBJPROP_COLOR,clr); ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n"); ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray); return true; } return false; } //+------------------------------------------------------------------+ //| Return the button status | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| Set the button status | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); } //+------------------------------------------------------------------+ //| Transform enumeration into the button text | //+------------------------------------------------------------------+ string EnumToButtText(const ENUM_BUTTONS member) { string txt=StringSubstr(EnumToString(member),5); StringToLower(txt); StringReplace(txt,"buy","Buy"); StringReplace(txt,"sell","Sell"); StringReplace(txt,"_limit"," Limit"); StringReplace(txt,"_stop"," Stop"); StringReplace(txt,"close_","Close "); StringReplace(txt,"2"," 1/2"); StringReplace(txt,"_by_"," by "); StringReplace(txt,"profit_","Profit "); return txt; } //+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Convert the button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Open Buy position trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp); } //--- If the BUTT_BUY_LIMIT button is pressed: Set BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Get the correct order placement price relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- Set BuyLimit order trade.BuyLimit(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Get the correct order placement price relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit); //--- Set BuyStop order trade.BuyStop(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Get the correct BuyStop price relative to StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Calculate BuyLimit order price relative to BuyStop placement level considering StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop); //--- Get correct StopLoss and TakeProfit prices relative to order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit); //--- Set BuyStopLimit order trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- If the BUTT_SELL button is pressed: Open Sell position else if(button==EnumToString(BUTT_SELL)) { //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit); //--- Open Sell position trade.Sell(lot,Symbol(),0,sl,tp); } //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Get the correct order placement price relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending); //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit); //--- Set SellLimit order trade.SellLimit(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Get the correct order placement price relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit); //--- Set SellStop order trade.SellStop(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Get the correct SellStop order price relative to StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Calculate SellLimit order price relative to SellStop level considering StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop); //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit); //--- Set SellStopLimit order trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Get the Buy position ticket and close the position by the ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_CLOSE_BUY2 button is pressed: Close the half of the Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY2)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Calculate the closed volume and close the half of the Buy position by the ticket trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0)); } } } //--- If the BUTT_CLOSE_BUY_BY_SELL button is pressed: Close Buy with the maximum profit by the opposite Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)) { //--- Get the list of all open positions CArrayObj* list_buy=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); //--- Get the list of all open positions CArrayObj* list_sell=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE) { //--- Select the Buy position with the maximum profit COrder* position_buy=list_buy.At(index_buy); //--- Select the Sell position with the maximum profit COrder* position_sell=list_sell.At(index_sell); if(position_buy!=NULL && position_sell!=NULL) { //--- Close the Buy position by the opposite Sell one trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket()); } } } //--- If the BUTT_CLOSE_SELL button is pressed: Close Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Get the Sell position ticket and close the position by the ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_CLOSE_SELL2 button is pressed: Close the half of the Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL2)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Calculate the closed volume and close the half of the Sell position by the ticket trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0)); } } } //--- If the BUTT_CLOSE_SELL_BY_BUY button is pressed: Close Sell with the maximum profit by the opposite Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)) { //--- Get the list of all open positions CArrayObj* list_sell=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); //--- Get the list of all open positions CArrayObj* list_buy=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE) { //--- Select the Sell position with the maximum profit COrder* position_sell=list_sell.At(index_sell); //--- Select the Buy position with the maximum profit COrder* position_buy=list_buy.At(index_buy); if(position_sell!=NULL && position_buy!=NULL) { //--- Close the Sell position by the opposite Buy one trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket()); } } } //--- If the BUTT_CLOSE_ALL is pressed: Close all positions starting with the one with the least profit else if(button==EnumToString(BUTT_CLOSE_ALL)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); if(list!=NULL) { //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); int total=list.Total(); //--- In the loop from the position with the least profit for(int i=0;i<total;i++) { COrder* position=list.At(i); if(position==NULL) continue; //--- close each position by its ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_PROFIT_WITHDRAWAL button is pressed: Withdraw funds from the account if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- If the program is launched in the tester if(MQLInfoInteger(MQL_TESTER)) { //--- Emulate funds withdrawal TesterWithdrawal(withdrawal); } } //--- Wait for 1/10 of a second Sleep(100); //--- "Unpress" the button and redraw the chart ButtonState(button_name,false); ChartRedraw(); } } //+------------------------------------------------------------------+
El código parece bien largo para ser "tan sencillo" ... Pero esto es solo el inicio, posteriormente todo será mucho más sencillo para el usuario final; la mayoría de las acciones que hemos realizado aquí estarán ocultas en el código de la biblioteca: el usuario se encontrará con cómodo mecanismo, muy fácil de utilizar.
Vamos a iniciar el asesor en el simulador y pulsar los botones:
Todo funciona correctamente, y en el diario se muestran mensajes sobre los eventos sucedidos.
Debemos notar que, en este momento, siempre se fija el último evento. Es decir, si cerramos de una vez varias posiciones, en el evento entrará solo la última posición de la multitud cerrada. Solo es posible monitorear el evento de cierre múltiple según el número de nuevas transacciones u órdenes en la historia, obteniendo después la lista de todas las nuevas posiciones cerradas según su número y determinando ya sus componentes al completo. A continuación, haremos para ello una clase aparte de colección de eventos para trabajar con los eventos de la cuenta, para que así en el programa siempre se pueda tener acceso a todos los eventos sucedidos.
Ahora, todos los mensajes sobre eventos en el diario del simulador se muestran en el método CEngine::WorkWithHedgeCollections() del objeto básico de la biblioteca, y nosotros necesitamos que el programa personalizado conozca los códigos de los eventos y comprenda lo que ha sucedido en la cuenta, para así crear la lógica de la reacción del programa a este u otro evento. Para ello, en esta etapa, y solo como comprobación, vamos a crear en el objeto básico de la biblioteca un método que guardará el código del último evento, así como el método que codifica este código, que consta de un conjunto de banderas de eventos.
En el siguiente artículo crearemos una clase completa para trabajar con los eventos de la cuenta.
En el cuerpo de la clase CEngine, definimos el método que descifra el código del evento y establece el código del evento comercial en la cuenta; el método que comprueba la presencia de la bandera de evento en el código del evento y el método para obtener el último evento comercial del programa que realiza la llamada, así como el método que resetea el valor del último evento comercial:
//+------------------------------------------------------------------+ //| 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 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_market_trade_event; // Account trading event flag bool m_is_history_trade_event; // Account history trading event flag int m_trade_event_code; // Account trading event status code ENUM_TRADE_EVENT m_acc_trade_event; // Account trading event //--- Decode the event code and set the trading event on the account void SetTradeEvent(void); //--- Return the counter index by id int CounterIndex(const int id) const; //--- Return the (1) first launch flag, (2) presence of the flag in the trading event bool IsFirstStart(void); bool IsTradeEventFlag(const int event_code) const { return (this.m_trade_event_code&event_code)==event_code; } //--- Working with (1) hedging, (2) netting collections void WorkWithHedgeCollections(void); void WorkWithNettoCollections(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 market order 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: //--- 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* GetListHistoryDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- Reset the last trading event void ResetLastTradeEvent(void) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; } //--- Return the (1) last trading event, (2) trading event code, (3) hedge account flag ENUM_TRADE_EVENT LastTradeEvent(void) const { return this.m_acc_trade_event; } int TradeEventCode(void) const { return this.m_trade_event_code; } bool IsHedge(void) const { return this.m_is_hedge; } //--- Create the timer counter void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- Timer void OnTimer(void); //--- Constructor/destructor CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
Fuera del cuerpo de la clase, escribimos el método para descifrar el evento comercial (escribiremos todas las aclaraciones directamente en el código):
//+------------------------------------------------------------------+ //| Decode the event code and set a trading event | //+------------------------------------------------------------------+ void CEngine::SetTradeEvent(void) { //--- No trading event. Exit if(this.m_trade_event_code==TRADE_EVENT_FLAG_NO_EVENT) return; //--- Pending order is set (check if the event code is matched since there can be only one flag here) if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_PLASED) { this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED; Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } //--- Pending order is removed (check if the event code is matched since there can be only one flag here) if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED) { this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED; Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } //--- Position is opened (Check for multiple flags in the event code) if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED)) { //--- If this pending order is activated by the price if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED)) { this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL); Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL); Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } //--- Position is closed (Check for multiple flags in the event code) if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED)) { //--- if the position is closed by StopLoss if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_SL)) { //--- check the partial closing flag and set the "Position closed by StopLoss" or "Position closed by StopLoss partially" trading event this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL); Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } //--- if the position is closed by TakeProfit else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_TP)) { //--- check the partial closing flag and set the "Position closed by TakeProfit" or "Position closed by TakeProfit partially" trading event this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP); Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } //--- if the position is closed by an opposite one else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_BY_POS)) { //--- check the partial closing flag and set the "Position closed by opposite one" or "Position closed by opposite one partially" event this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS); Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } //--- If the position is closed else { //--- check the partial closing flag and set the "Position closed" or "Position closed partially" event this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL); Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } } //--- Balance operation on the account (clarify the event by the deal type) if(this.m_trade_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE) { //--- Initialize the trading event this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; //--- Take the last deal COrder* deal=this.GetLastDeal(); if(deal!=NULL) { ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE); //--- if the deal is balance operation if(deal_type==DEAL_TYPE_BALANCE) { //--- check the deal profit and set the event (funds deposit or withdrawal) this.m_acc_trade_event=(deal.Profit()>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL); } //--- Remaining balance operation types match the ENUM_DEAL_TYPE enumeration starting with DEAL_TYPE_CREDIT else if(deal_type>DEAL_TYPE_BALANCE) { //--- set the event this.m_acc_trade_event=(ENUM_TRADE_EVENT)deal_type; } } Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event)); return; } } //+------------------------------------------------------------------+
Ahora, al final del método WorkWithHedgeCollections(), que se encarga de comprobar y crear el código del evento comercial, escribimos la llamada del método de encriptado del evento y eliminamos la muestra de la descripción de eventos en el diario:
//+------------------------------------------------------------------+ //| Check trading events (hedging) | //+------------------------------------------------------------------+ void CEngine::WorkWithHedgeCollections(void) { //--- Initialize trading event code and flags this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT; this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Update the lists this.m_market.Refresh(); this.m_history.Refresh(); //--- Actions during the first lanch if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Check the market status and account history changes this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- #ifdef __MQL4__ #else // MQL5 //--- If an event relates only to market orders and positions if(this.m_is_market_trade_event && !this.m_is_history_trade_event) { //--- If the number of pending orders increased if(this.m_market.NewPendingOrders()>0) { //--- Add the pending order placement flag this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED; } //--- If the number of market orders increased if(this.m_market.NewMarketOrders()>0) { //--- do not add the event flag //--- ... } } //--- If an event relates only to historical orders and deals else if(this.m_is_history_trade_event && !this.m_is_market_trade_event) { //--- If a new deal appears if(this.m_history.NewDeals()>0) { //--- Add the flag of an account balance event this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE; } } //--- If events are related to market and historical orders and positions else if(this.m_is_market_trade_event && this.m_is_history_trade_event) { //--- If the number of pending orders decreased and no new deals appeared if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0) { //--- Add the flag of removing a pending order this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED; } //--- If there is a new deal and a new historical order if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0) { //--- Take the last deal COrder* deal=this.GetLastDeal(); if(deal!=NULL) { //--- In case of a market entry deal if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN) { //--- Add the position opening flag this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED; //--- If the number of pending orders decreased if(this.m_market.NewPendingOrders()<0) { //--- Add the flag of a pending order activation this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED; } //--- Take the order's ticket ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(order_ticket); if(order!=NULL) { //--- If the current order volume exceeds zero if(order.VolumeCurrent()>0) { //--- Add the partial execution flag this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; } } } //--- In case of a market exit deal if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT) { //--- Add the position closing flag this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED; //--- Take the deal's order ticket ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(order_ticket); if(order!=NULL) { //--- If the deal position is still present on the market COrder* pos=this.GetPosition(deal.PositionID()); if(pos!=NULL) { //--- Add the partial execution flag this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; } //--- Otherwise, if the position is closed in full else { //--- If the order has the flag of closing by StopLoss if(order.IsCloseByStopLoss()) { //--- Add the flag of closing by StopLoss this.m_trade_event_code+=TRADE_EVENT_FLAG_SL; } //--- If the order has the flag of closing by TakeProfit if(order.IsCloseByTakeProfit()) { //--- Add the flag of closing by TakeProfit this.m_trade_event_code+=TRADE_EVENT_FLAG_TP; } } } } //--- If closed by the opposite one if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY) { //--- Add the position closing flag this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED; //--- Add the flag of closing by the opposite one this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS; //--- Take the deal order ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(ticket_from); if(order!=NULL) { //--- If the order position is still present on the market COrder* pos=this.GetPosition(order.PositionID()); if(pos!=NULL) { //--- Add the partial execution flag this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; } } } //--- end of the last deal handling block } } } #endif this.SetTradeEvent(); } //+------------------------------------------------------------------+
De esta forma, después de crear el código del evento en el método WorkWithHedgeCollections(), llamamos de inmediato el método de descifrado del evento. Pero, ¿qué nos impide descifrarlo directamente? El problema es que el método de descifrado actual es temporal, para comprobar que se ejecuta correctamente. En los próximos artículos, crearemos una clase completa de eventos comerciales. Por eso, lo hemos hecho así temporalmente.
Ya ahora, el método SetTradeEvent() determina un evento comercial, registrando su valor en la variable de miembro de la clase m_acc_trade_event; asimismo, los métodos LastTradeEvent() y ResetLastTradeEvent() permiten leer en el programa que realiza la llamada el valor de esta variable como el último evento comercial en la cuenta y resetearlo (por analogía con GetLastError()).
En el asesor de prueba TestDoEasyPart04.mqh, añadimos algunas líneas en su manejador OnTick() para leer el último evento comercial y mostrarlo en los comentarios en el gráfico:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- static ENUM_TRADE_EVENT last_event=WRONG_VALUE; if(MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); int total=ObjectsTotal(0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } if(engine.LastTradeEvent()!=last_event) { Comment("\nLast trade event: ",EnumToString(engine.LastTradeEvent())); last_event=engine.LastTradeEvent(); } } //+------------------------------------------------------------------+
Ahora, si iniciamos este asesor en el simulador de estrategias y clicamos en los botones, en el diario se mostrarán los eventos comerciales sucedidos del método CEngine::SetTradeEvent(), mientras que en los comentarios del gráfico se representará la descripción del último evento sucedido en la cuenta, obtenido de la biblioteca mediante el método engine.LastTradeEvent():
¿Qué es lo próximo?
En el siguiente artículo, crearemos las clases de los objetos de eventos, la colección de objetos de eventos por analogía con las coleccciones de órdenes y posiciones, y enseñaremos al objeto básico de la biblioteca a enviar eventos al programa.
Más abajo, se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos de los asesores 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: