fast-ta/Tests/RollingSumPipeline.Indicator.Test.mq5

240 lines
8.9 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| RollingSumPipeline.Indicator.Test |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_plots 0
#include "..\\Storage\\BarSeriesBuffer.mqh"
#include "..\\Algorithms\\RollingSum.mqh"
// Pipeline Component Objects
CBarSeriesBuffer g_inputBuffer;
CRollingSum g_rollingSum;
CBarSeriesBuffer g_outputBuffer;
const int TEST_CAPACITY = 5; // Low capacity to easily trigger full/outgoing states
bool g_resetTriggered = false;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
if(!g_inputBuffer.Init(TEST_CAPACITY) || !g_outputBuffer.Init(TEST_CAPACITY))
{
Print("FAIL: Pipeline buffer initialization failed.");
return(INIT_FAILED);
}
g_rollingSum.Reset();
g_resetTriggered = false;
PrintFormat("PASS: Pipeline initialized. Capacity: %d bars.", TEST_CAPACITY);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(rates_total < TEST_CAPACITY + 5)
{
PrintFormat("INFO: Insufficient data. Rates: %d, Required: %d", rates_total, TEST_CAPACITY + 5);
return(0);
}
// Main historical loop from limit up to rates_total - 2
const int limit = prev_calculated > 0 ? prev_calculated - 1 : 0;
for(int i = limit; i < rates_total - 1 && !_StopFlag; i++)
{
// --- PIPELINE STEP 1 & 2: Pre-Update Capture ---
double previousValue = 0;
bool hasPrevious = g_inputBuffer.At(0, previousValue);
double outgoingValue = 0;
bool isFullBeforeUpdate = g_inputBuffer.IsFull();
if(isFullBeforeUpdate)
{
g_inputBuffer.At(g_inputBuffer.Capacity() - 1, outgoingValue);
}
// --- PIPELINE STEP 3: Update Input Buffer ---
ESeriesEvent event = g_inputBuffer.Update(time[i], close[i]);
// --- PIPELINE STEPS 4 - 8: Event Driven State Machine ---
switch(event)
{
case SERIES_FIRST_SAMPLE:
// Step 4: FIRST_SAMPLE -> RollingSum.Update(value, 0) -> RollOver().
g_rollingSum.Update(close[i], 0.0);
g_rollingSum.RollOver();
if(prev_calculated == 0)
PrintFormat("PASS [Step 4]: FIRST_SAMPLE handled. Value added: %f. Sum: %f", close[i], g_rollingSum.Value());
break;
case SERIES_APPEND:
if(!isFullBeforeUpdate)
{
// Step 5: APPEND + not full before update -> RollingSum.Update(value, 0) -> RollOver().
g_rollingSum.Update(close[i], 0.0);
g_rollingSum.RollOver();
if(i <= 3 && prev_calculated == 0)
PrintFormat("PASS [Step 5]: APPEND (Not Full) at index %d. Added: %f. Sum: %f", i, close[i], g_rollingSum.Value());
}
else
{
// Step 6: APPEND + full before update -> RollingSum.Update(value, outgoing) -> RollOver().
g_rollingSum.Update(close[i], outgoingValue);
g_rollingSum.RollOver();
if(i == TEST_CAPACITY && prev_calculated == 0)
PrintFormat("PASS [Step 6]: APPEND (Full). Added: %f, Subtracted Outgoing: %f. Sum: %f", close[i], outgoingValue, g_rollingSum.Value());
}
break;
case SERIES_RESET:
// Step 8: RESET -> RollingSum.Reset() -> Update(value, 0) -> RollOver().
g_rollingSum.Reset();
g_rollingSum.Update(close[i], 0.0);
g_rollingSum.RollOver();
PrintFormat("PASS [Step 8]: RESET handled. Sum reset & anchored to: %f", g_rollingSum.Value());
break;
case SERIES_REPLACE_LAST:
// Fallback precaution for history iteration
break;
}
// --- PIPELINE STEPS 9 & 10: Propagate to Output Buffer ---
double finalSum = g_rollingSum.Value();
g_outputBuffer.Update(time[i], finalSum);
// --- OPTIONAL: Synthetic History Reset Simulation ---
if(i == 5 && prev_calculated == 0 && !g_resetTriggered)
{
Print("--- Executing Synthetic Pipeline Reset Verification ---");
g_resetTriggered = true;
// Trigger reset event explicitly by rewinding time
datetime syntheticPastTime = time[i] - 3600;
ESeriesEvent resetEvent = g_inputBuffer.Update(syntheticPastTime, close[i]);
if(resetEvent == SERIES_RESET)
{
g_rollingSum.Reset();
g_rollingSum.Update(close[i], 0.0);
g_rollingSum.RollOver();
g_outputBuffer.Update(syntheticPastTime, g_rollingSum.Value());
PrintFormat("PASS [Pipeline Reset]: Step 8 pipeline execution verified on synthetic time rewind.");
}
// Fast-forward re-align back to history stream
g_inputBuffer.Update(time[i], close[i]);
g_rollingSum.Update(close[i], 0.0);
g_rollingSum.RollOver();
}
}
// --- Live Market Bar Simulation & Tick Phase Verification (i == rates_total - 1) ---
if(!_StopFlag)
{
int liveIdx = rates_total - 1;
// -- LIVE TICK 1: Simulate standard incoming live bar or update --
double liveVal1 = close[liveIdx];
double prevVal1 = 0;
g_inputBuffer.At(0, prevVal1);
double outVal1 = 0;
bool full1 = g_inputBuffer.IsFull();
if(full1) g_inputBuffer.At(g_inputBuffer.Capacity() - 1, outVal1);
ESeriesEvent ev1 = g_inputBuffer.Update(time[liveIdx], liveVal1);
if(ev1 == SERIES_APPEND)
{
g_rollingSum.Update(liveVal1, full1 ? outVal1 : 0.0);
g_rollingSum.RollOver();
}
else if(ev1 == SERIES_REPLACE_LAST)
{
g_rollingSum.RollBack();
g_rollingSum.Update(liveVal1, prevVal1);
}
g_outputBuffer.Update(time[liveIdx], g_rollingSum.Value());
// -- LIVE TICK 2: Force continuous fluctuating market tick on same bar --
double liveVal2 = close[liveIdx] + 0.0025; // Continuous tick price change
// Step 1: Pre-Update Capture on Tick 2
double prevVal2 = 0;
bool hasPrev2 = g_inputBuffer.At(0, prevVal2); // Captures current live value before update
// Step 3: Call Update (Triggers same bar condition)
ESeriesEvent ev2 = g_inputBuffer.Update(time[liveIdx], liveVal2);
if(ev2 == SERIES_REPLACE_LAST)
{
// Step 7: REPLACE_LAST -> RollBack() -> RollingSum.Update(value, previous).
g_rollingSum.RollBack();
g_rollingSum.Update(liveVal2, prevVal2);
PrintFormat("PASS [Step 7]: REPLACE_LAST verified on live tick. Rollback successful. Previous: %f, New Tick: %f, New Sum: %f",
prevVal2, liveVal2, g_rollingSum.Value());
}
else
{
PrintFormat("FAIL: Expected SERIES_REPLACE_LAST on live tick update, got %d", ev2);
}
// Step 9 & 10: Read result and update output buffer
g_outputBuffer.Update(time[liveIdx], g_rollingSum.Value());
// -- FINAL STEP: Pipeline Integrity Cross-Verification Matrix --
if(prev_calculated == 0)
{
Print("--- Running Pipeline Verification Against Raw Array Math ---");
double manualSum = 0.0;
int barsToSum = MathMin(g_inputBuffer.Count(), TEST_CAPACITY);
// Calculate structural moving sum directly from current active window of live chart array
for(int k = 0; k < barsToSum; k++)
{
int arrayIndex = liveIdx - k;
// Compensate for liveTick 2 synthetic adjustments
manualSum += (k == 0) ? liveVal2 : close[arrayIndex];
}
double pipelineSum = g_rollingSum.Value();
double outputBufferSum = 0;
g_outputBuffer.At(0, outputBufferSum);
if(NormalizeDouble(pipelineSum - manualSum, _Digits) == 0 && NormalizeDouble(outputBufferSum - manualSum, _Digits) == 0)
{
PrintFormat("PASS [Steps 9 & 10]: Multi-stage rolling pipeline matches raw array math perfectly.");
PrintFormat("SUCCESS: Pipeline integration test passed! Value matching verified: %f", outputBufferSum);
}
else
{
PrintFormat("FAIL: Pipeline mismatch! Pipeline: %f, OutputBuffer: %f, Manual Array Sum: %f",
pipelineSum, outputBufferSum, manualSum);
}
}
}
return(rates_total);
}
//+------------------------------------------------------------------+