using Skender.Stock.Indicators;
namespace QuanTAlib.Tests;
///
/// 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.
///
public sealed class ValidationTestData : IDisposable
{
///
/// Default number of bars for validation tests.
/// 10000 bars ensures sufficient convergence for most indicators.
///
public const int DefaultCount = 10000;
///
/// Default starting price for generated data.
///
public const double DefaultStartPrice = 1000.0;
///
/// Default annual drift for GBM (5%).
///
public const double DefaultMu = 0.05;
///
/// Default annual volatility for GBM (200%).
/// High volatility ensures diverse price scenarios.
///
public const double DefaultSigma = 2.0;
///
/// Default random seed for reproducibility.
///
public const int DefaultSeed = 123;
///
/// Gets the generated bar series.
///
public TBarSeries Bars { get; }
///
/// Gets the close price series.
///
public TSeries Data { get; }
///
/// Gets the quotes in Skender.Stock.Indicators format.
///
public IReadOnlyList SkenderQuotes { get; }
///
/// Gets the raw close price data as a ReadOnlyMemory for span-based APIs.
///
public ReadOnlyMemory RawData { get; }
///
/// Gets the raw open prices as read-only memory.
///
public ReadOnlyMemory OpenPrices { get; }
///
/// Gets the raw high prices as read-only memory.
///
public ReadOnlyMemory HighPrices { get; }
///
/// Gets the raw low prices as read-only memory.
///
public ReadOnlyMemory LowPrices { get; }
///
/// Gets the raw close prices as read-only memory.
///
public ReadOnlyMemory ClosePrices { get; }
///
/// Gets the raw volume data as read-only memory.
///
public ReadOnlyMemory VolumeData { get; }
///
/// Gets the timestamps as read-only memory.
///
public ReadOnlyMemory Timestamps { get; }
///
/// Gets the number of bars in the dataset.
///
public int Count => Bars.Count;
///
/// Creates validation test data with default parameters.
///
public ValidationTestData()
: this(DefaultCount, DefaultStartPrice, DefaultMu, DefaultSigma, DefaultSeed)
{
}
///
/// Creates validation test data with specified parameters.
///
/// Number of bars to generate
/// Starting price
/// Annual drift rate
/// Annual volatility
/// Random seed for reproducibility
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;
}
///
/// 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.
///
/// Number of bars to generate (must be between 1 and current Count)
/// A new ValidationTestData instance with freshly generated data
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);
}
///
/// Gets the close price span for SIMD operations.
///
public ReadOnlySpan GetCloseSpan() => ClosePrices.Span;
///
/// Gets the high price span for SIMD operations.
///
public ReadOnlySpan GetHighSpan() => HighPrices.Span;
///
/// Gets the low price span for SIMD operations.
///
public ReadOnlySpan GetLowSpan() => LowPrices.Span;
///
/// Gets the open price span for SIMD operations.
///
public ReadOnlySpan GetOpenSpan() => OpenPrices.Span;
///
/// Gets the volume span for SIMD operations.
///
public ReadOnlySpan GetVolumeSpan() => VolumeData.Span;
///
/// Disposes of resources (no-op, but implements pattern for test fixtures).
///
public void Dispose()
{
// No unmanaged resources to dispose
// Implemented for IDisposable pattern compatibility with test fixtures
}
}