164 lines
5.8 KiB
MQL5
164 lines
5.8 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| CSyntheticEngine.mqh |
|
|
//| Synthetic Test Harness for MetaTrader 5 |
|
|
//| Institutional Architecture in MQL5 - Part 1 Series |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef SYNTHETIC_TEST_HARNESS_CSYNTHETIC_ENGINE_MQH
|
|
#define SYNTHETIC_TEST_HARNESS_CSYNTHETIC_ENGINE_MQH
|
|
|
|
#include "IMarketEnvironment.mqh"
|
|
#include "ITickProvider.mqh"
|
|
#include "ErrorCodes.mqh"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CSyntheticEngine: the simulation motor. |
|
|
//| |
|
|
//| Implements IMarketEnvironment by delegating price/time queries |
|
|
//| to the current MockTick loaded from an injected ITickProvider. |
|
|
//| The engine has ZERO calls to native MT5 global functions - |
|
|
//| this is ASR-1 (testability without a live terminal). |
|
|
//| |
|
|
//| Ownership: non-owning reference to ITickProvider. The caller |
|
|
//| (test script) creates the provider, injects it, and destroys it. |
|
|
//| |
|
|
//| Lifecycle: |
|
|
//| 1. Construct with a provider pointer |
|
|
//| 2. Loop: ProcessNextTick() -> query Get*() / TryExecuteOrder() |
|
|
//| 3. ProcessNextTick() returns false when data is exhausted |
|
|
//+------------------------------------------------------------------+
|
|
class CSyntheticEngine : public IMarketEnvironment
|
|
{
|
|
private:
|
|
ITickProvider *m_provider;
|
|
MockTick m_current_tick;
|
|
bool m_tick_loaded;
|
|
int m_ticks_processed;
|
|
|
|
public:
|
|
//+---------------------------------------------------------------+
|
|
//| Constructor: receives the tick provider via Dependency |
|
|
//| Injection. Does NOT take ownership of the pointer. |
|
|
//+---------------------------------------------------------------+
|
|
CSyntheticEngine(ITickProvider *provider)
|
|
{
|
|
m_provider = provider;
|
|
m_tick_loaded = false;
|
|
m_ticks_processed = 0;
|
|
ZeroMemory(m_current_tick);
|
|
}
|
|
|
|
//+---------------------------------------------------------------+
|
|
//| Destructor: nullifies the non-owning pointer. Does NOT delete. |
|
|
//+---------------------------------------------------------------+
|
|
~CSyntheticEngine(void)
|
|
{
|
|
m_provider = NULL;
|
|
}
|
|
|
|
//+---------------------------------------------------------------+
|
|
//| ProcessNextTick: advances the simulation by one tick. |
|
|
//| Returns false when all ticks are exhausted or the provider |
|
|
//| is NULL. |
|
|
//+---------------------------------------------------------------+
|
|
bool ProcessNextTick(void)
|
|
{
|
|
if(m_provider == NULL)
|
|
return(false);
|
|
|
|
if(!m_provider.HasNext())
|
|
return(false);
|
|
|
|
m_tick_loaded = m_provider.GetNextTick(m_current_tick);
|
|
|
|
if(m_tick_loaded)
|
|
m_ticks_processed++;
|
|
|
|
return(m_tick_loaded);
|
|
}
|
|
|
|
//--- IMarketEnvironment: price queries delegate to current tick --
|
|
|
|
double GetBid(void)
|
|
{
|
|
return(m_current_tick.bid);
|
|
}
|
|
|
|
double GetAsk(void)
|
|
{
|
|
return(m_current_tick.ask);
|
|
}
|
|
|
|
double GetSpread(void)
|
|
{
|
|
return(m_current_tick.spread_points);
|
|
}
|
|
|
|
//--- IMarketEnvironment: time abstraction -------------------------
|
|
|
|
datetime GetTimeCurrent(void)
|
|
{
|
|
return(m_current_tick.time);
|
|
}
|
|
|
|
//+---------------------------------------------------------------+
|
|
//| TryExecuteOrder: simulates order execution with error |
|
|
//| injection from the current tick. |
|
|
//| |
|
|
//| Uses the same MqlTradeRequest/MqlTradeResult structures as |
|
|
//| the native OrderSend(). This ensures test code and production |
|
|
//| code use identical call patterns — no signature mismatch. |
|
|
//| |
|
|
//| Behavior: |
|
|
//| - tick not loaded -> fails with ERR_OFF_QUOTES |
|
|
//| - injected_error != 0 -> fails with that error code |
|
|
//| - injected_error == 0 -> succeeds with TRADE_RETCODE_DONE |
|
|
//+---------------------------------------------------------------+
|
|
bool TryExecuteOrder(MqlTradeRequest &request, MqlTradeResult &result)
|
|
{
|
|
ZeroMemory(result);
|
|
|
|
//--- Guard: no tick has been loaded yet
|
|
if(!m_tick_loaded)
|
|
{
|
|
result.retcode = SYNTH_ERR_OFF_QUOTES;
|
|
return(false);
|
|
}
|
|
|
|
//--- Check for injected chaos
|
|
if(IsExecutionError(m_current_tick.injected_error))
|
|
{
|
|
result.retcode = (uint)m_current_tick.injected_error;
|
|
return(false);
|
|
}
|
|
|
|
//--- Simulate a successful fill with realistic field values
|
|
result.retcode = TRADE_RETCODE_DONE;
|
|
result.volume = request.volume;
|
|
result.bid = m_current_tick.bid;
|
|
result.ask = m_current_tick.ask;
|
|
|
|
//--- Fill execution price based on order direction
|
|
if(request.type == ORDER_TYPE_BUY)
|
|
result.price = m_current_tick.ask;
|
|
else
|
|
result.price = m_current_tick.bid;
|
|
|
|
return(true);
|
|
}
|
|
|
|
//--- Diagnostic accessors (not part of IMarketEnvironment) -------
|
|
|
|
int GetTicksProcessed(void)
|
|
{
|
|
return(m_ticks_processed);
|
|
}
|
|
|
|
bool IsTickLoaded(void)
|
|
{
|
|
return(m_tick_loaded);
|
|
}
|
|
};
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
#endif
|