Files
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

280 lines
8.5 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 WavgTests
{
// ── A) Constructor validation ────────────────────────────────────────────
[Fact]
public void Constructor_ThrowsOnZeroPeriod()
{
Assert.Throws<ArgumentException>(() => new Wavg(0));
Assert.Throws<ArgumentException>(() => new Wavg(-1));
}
[Fact]
public void Constructor_SetsName()
{
var wavg = new Wavg(14);
Assert.Equal("Wavg(14)", wavg.Name);
}
[Fact]
public void Constructor_SetsWarmupPeriod()
{
var wavg = new Wavg(20);
Assert.Equal(20, wavg.WarmupPeriod);
}
[Fact]
public void Constructor_ValidPeriod1()
{
var wavg = new Wavg(1);
Assert.NotNull(wavg);
}
// ── B) Basic calculation ─────────────────────────────────────────────────
[Fact]
public void Update_ReturnsValue()
{
var wavg = new Wavg(5);
TValue result = wavg.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(result.Value, wavg.Last.Value);
}
[Fact]
public void IsHot_FalseUntilWindowFull()
{
var wavg = new Wavg(5);
for (int i = 0; i < 4; i++)
{
wavg.Update(new TValue(DateTime.UtcNow, i + 1.0));
Assert.False(wavg.IsHot);
}
wavg.Update(new TValue(DateTime.UtcNow, 5.0));
Assert.True(wavg.IsHot);
}
[Fact]
public void SingleValue_ReturnsThatValue()
{
var wavg = new Wavg(5);
TValue result = wavg.Update(new TValue(DateTime.UtcNow, 42.0));
Assert.Equal(42.0, result.Value, 10);
}
[Fact]
public void KnownValue_CorrectWeightedAverage()
{
// period=4, values=[1,2,3,4] (oldest→newest)
// weights = [1,2,3,4], denom = 4*5/2 = 10
// WAVG = (1*1 + 2*2 + 3*3 + 4*4) / 10 = (1+4+9+16)/10 = 30/10 = 3.0
var wavg = new Wavg(4);
wavg.Update(new TValue(DateTime.UtcNow, 1.0));
wavg.Update(new TValue(DateTime.UtcNow, 2.0));
wavg.Update(new TValue(DateTime.UtcNow, 3.0));
TValue result = wavg.Update(new TValue(DateTime.UtcNow, 4.0));
Assert.Equal(3.0, result.Value, 10);
}
[Fact]
public void AllSameValues_ReturnsValue()
{
// All weights × same value / sum_weights = value
var wavg = new Wavg(10);
for (int i = 0; i < 10; i++)
{
wavg.Update(new TValue(DateTime.UtcNow, 5.0));
}
Assert.Equal(5.0, wavg.Last.Value, 10);
}
[Fact]
public void SlidingWindow_DropsOldest()
{
// Fill with [1,2,3,4,5], then slide in 6
// After sliding: window=[2,3,4,5,6]
// WAVG = (1*2 + 2*3 + 3*4 + 4*5 + 5*6)/15 = (2+6+12+20+30)/15 = 70/15
var wavg = new Wavg(5);
for (int i = 1; i <= 5; i++)
{
wavg.Update(new TValue(DateTime.UtcNow, i));
}
TValue result = wavg.Update(new TValue(DateTime.UtcNow, 6.0));
Assert.Equal(70.0 / 15.0, result.Value, 10);
}
// ── C) State + bar correction ────────────────────────────────────────────
[Fact]
public void BarCorrection_IsNewFalse_RewritesLastBar()
{
var wavg = new Wavg(4);
var t = DateTime.UtcNow;
wavg.Update(new TValue(t, 1.0));
wavg.Update(new TValue(t, 2.0));
wavg.Update(new TValue(t, 3.0));
wavg.Update(new TValue(t, 4.0));
double before = wavg.Last.Value; // WAVG([1,2,3,4]) = (1+4+9+16)/10 = 3.0
// Correct last bar to different value
wavg.Update(new TValue(t, 10.0), isNew: false);
double corrected = wavg.Last.Value;
Assert.NotEqual(before, corrected); // correction changes result ✓
// Next new bar with value=4: window slides from corrected state [1,2,3,10] to [2,3,10,4]
// WAVG([2,3,10,4]) = (1*2+2*3+3*10+4*4)/10 = (2+6+30+16)/10 = 54/10 = 5.4
wavg.Update(new TValue(t, 4.0), isNew: true);
Assert.True(double.IsFinite(wavg.Last.Value)); // finite result
Assert.NotEqual(corrected, wavg.Last.Value); // new bar shifts the result
}
[Fact]
public void Reset_ClearsState()
{
var wavg = new Wavg(5);
for (int i = 0; i < 5; i++)
{
wavg.Update(new TValue(DateTime.UtcNow, 100.0));
}
Assert.True(wavg.IsHot);
wavg.Reset();
Assert.False(wavg.IsHot);
Assert.Equal(0, wavg.Last.Value);
}
// ── D) Warmup/convergence ────────────────────────────────────────────────
[Fact]
public void IsHot_FlipsAtPeriod()
{
int period = 8;
var wavg = new Wavg(period);
for (int i = 0; i < period - 1; i++)
{
wavg.Update(new TValue(DateTime.UtcNow, i));
Assert.False(wavg.IsHot);
}
wavg.Update(new TValue(DateTime.UtcNow, period));
Assert.True(wavg.IsHot);
}
// ── E) Robustness ───────────────────────────────────────────────────────
[Fact]
public void NaN_UsesLastValidValue()
{
var wavg = new Wavg(5);
for (int i = 0; i < 5; i++)
{
wavg.Update(new TValue(DateTime.UtcNow, 10.0));
}
wavg.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(wavg.Last.Value));
wavg.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
Assert.True(double.IsFinite(wavg.Last.Value));
}
[Fact]
public void AllNaN_DoesNotThrow()
{
var wavg = new Wavg(5);
for (int i = 0; i < 10; i++)
{
TValue result = wavg.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(result.Value));
}
}
// ── F) Consistency ────────────────────────────────────────────────────────
[Fact]
public void Consistency_BatchEqualsStreaming()
{
var rng = new GBM(startPrice: 100, mu: 0.0002, sigma: 0.02, seed: 99);
int n = 100;
int period = 14;
var prices = new double[n];
var times = new long[n];
var t0 = DateTime.UtcNow;
for (int i = 0; i < n; i++)
{
TBar bar = rng.Next();
prices[i] = bar.Close;
times[i] = (t0.AddMinutes(i)).Ticks;
}
// Streaming
var streamWavg = new Wavg(period);
double lastStream = 0;
for (int i = 0; i < n; i++)
{
lastStream = streamWavg.Update(new TValue(new DateTime(times[i], DateTimeKind.Utc), prices[i])).Value;
}
// Span batch
var spanOutput = new double[n];
Wavg.Batch(prices, spanOutput, period);
Assert.Equal(lastStream, spanOutput[n - 1], 6);
}
[Fact]
public void Consistency_SpanValidatesLengths()
{
var src = new double[10];
var dst = new double[9];
Assert.Throws<ArgumentException>(() => Wavg.Batch(src, dst, 5));
}
[Fact]
public void Consistency_SpanValidatesPeriod()
{
var src = new double[10];
var dst = new double[10];
Assert.Throws<ArgumentException>(() => Wavg.Batch(src, dst, 0));
}
// ── G) Eventing ──────────────────────────────────────────────────────────
[Fact]
public void Pub_FiresOnUpdate()
{
var wavg = new Wavg(5);
int fireCount = 0;
wavg.Pub += (object? _, in TValueEventArgs _) => fireCount++;
for (int i = 0; i < 10; i++)
{
wavg.Update(new TValue(DateTime.UtcNow, i));
}
Assert.Equal(10, fireCount);
}
[Fact]
public void Chaining_EventBased_Works()
{
var wavg1 = new Wavg(5);
var wavg2 = new Wavg(wavg1, 3);
for (int i = 0; i < 20; i++)
{
wavg1.Update(new TValue(DateTime.UtcNow, i + 1.0));
}
Assert.True(double.IsFinite(wavg2.Last.Value));
}
}