mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-04 04:07:42 +00:00
145 lines
5.0 KiB
C#
145 lines
5.0 KiB
C#
using Xunit;
|
|
using System.Reflection;
|
|
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);
|
|
}
|
|
|
|
private static readonly ITValue[] indicators = new ITValue[]
|
|
{
|
|
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)
|
|
{
|
|
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++)
|
|
{
|
|
TBar item1 = GenerateRandomBar(isNew: true);
|
|
InvokeCalc(indicator1, calcMethod, item1);
|
|
|
|
for (int j = 0; j < Corrections; j++)
|
|
{
|
|
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);
|
|
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 });
|
|
}
|
|
}
|