MQL5-SyntheticTestHarness/MQL5/Include/SyntheticTestHarness/MockPositionStore.mqh

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
//+------------------------------------------------------------------+