403 lines
13 KiB
MQL5
403 lines
13 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| MockPositionStore.mqh |
|
|
//| Synthetic Test Harness for MetaTrader 5 |
|
|
//| Institutional Architecture in MQL5 - Part 2 Series |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef SYNTHETIC_TEST_HARNESS_MOCK_POSITION_STORE_MQH
|
|
#define SYNTHETIC_TEST_HARNESS_MOCK_POSITION_STORE_MQH
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Mock data structure for an open position |
|
|
//+------------------------------------------------------------------+
|
|
struct SMockPosition
|
|
{
|
|
ulong ticket;
|
|
string symbol;
|
|
long magic;
|
|
ENUM_POSITION_TYPE type;
|
|
double volume;
|
|
double price_open;
|
|
double price_current;
|
|
double sl;
|
|
double tp;
|
|
double profit;
|
|
double swap;
|
|
datetime time_open;
|
|
string comment;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Mock data structure for a closed deal (history) |
|
|
//+------------------------------------------------------------------+
|
|
struct SMockDeal
|
|
{
|
|
ulong ticket;
|
|
ulong order;
|
|
string symbol;
|
|
long magic;
|
|
ENUM_DEAL_TYPE type;
|
|
ENUM_DEAL_ENTRY entry;
|
|
double volume;
|
|
double price;
|
|
double profit;
|
|
double commission;
|
|
double swap;
|
|
datetime time;
|
|
string comment;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Mock data structure for a pending order |
|
|
//+------------------------------------------------------------------+
|
|
struct SMockPendingOrder
|
|
{
|
|
ulong ticket;
|
|
string symbol;
|
|
long magic;
|
|
ENUM_ORDER_TYPE type;
|
|
ENUM_ORDER_STATE state;
|
|
double volume_initial;
|
|
double volume_current;
|
|
double price_open;
|
|
double sl;
|
|
double tp;
|
|
datetime time_setup;
|
|
string comment;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CMockPositionStore: In-memory store for synthetic account state |
|
|
//| |
|
|
//| Single Responsibility: Decoupled container managing synthetic |
|
|
//| positions, orders, and historical deals for unit testing without |
|
|
//| coupling to terminal state or CSyntheticEngine tick simulation. |
|
|
//+------------------------------------------------------------------+
|
|
class CMockPositionStore
|
|
{
|
|
private:
|
|
SMockPosition m_positions[];
|
|
SMockDeal m_deals[];
|
|
SMockPendingOrder m_orders[];
|
|
|
|
int m_selected_position_idx;
|
|
ulong m_selected_deal_ticket;
|
|
ulong m_selected_order_ticket;
|
|
bool m_history_selected;
|
|
datetime m_history_from;
|
|
datetime m_history_to;
|
|
|
|
public:
|
|
//+---------------------------------------------------------------+
|
|
//| Constructor & Destructor |
|
|
//+---------------------------------------------------------------+
|
|
CMockPositionStore(void)
|
|
{
|
|
m_selected_position_idx = -1;
|
|
m_selected_deal_ticket = 0;
|
|
m_selected_order_ticket = 0;
|
|
m_history_selected = false;
|
|
m_history_from = 0;
|
|
m_history_to = 0;
|
|
Clear();
|
|
}
|
|
|
|
~CMockPositionStore(void)
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
//+---------------------------------------------------------------+
|
|
//| Clear all mock storage data |
|
|
//+---------------------------------------------------------------+
|
|
void Clear(void)
|
|
{
|
|
ArrayFree(m_positions);
|
|
ArrayFree(m_deals);
|
|
ArrayFree(m_orders);
|
|
m_selected_position_idx = -1;
|
|
m_selected_deal_ticket = 0;
|
|
m_selected_order_ticket = 0;
|
|
m_history_selected = false;
|
|
}
|
|
|
|
//+---------------------------------------------------------------+
|
|
//| Position Setup Helpers (Used by Unit Tests) |
|
|
//+---------------------------------------------------------------+
|
|
void AddPosition(const SMockPosition &pos)
|
|
{
|
|
int size = ArraySize(m_positions);
|
|
ArrayResize(m_positions, size + 1);
|
|
m_positions[size] = pos;
|
|
}
|
|
|
|
bool RemovePosition(const ulong ticket)
|
|
{
|
|
int size = ArraySize(m_positions);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_positions[i].ticket == ticket)
|
|
{
|
|
for(int j = i; j < size - 1; j++)
|
|
m_positions[j] = m_positions[j + 1];
|
|
ArrayResize(m_positions, size - 1);
|
|
if(m_selected_position_idx >= ArraySize(m_positions))
|
|
m_selected_position_idx = -1;
|
|
return(true);
|
|
}
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
//+---------------------------------------------------------------+
|
|
//| Position Queries (Mirrors MT5 API) |
|
|
//+---------------------------------------------------------------+
|
|
int PositionsTotal(void) const
|
|
{
|
|
return(ArraySize(m_positions));
|
|
}
|
|
|
|
string PositionGetSymbol(const int index)
|
|
{
|
|
if(index < 0 || index >= ArraySize(m_positions))
|
|
{
|
|
m_selected_position_idx = -1;
|
|
return("");
|
|
}
|
|
m_selected_position_idx = index;
|
|
return(m_positions[index].symbol);
|
|
}
|
|
|
|
long PositionGetInteger(const ENUM_POSITION_PROPERTY_INTEGER property) const
|
|
{
|
|
if(m_selected_position_idx < 0 || m_selected_position_idx >= ArraySize(m_positions))
|
|
return(0);
|
|
|
|
switch(property)
|
|
{
|
|
case POSITION_TICKET: return((long)m_positions[m_selected_position_idx].ticket);
|
|
case POSITION_MAGIC: return(m_positions[m_selected_position_idx].magic);
|
|
case POSITION_TYPE: return((long)m_positions[m_selected_position_idx].type);
|
|
case POSITION_TIME: return((long)m_positions[m_selected_position_idx].time_open);
|
|
default: return(0);
|
|
}
|
|
}
|
|
|
|
double PositionGetDouble(const ENUM_POSITION_PROPERTY_DOUBLE property) const
|
|
{
|
|
if(m_selected_position_idx < 0 || m_selected_position_idx >= ArraySize(m_positions))
|
|
return(0.0);
|
|
|
|
switch(property)
|
|
{
|
|
case POSITION_VOLUME: return(m_positions[m_selected_position_idx].volume);
|
|
case POSITION_PRICE_OPEN: return(m_positions[m_selected_position_idx].price_open);
|
|
case POSITION_PRICE_CURRENT: return(m_positions[m_selected_position_idx].price_current);
|
|
case POSITION_SL: return(m_positions[m_selected_position_idx].sl);
|
|
case POSITION_TP: return(m_positions[m_selected_position_idx].tp);
|
|
case POSITION_PROFIT: return(m_positions[m_selected_position_idx].profit);
|
|
case POSITION_SWAP: return(m_positions[m_selected_position_idx].swap);
|
|
default: return(0.0);
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------+
|
|
//| Deal / History Setup Helpers (Used by Unit Tests) |
|
|
//+---------------------------------------------------------------+
|
|
void AddDeal(const SMockDeal &deal)
|
|
{
|
|
int size = ArraySize(m_deals);
|
|
ArrayResize(m_deals, size + 1);
|
|
m_deals[size] = deal;
|
|
}
|
|
|
|
bool HistorySelect(const datetime from_date, const datetime to_date)
|
|
{
|
|
m_history_selected = true;
|
|
m_history_from = from_date;
|
|
m_history_to = to_date;
|
|
return(true);
|
|
}
|
|
|
|
int HistoryDealsTotal(void) const
|
|
{
|
|
if(!m_history_selected)
|
|
return(0);
|
|
|
|
int count = 0;
|
|
int size = ArraySize(m_deals);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_deals[i].time >= m_history_from && m_deals[i].time <= m_history_to)
|
|
count++;
|
|
}
|
|
return(count);
|
|
}
|
|
|
|
ulong HistoryDealGetTicket(const int index)
|
|
{
|
|
if(!m_history_selected)
|
|
return(0);
|
|
|
|
int count = 0;
|
|
int size = ArraySize(m_deals);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_deals[i].time >= m_history_from && m_deals[i].time <= m_history_to)
|
|
{
|
|
if(count == index)
|
|
{
|
|
m_selected_deal_ticket = m_deals[i].ticket;
|
|
return(m_deals[i].ticket);
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
m_selected_deal_ticket = 0;
|
|
return(0);
|
|
}
|
|
|
|
long HistoryDealGetInteger(const ulong ticket, const ENUM_DEAL_PROPERTY_INTEGER property) const
|
|
{
|
|
int size = ArraySize(m_deals);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_deals[i].ticket == ticket)
|
|
{
|
|
switch(property)
|
|
{
|
|
case DEAL_TICKET: return((long)m_deals[i].ticket);
|
|
case DEAL_ORDER: return((long)m_deals[i].order);
|
|
case DEAL_TIME: return((long)m_deals[i].time);
|
|
case DEAL_TYPE: return((long)m_deals[i].type);
|
|
case DEAL_ENTRY: return((long)m_deals[i].entry);
|
|
case DEAL_MAGIC: return(m_deals[i].magic);
|
|
default: return(0);
|
|
}
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
double HistoryDealGetDouble(const ulong ticket, const ENUM_DEAL_PROPERTY_DOUBLE property) const
|
|
{
|
|
int size = ArraySize(m_deals);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_deals[i].ticket == ticket)
|
|
{
|
|
switch(property)
|
|
{
|
|
case DEAL_VOLUME: return(m_deals[i].volume);
|
|
case DEAL_PRICE: return(m_deals[i].price);
|
|
case DEAL_COMMISSION: return(m_deals[i].commission);
|
|
case DEAL_SWAP: return(m_deals[i].swap);
|
|
case DEAL_PROFIT: return(m_deals[i].profit);
|
|
default: return(0.0);
|
|
}
|
|
}
|
|
}
|
|
return(0.0);
|
|
}
|
|
|
|
string HistoryDealGetString(const ulong ticket, const ENUM_DEAL_PROPERTY_STRING property) const
|
|
{
|
|
int size = ArraySize(m_deals);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_deals[i].ticket == ticket)
|
|
{
|
|
switch(property)
|
|
{
|
|
case DEAL_SYMBOL: return(m_deals[i].symbol);
|
|
case DEAL_COMMENT: return(m_deals[i].comment);
|
|
default: return("");
|
|
}
|
|
}
|
|
}
|
|
return("");
|
|
}
|
|
|
|
//+---------------------------------------------------------------+
|
|
//| Pending Order Setup Helpers (Used by Unit Tests) |
|
|
//+---------------------------------------------------------------+
|
|
void AddOrder(const SMockPendingOrder &order)
|
|
{
|
|
int size = ArraySize(m_orders);
|
|
ArrayResize(m_orders, size + 1);
|
|
m_orders[size] = order;
|
|
}
|
|
|
|
bool RemoveOrder(const ulong ticket)
|
|
{
|
|
int size = ArraySize(m_orders);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_orders[i].ticket == ticket)
|
|
{
|
|
for(int j = i; j < size - 1; j++)
|
|
m_orders[j] = m_orders[j + 1];
|
|
ArrayResize(m_orders, size - 1);
|
|
return(true);
|
|
}
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
int OrdersTotal(void) const
|
|
{
|
|
return(ArraySize(m_orders));
|
|
}
|
|
|
|
ulong OrderGetTicket(const int index)
|
|
{
|
|
if(index < 0 || index >= ArraySize(m_orders))
|
|
{
|
|
m_selected_order_ticket = 0;
|
|
return(0);
|
|
}
|
|
m_selected_order_ticket = m_orders[index].ticket;
|
|
return(m_orders[index].ticket);
|
|
}
|
|
|
|
long OrderGetInteger(const ulong ticket, const ENUM_ORDER_PROPERTY_INTEGER property) const
|
|
{
|
|
int size = ArraySize(m_orders);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_orders[i].ticket == ticket)
|
|
{
|
|
switch(property)
|
|
{
|
|
case ORDER_TICKET: return((long)m_orders[i].ticket);
|
|
case ORDER_MAGIC: return(m_orders[i].magic);
|
|
case ORDER_TYPE: return((long)m_orders[i].type);
|
|
case ORDER_STATE: return((long)m_orders[i].state);
|
|
case ORDER_TIME_SETUP: return((long)m_orders[i].time_setup);
|
|
default: return(0);
|
|
}
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
string OrderGetString(const ulong ticket, const ENUM_ORDER_PROPERTY_STRING property) const
|
|
{
|
|
int size = ArraySize(m_orders);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
if(m_orders[i].ticket == ticket)
|
|
{
|
|
switch(property)
|
|
{
|
|
case ORDER_SYMBOL: return(m_orders[i].symbol);
|
|
case ORDER_COMMENT: return(m_orders[i].comment);
|
|
default: return("");
|
|
}
|
|
}
|
|
}
|
|
return("");
|
|
}
|
|
};
|
|
|
|
#endif
|
|
//+------------------------------------------------------------------+
|