forked from nique_372/MQLArticles
241 lines
19 KiB
MQL5
241 lines
19 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 MQLARTICLES_RM_OCOORDER_MQH
| |
#define MQLARTICLES_RM_OCOORDER_MQH
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
#include "AccountStatus.mqh"
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
#define IsOrder(type) EsUnaOrderPendiente[type]
| |
// #define CACCOUNT_STATUS_ACTIVE_ON_ORDER_DELETE
| |
| |
//+------------------------------------------------------------------+
| |
//| Clase COcoOrder optimizada |
| |
//+------------------------------------------------------------------+
| |
class COcoOrder : public CAccountGestor
| |
{
| |
private:
| |
CHashMap<ulong, ulong> mapa_1_key;
| |
CHashMap<ulong, ulong> 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) == 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 ROrder &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 // OCO_ORDER_MORE_FAST
| |
| |
}
| |
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 // OCO_ORDER_MORE_FAST
| |
}
| |
}
| |
//+------------------------------------------------------------------+
| |
#endif // MQLARTICLES_RM_OCOORDER_MQH
| |
| |
//+------------------------------------------------------------------+
|