event tests

This commit is contained in:
Miha Kralj
2024-10-08 09:25:01 -07:00
parent b7b5a4a1bf
commit af234594cc
15 changed files with 391 additions and 200 deletions
-1
View File
@@ -1,6 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>QuanTAlib.Tests</RootNamespace>
<AssemblyName>QuanTAlib.Tests</AssemblyName>
</PropertyGroup>
+72
View File
@@ -0,0 +1,72 @@
namespace QuanTAlib;
public class EventingTests
{
[Fact]
public void VerifyEventBasedCalculations()
{
// Create a random number generator with a fixed seed for reproducibility
var random = new Random(42);
// Create an input series to hold our random values
var input = new TSeries();
int p = 10;
// Create a list of indicator pairs (direct calculation and event-based)
var indicators = new List<(AbstractBase Direct, AbstractBase EventBased)>
{
(new Afirma(p,p,Afirma.WindowType.BlackmanHarris), new Afirma(input, p,p,Afirma.WindowType.BlackmanHarris)),
(new Alma(p), new Alma(input, p)),
(new Convolution([1,2,3,2,1]), new Convolution(input, [1,2,3,2,1])),
(new Dema(p), new Dema(input, p)),
(new Dsma(p), new Dsma(input, p)),
(new Dwma(p), new Dwma(input, p)),
(new Ema(p), new Ema(input, p)),
(new Epma(p), new Epma(input, p)),
(new Frama(p), new Frama(input, p)),
(new Fwma(p), new Fwma(input, p)),
(new Gma(p), new Gma(input, p)),
(new Hma(p), new Hma(input, p)),
(new Htit(), new Htit(input)),
(new Hwma(p), new Hwma(input, p)),
(new Jma(p), new Jma(input, p)),
(new Kama(p), new Kama(input, p)),
(new Ltma(gamma: 0.2), new Ltma(input, gamma: 0.2)),
(new Maaf(p), new Maaf(input, p)),
(new Mama(p), new Mama(input, p)),
(new Mgdi(p), new Mgdi(input, p)),
(new Mma(p), new Mma(input, p)),
(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)),
(new Rema(p), new Rema(input, p)),
(new Rma(p), new Rma(input, p)),
(new Sma(p), new Sma(input, p)),
(new Wma(p), new Wma(input, p)),
(new Rma(p), new Rma(input, p)),
(new Tema(p), new Tema(input, p)),
(new Kama(2, 30, 6), new Kama(input, 2, 30, 6)),
(new Zlema(p), new Zlema(input, p))
};
// Generate 200 random values and feed them to both direct and event-based indicators
for (int i = 0; i < 200; i++)
{
double randomValue = random.NextDouble() * 100;
input.Add(randomValue);
// Calculate direct indicators
foreach (var (direct, _) in indicators)
{
direct.Calc(randomValue);
}
}
// Compare the results of direct and event-based calculations
foreach (var (direct, eventBased) in indicators)
{
Assert.Equal(direct.Value, eventBased.Value, 9);
}
}
}
+92 -8
View File
@@ -4,14 +4,19 @@ using System.Diagnostics.CodeAnalysis;
namespace QuanTAlib;
/// <summary>
/// Contains unit tests for bar-based indicators in QuanTAlib.
/// </summary>
[SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")]
public class BarIndicatorTests
{
private readonly Random rnd;
private const int SeriesLen = 1000;
private const int Corrections = 100;
/// <summary>
/// Initializes a new instance of the BarIndicatorTests class.
/// </summary>
public BarIndicatorTests()
{
rnd = new Random((int)DateTime.Now.Ticks);
@@ -19,9 +24,14 @@ public class BarIndicatorTests
private static readonly ITValue[] indicators = new ITValue[]
{
new Atr(period: 14),
new Atr(period: 14),
// Add other TBar-based indicators here
};
/// <summary>
/// Tests if the indicator produces consistent results when processing new and updated bars.
/// </summary>
/// <param name="indicator">The indicator to test.</param>
[Theory]
[MemberData(nameof(GetIndicators))]
public void IndicatorIsNew(ITValue indicator)
@@ -29,7 +39,7 @@ public class BarIndicatorTests
var indicator1 = indicator;
var indicator2 = indicator;
MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!;
MethodInfo calcMethod = FindCalcMethod(indicator.GetType());
if (calcMethod == null)
{
throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}");
@@ -37,22 +47,96 @@ public class BarIndicatorTests
for (int i = 0; i < SeriesLen; i++)
{
TBar item1 = new(Time: DateTime.Now, Open: rnd.Next(-100, 100), High: rnd.Next(-100, 100), Low: rnd.Next(-100, 100), Close: rnd.Next(-100, 100), Volume: rnd.Next(-1000, 1000), IsNew: true);
calcMethod.Invoke(indicator1, new object[] { item1 });
TBar item1 = GenerateRandomBar(isNew: true);
InvokeCalc(indicator1, calcMethod, item1);
for (int j = 0; j < Corrections; j++)
{
item1 = new(Time: DateTime.Now, Open: rnd.Next(-100, 100), High: rnd.Next(-100, 100), Low: rnd.Next(-100, 100), Close: rnd.Next(-100, 100), Volume: rnd.Next(-1000, 1000), IsNew: false);
calcMethod.Invoke(indicator1, new object[] { item1 });
item1 = GenerateRandomBar(isNew: false);
InvokeCalc(indicator1, calcMethod, item1);
}
var item2 = new TBar(item1.Time, item1.Open, item1.High, item1.Low, item1.Close, item1.Volume, IsNew: true);
calcMethod.Invoke(indicator2, new object[] { item2 });
InvokeCalc(indicator2, calcMethod, item2);
Assert.Equal(indicator1.Value, indicator2.Value);
}
}
/// <summary>
/// Finds the appropriate Calc method for the given indicator type.
/// </summary>
/// <param name="type">The type of the indicator.</param>
/// <returns>The MethodInfo for the Calc method.</returns>
private static MethodInfo FindCalcMethod(Type type)
{
while (type != null && type != typeof(object))
{
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => m.Name == "Calc")
.ToList();
if (methods.Count > 0)
{
// Prefer the method with TBar parameter
var method = methods.FirstOrDefault(m =>
{
var parameters = m.GetParameters();
return parameters.Length == 1 && parameters[0].ParameterType == typeof(TBar);
});
// If not found, return the first method
return method ?? methods.First();
}
type = type.BaseType!;
}
return null!;
}
/// <summary>
/// Invokes the Calc method on the given indicator with the provided input.
/// </summary>
/// <param name="indicator">The indicator instance.</param>
/// <param name="calcMethod">The Calc method to invoke.</param>
/// <param name="input">The input TBar.</param>
private static void InvokeCalc(ITValue indicator, MethodInfo calcMethod, TBar input)
{
var parameters = calcMethod.GetParameters();
if (parameters.Length == 1)
{
calcMethod.Invoke(indicator, new object[] { input });
}
else if (parameters.Length == 2)
{
calcMethod.Invoke(indicator, new object[] { input, double.NaN });
}
else
{
throw new InvalidOperationException($"Invalid number of parameters for Calc method in indicator type: {indicator.GetType().Name}");
}
}
/// <summary>
/// Generates a random TBar for testing purposes.
/// </summary>
/// <param name="isNew">Indicates whether the generated bar should be marked as new.</param>
/// <returns>A randomly generated TBar.</returns>
private TBar GenerateRandomBar(bool isNew)
{
double open = rnd.NextDouble() * 200 - 100;
double close = rnd.NextDouble() * 200 - 100;
double high = Math.Max(open, close) + rnd.NextDouble() * 10;
double low = Math.Min(open, close) - rnd.NextDouble() * 10;
long volume = rnd.Next(0, 10000);
return new TBar(Time: DateTime.Now, Open: open, High: high, Low: low, Close: close, Volume: volume, IsNew: isNew);
}
/// <summary>
/// Provides the list of indicators for parameterized tests.
/// </summary>
/// <returns>An enumerable of object arrays, each containing an indicator instance.</returns>
public static IEnumerable<object[]> GetIndicators()
{
return indicators.Select(indicator => new object[] { indicator });
+93 -44
View File
@@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis;
namespace QuanTAlib;
[SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")]
public class IndicatorTests
{
private readonly Random rnd;
@@ -17,49 +16,56 @@ public class IndicatorTests
rnd = new Random((int)DateTime.Now.Ticks);
}
// skipcq: CS-R1055
private static readonly ITValue[] indicators =
{
new Ema(period: 10, useSma: true),
new Alma(period: 14, offset: 0.85, sigma: 6),
new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman),
new Convolution(new double[] { 1.0, 2, 3, 2, 1 }),
new Dema(period: 14),
new Dsma(period: 14),
new Dwma(period: 14),
new Epma(period: 14),
new Frama(period: 14),
new Fwma(period: 14),
new Gma(period: 14),
new Hma(period: 14),
new Hwma(period: 14),
new Kama(period: 14),
new Mama(fastLimit: 0.5, slowLimit: 0.05),
new Mgdi(period: 14),
new Mma(period: 14),
new Qema(),
new Rema(period: 14),
new Rma(period: 14),
new Sinema(period: 14),
new Sma(period: 14),
new Smma(period: 14),
new T3(period: 14),
new Tema(period: 14),
new Trima(period: 14),
new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2),
new Wma(period: 14),
new Zlema(period: 14),
new Entropy(period: 14),
new Kurtosis(period: 14),
new Max(period: 14, decay: 0.01),
new Min(period: 14, decay: 0.01),
new Median(period: 14),
new Mode(period: 14),
new Percentile(period: 14, percent: 50),
new Skew(period: 14),
new Stddev(period: 14),
new Variance(period: 14),
new Zscore(period: 14)
new Alma(period: 14, offset: 0.85, sigma: 6),
new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman),
new Convolution(new[] { 1.0, 2, 3, 2, 1 }),
new Dema(period: 14),
new Dsma(period: 14),
new Dwma(period: 14),
new Epma(period: 14),
new Frama(period: 14),
new Fwma(period: 14),
new Gma(period: 14),
new Hma(period: 14),
new Hwma(period: 14),
new Kama(period: 14),
new Mama(fastLimit: 0.5, slowLimit: 0.05),
new Mgdi(period: 14),
new Mma(period: 14),
new Qema(),
new Rema(period: 14),
new Rma(period: 14),
new Sinema(period: 14),
new Sma(period: 14),
new Smma(period: 14),
new T3(period: 14),
new Tema(period: 14),
new Trima(period: 14),
new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2),
new Wma(period: 14),
new Zlema(period: 14),
new Curvature(period: 14),
new Entropy(period: 14),
new Kurtosis(period: 14),
new Max(period: 14, decay: 0.01),
new Median(period: 14),
new Min(period: 14, decay: 0.01),
new Median(period: 14),
new Mode(period: 14),
new Percentile(period: 14, percent: 50),
new Skew(period: 14),
new Slope(period: 14),
new Stddev(period: 14),
new Variance(period: 14),
new Zscore(period: 14),
new Historical(period: 14),
new Realized(period: 14)
};
[Theory]
@@ -69,7 +75,7 @@ public class IndicatorTests
var indicator1 = indicator;
var indicator2 = indicator;
MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!;
MethodInfo calcMethod = FindCalcMethod(indicator.GetType());
if (calcMethod == null)
{
throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}");
@@ -78,21 +84,64 @@ public class IndicatorTests
for (int i = 0; i < SeriesLen; i++)
{
TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true);
calcMethod.Invoke(indicator1, new object[] { item1 });
InvokeCalc(indicator1, calcMethod, item1);
for (int j = 0; j < Corrections; j++)
{
item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false);
calcMethod.Invoke(indicator1, new object[] { item1 });
InvokeCalc(indicator1, calcMethod, item1);
}
var item2 = new TValue(item1.Time, item1.Value, IsNew: true);
calcMethod.Invoke(indicator2, new object[] { item2 });
InvokeCalc(indicator2, calcMethod, item2);
Assert.Equal(indicator1.Value, indicator2.Value);
}
}
private static MethodInfo FindCalcMethod(Type type)
{
while (type != null && type != typeof(object))
{
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => m.Name == "Calc")
.ToList();
if (methods.Count > 0)
{
// Prefer the method with TValue parameter
var method = methods.FirstOrDefault(m =>
{
var parameters = m.GetParameters();
return parameters.Length == 1 && parameters[0].ParameterType == typeof(TValue);
});
// If not found, return the first method
return method ?? methods.First();
}
type = type.BaseType!;
}
return null!;
}
private static void InvokeCalc(ITValue indicator, MethodInfo calcMethod, TValue input)
{
var parameters = calcMethod.GetParameters();
if (parameters.Length == 1)
{
calcMethod.Invoke(indicator, new object[] { input });
}
else if (parameters.Length == 2)
{
calcMethod.Invoke(indicator, new object[] { input, double.NaN });
}
else
{
throw new InvalidOperationException($"Invalid number of parameters for Calc method in indicator type: {indicator.GetType().Name}");
}
}
public static IEnumerable<object[]> GetIndicators()
{
return indicators.Select(indicator => new object[] { indicator });