MQL5-SyntheticTestHarness/MQL5/Scripts/SyntheticTestHarness/TestBehaviorInjection.mq5

233 lines
8.7 KiB
MQL5

//+------------------------------------------------------------------+
//| TestBehaviorInjection.mq5 |
//| Synthetic Test Harness for MetaTrader 5 |
//| Institutional Architecture in MQL5 - Part 1 Series |
//+------------------------------------------------------------------+
#property copyright "Institutional Architecture in MQL5 Series"
#property version "1.00"
#property description "Synthetic test: Dynamic Behavior Injection for Chaos Engineering"
#property description "Validates function pointer delegates, stateful requotes, and on-the-fly fault switching."
#property script_show_inputs
#property strict
#include <SyntheticTestHarness\CSyntheticEngine.mqh>
#include <SyntheticTestHarness\BehaviorLibrary.mqh>
#include <SyntheticTestHarness\ErrorCodes.mqh>
#include <SyntheticTestHarness\CAssert.mqh>
//+------------------------------------------------------------------+
//| Helper: Build minimal BUY trade request |
//+------------------------------------------------------------------+
void BuildTestBuyRequest(MqlTradeRequest &request, const double volume = 0.10)
{
ZeroMemory(request);
request.action = TRADE_ACTION_DEAL;
request.type = ORDER_TYPE_BUY;
request.volume = volume;
request.price = 1.10000;
request.symbol = "EURUSD";
}
//+------------------------------------------------------------------+
//| Custom Behavior Mock Functions for Price & DOM Testing |
//+------------------------------------------------------------------+
double CustomSpreadSpikeBehavior(void)
{
return(250.0); // Custom 250 point spread injection
}
bool CustomMarketBookBehavior(const string symbol, MqlBookInfo &book[])
{
ArrayResize(book, 2);
book[0].type = BOOK_TYPE_BUY;
book[0].price = 1.10000;
book[0].volume = 100;
book[1].type = BOOK_TYPE_SELL;
book[1].price = 1.10025;
book[1].volume = 150;
return(true);
}
//+------------------------------------------------------------------+
//| TEST 1: Default Behavior Without Injection or Ticks |
//| Pattern: Arrange -> Act -> Assert |
//+------------------------------------------------------------------+
void TestDefaultEnvironmentState(void)
{
Log("--- Test 1: Default Environment State ---");
// [Arrange]
CSyntheticEngine engine; // Standalone engine without tick provider
// [Act & Assert]
AssertDoubleEqual("Default Bid price is 1.10000", 1.10000, engine.GetBid());
AssertDoubleEqual("Default Ask price is 1.10020", 1.10020, engine.GetAsk());
AssertDoubleEqual("Default Spread is 20 points", 20.0, engine.GetSpread());
MqlBookInfo book[];
AssertFalse("Default MarketBookGet returns false", engine.MarketBookGet("EURUSD", book));
}
//+------------------------------------------------------------------+
//| TEST 2: Stateful Requote Behavior Cycle (Behavior_RequoteTwice) |
//| Pattern: Arrange -> Act -> Assert |
//| Verifies: Call 1 & 2 return REQUOTE, Call 3 returns DONE. |
//+------------------------------------------------------------------+
void TestStatefulRequoteBehavior(void)
{
Log("--- Test 2: Stateful Requote Twice Behavior ---");
// [Arrange]
CSyntheticEngine engine;
engine.MockOrderSend(Behavior_RequoteTwice); // Inject stateful behavior
MqlTradeRequest request;
MqlTradeResult result;
BuildTestBuyRequest(request, 1.0);
// [Act 1: First Attempt]
bool success1 = engine.OrderSend(request, result);
// [Assert 1]
AssertFalse("Attempt 1 fails due to requote", success1);
AssertUintEqual("Attempt 1 retcode is TRADE_RETCODE_REQUOTE",
SYNTH_ERR_REQUOTE, result.retcode);
// [Act 2: Second Attempt]
bool success2 = engine.OrderSend(request, result);
// [Assert 2]
AssertFalse("Attempt 2 fails due to requote", success2);
AssertUintEqual("Attempt 2 retcode is TRADE_RETCODE_REQUOTE",
SYNTH_ERR_REQUOTE, result.retcode);
// [Act 3: Third Attempt]
bool success3 = engine.OrderSend(request, result);
// [Assert 3]
AssertTrue("Attempt 3 succeeds after retry", success3);
AssertUintEqual("Attempt 3 retcode is TRADE_RETCODE_DONE",
TRADE_RETCODE_DONE, result.retcode);
AssertDoubleEqual("Filled volume matches request", 1.0, result.volume);
}
//+------------------------------------------------------------------+
//| TEST 3: On-the-fly Hot Swapping of Injected Behaviors |
//| Pattern: Arrange -> Act -> Assert |
//| Verifies: Switching from Requote to Fatal Disconnect mid-flight. |
//+------------------------------------------------------------------+
void TestHotSwappingBehavior(void)
{
Log("--- Test 3: Hot-Swapping Behavior to Fatal Disconnect ---");
// [Arrange]
CSyntheticEngine engine;
engine.MockOrderSend(Behavior_RequoteTwice);
MqlTradeRequest request;
MqlTradeResult result;
BuildTestBuyRequest(request, 0.5);
// Consume one requote
engine.OrderSend(request, result);
AssertUintEqual("Initial behavior is REQUOTE", SYNTH_ERR_REQUOTE, result.retcode);
// [Act: Hot-Swap Behavior Dynamically]
engine.MockOrderSend(Behavior_FatalDisconnect);
bool success = engine.OrderSend(request, result);
// [Assert]
AssertFalse("Fatal disconnect causes immediate failure", success);
AssertUintEqual("Retcode reflects TRADE_RETCODE_CONNECTION",
TRADE_RETCODE_CONNECTION, result.retcode);
}
//+------------------------------------------------------------------+
//| TEST 4: Behavior Resetting (Reversion to Default Engine State) |
//| Pattern: Arrange -> Act -> Assert |
//+------------------------------------------------------------------+
void TestBehaviorReset(void)
{
Log("--- Test 4: Resetting Injected Behaviors ---");
// [Arrange]
CSyntheticEngine engine;
engine.MockOrderSend(Behavior_FatalDisconnect);
MqlTradeRequest request;
MqlTradeResult result;
BuildTestBuyRequest(request, 0.1);
// Verify injected error is active
engine.OrderSend(request, result);
AssertUintEqual("Injected disconnect is active", TRADE_RETCODE_CONNECTION, result.retcode);
// [Act: Reset Behavior]
engine.ResetOrderSend();
// [Assert: Engine reverts to default tickless guard]
bool success = engine.OrderSend(request, result);
AssertFalse("Default without loaded tick returns SYNTH_ERR_PRICE_OFF", success);
AssertUintEqual("Retcode is SYNTH_ERR_PRICE_OFF", SYNTH_ERR_PRICE_OFF, result.retcode);
}
//+------------------------------------------------------------------+
//| TEST 5: Custom Price & DOM Behavior Injection |
//| Pattern: Arrange -> Act -> Assert |
//+------------------------------------------------------------------+
void TestCustomPriceAndDOMInjection(void)
{
Log("--- Test 5: Custom Price & DOM Behavior Injection ---");
// [Arrange]
CSyntheticEngine engine;
engine.MockGetSpread(CustomSpreadSpikeBehavior);
engine.MockMarketBookGet(CustomMarketBookBehavior);
// [Act & Assert: Price Behavior]
AssertDoubleEqual("Custom injected spread is 250 points", 250.0, engine.GetSpread());
// [Act & Assert: DOM Behavior]
MqlBookInfo book[];
bool has_book = engine.MarketBookGet("EURUSD", book);
AssertTrue("Custom MarketBookGet succeeds", has_book);
AssertIntEqual("Custom book has 2 entries", 2, ArraySize(book));
AssertDoubleEqual("Book best bid is 1.10000", 1.10000, book[0].price);
AssertDoubleEqual("Book best ask is 1.10025", 1.10025, book[1].price);
// [Reset]
engine.ResetAllBehaviors();
AssertDoubleEqual("Reset restores default spread of 20 points", 20.0, engine.GetSpread());
}
//+------------------------------------------------------------------+
//| Script Program Start Function |
//+------------------------------------------------------------------+
void OnStart(void)
{
CAssert::Reset();
Log("==========================================================");
Log(" SYNTHETIC TEST HARNESS - Behavior Injection Runner ");
Log(" Institutional Architecture in MQL5 - Part 1 Upgrade ");
Log("==========================================================");
Log("");
// Execute test suite
TestDefaultEnvironmentState();
Log("");
TestStatefulRequoteBehavior();
Log("");
TestHotSwappingBehavior();
Log("");
TestBehaviorReset();
Log("");
TestCustomPriceAndDOMInjection();
Log("");
//--- Test Summary Output
CAssert::Summary("Behavior Injection Test Suite", true);
}
//+------------------------------------------------------------------+