//+------------------------------------------------------------------+ //| 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 OCO_ORDER_MQH #define OCO_ORDER_MQH #include "RM_Functions.mqh" #define IsOrder(type) EsUnaOrderPendiente[type] //+------------------------------------------------------------------+ //| Clase COcoOrder optimizada | //+------------------------------------------------------------------+ class COcoOrder : public CAccountGestor { private: CHashMap mapa_1_key; CHashMap mapa_2_key; CTrade trade; public: //--- bool AddOrders(ulong ticket1, ulong ticket2); void OnOrderDelete(const ROnOrderDelete& order) override; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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) == false) { 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; } ENUM_ORDER_TYPE type1 = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); if(IsOrder(type1) == false) { LogError(StringFormat("Tipo de orden inválido para ticket1 %I64u: %s", ticket1, EnumToString(type1)), FUNCION_ACTUAL); return false; } //--- Validar ticket2 ResetLastError(); if(OrderSelect(ticket2) == false) { 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; } ENUM_ORDER_TYPE type2 = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); if(IsOrder(type2) == false) { 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_FAST //+------------------------------------------------------------------+ void COcoOrder::OnOrderDelete(const ROnOrderDelete &order) { ulong ticket_extra; //ticke objetivo const ulong ticket_i = order.order_ticket; //--- if(mapa_1_key.TryGetValue(ticket_i, ticket_extra)) //TICKET 1 SE CIERRA { #ifdef OCO_ORDER_MORE_FAST 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); } //--- Eliminamos igualmente mapa_1_key.Remove(ticket_i); mapa_2_key.Remove(ticket_extra); #else 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); mapa_1_key.Remove(ticket_i); mapa_2_key.Remove(ticket_extra); return; 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); mapa_1_key.Remove(ticket_i); mapa_2_key.Remove(ticket_extra); return; case ORDER_STATE_FILLED: { LogInfo(StringFormat("OCO ejecutado: orden %I64u completada, cancelando contraria %I64u", ticket_i, ticket_extra), FUNCION_ACTUAL); mapa_1_key.Remove(ticket_i); mapa_2_key.Remove(ticket_extra); ResetLastError(); if(!trade.OrderDelete(ticket_extra)) LogError(StringFormat("Fallo al cancelar orden contraria %I64u. Error: %d", ticket_extra, GetLastError()), FUNCION_ACTUAL); return; } default: LogWarning(StringFormat("Estado desconocido en orden %I64u: %d", ticket_i, order.order_state), FUNCION_ACTUAL); mapa_1_key.Remove(ticket_i); mapa_2_key.Remove(ticket_extra); return; } #endif } else if(mapa_2_key.TryGetValue(ticket_i, ticket_extra)) //TICKET 2 SE CIERRA { #ifdef OCO_ORDER_MORE_FAST 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); } //--- Eliminamos igualmente mapa_2_key.Remove(ticket_i); mapa_1_key.Remove(ticket_extra); #else 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); mapa_2_key.Remove(ticket_i); mapa_1_key.Remove(ticket_extra); return; 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); mapa_2_key.Remove(ticket_i); mapa_1_key.Remove(ticket_extra); return; 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); mapa_2_key.Remove(ticket_i); mapa_1_key.Remove(ticket_extra); ResetLastError(); if(!trade.OrderDelete(ticket_extra)) LogError(StringFormat("Fallo al cancelar orden contraria %I64u. Error: %d", ticket_extra, GetLastError()), FUNCION_ACTUAL); return; } default: LogWarning(StringFormat("Estado desconocido en orden %I64u: %d", ticket_i, order.order_state), FUNCION_ACTUAL); mapa_2_key.Remove(ticket_i); mapa_1_key.Remove(ticket_extra); return; } #endif } } //+------------------------------------------------------------------+ #endif