Files
QuanTAlib/lib/trends/blma/Blma.Tests.cs
T
Miha Kralj d7dbd7078a Refactor event handling and improve argument validation across indicators
- Updated event handler signatures to use TValueEventArgs for consistency in Mama, Mgdi, Pwma, Rma, Sma, Ssf, Super, T3, Tema, Trima, Usf, Vidya, Wma, and Atr classes.
- Enhanced argument validation by specifying parameter names in exceptions for clarity.
- Adjusted tests to align with new event handler signatures.
- Improved code readability and maintainability by using structured records and lambda expressions.
2025-12-27 15:46:28 -08:00

179 lines
5.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class BlmaTests
{
private readonly GBM _gbm;
public BlmaTests()
{
_gbm = new GBM();
}
[Fact]
public void Constructor_ValidatesInput()
{
Assert.Throws<ArgumentOutOfRangeException>(() => new Blma(0));
Assert.Throws<ArgumentOutOfRangeException>(() => new Blma(-1));
}
[Fact]
public void Constructor_ValidatesSource()
{
Assert.Throws<NullReferenceException>(() => new Blma(null!, 10));
}
[Fact]
public void BasicCalculation_MatchesManual()
{
var blma = new Blma(3);
var input = new[] { 10.0, 20.0, 30.0 };
// Bar 1: Count=1. Weights for n=1: [1]. Result = 10.
var r1 = blma.Update(new TValue(DateTime.UtcNow, input[0]));
Assert.Equal(10.0, r1.Value);
// Bar 2: Count=2. Weights for n=2 sum to 0. Fallback to average: (10+20)/2 = 15.
var r2 = blma.Update(new TValue(DateTime.UtcNow, input[1]));
Assert.Equal(15.0, r2.Value);
// Bar 3: Count=3. Weights [0, 1, 0]. Sum=1. Result=20.
var r3 = blma.Update(new TValue(DateTime.UtcNow, input[2]));
Assert.Equal(20.0, r3.Value, 1e-6);
}
[Fact]
public void AllModes_ProduceSameResult()
{
int period = 10;
var bars = _gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = new Blma(period).Update(series);
double expected = batchSeries.Last.Value;
// 2. Span Mode
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Blma.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Blma(period);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, 1e-9);
Assert.Equal(expected, streamingResult, 1e-9);
}
[Fact]
public void NaN_Handling()
{
var blma = new Blma(5);
blma.Update(new TValue(DateTime.UtcNow, 10));
blma.Update(new TValue(DateTime.UtcNow, 20));
// For N=2, weights sum to 0. Fallback to average: (10+20)/2 = 15.
var result = blma.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.Equal(15.0, result.Value); // Should return last valid value
Assert.Equal(15.0, blma.Last.Value); // Should retain last valid value
}
[Fact]
public void IsNew_Behavior()
{
var blma = new Blma(3);
// Bar 1
blma.Update(new TValue(DateTime.UtcNow, 10), isNew: true);
// Bar 2
blma.Update(new TValue(DateTime.UtcNow, 20), isNew: true);
// Bar 3 (Update)
blma.Update(new TValue(DateTime.UtcNow, 30), isNew: true);
var val1 = blma.Last.Value;
// Bar 3 (Correction)
blma.Update(new TValue(DateTime.UtcNow, 40), isNew: false);
var val2 = blma.Last.Value;
// For Blackman window, the newest value (index N-1) has weight 0.
// So changing the newest value does NOT change the current result.
Assert.Equal(val1, val2);
// However, the internal buffer MUST be updated.
// Case A: Bar 3 = 40 (current state)
blma.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
var valWith40 = blma.Last.Value;
// Case B: Reconstruct scenario with Bar 3 = 30
var blma2 = new Blma(3);
blma2.Update(new TValue(DateTime.UtcNow, 10), isNew: true);
blma2.Update(new TValue(DateTime.UtcNow, 20), isNew: true);
blma2.Update(new TValue(DateTime.UtcNow, 30), isNew: true);
blma2.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
var valWith30 = blma2.Last.Value;
Assert.NotEqual(valWith30, valWith40);
}
[Fact]
public void Prime_PreservesTimestamps()
{
var blma = new Blma(5);
var input = new double[] { 1, 2, 3, 4, 5 };
var timestamps = new List<DateTime>();
blma.Pub += (object? sender, TValueEventArgs args) => timestamps.Add(args.Value.AsDateTime);
blma.Prime(input);
Assert.Equal(input.Length, timestamps.Count);
// Verify timestamps are unique and increasing
for (int i = 1; i < timestamps.Count; i++)
{
Assert.True(timestamps[i] > timestamps[i-1], $"Timestamp at {i} ({timestamps[i].Ticks}) should be greater than {i-1} ({timestamps[i-1].Ticks})");
}
}
[Fact]
public void Prime_Overload_UsesProvidedTimestamps()
{
var blma = new Blma(5);
var now = DateTime.UtcNow;
TValue[] input =
[
new(now, 1),
new(now.AddMinutes(1), 2),
new(now.AddMinutes(2), 3)
];
var timestamps = new List<DateTime>();
blma.Pub += (object? sender, TValueEventArgs args) => timestamps.Add(args.Value.AsDateTime);
blma.Prime(input);
Assert.Equal(input.Length, timestamps.Count);
Assert.Equal(input[0].AsDateTime, timestamps[0]);
Assert.Equal(input[1].AsDateTime, timestamps[1]);
Assert.Equal(input[2].AsDateTime, timestamps[2]);
}
}