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

673 lines
20 KiB
C#

namespace QuanTAlib.Tests;
public class AdxvmaTests
{
// ==================== A) Constructor Validation ====================
[Fact]
public void Adxvma_Constructor_ThrowsOnZeroPeriod()
{
Assert.Throws<ArgumentException>(() => new Adxvma(period: 0));
}
[Fact]
public void Adxvma_Constructor_ThrowsOnNegativePeriod()
{
Assert.Throws<ArgumentException>(() => new Adxvma(period: -1));
}
[Fact]
public void Adxvma_Constructor_AcceptsValidPeriod()
{
var adxvma = new Adxvma(period: 14);
Assert.NotNull(adxvma);
Assert.Equal("Adxvma(14)", adxvma.Name);
}
[Fact]
public void Adxvma_Constructor_PeriodOneIsValid()
{
var adxvma = new Adxvma(period: 1);
Assert.NotNull(adxvma);
Assert.Equal("Adxvma(1)", adxvma.Name);
}
[Fact]
public void Adxvma_Constructor_DefaultPeriodIs14()
{
var adxvma = new Adxvma();
Assert.Equal("Adxvma(14)", adxvma.Name);
}
// ==================== B) Basic Calculation ====================
[Fact]
public void Adxvma_Calc_ReturnsValue()
{
var adxvma = new Adxvma();
Assert.Equal(0, adxvma.Last.Value);
TValue result = adxvma.Update(new TValue(DateTime.UtcNow, 100));
Assert.True(result.Value > 0);
Assert.Equal(result.Value, adxvma.Last.Value);
}
[Fact]
public void Adxvma_TBar_ReturnsValue()
{
var adxvma = new Adxvma();
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
TValue result = adxvma.Update(bar, isNew: true);
Assert.True(double.IsFinite(result.Value));
Assert.Equal(result.Value, adxvma.Last.Value);
}
[Fact]
public void Adxvma_TBar_UsesOHLC_ForTrueRange()
{
var adxvma = new Adxvma();
var time = DateTime.UtcNow;
// Feed bars with varying volatility
for (int i = 0; i < 100; i++)
{
var bar = new TBar(time.AddMinutes(i), 100, 105, 95, 100, 1000);
adxvma.Update(bar, isNew: true);
}
Assert.True(double.IsFinite(adxvma.Last.Value));
Assert.True(adxvma.IsHot, "Expected IsHot=true after 100 bars");
}
[Fact]
public void Adxvma_Properties_Accessible()
{
var adxvma = new Adxvma();
Assert.Equal(0, adxvma.Last.Value);
Assert.False(adxvma.IsHot);
adxvma.Update(new TValue(DateTime.UtcNow, 100));
Assert.NotEqual(0, adxvma.Last.Value);
}
// ==================== C) State + Bar Correction ====================
[Fact]
public void Adxvma_IsNew_True_AdvancesState()
{
var adxvma = new Adxvma();
adxvma.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
double value1 = adxvma.Last.Value;
adxvma.Update(new TValue(DateTime.UtcNow, 105), isNew: true);
double value2 = adxvma.Last.Value;
Assert.NotEqual(value1, value2);
}
[Fact]
public void Adxvma_IsNew_False_UpdatesValue()
{
var adxvma = new Adxvma();
adxvma.Update(new TValue(DateTime.UtcNow, 100));
adxvma.Update(new TValue(DateTime.UtcNow, 110), isNew: true);
double beforeUpdate = adxvma.Last.Value;
adxvma.Update(new TValue(DateTime.UtcNow, 120), isNew: false);
double afterUpdate = adxvma.Last.Value;
// Update should change the value
Assert.NotEqual(beforeUpdate, afterUpdate);
}
[Fact]
public void Adxvma_IterativeCorrections_RestoreToOriginalState()
{
var adxvma = new Adxvma();
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 10 new values
TValue tenthInput = default;
for (int i = 0; i < 10; i++)
{
var bar = gbm.Next(isNew: true);
tenthInput = new TValue(bar.Time, bar.Close);
adxvma.Update(tenthInput, isNew: true);
}
// Remember state after 10 values
double afterTen = adxvma.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
adxvma.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 10th input again with isNew=false
TValue restored = adxvma.Update(tenthInput, isNew: false);
// Should match the original state after 10 values
Assert.Equal(afterTen, restored.Value, 1e-10);
}
[Fact]
public void Adxvma_TBar_BarCorrection_Works()
{
var adxvma = new Adxvma();
var time = DateTime.UtcNow;
// Feed some history
for (int i = 0; i < 5; i++)
{
var bar = new TBar(time.AddMinutes(i), 100, 105, 95, 102, 1000);
adxvma.Update(bar, isNew: true);
}
// New bar
var newBar = new TBar(time.AddMinutes(5), 102, 108, 99, 106, 1200);
adxvma.Update(newBar, isNew: true);
double afterNewBar = adxvma.Last.Value;
// Correction with different bar
var corrBar = new TBar(time.AddMinutes(5), 103, 107, 100, 104, 1100);
adxvma.Update(corrBar, isNew: false);
double afterCorrection = adxvma.Last.Value;
// Different bar data should produce different result
Assert.NotEqual(afterNewBar, afterCorrection);
// Correction with original bar should restore state
adxvma.Update(newBar, isNew: false);
Assert.Equal(afterNewBar, adxvma.Last.Value, 1e-10);
}
[Fact]
public void Adxvma_Reset_ClearsState()
{
var adxvma = new Adxvma();
adxvma.Update(new TValue(DateTime.UtcNow, 100));
adxvma.Update(new TValue(DateTime.UtcNow, 105));
double valueBefore = adxvma.Last.Value;
adxvma.Reset();
Assert.Equal(0, adxvma.Last.Value);
// After reset, should accept new values
adxvma.Update(new TValue(DateTime.UtcNow, 50));
Assert.NotEqual(0, adxvma.Last.Value);
Assert.NotEqual(valueBefore, adxvma.Last.Value);
}
// ==================== D) Warmup / Convergence ====================
[Fact]
public void Adxvma_IsHot_BecomesTrueAfterWarmup()
{
var adxvma = new Adxvma(period: 14);
// IsHot requires barCount >= period * 2 = 28
Assert.False(adxvma.IsHot);
int steps = 0;
while (!adxvma.IsHot && steps < 1000)
{
var bar = new TBar(DateTime.UtcNow.AddMinutes(steps), 100, 105, 95, 100, 1000);
adxvma.Update(bar, isNew: true);
steps++;
}
Assert.True(adxvma.IsHot);
Assert.True(steps <= 28, $"Expected IsHot within 28 bars but took {steps}");
}
[Fact]
public void Adxvma_WarmupPeriod_EqualsDoubleThePeriod()
{
var adxvma = new Adxvma(period: 10);
Assert.Equal(20, adxvma.WarmupPeriod);
var adxvma2 = new Adxvma(period: 14);
Assert.Equal(28, adxvma2.WarmupPeriod);
}
[Fact]
public void Adxvma_ConstantOHLC_ConvergesToClose()
{
var adxvma = new Adxvma();
var time = DateTime.UtcNow;
// Feed constant OHLC bars
for (int i = 0; i < 200; i++)
{
var bar = new TBar(time.AddMinutes(i), 100, 100, 100, 100, 1000);
adxvma.Update(bar, isNew: true);
}
// With constant input, should converge to close
Assert.Equal(100.0, adxvma.Last.Value, 1e-9);
}
[Fact]
public void Adxvma_ConstantTValue_ConvergesToInput()
{
var adxvma = new Adxvma();
// Feed constant values via TValue (synthetic bar: O=H=L=C, TR=0)
for (int i = 0; i < 200; i++)
{
adxvma.Update(new TValue(DateTime.UtcNow, 42.5));
}
// With constant input, ADXVMA should converge to input
Assert.Equal(42.5, adxvma.Last.Value, 1e-9);
}
// ==================== E) Robustness ====================
[Fact]
public void Adxvma_NaN_Input_UsesLastValidValue()
{
var adxvma = new Adxvma();
adxvma.Update(new TValue(DateTime.UtcNow, 100));
adxvma.Update(new TValue(DateTime.UtcNow, 110));
var resultAfterNaN = adxvma.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(resultAfterNaN.Value));
Assert.NotEqual(0, resultAfterNaN.Value);
}
[Fact]
public void Adxvma_Infinity_Input_UsesLastValidValue()
{
var adxvma = new Adxvma();
adxvma.Update(new TValue(DateTime.UtcNow, 100));
adxvma.Update(new TValue(DateTime.UtcNow, 110));
var resultAfterPosInf = adxvma.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
Assert.True(double.IsFinite(resultAfterPosInf.Value));
var resultAfterNegInf = adxvma.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity));
Assert.True(double.IsFinite(resultAfterNegInf.Value));
}
[Fact]
public void Adxvma_MultipleNaN_ContinuesWithLastValid()
{
var adxvma = new Adxvma();
adxvma.Update(new TValue(DateTime.UtcNow, 100));
adxvma.Update(new TValue(DateTime.UtcNow, 110));
adxvma.Update(new TValue(DateTime.UtcNow, 120));
var r1 = adxvma.Update(new TValue(DateTime.UtcNow, double.NaN));
var r2 = adxvma.Update(new TValue(DateTime.UtcNow, double.NaN));
var r3 = adxvma.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(r1.Value));
Assert.True(double.IsFinite(r2.Value));
Assert.True(double.IsFinite(r3.Value));
}
[Fact]
public void Adxvma_BatchCalc_HandlesNaN()
{
var adxvma = new Adxvma();
var series = new TSeries();
series.Add(DateTime.UtcNow.Ticks, 100);
series.Add(DateTime.UtcNow.Ticks + 1, 110);
series.Add(DateTime.UtcNow.Ticks + 2, double.NaN);
series.Add(DateTime.UtcNow.Ticks + 3, 120);
series.Add(DateTime.UtcNow.Ticks + 4, double.PositiveInfinity);
series.Add(DateTime.UtcNow.Ticks + 5, 130);
var results = adxvma.Update(series);
foreach (var result in results)
{
Assert.True(double.IsFinite(result.Value), $"Expected finite value but got {result.Value}");
}
}
[Fact]
public void Adxvma_Reset_ClearsLastValidValue()
{
var adxvma = new Adxvma();
adxvma.Update(new TValue(DateTime.UtcNow, 100));
adxvma.Update(new TValue(DateTime.UtcNow, double.NaN));
adxvma.Reset();
// After reset, first valid value should establish new baseline
var result = adxvma.Update(new TValue(DateTime.UtcNow, 50));
Assert.Equal(50.0, result.Value, 1e-10);
}
// ==================== F) Consistency (All Modes Match) ====================
[Fact]
public void Adxvma_BatchCalc_MatchesIterativeCalc()
{
var adxvmaIterative = new Adxvma();
var adxvmaBatch = new Adxvma();
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
var series = new TSeries();
for (int i = 0; i < 100; i++)
{
var bar = gbm.Next(isNew: true);
series.Add(bar.Time, bar.Close);
}
Assert.True(series.Count > 0);
// Calculate iteratively
var iterativeResults = new TSeries();
foreach (var item in series)
{
iterativeResults.Add(adxvmaIterative.Update(item));
}
// Calculate batch
var batchResults = adxvmaBatch.Update(series);
// Compare
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
{
Assert.Equal(iterativeResults[i].Value, batchResults[i].Value, 1e-10);
Assert.Equal(iterativeResults[i].Time, batchResults[i].Time);
}
}
[Fact]
public void Adxvma_TBarSeries_MatchesIterativeTBar()
{
var adxvmaIterative = new Adxvma();
var adxvmaBatch = new Adxvma();
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.15, seed: 42);
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Calculate iteratively with TBar
var iterativeResults = new TSeries();
foreach (var bar in bars)
{
iterativeResults.Add(adxvmaIterative.Update(bar, isNew: true));
}
// Calculate batch with TBarSeries
var batchResults = adxvmaBatch.Update(bars);
// Compare
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
{
Assert.Equal(iterativeResults[i].Value, batchResults[i].Value, 1e-10);
}
}
[Fact]
public void Adxvma_AllModes_ProduceSameResult()
{
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 = Adxvma.Batch(series);
double expected = batchSeries.Last.Value;
// 2. Streaming Mode
var streamingInd = new Adxvma();
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 3. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Adxvma(pubSource);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
}
// ==================== G) TBar-specific Tests ====================
[Fact]
public void Adxvma_TBarSeries_BatchWorks()
{
var gbm = new GBM(startPrice: 100, mu: 0.02, sigma: 0.15, seed: 42);
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var result = Adxvma.Batch(bars);
Assert.Equal(200, result.Count);
Assert.All(result, tv => Assert.True(double.IsFinite(tv.Value)));
}
[Fact]
public void Adxvma_TValue_SyntheticBar_ProducesValidOutput()
{
// TValue creates synthetic bar: O=H=L=C → TR=0 → ADX→0 → sc→0 → flat line
var adxvma = new Adxvma();
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
for (int i = 0; i < 50; i++)
{
var bar = gbm.Next(isNew: true);
var result = adxvma.Update(new TValue(bar.Time, bar.Close), isNew: true);
Assert.True(double.IsFinite(result.Value));
}
}
[Fact]
public void Adxvma_StrongTrend_HighADX_TracksPrice()
{
var adxvma = new Adxvma(period: 14);
var time = DateTime.UtcNow;
// Feed strong uptrend bars (large +DM consistently)
for (int i = 0; i < 50; i++)
{
double price = 100 + i * 2;
var bar = new TBar(time.AddMinutes(i), price, price + 1, price - 0.5, price + 0.5, 1000);
adxvma.Update(bar, isNew: true);
}
// In a strong trend, ADX is high so sc ≈ 1, ADXVMA should track price closely
double adxvmaValue = adxvma.Last.Value;
// Should be within reasonable proximity of recent prices
Assert.True(adxvmaValue > 100, $"ADXVMA ({adxvmaValue}) should be well above 100 in a strong uptrend");
}
[Fact]
public void Adxvma_Calculate_TBarSeries_ReturnsIndicator()
{
var gbm = new GBM(startPrice: 100, mu: 0.02, sigma: 0.15, seed: 42);
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var (results, indicator) = Adxvma.Calculate(bars);
Assert.Equal(200, results.Count);
Assert.NotNull(indicator);
Assert.True(indicator.IsHot);
Assert.Equal(results.Last.Value, indicator.Last.Value, 1e-10);
}
[Fact]
public void Adxvma_Calculate_TSeries_ReturnsIndicator()
{
var gbm = new GBM(startPrice: 100, mu: 0.02, sigma: 0.15, seed: 42);
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var (results, indicator) = Adxvma.Calculate(series);
Assert.Equal(200, results.Count);
Assert.NotNull(indicator);
Assert.True(indicator.IsHot);
Assert.Equal(results.Last.Value, indicator.Last.Value, 1e-10);
}
// ==================== H) Chainability ====================
[Fact]
public void Adxvma_Chainability_Works()
{
var source = new TSeries();
var adxvma = new Adxvma(source);
source.Add(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, adxvma.Last.Value, 1e-10);
}
// ==================== Prime Tests ====================
[Fact]
public void Adxvma_Prime_SetsStateCorrectly()
{
var adxvma = new Adxvma();
double[] history = [10, 20, 30, 40, 50];
adxvma.Prime(history);
var verifyAdxvma = new Adxvma();
foreach (var val in history)
{
verifyAdxvma.Update(new TValue(DateTime.UtcNow, val));
}
Assert.Equal(verifyAdxvma.Last.Value, adxvma.Last.Value, 1e-10);
// Verify it continues correctly
adxvma.Update(new TValue(DateTime.UtcNow, 60));
verifyAdxvma.Update(new TValue(DateTime.UtcNow, 60));
Assert.Equal(verifyAdxvma.Last.Value, adxvma.Last.Value, 1e-10);
}
[Fact]
public void Adxvma_Prime_HandlesNaN_InHistory()
{
var adxvma = new Adxvma();
double[] history = [10, 20, double.NaN, 40, 50];
adxvma.Prime(history);
var verifyAdxvma = new Adxvma();
foreach (var val in history)
{
verifyAdxvma.Update(new TValue(DateTime.UtcNow, val));
}
Assert.Equal(verifyAdxvma.Last.Value, adxvma.Last.Value, 1e-10);
}
[Fact]
public void Adxvma_Prime_ThenUpdate_StateWorksCorrectly()
{
var adxvma = new Adxvma();
double[] history = [10, 20, 30, 40, 50];
adxvma.Prime(history);
double afterPrime = adxvma.Last.Value;
// After Prime, isNew=true should advance the state
adxvma.Update(new TValue(DateTime.UtcNow, 60), isNew: true);
double afterNewBar = adxvma.Last.Value;
Assert.NotEqual(afterPrime, afterNewBar);
// isNew=false with different value should recalculate
adxvma.Update(new TValue(DateTime.UtcNow, 70), isNew: false);
double afterCorrection = adxvma.Last.Value;
Assert.NotEqual(afterNewBar, afterCorrection);
// isNew=false with original value should restore
adxvma.Update(new TValue(DateTime.UtcNow, 60), isNew: false);
Assert.Equal(afterNewBar, adxvma.Last.Value, 1e-10);
}
// ==================== Dispose Test ====================
[Fact]
public void Adxvma_Dispose_DoesNotThrow()
{
var adxvma = new Adxvma();
adxvma.Update(new TValue(DateTime.UtcNow, 100));
adxvma.Dispose();
// Should be able to create a new one after dispose
var adxvma2 = new Adxvma();
Assert.NotNull(adxvma2);
}
// ==================== Parameter Variation Tests ====================
[Fact]
public void Adxvma_ParameterVariations_ProduceValidResults()
{
var gbm = new GBM(startPrice: 100, mu: 0.02, sigma: 0.15, seed: 42);
var adxvma1 = new Adxvma(7);
var adxvma2 = new Adxvma(14);
var adxvma3 = new Adxvma(28);
for (int i = 0; i < 200; i++)
{
var bar = gbm.Next(isNew: true);
var tv = new TValue(bar.Time, bar.Close);
adxvma1.Update(tv, isNew: true);
adxvma2.Update(tv, isNew: true);
adxvma3.Update(tv, isNew: true);
}
Assert.True(double.IsFinite(adxvma1.Last.Value));
Assert.True(double.IsFinite(adxvma2.Last.Value));
Assert.True(double.IsFinite(adxvma3.Last.Value));
}
[Fact]
public void Adxvma_DifferentPeriods_DifferentSmoothness()
{
var gbm = new GBM(startPrice: 100, mu: 0.02, sigma: 0.15, seed: 42);
var bars = gbm.Fetch(300, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var results7 = Adxvma.Batch(bars, period: 7);
var results28 = Adxvma.Batch(bars, period: 28);
// Both should produce valid values, but longer period should be smoother
Assert.All(results7, tv => Assert.True(double.IsFinite(tv.Value)));
Assert.All(results28, tv => Assert.True(double.IsFinite(tv.Value)));
// Different periods should produce different results
Assert.NotEqual(results7.Last.Value, results28.Last.Value);
}
}