forked from rosh/MQL5Book
129 lines
4.4 KiB
MQL5
129 lines
4.4 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| TradeGuard.mqh |
| |
//| Copyright 2022, MetaQuotes Ltd. |
| |
//| https://www.mql5.com |
| |
//| Ensure orders (especially pending) are transformed into positions|
| |
//+------------------------------------------------------------------+
| |
#include "Defines.mqh"
| |
#include "TradeUtils.mqh"
| |
| |
//+------------------------------------------------------------------+
| |
//| Track given orders (tickets) until they're reflected in positions|
| |
//+------------------------------------------------------------------+
| |
class TradeGuard
| |
{
| |
ulong tickets[];
| |
public:
| |
void push(const ulong t) // add new order to monitor
| |
{
| |
if(!check(t)) return; // do not accept invalid ticket
| |
if(ArraySize(tickets) > 0)
| |
{
| |
const int i = ArrayBsearch(tickets, t);
| |
if(tickets[i] == t) return;
| |
}
| |
PUSH(tickets, t);
| |
ArraySort(tickets);
| |
}
| |
| |
int check(const ulong ticket) // order or position for given order must exist
| |
{
| |
static ulong prevmissing = 0;
| |
if(OrderSelect(ticket)) return 1;
| |
else if(HistoryOrderGetInteger(ticket, ORDER_TICKET) == ticket
| |
|| HistoryOrderSelect(ticket))
| |
{
| |
const ulong id = HistoryOrderGetInteger(ticket, ORDER_POSITION_ID);
| |
if(id > 0)
| |
{
| |
if(TU::PositionSelectById(id)) // existing position
| |
{
| |
if(prevmissing == ticket)
| |
{
| |
PrintFormat("Order found (position opened): %ld (%ld)", ticket, id);
| |
prevmissing = 0;
| |
}
| |
return 2;
| |
}
| |
else if(HistorySelectByPosition(id)) // position already closed
| |
{
| |
for(int i = 0; i < HistoryDealsTotal(); ++i)
| |
{
| |
const ulong deal = HistoryDealGetTicket(i);
| |
if(HistoryDealGetInteger(deal, DEAL_ORDER) == ticket)
| |
{
| |
if(prevmissing == ticket)
| |
{
| |
PrintFormat("Order found (position closed): %ld (%ld)", ticket, id);
| |
prevmissing = 0;
| |
}
| |
return 2;
| |
}
| |
}
| |
}
| |
}
| |
else
| |
{
| |
const ENUM_ORDER_STATE state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(ticket, ORDER_STATE);
| |
if(state == ORDER_STATE_CANCELED
| |
|| state == ORDER_STATE_REJECTED
| |
|| state == ORDER_STATE_EXPIRED)
| |
{
| |
PrintFormat("Order changed: %ld %s", ticket, EnumToString(state));
| |
return 2;
| |
}
| |
}
| |
}
| |
| |
// neither order (active or historical), nor position found - usynced state
| |
if(prevmissing != ticket)
| |
{
| |
Print("Order is missing: ", ticket);
| |
prevmissing = ticket;
| |
}
| |
return 0;
| |
}
| |
| |
ulong unsynced() // check all orders for existence, history record, or transformation into positions
| |
{
| |
for(int i = ArraySize(tickets) - 1; i >= 0; --i)
| |
{
| |
const int state = check(tickets[i]);
| |
if(!state) return tickets[i]; // a problem found, return missing order ticket
| |
else if(state == 2) // order is in history and bound to position or cancelled
| |
{
| |
ArrayRemove(tickets, i, 1);
| |
}
| |
}
| |
return 0; // all is in sync
| |
}
| |
| |
bool waitsync(const uint msc = 1000) // wait until all orders/positions found or timeout
| |
{
| |
const uint start = GetTickCount();
| |
bool result = false;
| |
while((result = unsynced()) && GetTickCount() - start < msc);
| |
return !result;
| |
}
| |
| |
void pop(ulong t = 0) // remove problematic order from the guard
| |
{
| |
if(!t) t = unsynced();
| |
if(t)
| |
{
| |
const int i = ArrayBsearch(tickets, t);
| |
if(tickets[i] == t)
| |
{
| |
Print("Order dropped: ", t);
| |
ArrayRemove(tickets, i, 1);
| |
return;
| |
}
| |
}
| |
}
| |
| |
void reset() // total clean up
| |
{
| |
ArrayResize(tickets, 0);
| |
}
| |
};
| |
//+------------------------------------------------------------------+
|