Dpo chart + refactored tests

This commit is contained in:
Miha Kralj
2024-11-07 09:55:19 -08:00
parent a0ffd952af
commit 69aef2b127
8 changed files with 416 additions and 864 deletions
+164 -159
View File
@@ -1,172 +1,112 @@
using Xunit;
using System.Security.Cryptography;
using System.Reflection;
#pragma warning disable S1944, S2053, S2222, S2259, S2583, S2589, S3329, S3655, S3900, S3949, S3966, S4158, S4347, S5773, S6781
namespace QuanTAlib;
namespace QuanTAlib.Tests;
public class EventingTests
{
[Fact]
public void EventBasedCalculations()
private const int TestDataPoints = 200;
private const int DefaultPeriod = 10;
private const double Tolerance = 1e-9;
private static readonly (string Name, object[] DirectParams, object[] EventParams)[] ValueIndicators = new[]
{
// Create a cryptographically secure random number generator
using var rng = RandomNumberGenerator.Create();
("Afirma", new object[] { DefaultPeriod, DefaultPeriod, Afirma.WindowType.BlackmanHarris }, new object[] { new TSeries(), DefaultPeriod, DefaultPeriod, Afirma.WindowType.BlackmanHarris }),
("Alma", new object[] { DefaultPeriod, 0.85, 6.0 }, new object[] { new TSeries(), DefaultPeriod, 0.85, 6.0 }),
("Convolution", new object[] { new double[] {1,2,3,2,1} }, new object[] { new TSeries(), new double[] {1,2,3,2,1} }),
("Dema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Dsma", new object[] { DefaultPeriod, 0.9 }, new object[] { new TSeries(), DefaultPeriod, 0.9 }),
("Dwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Ema", new object[] { DefaultPeriod, true }, new object[] { new TSeries(), DefaultPeriod, true }),
("Epma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Pwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Frama", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Fwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Gma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Hma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Htit", new object[] { }, new object[] { new TSeries() }),
("Hwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Jma", new object[] { DefaultPeriod, 0, 0.45, 10 }, new object[] { new TSeries(), DefaultPeriod, 0, 0.45, 10 }),
("Kama", new object[] { DefaultPeriod, 2, 30 }, new object[] { new TSeries(), DefaultPeriod, 2, 30 }),
("Ltma", new object[] { 0.2 }, new object[] { new TSeries(), 0.2 }),
("Maaf", new object[] { 39, 0.002 }, new object[] { new TSeries(), 39, 0.002 }),
("Mama", new object[] { 0.5, 0.05 }, new object[] { new TSeries(), 0.5, 0.05 }),
("Mgdi", new object[] { DefaultPeriod, 0.6 }, new object[] { new TSeries(), DefaultPeriod, 0.6 }),
("Mma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Qema", new object[] { 0.2, 0.2, 0.2, 0.2 }, new object[] { new TSeries(), 0.2, 0.2, 0.2, 0.2 }),
("Rema", new object[] { DefaultPeriod, 0.5 }, new object[] { new TSeries(), DefaultPeriod, 0.5 }),
("Rma", new object[] { DefaultPeriod, true }, new object[] { new TSeries(), DefaultPeriod, true }),
("Sma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Wma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Tema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Zlema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Sinema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Smma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("T3", new object[] { DefaultPeriod, 0.7, true }, new object[] { new TSeries(), DefaultPeriod, 0.7, true }),
("Trima", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Vidya", new object[] { DefaultPeriod, 0, 0.2 }, new object[] { new TSeries(), DefaultPeriod, 0, 0.2 }),
("Apo", new object[] { 12, 26 }, new object[] { new TSeries(), 12, 26 }),
("Macd", new object[] { 12, 26, 9 }, new object[] { new TSeries(), 12, 26, 9 }),
("Rsi", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Rsx", new object[] { DefaultPeriod, 0, 0.55 }, new object[] { new TSeries(), DefaultPeriod, 0, 0.55 }),
("Cmo", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Cog", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Curvature", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Entropy", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Kurtosis", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Max", new object[] { DefaultPeriod, 0.0 }, new object[] { new TSeries(), DefaultPeriod, 0.0 }),
("Median", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Min", new object[] { DefaultPeriod, 0.0 }, new object[] { new TSeries(), DefaultPeriod, 0.0 }),
("Mode", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Percentile", new object[] { DefaultPeriod, 0.5 }, new object[] { new TSeries(), DefaultPeriod, 0.5 }),
("Skew", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Slope", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Stddev", new object[] { DefaultPeriod, false }, new object[] { new TSeries(), DefaultPeriod, false }),
("Variance", new object[] { DefaultPeriod, false }, new object[] { new TSeries(), DefaultPeriod, false }),
("Zscore", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Beta", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Corr", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Hv", new object[] { DefaultPeriod, false }, new object[] { new TSeries(), DefaultPeriod, false }),
("Jvolty", new object[] { DefaultPeriod, 0 }, new object[] { new TSeries(), DefaultPeriod, 0 }),
("Rv", new object[] { DefaultPeriod, false }, new object[] { new TSeries(), DefaultPeriod, false }),
("Rvi", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Mae", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Mapd", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Mape", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Mase", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Mda", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Me", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Mpe", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Mse", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Msle", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Rae", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Rmse", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Rmsle", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Rse", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Smape", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Rsquared", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
("Huber", new object[] { DefaultPeriod, 1.0 }, new object[] { new TSeries(), DefaultPeriod, 1.0 })
};
// Create input series to hold our random values
var input = new TSeries();
var barInput = new TBarSeries();
int p = 10;
private static readonly (string Name, object[] DirectParams, object[] EventParams)[] BarIndicators = new[]
{
("Adl", new object[] { }, new object[] { new TBarSeries() }),
("Adosc", new object[] { 3, 10 }, new object[] { new TBarSeries(), 3, 10 }),
("Aobv", new object[] { }, new object[] { new TBarSeries() }),
("Cmf", new object[] { 20 }, new object[] { new TBarSeries(), 20 }),
("Eom", new object[] { 14 }, new object[] { new TBarSeries(), 14 }),
("Kvo", new object[] { 34, 55 }, new object[] { new TBarSeries(), 34, 55 }),
("Atr", new object[] { 14 }, new object[] { new TBarSeries(), 14 }),
("Chop", new object[] { 14 }, new object[] { new TBarSeries(), 14 }),
("Dosc", new object[] { }, new object[] { new TBarSeries() })
};
// Create a list of value-based indicator pairs
var valueIndicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)>
{
("Afirma", new Afirma(p,p,Afirma.WindowType.BlackmanHarris), new Afirma(input, p,p,Afirma.WindowType.BlackmanHarris)),
("Alma", new Alma(p), new Alma(input, p)),
("Convolution", new Convolution(new double[] {1,2,3,2,1}), new Convolution(input, new double[] {1,2,3,2,1})),
("Dema", new Dema(p), new Dema(input, p)),
("Dsma", new Dsma(p), new Dsma(input, p)),
("Dwma", new Dwma(p), new Dwma(input, p)),
("Ema", new Ema(p), new Ema(input, p)),
("Epma", new Epma(p), new Epma(input, p)),
("Pwma", new Pwma(p), new Pwma(input, p)),
("Frama", new Frama(p), new Frama(input, p)),
("Fwma", new Fwma(p), new Fwma(input, p)),
("Gma", new Gma(p), new Gma(input, p)),
("Hma", new Hma(p), new Hma(input, p)),
("Htit", new Htit(), new Htit(input)),
("Hwma", new Hwma(p), new Hwma(input, p)),
("Jma", new Jma(p), new Jma(input, p)),
("Kama", new Kama(p), new Kama(input, p)),
("Ltma", new Ltma(gamma: 0.2), new Ltma(input, gamma: 0.2)),
("Maaf", new Maaf(p), new Maaf(input, p)),
("Mama", new Mama(p), new Mama(input, p)),
("Mgdi", new Mgdi(p, kFactor: 0.6), new Mgdi(input, p, kFactor: 0.6)),
("Mma", new Mma(p), new Mma(input, p)),
("Qema", new Qema(k1: 0.2, k2: 0.2, k3: 0.2, k4: 0.2), new Qema(input, k1: 0.2, k2: 0.2, k3: 0.2, k4: 0.2)),
("Rema", new Rema(p), new Rema(input, p)),
("Rma", new Rma(p), new Rma(input, p)),
("Sma", new Sma(p), new Sma(input, p)),
("Wma", new Wma(p), new Wma(input, p)),
("Rma", new Rma(p), new Rma(input, p)),
("Tema", new Tema(p), new Tema(input, p)),
("Kama", new Kama(2, 30, 6), new Kama(input, 2, 30, 6)),
("Zlema", new Zlema(p), new Zlema(input, p)),
("Sinema", new Sinema(p), new Sinema(input, p)),
("Smma", new Smma(p), new Smma(input, p)),
("T3", new T3(p), new T3(input, p)),
("Trima", new Trima(p), new Trima(input, p)),
("Vidya", new Vidya(p), new Vidya(input, p)),
("Apo", new Apo(12, 26), new Apo(input, 12, 26)),
("Macd", new Macd(12, 26, 9), new Macd(input, 12, 26, 9)),
("Rsi", new Rsi(p), new Rsi(input, p)),
("Rsx", new Rsx(p), new Rsx(input, p)),
("Cmo", new Cmo(p), new Cmo(input, p)),
("Cog", new Cog(p), new Cog(input, p)),
("Curvature", new Curvature(p), new Curvature(input, p)),
("Entropy", new Entropy(p), new Entropy(input, p)),
("Kurtosis", new Kurtosis(p), new Kurtosis(input, p)),
("Max", new Max(p), new Max(input, p)),
("Median", new Median(p), new Median(input, p)),
("Min", new Min(p), new Min(input, p)),
("Mode", new Mode(p), new Mode(input, p)),
("Percentile", new Percentile(p, 0.5), new Percentile(input, p, 0.5)),
("Skew", new Skew(p), new Skew(input, p)),
("Slope", new Slope(p), new Slope(input, p)),
("Stddev", new Stddev(p), new Stddev(input, p)),
("Variance", new Variance(p), new Variance(input, p)),
("Zscore", new Zscore(p), new Zscore(input, p)),
("Beta", new Beta(p), new Beta(input, p)),
("Corr", new Corr(p), new Corr(input, p)),
// Volatility indicators (value-based)
("Hv", new Hv(p), new Hv(input, p)),
("Jvolty", new Jvolty(p), new Jvolty(input, p)),
("Rv", new Rv(p), new Rv(input, p)),
("Rvi", new Rvi(p), new Rvi(input, p)),
// Error classes
("Mae", new Mae(p), new Mae(input, p)),
("Mapd", new Mapd(p), new Mapd(input, p)),
("Mape", new Mape(p), new Mape(input, p)),
("Mase", new Mase(p), new Mase(input, p)),
("Mda", new Mda(p), new Mda(input, p)),
("Me", new Me(p), new Me(input, p)),
("Mpe", new Mpe(p), new Mpe(input, p)),
("Mse", new Mse(p), new Mse(input, p)),
("Msle", new Msle(p), new Msle(input, p)),
("Rae", new Rae(p), new Rae(input, p)),
("Rmse", new Rmse(p), new Rmse(input, p)),
("Rmsle", new Rmsle(p), new Rmsle(input, p)),
("Rse", new Rse(p), new Rse(input, p)),
("Smape", new Smape(p), new Smape(input, p)),
("Rsquared", new Rsquared(p), new Rsquared(input, p)),
("Huber", new Huber(p), new Huber(input, p))
};
public static IEnumerable<object[]> GetValueIndicatorData()
=> ValueIndicators.Select(x => new object[] { x.Name, x.DirectParams, x.EventParams });
// Create a list of bar-based indicator pairs
var barIndicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)>
{
// Volume indicators
("Adl", new Adl(), new Adl(barInput)),
("Adosc", new Adosc(3, 10), new Adosc(barInput, 3, 10)),
("Aobv", new Aobv(), new Aobv(barInput)),
("Cmf", new Cmf(20), new Cmf(barInput, 20)),
("Eom", new Eom(14), new Eom(barInput, 14)),
("Kvo", new Kvo(34, 55), new Kvo(barInput, 34, 55)),
// Volatility indicators (bar-based)
("Atr", new Atr(14), new Atr(barInput, 14)),
// Oscillators (bar-based)
("Chop", new Chop(14), new Chop(barInput, 14)),
("Dosc", new Dosc(), new Dosc(barInput))
};
// Generate 200 random values and feed them to indicators
for (int i = 0; i < 200; i++)
{
// Generate random value for value-based indicators
double randomValue = GetRandomDouble(rng) * 100;
input.Add(randomValue);
// Calculate value-based indicators
foreach (var (_, direct, _) in valueIndicators)
{
direct.Calc(randomValue);
}
// Generate random bar for bar-based indicators
var bar = new TBar(
DateTime.Now,
randomValue,
randomValue + Math.Abs(GetRandomDouble(rng) * 10),
randomValue - Math.Abs(GetRandomDouble(rng) * 10),
randomValue + (GetRandomDouble(rng) * 5),
Math.Abs(GetRandomDouble(rng) * 1000),
true
);
barInput.Add(bar);
// Calculate bar-based indicators
foreach (var (_, direct, _) in barIndicators)
{
direct.Calc(bar);
}
}
// Compare the results for value-based indicators
foreach (var (name, direct, eventBased) in valueIndicators)
{
bool areEqual = (double.IsNaN(direct.Value) && double.IsNaN(eventBased.Value)) ||
Math.Abs(direct.Value - eventBased.Value) < 1e-9;
Assert.True(areEqual, $"Value indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}");
}
// Compare the results for bar-based indicators
foreach (var (name, direct, eventBased) in barIndicators)
{
bool areEqual = (double.IsNaN(direct.Value) && double.IsNaN(eventBased.Value)) ||
Math.Abs(direct.Value - eventBased.Value) < 1e-9;
Assert.True(areEqual, $"Bar indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}");
}
}
public static IEnumerable<object[]> GetBarIndicatorData()
=> BarIndicators.Select(x => new object[] { x.Name, x.DirectParams, x.EventParams });
private static double GetRandomDouble(RandomNumberGenerator rng)
{
@@ -174,4 +114,69 @@ public class EventingTests
rng.GetBytes(bytes);
return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue;
}
private static TBar GenerateRandomBar(RandomNumberGenerator rng, double baseValue)
{
return new TBar(
DateTime.Now,
baseValue,
baseValue + Math.Abs(GetRandomDouble(rng) * 10),
baseValue - Math.Abs(GetRandomDouble(rng) * 10),
baseValue + (GetRandomDouble(rng) * 5),
Math.Abs(GetRandomDouble(rng) * 1000),
true
);
}
[Theory]
[MemberData(nameof(GetValueIndicatorData))]
public void ValueIndicatorEventTest(string indicatorName, object[] directParams, object[] eventParams)
{
using var rng = RandomNumberGenerator.Create();
var input = (TSeries)eventParams[0];
// Create indicator instances using reflection
var indicatorType = Type.GetType($"QuanTAlib.{indicatorName}, QuanTAlib")!;
var directIndicator = (AbstractBase)Activator.CreateInstance(indicatorType, directParams)!;
var eventIndicator = (AbstractBase)Activator.CreateInstance(indicatorType, eventParams)!;
// Generate test data and calculate
for (int i = 0; i < TestDataPoints; i++)
{
double randomValue = GetRandomDouble(rng) * 100;
input.Add(randomValue);
directIndicator.Calc(randomValue);
}
bool areEqual = (double.IsNaN(directIndicator.Value) && double.IsNaN(eventIndicator.Value)) ||
Math.Abs(directIndicator.Value - eventIndicator.Value) < Tolerance;
Assert.True(areEqual, $"Value indicator {indicatorName} failed: Expected {directIndicator.Value}, Actual {eventIndicator.Value}");
}
[Theory]
[MemberData(nameof(GetBarIndicatorData))]
public void BarIndicatorEventTest(string indicatorName, object[] directParams, object[] eventParams)
{
using var rng = RandomNumberGenerator.Create();
var barInput = (TBarSeries)eventParams[0];
// Create indicator instances using reflection
var indicatorType = Type.GetType($"QuanTAlib.{indicatorName}, QuanTAlib")!;
var directIndicator = (AbstractBase)Activator.CreateInstance(indicatorType, directParams)!;
var eventIndicator = (AbstractBase)Activator.CreateInstance(indicatorType, eventParams)!;
// Generate test data and calculate
for (int i = 0; i < TestDataPoints; i++)
{
var bar = GenerateRandomBar(rng, GetRandomDouble(rng) * 100);
barInput.Add(bar);
directIndicator.Calc(bar);
}
bool areEqual = (double.IsNaN(directIndicator.Value) && double.IsNaN(eventIndicator.Value)) ||
Math.Abs(directIndicator.Value - eventIndicator.Value) < Tolerance;
Assert.True(areEqual, $"Bar indicator {indicatorName} failed: Expected {directIndicator.Value}, Actual {eventIndicator.Value}");
}
}