mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-02 11:37:42 +00:00
4.9 KiB
4.9 KiB
Recommended Test Pattern for Indicators
This document outlines the standard set of unit tests that every indicator in QuanTAlib should implement to ensure correctness, consistency, and robustness.
1. Standard Unit Tests ([Name].Tests.cs)
These tests verify the internal logic, state management, and API contract of the indicator.
Constructor & Validation
Constructor_ValidatesInput: Verify that invalid parameters (e.g.,period <= 0) throwArgumentException.Constructor_ValidatesOptionalArgs: If applicable, verify other parameters (e.g.,alpha,sigma).
Basic Functionality
Calc_ReturnsValue: VerifyUpdatereturns a validTValueand updates theLastproperty.FirstValue_ReturnsExpected: Verify the first output value (often the input itself for averages).Properties_Accessible: VerifyLast,IsHot,Name, etc., are accessible and initialized correctly.
State Management & Bar Correction
Calc_IsNew_AcceptsParameter: Verify thatisNew: trueadvances the state.Calc_IsNew_False_UpdatesValue: Verify thatisNew: falseupdates the current value without advancing state (intra-bar update).IterativeCorrections_RestoreToOriginalState: Critical test.- Feed
Nvalues. - Remember state.
- Feed
Mupdates withisNew: false. - Feed the original $N$-th value again with
isNew: false. - Verify state matches the remembered state.
- Feed
Reset_ClearsState: VerifyReset()clears all internal state and the indicator behaves like a new instance.
Warmup & Convergence
IsHot_BecomesTrueWhenBufferFull: VerifyIsHotbecomes true after the expected number of periods.IsHot_IsPeriodDependent: If applicable, verify warmup time scales with period.
Robustness (NaN/Infinity)
NaN_Input_UsesLastValidValue: Verify thatNaNinput does not crash and typically carries forward the last valid value.Infinity_Input_UsesLastValidValue: Verify handling ofPositiveInfinityandNegativeInfinity.MultipleNaN_ContinuesWithLastValid: Verify behavior with consecutive invalid inputs.BatchCalc_HandlesNaN: Verify batch processing handlesNaNcorrectly.
Consistency
BatchCalc_MatchesIterativeCalc: Verify thatUpdate(TSeries)produces the same results as a loop ofUpdate(TValue).AllModes_ProduceSameResult: Crucial. Verify that all 4 usage modes produce identical results:- Batch:
Indicator.Calculate(TSeries) - Span:
Indicator.Calculate(ReadOnlySpan, Span) - Streaming:
new Indicator().Update(TValue) - Eventing:
new Indicator(source).Update()
- Batch:
Span API (High Performance)
SpanCalc_ValidatesInput: Verify input/output buffer length checks.SpanCalc_MatchesTSeriesCalc: Verify Span API output matches TSeries API output.SpanCalc_ZeroAllocation: Verify the method runs without obvious errors on large datasets (allocation verified via benchmarks, but this ensures no OOM or stack overflow).SpanCalc_HandlesNaN: Verify Span API handles invalid inputs safely.
2. Validation Tests ([Name].Validation.Tests.cs)
These tests compare the indicator's output against established external libraries to ensure mathematical accuracy.
- Compare against Skender.Stock.Indicators: Primary validation target.
- Compare against TA-Lib: Secondary validation target.
- Compare against Python (pandas-ta/talib): If C# libs are unavailable.
- Tolerance: Typically
1e-6to1e-9.
3. Example Test Template
[Fact]
public void AllModes_ProduceSameResult()
{
// Arrange
int period = 10;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = MyIndicator.Calculate(series, period);
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];
MyIndicator.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new MyIndicator(period);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new MyIndicator(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, precision: 9);
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
}