Files
QuanTAlib/Tests/test_iTValue.cs
T
2024-10-09 14:55:11 -07:00

158 lines
5.0 KiB
C#

using Xunit;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
namespace QuanTAlib;
public class IndicatorTests
{
private readonly RandomNumberGenerator rng;
private const int SeriesLen = 1000;
private const int Corrections = 100;
public IndicatorTests()
{
rng = RandomNumberGenerator.Create();
}
private int GetRandomNumber(int minValue, int maxValue)
{
byte[] randomBytes = new byte[4];
rng.GetBytes(randomBytes);
int randomInt = BitConverter.ToInt32(randomBytes, 0);
return Math.Abs(randomInt % (maxValue - minValue)) + minValue;
}
// 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[] { 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]
[MemberData(nameof(GetIndicators))]
public void IndicatorIsNew(ITValue indicator)
{
var indicator1 = indicator;
var indicator2 = indicator;
MethodInfo calcMethod = FindCalcMethod(indicator.GetType());
if (calcMethod == null)
{
throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}");
}
for (int i = 0; i < SeriesLen; i++)
{
TValue item1 = new(Time: DateTime.Now, Value: GetRandomNumber(-100, 100), IsNew: true);
InvokeCalc(indicator1, calcMethod, item1);
for (int j = 0; j < Corrections; j++)
{
item1 = new(Time: DateTime.Now, Value: GetRandomNumber(-100, 100), IsNew: false);
InvokeCalc(indicator1, calcMethod, item1);
}
var item2 = new TValue(item1.Time, item1.Value, IsNew: true);
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 });
}
}