mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 12:38:06 +00:00
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
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
using TradingPlatform.BusinessLayer;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public sealed class TtmWaveIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Contains("TTM Wave", indicator.Name, StringComparison.Ordinal);
|
||||
Assert.True(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_MinHistoryDepths_EqualsZero()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
|
||||
Assert.Equal(0, TtmWaveIndicator.MinHistoryDepths);
|
||||
IWatchlistIndicator watchlistIndicator = indicator;
|
||||
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_ShortName_IncludesIdentifier()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("TTM_Wave", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_SourceCodeLink_IsValid()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("TtmWave", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_Initialize_CreatesLineSeries()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
|
||||
indicator.Initialize();
|
||||
|
||||
// 6 wave histograms + 1 zero line = 7 series
|
||||
Assert.Equal(7, indicator.LinesSeries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 800; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i * 0.1, 110 + i * 0.1, 90 + i * 0.1, 105 + i * 0.1);
|
||||
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
}
|
||||
|
||||
// Wave A1 (index 4 — added 5th in constructor order: C1,C2,B1,B2,A1,A2,Zero)
|
||||
double waveA1 = indicator.LinesSeries[4].GetValue(0);
|
||||
|
||||
Assert.True(double.IsFinite(waveA1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 800; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i * 0.1, 110 + i * 0.1, 90 + i * 0.1, 105 + i * 0.1);
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
}
|
||||
|
||||
// Simulate a new bar
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(800), 180, 190, 170, 185);
|
||||
var newArgs = new UpdateArgs(UpdateReason.NewBar);
|
||||
indicator.ProcessUpdate(newArgs);
|
||||
|
||||
double waveA1 = indicator.LinesSeries[4].GetValue(0);
|
||||
|
||||
Assert.True(double.IsFinite(waveA1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_ZeroLine_IsSet()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
}
|
||||
|
||||
// Zero line is the last series (index 6)
|
||||
double zero = indicator.LinesSeries[6].GetValue(0);
|
||||
Assert.Equal(0.0, zero, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_Description_IsSet()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
|
||||
Assert.NotNull(indicator.Description);
|
||||
Assert.NotEmpty(indicator.Description);
|
||||
Assert.Contains("TTM", indicator.Description, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmWaveIndicator_AllSeries_ProduceFiniteValues()
|
||||
{
|
||||
var indicator = new TtmWaveIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 800; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i * 0.1, 110 + i * 0.1, 90 + i * 0.1, 105 + i * 0.1);
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
}
|
||||
|
||||
// All 7 series should have finite values
|
||||
for (int s = 0; s < 7; s++)
|
||||
{
|
||||
double val = indicator.LinesSeries[s].GetValue(0);
|
||||
Assert.True(double.IsFinite(val), $"Series {s} value not finite: {val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,735 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// A) Constructor Validation
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveConstructorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_Default_SetsName()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
Assert.Equal("TtmWave", wave.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Default_NotHot()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
Assert.False(wave.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WarmupPeriod_Is752()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
// max(8, 377) + 377 - 2 = 752
|
||||
Assert.Equal(752, wave.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Chaining_SubscribesToSource()
|
||||
{
|
||||
var source = new Ema(10);
|
||||
using var wave = new TtmWave(source);
|
||||
Assert.Equal("TtmWave", wave.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_DefaultOutputs_AreDefault()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
Assert.Equal(0, wave.WaveA1.Value);
|
||||
Assert.Equal(0, wave.WaveA2.Value);
|
||||
Assert.Equal(0, wave.WaveB1.Value);
|
||||
Assert.Equal(0, wave.WaveB2.Value);
|
||||
Assert.Equal(0, wave.WaveC1.Value);
|
||||
Assert.Equal(0, wave.WaveC2.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// B) Basic Calculation
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveBasicTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ReturnsTValue()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var input = new TValue(DateTime.UtcNow, 100.0);
|
||||
var result = wave.Update(input);
|
||||
Assert.IsType<TValue>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_Last_IsAccessible()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var input = new TValue(DateTime.UtcNow, 100.0);
|
||||
wave.Update(input);
|
||||
Assert.Equal(wave.Wave1.Value, wave.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_AllWaves_PopulatedAfterUpdate()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(100);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
// After 100 bars, waves should have non-default values
|
||||
// (A wave should be non-zero since warmup for channel 1 is only 66)
|
||||
Assert.NotEqual(0, wave.WaveA2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_Wave1_EqualsWaveA2()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(100);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(wave.WaveA2.Value, wave.Wave1.Value);
|
||||
Assert.Equal(wave.WaveA2.Time, wave.Wave1.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_Wave2High_IsMaxOfC()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(800);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(Math.Max(wave.WaveC1.Value, wave.WaveC2.Value), wave.Wave2High);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_Wave2Low_IsMinOfC()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(800);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(Math.Min(wave.WaveC1.Value, wave.WaveC2.Value), wave.Wave2Low);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// C) State + Bar Correction
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveBarCorrectionTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_True_AdvancesState()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
double valBefore = wave.Last.Value;
|
||||
wave.Update(series[100], isNew: true);
|
||||
|
||||
Assert.NotEqual(valBefore, wave.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_False_RewritesCurrentBar()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
// First update as new bar
|
||||
wave.Update(series[100], isNew: true);
|
||||
double afterNew = wave.Last.Value;
|
||||
|
||||
// Update same bar with different value
|
||||
var modified = new TValue(series[100].Time, series[100].Value + 5.0);
|
||||
wave.Update(modified, isNew: false);
|
||||
|
||||
// Re-update with original value should restore
|
||||
wave.Update(series[100], isNew: false);
|
||||
double afterRestore = wave.Last.Value;
|
||||
|
||||
Assert.Equal(afterNew, afterRestore, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IterativeCorrections_Restore()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
// Multiple rewrites followed by same-value restore
|
||||
wave.Update(series[100], isNew: true);
|
||||
double baseline = wave.Last.Value;
|
||||
|
||||
for (int j = 0; j < 5; j++)
|
||||
{
|
||||
var tick = new TValue(series[100].Time, series[100].Value + (j * 2.0));
|
||||
wave.Update(tick, isNew: false);
|
||||
}
|
||||
|
||||
wave.Update(series[100], isNew: false);
|
||||
Assert.Equal(baseline, wave.Last.Value, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// D) Warmup / Convergence
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveWarmupTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsHot_FlipsAfterWarmup()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(800);
|
||||
|
||||
bool wasHot = false;
|
||||
int hotAt = -1;
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
if (wave.IsHot && !wasHot)
|
||||
{
|
||||
wasHot = true;
|
||||
hotAt = i;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(wasHot, "Indicator never became hot");
|
||||
// Should become hot at or near WarmupPeriod (752)
|
||||
Assert.True(hotAt <= wave.WarmupPeriod, $"Became hot at {hotAt}, expected <= {wave.WarmupPeriod}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsHot_StaysCold_BeforeWarmup()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(100);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
// 100 bars is not enough for 752 warmup
|
||||
Assert.False(wave.IsHot);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// E) Robustness (NaN / Infinity)
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveRobustnessTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NaN_Input_ProducesFiniteOutput()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(100);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
// Feed NaN
|
||||
var nanInput = new TValue(DateTime.UtcNow, double.NaN);
|
||||
wave.Update(nanInput, isNew: true);
|
||||
|
||||
// MACD internally handles NaN via Ema which substitutes last valid
|
||||
Assert.True(double.IsFinite(wave.Last.Value) || wave.Last.Value == 0,
|
||||
"NaN input should not propagate to output");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Infinity_Input_ProducesFiniteOutput()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(100);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
var infInput = new TValue(DateTime.UtcNow, double.PositiveInfinity);
|
||||
wave.Update(infInput, isNew: true);
|
||||
|
||||
// Should handle gracefully
|
||||
Assert.True(double.IsFinite(wave.Last.Value) || wave.Last.Value == 0,
|
||||
"Infinity input should not propagate to output");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchNaN_Safe()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
|
||||
// Feed mixture of valid and NaN
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
double val = (i % 10 == 0) ? double.NaN : 100.0 + i;
|
||||
wave.Update(new TValue(DateTime.UtcNow.AddMinutes(i), val), isNew: true);
|
||||
}
|
||||
|
||||
// Should not throw
|
||||
Assert.True(double.IsFinite(wave.Last.Value) || wave.Last.Value == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// F) Consistency (Batch == Streaming == Eventing)
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveConsistencyTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchCalc_EqualsStreaming()
|
||||
{
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
// Batch
|
||||
var batchResults = TtmWave.Batch(series);
|
||||
|
||||
// Streaming
|
||||
var streamWave = new TtmWave();
|
||||
var streamResults = new List<double>();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamWave.Update(series[i], isNew: true);
|
||||
streamResults.Add(streamWave.Last.Value);
|
||||
}
|
||||
|
||||
Assert.Equal(batchResults.Count, streamResults.Count);
|
||||
for (int i = 0; i < batchResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(batchResults.Values[i], streamResults[i], 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_ReturnsBothResults()
|
||||
{
|
||||
var series = GenerateSeries(200);
|
||||
var (results, indicator) = TtmWave.Calculate(series);
|
||||
|
||||
Assert.NotNull(results);
|
||||
Assert.NotNull(indicator);
|
||||
Assert.Equal(200, results.Count);
|
||||
Assert.Equal("TtmWave", indicator.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EventBased_MatchesStreaming()
|
||||
{
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
// Streaming
|
||||
var streamWave = new TtmWave();
|
||||
var streamResults = new List<double>();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamWave.Update(series[i], isNew: true);
|
||||
streamResults.Add(streamWave.Last.Value);
|
||||
}
|
||||
|
||||
// Event-based
|
||||
var eventSource = new Ema(1); // Pass-through: EMA(1) = identity
|
||||
using var eventWave = new TtmWave(eventSource);
|
||||
var eventResults = new List<double>();
|
||||
eventWave.Pub += (object? _, in TValueEventArgs args) => eventResults.Add(args.Value.Value);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
eventSource.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(streamResults.Count, eventResults.Count);
|
||||
for (int i = 0; i < streamResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamResults[i], eventResults[i], 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_TSeries_MatchesStreaming()
|
||||
{
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
// TSeries batch via Update
|
||||
var batchWave = new TtmWave();
|
||||
var batchResults = batchWave.Update(series);
|
||||
|
||||
// Streaming
|
||||
var streamWave = new TtmWave();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamWave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(series.Count, batchResults.Count);
|
||||
// Last values should match
|
||||
Assert.Equal(streamWave.Last.Value, batchResults.Values[^1], 10);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// G) Reset Tests
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveResetTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ClearsAllState()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
wave.Reset();
|
||||
|
||||
Assert.False(wave.IsHot);
|
||||
Assert.Equal(0, wave.WaveA1.Value);
|
||||
Assert.Equal(0, wave.WaveA2.Value);
|
||||
Assert.Equal(0, wave.WaveB1.Value);
|
||||
Assert.Equal(0, wave.WaveB2.Value);
|
||||
Assert.Equal(0, wave.WaveC1.Value);
|
||||
Assert.Equal(0, wave.WaveC2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ThenReprocess_MatchesOriginal()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
// First pass
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
double firstPassLast = wave.Last.Value;
|
||||
|
||||
// Reset and reprocess
|
||||
wave.Reset();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
double secondPassLast = wave.Last.Value;
|
||||
|
||||
Assert.Equal(firstPassLast, secondPassLast, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// H) Batch / Static API Tests
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveBatchTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_ReturnsCorrectLength()
|
||||
{
|
||||
var series = GenerateSeries(200);
|
||||
var result = TtmWave.Batch(series);
|
||||
Assert.Equal(200, result.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_EmptyInput_ReturnsEmpty()
|
||||
{
|
||||
var series = new TSeries([], []);
|
||||
var result = TtmWave.Batch(series);
|
||||
Assert.True(result.Count == 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_ReturnsWarmIndicator()
|
||||
{
|
||||
var series = GenerateSeries(800);
|
||||
var (results, indicator) = TtmWave.Calculate(series);
|
||||
|
||||
Assert.Equal(800, results.Count);
|
||||
Assert.True(indicator.IsHot);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// I) Prime Tests
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWavePrimeTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prime_SetsState()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(200);
|
||||
|
||||
wave.Prime(series);
|
||||
|
||||
// After priming, wave should have processed all data
|
||||
Assert.NotEqual(0, wave.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prime_ThenUpdate_ContinuesCorrectly()
|
||||
{
|
||||
var series = GenerateSeries(300);
|
||||
|
||||
// Reference: process all 300 bars
|
||||
var refWave = new TtmWave();
|
||||
for (int i = 0; i < 300; i++)
|
||||
{
|
||||
refWave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
// Prime with first 200, then stream remaining 100
|
||||
var primeWave = new TtmWave();
|
||||
var tList = new List<long>(200);
|
||||
var vList = new List<double>(200);
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
tList.Add(series.Times[i]);
|
||||
vList.Add(series.Values[i]);
|
||||
}
|
||||
var primeSeries = new TSeries(tList, vList);
|
||||
primeWave.Prime(primeSeries);
|
||||
|
||||
for (int i = 200; i < 300; i++)
|
||||
{
|
||||
primeWave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(refWave.Last.Value, primeWave.Last.Value, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prime_EmptySeries_NoOp()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var empty = new TSeries([], []);
|
||||
wave.Prime(empty);
|
||||
Assert.False(wave.IsHot);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// J) Event / Chainability Tests
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Pub_Fires_OnUpdate()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
int fireCount = 0;
|
||||
wave.Pub += (object? _, in TValueEventArgs _a) => fireCount++;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
wave.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i), isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(10, fireCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Chaining_PropagatesValues()
|
||||
{
|
||||
var source = new Ema(1);
|
||||
using var wave = new TtmWave(source);
|
||||
var received = new List<double>();
|
||||
wave.Pub += (object? _, in TValueEventArgs args) => received.Add(args.Value.Value);
|
||||
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
source.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i), isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(50, received.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_UnsubscribesFromSource()
|
||||
{
|
||||
var source = new Ema(1);
|
||||
var wave = new TtmWave(source);
|
||||
int fireCount = 0;
|
||||
wave.Pub += (object? _, in TValueEventArgs _a) => fireCount++;
|
||||
|
||||
source.Update(new TValue(DateTime.UtcNow, 100.0), isNew: true);
|
||||
Assert.Equal(1, fireCount);
|
||||
|
||||
wave.Dispose();
|
||||
|
||||
source.Update(new TValue(DateTime.UtcNow, 101.0), isNew: true);
|
||||
Assert.Equal(1, fireCount); // Should not fire again
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// K) Multi-Output Verification
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
public sealed class TtmWaveMultiOutputTests
|
||||
{
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).Close;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllSixWaves_HaveSameTimestamp()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(100);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
long t = wave.WaveA1.Time;
|
||||
Assert.Equal(t, wave.WaveA2.Time);
|
||||
Assert.Equal(t, wave.WaveB1.Time);
|
||||
Assert.Equal(t, wave.WaveB2.Time);
|
||||
Assert.Equal(t, wave.WaveC1.Time);
|
||||
Assert.Equal(t, wave.WaveC2.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WaveAmplitudes_IncreaseWithPeriod()
|
||||
{
|
||||
// Longer-period waves tend to have larger absolute values
|
||||
// after sufficient warmup, because they capture more price movement.
|
||||
// This is a soft heuristic test, not a hard rule.
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(1000);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
// Just verify all waves are finite and output different values
|
||||
Assert.True(double.IsFinite(wave.WaveA1.Value));
|
||||
Assert.True(double.IsFinite(wave.WaveA2.Value));
|
||||
Assert.True(double.IsFinite(wave.WaveB1.Value));
|
||||
Assert.True(double.IsFinite(wave.WaveB2.Value));
|
||||
Assert.True(double.IsFinite(wave.WaveC1.Value));
|
||||
Assert.True(double.IsFinite(wave.WaveC2.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Waves_IndependentValues()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
var series = GenerateSeries(800);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
// Different channels should produce different values
|
||||
// (extremely unlikely for all 6 to be identical with random data)
|
||||
var values = new HashSet<double>
|
||||
{
|
||||
wave.WaveA1.Value,
|
||||
wave.WaveA2.Value,
|
||||
wave.WaveB1.Value,
|
||||
wave.WaveB2.Value,
|
||||
wave.WaveC1.Value,
|
||||
wave.WaveC2.Value
|
||||
};
|
||||
|
||||
Assert.True(values.Count >= 3, "At least 3 of 6 wave values should be distinct");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// TTM Wave validation tests.
|
||||
/// No external libraries (Skender/TA-Lib/Tulip/Ooples) implement TTM Wave,
|
||||
/// so validation is self-consistency: streaming vs batch, prime vs cold,
|
||||
/// deterministic reproducibility, and multi-wave coherence checks.
|
||||
/// </summary>
|
||||
public sealed class TtmWaveValidationTests
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public TtmWaveValidationTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
private static TSeries GenerateSeries(int count, int seed = 42)
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.15, seed: seed);
|
||||
var bars = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
// Extract close prices into TSeries for TtmWave (which operates on single values)
|
||||
var t = new List<long>(count);
|
||||
var v = new List<double>(count);
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
t.Add(bars[i].Time);
|
||||
v.Add(bars[i].Close); // Close price
|
||||
}
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
// --- A) Streaming vs Batch agreement ---
|
||||
|
||||
[Fact]
|
||||
public void Streaming_Matches_Batch()
|
||||
{
|
||||
var series = GenerateSeries(1000);
|
||||
|
||||
var wave = new TtmWave();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
}
|
||||
|
||||
var batch = TtmWave.Batch(series);
|
||||
|
||||
Assert.Equal(wave.Last.Value, batch[^1].Value, 1e-10);
|
||||
_output.WriteLine($"Streaming last={wave.Last.Value:F10}, Batch last={batch[^1].Value:F10}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Streaming_Matches_Batch_AllValues()
|
||||
{
|
||||
var series = GenerateSeries(1000);
|
||||
int warmup = 752;
|
||||
|
||||
var wave = new TtmWave();
|
||||
var streamValues = new double[series.Count];
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
streamValues[i] = wave.Last.Value;
|
||||
}
|
||||
|
||||
var batch = TtmWave.Batch(series);
|
||||
|
||||
int mismatches = 0;
|
||||
for (int i = warmup; i < series.Count; i++)
|
||||
{
|
||||
double diff = Math.Abs(streamValues[i] - batch[i].Value);
|
||||
if (diff > 1e-8)
|
||||
{
|
||||
mismatches++;
|
||||
if (mismatches <= 5)
|
||||
{
|
||||
_output.WriteLine($"Mismatch at i={i}: stream={streamValues[i]:F10}, batch={batch[i].Value:F10}, diff={diff:E3}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(0, mismatches);
|
||||
}
|
||||
|
||||
// --- B) Primed vs Cold start agreement ---
|
||||
|
||||
[Fact]
|
||||
public void Primed_Matches_Cold_Start()
|
||||
{
|
||||
var series = GenerateSeries(1000);
|
||||
int splitAt = 800;
|
||||
|
||||
// Cold: process all at once
|
||||
var cold = new TtmWave();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
cold.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
}
|
||||
|
||||
// Primed: prime with first chunk, then stream remainder
|
||||
var primed = new TtmWave();
|
||||
var primeSeries = GenerateSubSeries(series, splitAt);
|
||||
primed.Prime(primeSeries);
|
||||
|
||||
for (int i = splitAt; i < series.Count; i++)
|
||||
{
|
||||
primed.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
}
|
||||
|
||||
double diff = Math.Abs(cold.Last.Value - primed.Last.Value);
|
||||
_output.WriteLine($"Cold={cold.Last.Value:F10}, Primed={primed.Last.Value:F10}, diff={diff:E3}");
|
||||
Assert.True(diff < 1e-8, $"Primed vs cold diff={diff:E3} exceeds tolerance");
|
||||
}
|
||||
|
||||
// --- C) Deterministic reproducibility ---
|
||||
|
||||
[Fact]
|
||||
public void Same_Input_Produces_Same_Output()
|
||||
{
|
||||
var series1 = GenerateSeries(1000, seed: 99);
|
||||
var series2 = GenerateSeries(1000, seed: 99);
|
||||
|
||||
var batch1 = TtmWave.Batch(series1);
|
||||
var batch2 = TtmWave.Batch(series2);
|
||||
|
||||
for (int i = 0; i < batch1.Count; i++)
|
||||
{
|
||||
Assert.Equal(batch1[i].Value, batch2[i].Value, 1e-15);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Different_Seed_Produces_Different_Output()
|
||||
{
|
||||
var series1 = GenerateSeries(1000, seed: 42);
|
||||
var series2 = GenerateSeries(1000, seed: 99);
|
||||
|
||||
var batch1 = TtmWave.Batch(series1);
|
||||
var batch2 = TtmWave.Batch(series2);
|
||||
|
||||
bool anyDifferent = false;
|
||||
for (int i = 800; i < batch1.Count; i++)
|
||||
{
|
||||
if (Math.Abs(batch1[i].Value - batch2[i].Value) > 1e-6)
|
||||
{
|
||||
anyDifferent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(anyDifferent, "Different seeds should produce different outputs");
|
||||
}
|
||||
|
||||
// --- D) Multi-wave coherence ---
|
||||
|
||||
[Fact]
|
||||
public void All_Six_Waves_Produce_Finite_Values()
|
||||
{
|
||||
var series = GenerateSeries(1000);
|
||||
var wave = new TtmWave();
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(wave.WaveA1.Value), "WaveA1 not finite");
|
||||
Assert.True(double.IsFinite(wave.WaveA2.Value), "WaveA2 not finite");
|
||||
Assert.True(double.IsFinite(wave.WaveB1.Value), "WaveB1 not finite");
|
||||
Assert.True(double.IsFinite(wave.WaveB2.Value), "WaveB2 not finite");
|
||||
Assert.True(double.IsFinite(wave.WaveC1.Value), "WaveC1 not finite");
|
||||
Assert.True(double.IsFinite(wave.WaveC2.Value), "WaveC2 not finite");
|
||||
|
||||
_output.WriteLine($"A1={wave.WaveA1.Value:F6}, A2={wave.WaveA2.Value:F6}");
|
||||
_output.WriteLine($"B1={wave.WaveB1.Value:F6}, B2={wave.WaveB2.Value:F6}");
|
||||
_output.WriteLine($"C1={wave.WaveC1.Value:F6}, C2={wave.WaveC2.Value:F6}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Wave_Magnitudes_Follow_Expected_Ordering()
|
||||
{
|
||||
// Longer-period MACD channels should generally have larger absolute histograms
|
||||
// (wider slow EMA separation from fast). Not guaranteed per-bar, but on average.
|
||||
var series = GenerateSeries(2000);
|
||||
var wave = new TtmWave();
|
||||
|
||||
double sumAbsA = 0, sumAbsB = 0, sumAbsC = 0;
|
||||
int hotBars = 0;
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
if (wave.IsHot)
|
||||
{
|
||||
sumAbsA += Math.Abs(wave.WaveA1.Value) + Math.Abs(wave.WaveA2.Value);
|
||||
sumAbsB += Math.Abs(wave.WaveB1.Value) + Math.Abs(wave.WaveB2.Value);
|
||||
sumAbsC += Math.Abs(wave.WaveC1.Value) + Math.Abs(wave.WaveC2.Value);
|
||||
hotBars++;
|
||||
}
|
||||
}
|
||||
|
||||
double avgA = sumAbsA / (2 * hotBars);
|
||||
double avgB = sumAbsB / (2 * hotBars);
|
||||
double avgC = sumAbsC / (2 * hotBars);
|
||||
|
||||
_output.WriteLine($"Avg |A|={avgA:F6}, |B|={avgB:F6}, |C|={avgC:F6}, hotBars={hotBars}");
|
||||
|
||||
// Longer periods tend to produce larger histogram deviations on trending GBM data
|
||||
Assert.True(avgC > avgA * 0.5, $"Wave C avg ({avgC:F6}) should not be drastically smaller than A ({avgA:F6})");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TOS_Compatibility_Properties_Are_Consistent()
|
||||
{
|
||||
var series = GenerateSeries(1000);
|
||||
var wave = new TtmWave();
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
}
|
||||
|
||||
// Wave1 == WaveA2 (per TOS mapping)
|
||||
Assert.Equal(wave.WaveA2.Value, wave.Wave1.Value, 1e-15);
|
||||
|
||||
// Wave2High = max(C1, C2)
|
||||
Assert.Equal(Math.Max(wave.WaveC1.Value, wave.WaveC2.Value), wave.Wave2High, 1e-15);
|
||||
|
||||
// Wave2Low = min(C1, C2)
|
||||
Assert.Equal(Math.Min(wave.WaveC1.Value, wave.WaveC2.Value), wave.Wave2Low, 1e-15);
|
||||
|
||||
// Last == Wave1
|
||||
Assert.Equal(wave.Wave1.Value, wave.Last.Value, 1e-15);
|
||||
}
|
||||
|
||||
// --- E) Calculate returns warm indicator ---
|
||||
|
||||
[Fact]
|
||||
public void Calculate_Returns_Warm_Indicator()
|
||||
{
|
||||
var series = GenerateSeries(1000);
|
||||
var (results, indicator) = TtmWave.Calculate(series);
|
||||
|
||||
Assert.Equal(series.Count, results.Count);
|
||||
Assert.True(indicator.IsHot);
|
||||
Assert.Equal(results[^1].Value, indicator.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
// --- F) Reset produces clean slate ---
|
||||
|
||||
[Fact]
|
||||
public void Reset_Then_Replay_Matches_Fresh()
|
||||
{
|
||||
var series = GenerateSeries(1000);
|
||||
|
||||
var wave = new TtmWave();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
}
|
||||
double firstRun = wave.Last.Value;
|
||||
|
||||
wave.Reset();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
}
|
||||
double secondRun = wave.Last.Value;
|
||||
|
||||
Assert.Equal(firstRun, secondRun, 1e-15);
|
||||
}
|
||||
|
||||
// --- G) Large dataset stability ---
|
||||
|
||||
[Fact]
|
||||
public void Large_Dataset_No_Overflow()
|
||||
{
|
||||
var series = GenerateSeries(5000);
|
||||
var wave = new TtmWave();
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
}
|
||||
|
||||
Assert.True(wave.IsHot);
|
||||
Assert.True(double.IsFinite(wave.Last.Value), "Last value should be finite after 5000 bars");
|
||||
Assert.True(double.IsFinite(wave.WaveC1.Value), "WaveC1 should be finite after 5000 bars");
|
||||
Assert.True(double.IsFinite(wave.WaveC2.Value), "WaveC2 should be finite after 5000 bars");
|
||||
}
|
||||
|
||||
// --- H) Warm-up period validation ---
|
||||
|
||||
[Fact]
|
||||
public void WarmupPeriod_Is_752()
|
||||
{
|
||||
var wave = new TtmWave();
|
||||
Assert.Equal(752, wave.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsHot_False_Before_Warmup_True_After()
|
||||
{
|
||||
var series = GenerateSeries(1000);
|
||||
var wave = new TtmWave();
|
||||
|
||||
bool wasHot = false;
|
||||
int firstHotBar = -1;
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
wave.Update(new TValue(new DateTime(series.Times[i], DateTimeKind.Utc), series.Values[i]));
|
||||
if (wave.IsHot && !wasHot)
|
||||
{
|
||||
firstHotBar = i;
|
||||
wasHot = true;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(wasHot, "Should become hot before 1000 bars");
|
||||
_output.WriteLine($"First hot bar index: {firstHotBar}");
|
||||
|
||||
// IsHot should engage roughly around the warmup period
|
||||
Assert.True(firstHotBar > 0, "Should not be hot immediately");
|
||||
Assert.True(firstHotBar <= wave.WarmupPeriod, $"First hot bar {firstHotBar} should be <= WarmupPeriod {wave.WarmupPeriod}");
|
||||
}
|
||||
|
||||
// --- helper ---
|
||||
|
||||
private static TSeries GenerateSubSeries(TSeries source, int count)
|
||||
{
|
||||
var t = new List<long>(count);
|
||||
var v = new List<double>(count);
|
||||
for (int i = 0; i < count && i < source.Count; i++)
|
||||
{
|
||||
t.Add(source.Times[i]);
|
||||
v.Add(source.Values[i]);
|
||||
}
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user