Files
QuanTAlib/lib/volume/evwma/tests/Evwma.Validation.Tests.cs
Miha Kralj 060649192f docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files
- Remove 'C# Implementation Considerations' sections from 34 indicator .md files
- Delete 29 temp PowerShell scripts (_fix_mojibake.ps1, _hex_scan.ps1, etc.)
- Move test files into tests/ subdirectories for consistent project structure
- Add trader-focused bullet points to indicator documentation
2026-03-12 12:34:16 -07:00

207 lines
6.7 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace QuanTAlib.Tests;
public class EvwmaValidationTests
{
private readonly ValidationTestData _data;
public EvwmaValidationTests()
{
_data = new ValidationTestData();
}
// ============ External Library Validation ============
// EVWMA is not available in standard libraries (Skender, TA-Lib, Tulip, Ooples).
// Validation is performed via internal consistency and known-value tests.
[Fact]
public void Evwma_NotAvailable_Skender()
{
// Skender.Stock.Indicators does not have EVWMA
Assert.True(true, "EVWMA is not available in Skender");
}
[Fact]
public void Evwma_NotAvailable_Talib()
{
// TA-Lib does not have EVWMA
Assert.True(true, "EVWMA is not available in TA-Lib");
}
[Fact]
public void Evwma_NotAvailable_Tulip()
{
// Tulip does not have EVWMA
Assert.True(true, "EVWMA is not available in Tulip");
}
[Fact]
public void Evwma_NotAvailable_Ooples()
{
// Ooples does not have EVWMA
Assert.True(true, "EVWMA is not available in Ooples");
}
// ============ Internal Consistency Tests ============
[Fact]
public void Evwma_Streaming_Matches_Batch()
{
int period = 20;
// Streaming
var evwma = new Evwma(period);
var streamingValues = new List<double>();
foreach (var bar in _data.Bars)
{
streamingValues.Add(evwma.Update(bar).Value);
}
// Batch
var batchResult = Evwma.Batch(_data.Bars, period);
var batchValues = batchResult.Values.ToArray();
ValidationHelper.VerifyData(streamingValues.ToArray(), batchValues, 0, 100, 1e-10);
}
[Fact]
public void Evwma_Span_Matches_Streaming()
{
int period = 20;
// Streaming
var evwma = new Evwma(period);
var streamingValues = new List<double>();
foreach (var bar in _data.Bars)
{
streamingValues.Add(evwma.Update(bar).Value);
}
// Span
var price = _data.Bars.Close.Values.ToArray();
var volume = _data.Bars.Volume.Values.ToArray();
var spanValues = new double[price.Length];
Evwma.Batch(price, volume, spanValues, period);
ValidationHelper.VerifyData(streamingValues.ToArray(), spanValues, 0, 100, 1e-10);
}
[Fact]
public void Evwma_Batch_Matches_Span()
{
int period = 20;
// Batch
var batchResult = Evwma.Batch(_data.Bars, period);
var batchValues = batchResult.Values.ToArray();
// Span
var price = _data.Bars.Close.Values.ToArray();
var volume = _data.Bars.Volume.Values.ToArray();
var spanValues = new double[price.Length];
Evwma.Batch(price, volume, spanValues, period);
// Batch and Span use identical code path, should match exactly
ValidationHelper.VerifyData(batchValues, spanValues, 0, 100, 1e-12);
}
// ============ Known-Value Validation ============
[Fact]
public void Evwma_KnownValues_ManualCalculation()
{
// Manually compute EVWMA for a small series
// Period = 3
// Bar 0: price=100, vol=10 → sumVol=10, result=100 (first bar)
// Bar 1: price=110, vol=20 → sumVol=30, remain=10, result=(10*100+20*110)/30=3200/30≈106.6667
// Bar 2: price=105, vol=15 → sumVol=45, remain=30, result=(30*106.6667+15*105)/45=4725/45=105
// Wait: (30 × 106.6667 + 15 × 105) / 45 = (3200 + 1575) / 45 = 4775/45 ≈ 106.1111
// Bar 3: price=120, vol=25 → drop bar0 vol(10): sumVol=45-10+25=60, remain=35
// result = (35 * 106.1111 + 25 * 120) / 60 = (3713.889 + 3000) / 60 = 6713.889/60 ≈ 111.898
var evwma = new Evwma(3);
var bar0 = new TBar(DateTime.UtcNow, 100, 100, 100, 100, 10);
var r0 = evwma.Update(bar0);
Assert.Equal(100.0, r0.Value, 6);
var bar1 = new TBar(DateTime.UtcNow.AddMinutes(1), 110, 110, 110, 110, 20);
var r1 = evwma.Update(bar1);
// sumVol = 10 + 20 = 30; remainVol = 30 - 20 = 10
// result = (10 * 100 + 20 * 110) / 30 = 3200 / 30
Assert.Equal(3200.0 / 30.0, r1.Value, 10);
var bar2 = new TBar(DateTime.UtcNow.AddMinutes(2), 105, 105, 105, 105, 15);
var r2 = evwma.Update(bar2);
// sumVol = 10 + 20 + 15 = 45; remainVol = 45 - 15 = 30
// prevResult = 3200/30
// result = (30 * (3200/30) + 15 * 105) / 45 = (3200 + 1575) / 45 = 4775 / 45
Assert.Equal(4775.0 / 45.0, r2.Value, 10);
var bar3 = new TBar(DateTime.UtcNow.AddMinutes(3), 120, 120, 120, 120, 25);
var r3 = evwma.Update(bar3);
// Bar0 vol drops: sumVol = (10+20+15) - 10 + 25 = 60; remainVol = 60 - 25 = 35
// prevResult = 4775/45
// result = (35 * (4775/45) + 25 * 120) / 60
double prev = 4775.0 / 45.0;
double expected = (35.0 * prev + 25.0 * 120.0) / 60.0;
Assert.Equal(expected, r3.Value, 10);
}
[Fact]
public void Evwma_DifferentPeriods_ProduceDifferentResults()
{
int period1 = 5;
int period2 = 50;
var result1 = Evwma.Batch(_data.Bars, period1);
var result2 = Evwma.Batch(_data.Bars, period2);
// At later bars, different periods should produce different values
int idx = 100;
Assert.NotEqual(result1.Values[idx], result2.Values[idx], 6);
}
[Fact]
public void Evwma_ConstantPrice_ReturnsConstant()
{
// If all prices are the same, EVWMA should always return that price
// regardless of volume
int period = 10;
var bars = new TBarSeries();
var now = DateTime.UtcNow;
for (int i = 0; i < 50; i++)
{
bars.Add(new TBar(now.AddMinutes(i), 42.0, 42.0, 42.0, 42.0, 100 + i * 10));
}
var result = Evwma.Batch(bars, period);
for (int i = 0; i < 50; i++)
{
Assert.Equal(42.0, result.Values[i], 10);
}
}
[Fact]
public void Evwma_UniformVolume_BehavesLikeRunningAverage()
{
// With uniform volume=1 and period covering all bars,
// EVWMA degenerates to a specific recursive average
int period = 100;
var evwma = new Evwma(period);
double[] prices = [100, 110, 105, 120, 95, 115, 108, 112, 103, 118];
for (int i = 0; i < prices.Length; i++)
{
var tv = new TValue(DateTime.UtcNow.AddMinutes(i), prices[i]);
evwma.Update(tv);
}
// Result should be finite and within the price range
Assert.True(double.IsFinite(evwma.Last.Value));
Assert.True(evwma.Last.Value >= 90 && evwma.Last.Value <= 130,
$"EVWMA value {evwma.Last.Value} should be within price range");
}
}