forked from airat77786/MQL5Book
228 lines
7.5 KiB
MQL5
228 lines
7.5 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| TradeState.mqh |
| |||
//| Copyright 2022, MetaQuotes Ltd. |
| |||
//| https://www.mql5.com |
| |||
//+------------------------------------------------------------------+
| |||
#include "TradeState.mqh"
| |||
#include "PositionFilter.mqh"
| |||
#include "OrderFilter.mqh"
| |||
#include "DealFilter.mqh"
| |||
#include "AutoPtr.mqh"
| |||
#include "ArrayUtils.mqh"
| |||
#include "TypeName.mqh"
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Base class with array of objects storing trade properties for |
| |||
//| orders/deals/positions. |
| |||
//| T should be a TradeState class, F should be a TradeFilter, |
| |||
//| E is a property with identifier (usually SOME_TICKET) from ENUM |
| |||
//+------------------------------------------------------------------+
| |||
template<typename T,typename F,typename E>
| |||
class TradeCache
| |||
{
| |||
AutoPtr<T> data[];
| |||
const E property;
| |||
const int NOT_FOUND_ERROR;
| |||
| |||
static bool isNull(const AutoPtr<T> &element)
| |||
{
| |||
return element[] == NULL;
| |||
}
| |||
| |||
public:
| |||
TradeCache(const E id, const int error): property(id), NOT_FOUND_ERROR(error) { }
| |||
| |||
virtual string rtti() const
| |||
{
| |||
return TYPENAME(this);
| |||
}
| |||
| |||
virtual void onAdded(const T &state)
| |||
{
| |||
Print(rtti(), " added: ", state.get(property));
| |||
#ifdef PRINT_DETAILS
| |||
state.print();
| |||
#endif
| |||
}
| |||
| |||
virtual bool onRemoved(const T &state)
| |||
{
| |||
Print(rtti(), " removed: ", state.get(property));
| |||
return true; // please, wipe out the reference (false would keep it)
| |||
}
| |||
| |||
virtual void onUpdated(T &state, const int &changes[], const bool unexpected = false)
| |||
{
| |||
const ulong ticket = state.get(property);
| |||
Print(rtti(), " changed: ", ticket, (unexpected ? " unexpectedly" : ""));
| |||
for(int k = 0; k < ArraySize(changes); ++k)
| |||
{
| |||
switch(T::TradeState::type(changes[k]))
| |||
{
| |||
case PROP_TYPE_INTEGER:
| |||
Print(T::label(changes[k]), ": ",
| |||
state.stringify(changes[k]), " -> ",
| |||
state.stringifyRaw(changes[k]));
| |||
break;
| |||
case PROP_TYPE_DOUBLE:
| |||
Print(T::label(changes[k]), ": ",
| |||
state.stringify(changes[k]), " -> ",
| |||
state.stringifyRaw(changes[k]));
| |||
break;
| |||
case PROP_TYPE_STRING:
| |||
Print(T::label(changes[k]), ": ",
| |||
state.stringify(changes[k]), " -> ",
| |||
state.stringifyRaw(changes[k]));
| |||
break;
| |||
}
| |||
}
| |||
}
| |||
| |||
int size() const
| |||
{
| |||
return ArraySize(data);
| |||
}
| |||
| |||
T *operator[](int i) const
| |||
{
| |||
return data[i][]; // return pure pointer (T*) from inside AutoPtr object
| |||
}
| |||
| |||
void reset()
| |||
{
| |||
ArrayResize(data, 0); // this will call destructors of objects on pointers
| |||
}
| |||
| |||
void scan(F &f)
| |||
{
| |||
const int existedBefore = ArraySize(data);
| |||
| |||
ulong tickets[];
| |||
ArrayResize(tickets, existedBefore);
| |||
for(int i = 0; i < existedBefore; ++i)
| |||
{
| |||
tickets[i] = data[i][].get(property);
| |||
}
| |||
| |||
ulong objects[];
| |||
f.select(objects);
| |||
// if(_LastError != 0) Print(rtti(), " scan error: ", E2S(_LastError));
| |||
for(int i = 0, j; i < ArraySize(objects); ++i)
| |||
{
| |||
const ulong ticket = objects[i];
| |||
for(j = 0; j < existedBefore; ++j)
| |||
{
| |||
if(tickets[j] == ticket)
| |||
{
| |||
tickets[j] = 0; // mark as found
| |||
break;
| |||
}
| |||
}
| |||
| |||
if(j == existedBefore) // not found
| |||
{
| |||
const T *ptr = new T(ticket);
| |||
PUSH(data, ptr);
| |||
onAdded(*ptr);
| |||
}
| |||
else
| |||
{
| |||
ResetLastError();
| |||
int changes[];
| |||
if(data[j][].getChanges(changes))
| |||
{
| |||
onUpdated(data[j][], changes);
| |||
data[j][].update();
| |||
}
| |||
if(_LastError)
| |||
{
| |||
PrintFormat("%s: %lld (%s)", rtti(), ticket, E2S(_LastError));
| |||
if(_LastError == NOT_FOUND_ERROR) // for example, ERR_TRADE_POSITION_NOT_FOUND
| |||
{
| |||
if(onRemoved(data[j][]))
| |||
{
| |||
data[j] = NULL; // free up the object and array element
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
for(int j = 0; j < existedBefore; ++j)
| |||
{
| |||
if(tickets[j] == 0) continue; // skip already processed element
| |||
| |||
// this ticket was not found anymore, most likely deleted
| |||
int changes[];
| |||
ResetLastError();
| |||
if(data[j][].getChanges(changes))
| |||
{
| |||
if(_LastError == NOT_FOUND_ERROR) // for example, ERR_TRADE_POSITION_NOT_FOUND
| |||
{
| |||
if(onRemoved(data[j][]))
| |||
{
| |||
data[j] = NULL; // free up the object and array element
| |||
}
| |||
continue;
| |||
}
| |||
| |||
// NB! We should not normally fall down here
| |||
PrintFormat("Unexpected ticket: %lld (%s) %s", tickets[j], E2S(_LastError), rtti());
| |||
onUpdated(data[j][], changes, true);
| |||
data[j][].update();
| |||
}
| |||
else
| |||
{
| |||
#ifdef PRINT_DETAILS
| |||
PrintFormat("Orphaned element: %lld (%s) %s", tickets[j], E2S(_LastError), rtti());
| |||
#endif
| |||
}
| |||
}
| |||
| |||
ArrayPurger<AutoPtr<T>> p(data, isNull);
| |||
}
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Concrete class with array of positions and their properties |
| |||
//+------------------------------------------------------------------+
| |||
class PositionCache: public TradeCache<PositionState,PositionFilter,ENUM_POSITION_PROPERTY_INTEGER>
| |||
{
| |||
public:
| |||
PositionCache(const ENUM_POSITION_PROPERTY_INTEGER selector = POSITION_TICKET,
| |||
const int error = ERR_TRADE_POSITION_NOT_FOUND): TradeCache(selector, error) { }
| |||
virtual string rtti() const override
| |||
{
| |||
return TYPENAME(this);
| |||
}
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Concrete class with array of active orders and their properties |
| |||
//+------------------------------------------------------------------+
| |||
class OrderCache: public TradeCache<OrderState,OrderFilter,ENUM_ORDER_PROPERTY_INTEGER>
| |||
{
| |||
public:
| |||
OrderCache(const ENUM_ORDER_PROPERTY_INTEGER selector = ORDER_TICKET,
| |||
const int error = ERR_TRADE_ORDER_NOT_FOUND): TradeCache(selector, error) { }
| |||
virtual string rtti() const override
| |||
{
| |||
return TYPENAME(this);
| |||
}
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Concrete class with array of history orders and their properties |
| |||
//+------------------------------------------------------------------+
| |||
class HistoryOrderCache: public TradeCache<HistoryOrderState,HistoryOrderFilter,ENUM_ORDER_PROPERTY_INTEGER>
| |||
{
| |||
public:
| |||
HistoryOrderCache(const ENUM_ORDER_PROPERTY_INTEGER selector = ORDER_TICKET,
| |||
const int error = ERR_TRADE_ORDER_NOT_FOUND): TradeCache(selector, error) { }
| |||
virtual string rtti() const override
| |||
{
| |||
return TYPENAME(this);
| |||
}
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
|