mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-29 10:07:43 +00:00
222 lines
7.1 KiB
C#
222 lines
7.1 KiB
C#
using Skender.Stock.Indicators;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
/// <summary>
|
|
/// Provides standardized test data for validation tests.
|
|
/// Uses GBM (Geometric Brownian Motion) to generate realistic price data
|
|
/// and converts it to formats required by external validation libraries.
|
|
/// </summary>
|
|
public sealed class ValidationTestData : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Default number of bars for validation tests.
|
|
/// 10000 bars ensures sufficient convergence for most indicators.
|
|
/// </summary>
|
|
public const int DefaultCount = 10000;
|
|
|
|
/// <summary>
|
|
/// Default starting price for generated data.
|
|
/// </summary>
|
|
public const double DefaultStartPrice = 1000.0;
|
|
|
|
/// <summary>
|
|
/// Default annual drift for GBM (5%).
|
|
/// </summary>
|
|
public const double DefaultMu = 0.05;
|
|
|
|
/// <summary>
|
|
/// Default annual volatility for GBM (200%).
|
|
/// High volatility ensures diverse price scenarios.
|
|
/// </summary>
|
|
public const double DefaultSigma = 2.0;
|
|
|
|
/// <summary>
|
|
/// Default random seed for reproducibility.
|
|
/// </summary>
|
|
public const int DefaultSeed = 123;
|
|
|
|
/// <summary>
|
|
/// Gets the generated bar series.
|
|
/// </summary>
|
|
public TBarSeries Bars { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the close price series.
|
|
/// </summary>
|
|
public TSeries Data { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the quotes in Skender.Stock.Indicators format.
|
|
/// </summary>
|
|
public IReadOnlyList<Quote> SkenderQuotes { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the raw close price data as a ReadOnlyMemory for span-based APIs.
|
|
/// </summary>
|
|
public ReadOnlyMemory<double> RawData { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the raw open prices as read-only memory.
|
|
/// </summary>
|
|
public ReadOnlyMemory<double> OpenPrices { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the raw high prices as read-only memory.
|
|
/// </summary>
|
|
public ReadOnlyMemory<double> HighPrices { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the raw low prices as read-only memory.
|
|
/// </summary>
|
|
public ReadOnlyMemory<double> LowPrices { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the raw close prices as read-only memory.
|
|
/// </summary>
|
|
public ReadOnlyMemory<double> ClosePrices { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the raw volume data as read-only memory.
|
|
/// </summary>
|
|
public ReadOnlyMemory<double> VolumeData { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the timestamps as read-only memory.
|
|
/// </summary>
|
|
public ReadOnlyMemory<long> Timestamps { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the number of bars in the dataset.
|
|
/// </summary>
|
|
public int Count => Bars.Count;
|
|
|
|
/// <summary>
|
|
/// Creates validation test data with default parameters.
|
|
/// </summary>
|
|
public ValidationTestData()
|
|
: this(DefaultCount, DefaultStartPrice, DefaultMu, DefaultSigma, DefaultSeed)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates validation test data with specified parameters.
|
|
/// </summary>
|
|
/// <param name="count">Number of bars to generate</param>
|
|
/// <param name="startPrice">Starting price</param>
|
|
/// <param name="mu">Annual drift rate</param>
|
|
/// <param name="sigma">Annual volatility</param>
|
|
/// <param name="seed">Random seed for reproducibility</param>
|
|
public ValidationTestData(
|
|
int count,
|
|
double startPrice = DefaultStartPrice,
|
|
double mu = DefaultMu,
|
|
double sigma = DefaultSigma,
|
|
int seed = DefaultSeed)
|
|
{
|
|
var gbm = new GBM(startPrice, mu, sigma, seed: seed);
|
|
Bars = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
Data = Bars.Close;
|
|
|
|
// Extract raw arrays efficiently (avoid LINQ in hot path)
|
|
int barCount = Bars.Count;
|
|
var openPrices = new double[barCount];
|
|
var highPrices = new double[barCount];
|
|
var lowPrices = new double[barCount];
|
|
var closePrices = new double[barCount];
|
|
var volumeData = new double[barCount];
|
|
var timestamps = new long[barCount];
|
|
|
|
// Use span-based access for efficiency
|
|
var openSpan = Bars.OpenValues;
|
|
var highSpan = Bars.HighValues;
|
|
var lowSpan = Bars.LowValues;
|
|
var closeSpan = Bars.CloseValues;
|
|
var volumeSpan = Bars.VolumeValues;
|
|
var timeSpan = Bars.Times;
|
|
|
|
openSpan.CopyTo(openPrices);
|
|
highSpan.CopyTo(highPrices);
|
|
lowSpan.CopyTo(lowPrices);
|
|
closeSpan.CopyTo(closePrices);
|
|
volumeSpan.CopyTo(volumeData);
|
|
timeSpan.CopyTo(timestamps);
|
|
|
|
// Expose as ReadOnlyMemory to prevent external modification
|
|
OpenPrices = openPrices;
|
|
HighPrices = highPrices;
|
|
LowPrices = lowPrices;
|
|
ClosePrices = closePrices;
|
|
VolumeData = volumeData;
|
|
Timestamps = timestamps;
|
|
RawData = closePrices;
|
|
|
|
// Build Skender quotes without LINQ
|
|
var quotes = new Quote[barCount];
|
|
for (int i = 0; i < barCount; i++)
|
|
{
|
|
quotes[i] = new Quote
|
|
{
|
|
Date = new DateTime(timestamps[i], DateTimeKind.Utc),
|
|
Open = (decimal)openPrices[i],
|
|
High = (decimal)highPrices[i],
|
|
Low = (decimal)lowPrices[i],
|
|
Close = (decimal)closePrices[i],
|
|
Volume = (decimal)volumeData[i],
|
|
};
|
|
}
|
|
SkenderQuotes = quotes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new ValidationTestData instance with the specified bar count.
|
|
/// Note: This regenerates data using the same seed rather than slicing existing data,
|
|
/// ensuring deterministic results but not reusing the parent's generated bars.
|
|
/// </summary>
|
|
/// <param name="count">Number of bars to generate (must be between 1 and current Count)</param>
|
|
/// <returns>A new ValidationTestData instance with freshly generated data</returns>
|
|
public ValidationTestData CreateSubset(int count)
|
|
{
|
|
if (count <= 0 || count > Count)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(count), count, $"Count must be between 1 and {Count}");
|
|
}
|
|
|
|
return new ValidationTestData(count, DefaultStartPrice, DefaultMu, DefaultSigma, DefaultSeed);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the close price span for SIMD operations.
|
|
/// </summary>
|
|
public ReadOnlySpan<double> GetCloseSpan() => ClosePrices.Span;
|
|
|
|
/// <summary>
|
|
/// Gets the high price span for SIMD operations.
|
|
/// </summary>
|
|
public ReadOnlySpan<double> GetHighSpan() => HighPrices.Span;
|
|
|
|
/// <summary>
|
|
/// Gets the low price span for SIMD operations.
|
|
/// </summary>
|
|
public ReadOnlySpan<double> GetLowSpan() => LowPrices.Span;
|
|
|
|
/// <summary>
|
|
/// Gets the open price span for SIMD operations.
|
|
/// </summary>
|
|
public ReadOnlySpan<double> GetOpenSpan() => OpenPrices.Span;
|
|
|
|
/// <summary>
|
|
/// Gets the volume span for SIMD operations.
|
|
/// </summary>
|
|
public ReadOnlySpan<double> GetVolumeSpan() => VolumeData.Span;
|
|
|
|
/// <summary>
|
|
/// Disposes of resources (no-op, but implements pattern for test fixtures).
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
// No unmanaged resources to dispose
|
|
// Implemented for IDisposable pattern compatibility with test fixtures
|
|
}
|
|
}
|