MQLArticles/RM/OcoOrder.mqh
2025-09-22 09:09:20 -05:00

214 lines
16 KiB
MQL5

//+------------------------------------------------------------------+
//| 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<ulong, ulong> mapa_1_key;
CHashMap<ulong, ulong> 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