214 lines
16 KiB
MQL5
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
|
|
|