Files
QuanTAlib/MODELS.md
T
2025-12-14 21:55:40 -08:00

114 lines
4.9 KiB
Markdown

# 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`) throw `ArgumentException`.
- **`Constructor_ValidatesOptionalArgs`**: If applicable, verify other parameters (e.g., `alpha`, `sigma`).
### Basic Functionality
- **`Calc_ReturnsValue`**: Verify `Update` returns a valid `TValue` and updates the `Last` property.
- **`FirstValue_ReturnsExpected`**: Verify the first output value (often the input itself for averages).
- **`Properties_Accessible`**: Verify `Last`, `IsHot`, `Name`, etc., are accessible and initialized correctly.
### State Management & Bar Correction
- **`Calc_IsNew_AcceptsParameter`**: Verify that `isNew: true` advances the state.
- **`Calc_IsNew_False_UpdatesValue`**: Verify that `isNew: false` updates the current value without advancing state (intra-bar update).
- **`IterativeCorrections_RestoreToOriginalState`**: Critical test.
1. Feed $N$ values.
2. Remember state.
3. Feed $M$ updates with `isNew: false`.
4. Feed the original $N$-th value again with `isNew: false`.
5. Verify state matches the remembered state.
- **`Reset_ClearsState`**: Verify `Reset()` clears all internal state and the indicator behaves like a new instance.
### Warmup & Convergence
- **`IsHot_BecomesTrueWhenBufferFull`**: Verify `IsHot` becomes true after the expected number of periods.
- **`IsHot_IsPeriodDependent`**: If applicable, verify warmup time scales with period.
### Robustness (NaN/Infinity)
- **`NaN_Input_UsesLastValidValue`**: Verify that `NaN` input does not crash and typically carries forward the last valid value.
- **`Infinity_Input_UsesLastValidValue`**: Verify handling of `PositiveInfinity` and `NegativeInfinity`.
- **`MultipleNaN_ContinuesWithLastValid`**: Verify behavior with consecutive invalid inputs.
- **`BatchCalc_HandlesNaN`**: Verify batch processing handles `NaN` correctly.
### Consistency
- **`BatchCalc_MatchesIterativeCalc`**: Verify that `Update(TSeries)` produces the same results as a loop of `Update(TValue)`.
- **`AllModes_ProduceSameResult`**: **Crucial**. Verify that all 4 usage modes produce identical results:
1. **Batch**: `Indicator.Calculate(TSeries)`
2. **Span**: `Indicator.Calculate(ReadOnlySpan, Span)`
3. **Streaming**: `new Indicator().Update(TValue)`
4. **Eventing**: `new Indicator(source).Update()`
### 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-6` to `1e-9`.
## 3. Example Test Template
```csharp
[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);
}