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 } }