namespace QuanTAlib.Tests; /// /// HAMMA validation tests. /// Note: HAMMA is not available in TA-Lib, Tulip, Skender, or OoplesFinance. /// Validation is performed against internal consistency checks and mathematical verification. /// public sealed class HammaValidationTests : IDisposable { private readonly ValidationTestData _testData; private bool _disposed; public HammaValidationTests() { _testData = new ValidationTestData(count: 10000, seed: 42); } public void Dispose() { Dispose(true); } private void Dispose(bool disposing) { if (_disposed) { return; } _disposed = true; if (disposing) { _testData?.Dispose(); } } [Fact] public void Hamma_BatchMatchesStreaming() { int[] periods = { 5, 10, 20, 50 }; foreach (var period in periods) { // Calculate QuanTAlib HAMMA (batch TSeries) var hammaBatch = new Hamma(period); var batchResult = hammaBatch.Update(_testData.Data); // Calculate QuanTAlib HAMMA (streaming) var hammaStreaming = new Hamma(period); var streamingResults = new List(); foreach (var item in _testData.Data) { streamingResults.Add(hammaStreaming.Update(item).Value); } // Compare all records Assert.Equal(batchResult.Count, streamingResults.Count); for (int i = 0; i < batchResult.Count; i++) { Assert.Equal(batchResult[i].Value, streamingResults[i], 1e-10); } } } [Fact] public void Hamma_SpanMatchesBatch() { int[] periods = { 5, 10, 20, 50 }; // Prepare data for Span API ReadOnlySpan sourceData = _testData.RawData.Span; foreach (var period in periods) { // Calculate QuanTAlib HAMMA (Span API) double[] spanOutput = new double[sourceData.Length]; Hamma.Batch(sourceData, spanOutput.AsSpan(), period); // Calculate QuanTAlib HAMMA (batch TSeries) var hammaBatch = new Hamma(period); var batchResult = hammaBatch.Update(_testData.Data); // Compare all records Assert.Equal(batchResult.Count, spanOutput.Length); for (int i = 0; i < batchResult.Count; i++) { Assert.Equal(batchResult[i].Value, spanOutput[i], 1e-10); } } } [Fact] public void Hamma_EventingMatchesBatch() { int[] periods = { 5, 10, 20, 50 }; foreach (var period in periods) { // Calculate QuanTAlib HAMMA (batch TSeries) var hammaBatch = new Hamma(period); var batchResult = hammaBatch.Update(_testData.Data); // Calculate QuanTAlib HAMMA (eventing) var pubSource = new TSeries(); var hammaEventing = new Hamma(pubSource, period); var eventingResults = new List(); hammaEventing.Pub += (object? sender, in TValueEventArgs e) => eventingResults.Add(e.Value.Value); foreach (var item in _testData.Data) { pubSource.Add(item); } // Compare all records Assert.Equal(batchResult.Count, eventingResults.Count); for (int i = 0; i < batchResult.Count; i++) { Assert.Equal(batchResult[i].Value, eventingResults[i], 1e-10); } } } [Fact] public void Hamma_HammingWindow_WeightsAreSymmetric() { // Hamming window is symmetric: w[i] = w[period-1-i] int period = 11; // Odd period for exact center var hamma = new Hamma(period); // Feed symmetric data: [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1] double[] symmetricData = [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]; foreach (var val in symmetricData) { hamma.Update(new TValue(DateTime.UtcNow, val)); } // The HAMMA result should be reasonable (between min and max of data) Assert.True(hamma.Last.Value >= 1 && hamma.Last.Value <= 6); } [Fact] public void Hamma_KnownValues_ManualCalculation() { // Manual verification of HAMMA calculation with known values // period=5: w[i] = 0.54 - 0.46 * cos(2πi/4) int period = 5; var hamma = new Hamma(period); // Feed 5 values: [100, 102, 104, 103, 101] double[] prices = [100, 102, 104, 103, 101]; foreach (var price in prices) { hamma.Update(new TValue(DateTime.UtcNow, price)); } // Calculate expected manually using Hamming window formula double twoPiOverPm1 = 2.0 * Math.PI / (period - 1); double[] weights = new double[period]; double weightSum = 0; for (int i = 0; i < period; i++) { weights[i] = 0.54 - 0.46 * Math.Cos(twoPiOverPm1 * i); weightSum += weights[i]; } double expected = 0; for (int i = 0; i < period; i++) { expected += prices[i] * weights[i]; } expected /= weightSum; Assert.Equal(expected, hamma.Last.Value, 1e-10); } [Fact] public void Hamma_HammingCoefficients_Verify() { // Verify Hamming window coefficients match the standard formula // w[i] = 0.54 - 0.46 * cos(2πi/(N-1)) // For period=5: w[0]=0.08, w[1]≈0.54, w[2]=1.0, w[3]≈0.54, w[4]=0.08 int period = 5; double twoPiOverPm1 = 2.0 * Math.PI / (period - 1); double w0 = 0.54 - 0.46 * Math.Cos(0); // 0.08 double w1 = 0.54 - 0.46 * Math.Cos(twoPiOverPm1 * 1); // ≈0.54 double w2 = 0.54 - 0.46 * Math.Cos(twoPiOverPm1 * 2); // 1.0 double w3 = 0.54 - 0.46 * Math.Cos(twoPiOverPm1 * 3); // ≈0.54 double w4 = 0.54 - 0.46 * Math.Cos(twoPiOverPm1 * 4); // 0.08 Assert.Equal(0.08, w0, 1e-10); Assert.Equal(0.08, w4, 1e-10); Assert.Equal(1.0, w2, 1e-10); // w1 and w3 should be equal (symmetric) Assert.Equal(w1, w3, 1e-10); // All edge weights should be equal Assert.Equal(w0, w4, 1e-10); } }