fast-ta/Tests/RollingMinMax.Script.Test.mq5

470 lines
12 KiB
MQL5

//+------------------------------------------------------------------+
//| RollingMinMax.Script.Test.mq5 |
//+------------------------------------------------------------------+
#property script_show_inputs
#include "..\\Algorithms\\RollingMin.mqh"
#include "..\\Algorithms\\RollingMax.mqh"
//+------------------------------------------------------------------+
//| Compare |
//+------------------------------------------------------------------+
bool Equal(const double left,
const double right)
{
return(MathAbs(left - right) < 1e-10);
}
//+------------------------------------------------------------------+
//| Brute Force Minimum |
//+------------------------------------------------------------------+
double BruteMin(const double &values[],
const int count)
{
double result = values[0];
for(int i = 1; i < count; i++)
result = MathMin(result, values[i]);
return(result);
}
//+------------------------------------------------------------------+
//| Brute Force Maximum |
//+------------------------------------------------------------------+
double BruteMax(const double &values[],
const int count)
{
double result = values[0];
for(int i = 1; i < count; i++)
result = MathMax(result, values[i]);
return(result);
}
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
Print("--- Starting RollingMinMax Isolated Test ---");
// 1. Test Initialization
CRollingMin rollingMin;
CRollingMax rollingMax;
if(rollingMin.Init(0) || rollingMax.Init(0))
{
Print("FAIL: Initialization succeeded with zero capacity.");
return;
}
if(!rollingMin.Init(3) || !rollingMax.Init(3))
{
Print("FAIL: Initialization failed.");
return;
}
if(rollingMin.Value() != 0.0 || rollingMax.Value() != 0.0)
{
Print("FAIL: Invalid initial state.");
return;
}
// 2. Test Warmup
rollingMin.Append(30.0);
rollingMax.Append(30.0);
if(rollingMin.Value() != 30.0 || rollingMax.Value() != 30.0)
{
Print("FAIL: First warmup value incorrect.");
return;
}
rollingMin.Append(10.0);
rollingMax.Append(10.0);
if(rollingMin.Value() != 10.0 || rollingMax.Value() != 30.0)
{
Print("FAIL: Second warmup value incorrect.");
return;
}
rollingMin.Append(20.0);
rollingMax.Append(20.0);
if(rollingMin.Value() != 10.0 || rollingMax.Value() != 30.0)
{
Print("FAIL: Full warmup window incorrect.");
return;
}
// 3. Test Domination
rollingMin.Reset();
rollingMax.Reset();
rollingMin.Append(30.0);
rollingMax.Append(10.0);
rollingMin.Append(20.0);
rollingMax.Append(20.0);
rollingMin.Append(10.0);
rollingMax.Append(30.0);
rollingMin.Append(25.0);
rollingMax.Append(15.0);
if(rollingMin.Value() != 10.0 || rollingMax.Value() != 30.0)
{
Print("FAIL: Monotonic domination handling incorrect.");
return;
}
// 4. Test Expiry Boundaries
rollingMin.Reset();
rollingMax.Reset();
rollingMin.Append(10.0);
rollingMax.Append(30.0);
rollingMin.Append(20.0);
rollingMax.Append(20.0);
rollingMin.Append(30.0);
rollingMax.Append(10.0);
if(rollingMin.Value() != 10.0 || rollingMax.Value() != 30.0)
{
Print("FAIL: Pre-expiry boundary incorrect.");
return;
}
rollingMin.Append(40.0);
rollingMax.Append(5.0);
if(rollingMin.Value() != 20.0 || rollingMax.Value() != 20.0)
{
Print("FAIL: First expiry boundary incorrect.");
return;
}
rollingMin.Append(50.0);
rollingMax.Append(4.0);
if(rollingMin.Value() != 30.0 || rollingMax.Value() != 10.0)
{
Print("FAIL: Second expiry boundary incorrect.");
return;
}
// 5. Test Repeated Arbitrary ReplaceLast
rollingMin.Reset();
rollingMax.Reset();
rollingMin.Append(10.0);
rollingMax.Append(10.0);
rollingMin.Append(20.0);
rollingMax.Append(20.0);
rollingMin.Append(30.0);
rollingMax.Append(30.0);
if(!rollingMin.ReplaceLast(5.0) ||
!rollingMax.ReplaceLast(5.0))
{
Print("FAIL: First ReplaceLast execution failed.");
return;
}
if(rollingMin.Value() != 5.0 || rollingMax.Value() != 20.0)
{
Print("FAIL: First ReplaceLast result incorrect.");
return;
}
rollingMin.ReplaceLast(40.0);
rollingMax.ReplaceLast(40.0);
if(rollingMin.Value() != 10.0 || rollingMax.Value() != 40.0)
{
Print("FAIL: Second ReplaceLast result incorrect.");
return;
}
rollingMin.ReplaceLast(15.0);
rollingMax.ReplaceLast(15.0);
if(rollingMin.Value() != 10.0 || rollingMax.Value() != 20.0)
{
Print("FAIL: Third ReplaceLast result incorrect.");
return;
}
rollingMin.ReplaceLast(-5.0);
rollingMax.ReplaceLast(50.0);
if(rollingMin.Value() != -5.0 || rollingMax.Value() != 50.0)
{
Print("FAIL: Fourth ReplaceLast result incorrect.");
return;
}
// 6. Test Next Append Commit After Replacement
rollingMin.Reset();
rollingMax.Reset();
rollingMin.Append(10.0);
rollingMax.Append(10.0);
rollingMin.Append(20.0);
rollingMax.Append(20.0);
rollingMin.Append(30.0);
rollingMax.Append(30.0);
rollingMin.ReplaceLast(5.0);
rollingMax.ReplaceLast(50.0);
rollingMin.Append(40.0);
rollingMax.Append(40.0);
if(rollingMin.Value() != 5.0 || rollingMax.Value() != 50.0)
{
Print("FAIL: Replaced live value was not committed on next Append.");
return;
}
rollingMin.Append(60.0);
rollingMax.Append(5.0);
if(rollingMin.Value() != 5.0 || rollingMax.Value() != 50.0)
{
Print("FAIL: Committed replacement state expired too early.");
return;
}
rollingMin.Append(70.0);
rollingMax.Append(4.0);
if(rollingMin.Value() != 40.0 || rollingMax.Value() != 40.0)
{
Print("FAIL: Committed replacement expiry incorrect.");
return;
}
// 7. Test Reset
rollingMin.Reset();
rollingMax.Reset();
if(rollingMin.Value() != 0.0 || rollingMax.Value() != 0.0)
{
Print("FAIL: Reset did not restore empty state.");
return;
}
if(rollingMin.ReplaceLast(10.0) ||
rollingMax.ReplaceLast(10.0))
{
Print("FAIL: ReplaceLast succeeded after Reset.");
return;
}
rollingMin.Append(7.0);
rollingMax.Append(7.0);
if(rollingMin.Value() != 7.0 || rollingMax.Value() != 7.0)
{
Print("FAIL: Reuse after Reset incorrect.");
return;
}
// 8. Test Period 1
CRollingMin periodOneMin;
CRollingMax periodOneMax;
if(!periodOneMin.Init(1) || !periodOneMax.Init(1))
{
Print("FAIL: Period 1 initialization failed.");
return;
}
periodOneMin.Append(10.0);
periodOneMax.Append(10.0);
if(periodOneMin.Value() != 10.0 || periodOneMax.Value() != 10.0)
{
Print("FAIL: Period 1 first value incorrect.");
return;
}
periodOneMin.ReplaceLast(20.0);
periodOneMax.ReplaceLast(20.0);
if(periodOneMin.Value() != 20.0 || periodOneMax.Value() != 20.0)
{
Print("FAIL: Period 1 ReplaceLast incorrect.");
return;
}
periodOneMin.Append(30.0);
periodOneMax.Append(30.0);
if(periodOneMin.Value() != 30.0 || periodOneMax.Value() != 30.0)
{
Print("FAIL: Period 1 Append incorrect.");
return;
}
// 9. Compare Against Brute-Force Window Min/Max
const int period = 5;
CRollingMin testMin;
CRollingMax testMax;
if(!testMin.Init(period) || !testMax.Init(period))
{
Print("FAIL: Brute-force comparison initialization failed.");
return;
}
double source[] =
{
12.0, -5.0, 8.0, 8.0, 30.0,
4.0, -10.0, 25.0, 7.0, 40.0,
3.0, 3.0, -20.0, 50.0, 1.0
};
double window[];
ArrayResize(window, period);
int windowCount = 0;
for(int i = 0; i < ArraySize(source); i++)
{
testMin.Append(source[i]);
testMax.Append(source[i]);
if(windowCount < period)
{
window[windowCount] = source[i];
++windowCount;
}
else
{
for(int j = 1; j < period; j++)
window[j - 1] = window[j];
window[period - 1] = source[i];
}
double expectedMin = BruteMin(window, windowCount);
double expectedMax = BruteMax(window, windowCount);
double actualMin = testMin.Value();
double actualMax = testMax.Value();
if(!Equal(actualMin, expectedMin) ||
!Equal(actualMax, expectedMax))
{
PrintFormat("FAIL: Brute-force mismatch at index %d. Expected Min=%.1f, Actual Min=%.1f, Expected Max=%.1f, Actual Max=%.1f",
i,
expectedMin,
actualMin,
expectedMax,
actualMax);
return;
}
}
// 10. Brute-Force Stress Test with Interleaved Append and ReplaceLast
int periods[] = {1, 2, 3, 5, 20};
MathSrand(123456);
for(int p = 0; p < ArraySize(periods); p++)
{
int period = periods[p];
CRollingMin stressMin;
CRollingMax stressMax;
if(!stressMin.Init(period) || !stressMax.Init(period))
{
PrintFormat("FAIL: Stress test initialization failed for period %d.", period);
return;
}
double window[];
ArrayResize(window, period);
int windowCount = 0;
for(int operation = 0; operation < 10000; operation++)
{
bool append = (windowCount == 0 || MathRand() % 3 == 0);
double value = (double)(MathRand() % 20001 - 10000) / 100.0;
if(append)
{
stressMin.Append(value);
stressMax.Append(value);
if(windowCount < period)
{
window[windowCount] = value;
++windowCount;
}
else
{
for(int i = 1; i < period; i++)
window[i - 1] = window[i];
window[period - 1] = value;
}
}
else
{
if(!stressMin.ReplaceLast(value) ||
!stressMax.ReplaceLast(value))
{
PrintFormat("FAIL: Stress ReplaceLast failed. Period=%d, Operation=%d",
period, operation);
return;
}
window[windowCount - 1] = value;
}
double expectedMin = BruteMin(window, windowCount);
double expectedMax = BruteMax(window, windowCount);
double actualMin = stressMin.Value();
double actualMax = stressMax.Value();
if(!Equal(actualMin, expectedMin) ||
!Equal(actualMax, expectedMax))
{
PrintFormat("FAIL: Stress mismatch. Period=%d, Operation=%d, Type=%s, Value=%.2f, Expected Min=%.2f, Actual Min=%.2f, Expected Max=%.2f, Actual Max=%.2f",
period,
operation,
append ? "Append" : "ReplaceLast",
value,
expectedMin,
actualMin,
expectedMax,
actualMax);
return;
}
}
PrintFormat("PASS: Stress test completed for period %d.", period);
}
Print("SUCCESS: All RollingMinMax isolated tests passed!");
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+