2026-09-12 19:41:52 -05:00 | | | //+------------------------------------------------------------------+
|
| | | //| OcoOrder.mqh |
|
| | | //| Copyright 2025, Niquel Mendoza. |
|
| | | //| https://www.mql5.com/es/users/nique_372/news |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "Copyright 2025, Niquel Mendoza."
|
| | | #property link "https://www.mql5.com/es/users/nique_372/news"
|
| | | #property strict
|
| | |
|
| | | #ifndef MQLARTICLES_RM_OCOORDER_MQH
|
| | | #define MQLARTICLES_RM_OCOORDER_MQH
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | #include "AccountStatus.mqh"
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | // #define CACCOUNT_STATUS_ACTIVE_ON_ORDER_DELETE
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | namespace TSN
|
| | | {
|
| | | //+------------------------------------------------------------------+
|
| | | //| Clase COcoOrder optimizada |
|
| | | //+------------------------------------------------------------------+
|
| | | class COcoOrder : public CAccountGestor
|
| | | {
|
| | | private:
|
| | | CHashMapFastP(ulong, ulong, _fibbo) mapa_1_key;
|
| | | CHashMapFastP(ulong, ulong, _fibbo) mapa_2_key;
|
| | | CTrade trade;
|
| | |
|
| | | public:
|
| | | //---
|
| | | COcoOrder();
|
| | | ~COcoOrder();
|
| | |
|
| | | //---
|
| | | bool AddOrders(ulong ticket1, ulong ticket2);
|
| | | void OnOrderDelete(const ROrder& order) override;
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | COcoOrder::COcoOrder()
|
| | | {
|
| | | account_status.RegisterEvent(&this, ACCOUNT_STATUS_REG_FLAG_ON_ORDER_DELETE);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | COcoOrder::~COcoOrder()
|
| | | {
|
| | | if(account_status.IsActive()) // Solo eliminar si no es global
|
| | | account_status.UnregisterEvent(&this, ACCOUNT_STATUS_REG_FLAG_ON_ORDER_DELETE);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | bool COcoOrder::AddOrders(ulong ticket1, ulong ticket2)
|
| | | {
|
| | |
|
| | | //--- verificar si lso tickets son invalidos
|
| | | if(ticket1 == INVALID_TICKET || ticket2 == INVALID_TICKET)
|
| | | {
|
| | | LogError(StringFormat("Tickets inválidos: ticket1 = %I64u, ticket2 = %I64u", ticket1, ticket2), FUNCION_ACTUAL);
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- si son iguales
|
| | | if(mapa_1_key.ContainsKey(ticket1) || mapa_2_key.ContainsKey(ticket2))
|
| | | {
|
| | | LogWarning(StringFormat("OCO duplicado: tickets %I64u y %I64u ya existen", ticket1, ticket2), FUNCION_ACTUAL);
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Validar ticket1
|
| | | ResetLastError();
|
| | | if(!OrderSelect(ticket1))
|
| | | {
|
| | | int error = GetLastError();
|
| | | if(error == ERR_TRADE_ORDER_NOT_FOUND) // ORDER_NOT_FOUND
|
| | | LogWarning(StringFormat("Ticket1 %I64u no encontrado, posiblemente ya ejecutado", ticket1), FUNCION_ACTUAL);
|
| | | else
|
| | | LogError(StringFormat("No se puede seleccionar ticket1 %I64u, error: %d", ticket1, error), FUNCION_ACTUAL);
|
| | | return false;
|
| | | }
|
| | |
|
| | | const ENUM_ORDER_TYPE type1 = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
| | | if(!MQLArticles_IsPendingOrder(type1))
|
| | | {
|
| | | LogError(StringFormat("Tipo de orden inválido para ticket1 %I64u: %s", ticket1, EnumToString(type1)), FUNCION_ACTUAL);
|
| | | return false;
|
| | | }
|
| | |
|
| | | //--- Validar ticket2
|
| | | ResetLastError();
|
| | | if(!OrderSelect(ticket2))
|
| | | {
|
| | | int error = GetLastError();
|
| | | if(error == ERR_TRADE_ORDER_NOT_FOUND) // ORDER_NOT_FOUND
|
| | | LogWarning(StringFormat("Ticket2 %I64u no encontrado, posiblemente ya ejecutado", ticket2), FUNCION_ACTUAL);
|
| | | else
|
| | | LogError(StringFormat("No se puede seleccionar ticket2 %I64u, error: %d", ticket2, error), FUNCION_ACTUAL);
|
| | | return false;
|
| | | }
|
| | |
|
| | | const ENUM_ORDER_TYPE type2 = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
| | | if(!MQLArticles_IsPendingOrder(type2))
|
| | | {
|
| | | LogError(StringFormat("Tipo de orden inválido para ticket2 %I64u: %s", ticket2, EnumToString(type2)), FUNCION_ACTUAL);
|
| | | return false;
|
| | | }
|
| | |
|
| | | //---
|
| | | bool res = mapa_1_key.Add(ticket1, ticket2) && mapa_2_key.Add(ticket2, ticket1);
|
| | | return res;
|
| | | }
|
| | |
|
| | | //#define OCO_ORDER_MORE_PRINTS
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | void COcoOrder::OnOrderDelete(const ROrder &order)
|
| | | {
|
| | | ulong ticket_extra; //ticke objetivo
|
| | | const ulong ticket_i = order.order_ticket;
|
| | |
|
| | | //---
|
| | | if(mapa_1_key.TryGet(ticket_i, ticket_extra)) //TICKET 1 SE CIERRA
|
| | | {
|
| | | #ifndef OCO_ORDER_MORE_PRINTS
|
| | | if(order.order_state == ORDER_STATE_FILLED)
|
| | | {
|
| | | LogInfo(StringFormat("OCO ejecutado: orden %I64u completada, cancelando contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL);
|
| | | ResetLastError();
|
| | | if(!trade.OrderDelete(ticket_extra))
|
| | | LogError(StringFormat("Fallo al cancelar orden contraria %I64u. Error: %d", ticket_extra, GetLastError()), FUNCION_ACTUAL);
|
| | | }
|
| | | #else // OCO_ORDER_MORE_PRINTS
|
| | | switch(order.order_state)
|
| | | {
|
| | | case ORDER_STATE_EXPIRED: //ticke1 expiro pro lo tanto solo eliminado no cerramos el ticket 1
|
| | | LogInfo(StringFormat("Orden %I64u expiró, cancelando orden contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL);
|
| | | break;
|
| | |
|
| | | case ORDER_STATE_CANCELED: //ticket1 se cancelo pro lo tnatno solo lo eleiminas del mapa
|
| | | LogWarning(StringFormat("Orden %I64u cancelada manualmente, cancelando contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL);
|
| | | break;
|
| | | case ORDER_STATE_FILLED:
|
| | | {
|
| | | LogInfo(StringFormat("OCO ejecutado: orden %I64u completada, cancelando contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL);
|
| | | ResetLastError();
|
| | | if(!trade.OrderDelete(ticket_extra))
|
| | | LogError(StringFormat("Fallo al cancelar orden contraria %I64u. Error: %d", ticket_extra, GetLastError()), FUNCION_ACTUAL);
|
| | | break;
|
| | | }
|
| | | default:
|
| | | LogWarning(StringFormat("Estado desconocido en orden %I64u: %d", ticket_i, order.order_state), FUNCION_ACTUAL);
|
| | | break;
|
| | | }
|
| | | #endif // OCO_ORDER_MORE_PRINTS
|
| | |
|
| | |
|
| | | //--- Eliminamos igualmente
|
| | | mapa_1_key.Remove(ticket_i);
|
| | | mapa_2_key.Remove(ticket_extra);
|
| | | }
|
| | | else
|
| | | if(mapa_2_key.TryGet(ticket_i, ticket_extra)) //TICKET 2 SE CIERRA
|
| | | {
|
| | | #ifndef OCO_ORDER_MORE_PRINTS
|
| | | if(order.order_state == ORDER_STATE_FILLED) //ticket 2 se cerro y se abrio psociion eleiminalos el ticket1
|
| | | {
|
| | | LogInfo(StringFormat("OCO ejecutado: orden %I64u completada, cancelando contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL);
|
| | | ResetLastError();
|
| | | if(!trade.OrderDelete(ticket_extra))
|
| | | LogError(StringFormat("Fallo al cancelar orden contraria %I64u. Error: %d", ticket_extra, GetLastError()), FUNCION_ACTUAL);
|
| | | }
|
| | | #else // OCO_ORDER_MORE_PRINTS
|
| | | switch(order.order_state)
|
| | | {
|
| | | case ORDER_STATE_EXPIRED: //ticke2 expiro pro lo tanto solo eliminado no cerramos el ticket 2
|
| | | LogInfo(StringFormat("Orden %I64u expiró, cancelando orden contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL);
|
| | | break;
|
| | | case ORDER_STATE_CANCELED: //ticket2 se cancelo pro lo tnatno solo lo eleiminas del mapa 2
|
| | | LogWarning(StringFormat("Orden %I64u cancelada manualmente, cancelando contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL);
|
| | | break;
|
| | | case ORDER_STATE_FILLED: //ticket 2 se cerro y se abrio psociion eleiminalos el ticket1
|
| | | {
|
| | | LogInfo(StringFormat("OCO ejecutado: orden %I64u completada, cancelando contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL);
|
| | | ResetLastError();
|
| | | if(!trade.OrderDelete(ticket_extra))
|
| | | LogError(StringFormat("Fallo al cancelar orden contraria %I64u. Error: %d", ticket_extra, GetLastError()), FUNCION_ACTUAL);
|
| | | break;
|
| | | }
|
| | | default:
|
| | | LogWarning(StringFormat("Estado desconocido en orden %I64u: %d", ticket_i, order.order_state), FUNCION_ACTUAL);
|
| | | break;
|
| | | }
|
| | | #endif // OCO_ORDER_MORE_PRINTS
|
| | |
|
| | | //--- Eliminamos igualmente
|
| | | mapa_2_key.Remove(ticket_i);
|
| | | mapa_1_key.Remove(ticket_extra);
|
| | | }
|
| | | }
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | #endif // MQLARTICLES_RM_OCOORDER_MQH
|
| | |
|
| | | //+------------------------------------------------------------------+
|