mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 20:48:04 +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,120 @@
|
||||
using TradingPlatform.BusinessLayer;
|
||||
|
||||
namespace QuanTAlib.Quantower.Tests;
|
||||
|
||||
public class HtDcphaseIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void HtDcphaseIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new HtDcphaseIndicator();
|
||||
|
||||
Assert.Equal(SourceType.Close, indicator.Source);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("HT_DCPHASE - Ehlers Hilbert Transform Dominant Cycle Phase", indicator.Name);
|
||||
Assert.True(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphaseIndicator_MinHistoryDepths_EqualsLookback()
|
||||
{
|
||||
var indicator = new HtDcphaseIndicator();
|
||||
|
||||
Assert.Equal(63, HtDcphaseIndicator.MinHistoryDepths);
|
||||
Assert.Equal(63, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphaseIndicator_ShortName_IsFixed()
|
||||
{
|
||||
var indicator = new HtDcphaseIndicator();
|
||||
Assert.Equal("HT_DCPHASE", indicator.ShortName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphaseIndicator_Initialize_CreatesInternalHtDcphase()
|
||||
{
|
||||
var indicator = new HtDcphaseIndicator();
|
||||
|
||||
indicator.Initialize();
|
||||
|
||||
// 2 line series: DCPhase + Zero
|
||||
Assert.Equal(2, indicator.LinesSeries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphaseIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new HtDcphaseIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
|
||||
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
|
||||
Assert.Equal(1, indicator.LinesSeries[0].Count);
|
||||
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphaseIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new HtDcphaseIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(1), 102, 108, 100, 106);
|
||||
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
|
||||
Assert.Equal(2, indicator.LinesSeries[0].Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphaseIndicator_ProcessUpdate_NewTick_NoThrow()
|
||||
{
|
||||
var indicator = new HtDcphaseIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
|
||||
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
double first = indicator.LinesSeries[0].GetValue(0);
|
||||
|
||||
// simulate same-bar update should not advance or corrupt; value remains finite
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
|
||||
double second = indicator.LinesSeries[0].GetValue(0);
|
||||
|
||||
Assert.True(double.IsFinite(first));
|
||||
Assert.True(double.IsFinite(second));
|
||||
Assert.Equal(first, second);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphaseIndicator_MultipleUpdates_ProducesSequence()
|
||||
{
|
||||
var indicator = new HtDcphaseIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
double[] closes = { 100, 102, 104, 103, 105, 106, 107, 108 };
|
||||
|
||||
foreach (var close in closes)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now, close, close + 2, close - 2, close);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
now = now.AddMinutes(1);
|
||||
}
|
||||
|
||||
for (int j = 0; j < indicator.LinesSeries[0].Count; j++)
|
||||
{
|
||||
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(j)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
using System;
|
||||
using QuanTAlib;
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests.Cycles;
|
||||
|
||||
public class HtDcphaseTests
|
||||
{
|
||||
// ── Constructor ──────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsDefaults()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
Assert.Equal("HtDcphase", ht.Name);
|
||||
Assert.Equal(63, ht.WarmupPeriod);
|
||||
Assert.False(ht.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithPublisher_Subscribes()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var ht = new HtDcphase(source);
|
||||
Assert.False(ht.IsHot);
|
||||
|
||||
// Feed data through publisher
|
||||
for (int i = 0; i < 80; i++)
|
||||
{
|
||||
source.Add(new TValue(DateTime.UtcNow.AddMinutes(i), 100 + (Math.Sin(i * 0.3) * 10)));
|
||||
}
|
||||
|
||||
Assert.True(ht.IsHot);
|
||||
Assert.True(double.IsFinite(ht.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullPublisher_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new HtDcphase(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Last_DefaultBeforeAnyUpdate()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
Assert.Equal(default, ht.Last);
|
||||
}
|
||||
|
||||
// ── IsHot & Warmup ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Update_BecomesHotAfterWarmup()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
ht.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.True(ht.IsHot);
|
||||
Assert.True(double.IsFinite(ht.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsHot_FalseBeforeWarmup()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
ht.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100 + i));
|
||||
}
|
||||
Assert.False(ht.IsHot);
|
||||
}
|
||||
|
||||
// ── Update ──────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Update_FirstBarsReturnZero()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var result = ht.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
Assert.Equal(0.0, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PhaseRange_IsValid()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
ht.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
// After warmup, phase should be in valid range
|
||||
double phase = ht.Last.Value;
|
||||
Assert.True(phase >= -45.0 && phase <= 315.0,
|
||||
$"Phase {phase} should be in range [-45, 315]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ProducesFiniteValuesAfterWarmup()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var gbm = new GBM(seed: 99);
|
||||
var bars = gbm.Fetch(150, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
ht.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.True(ht.IsHot);
|
||||
Assert.True(double.IsFinite(ht.Last.Value));
|
||||
}
|
||||
|
||||
// ── Bar Correction ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void SameBarUpdate_ReturnsSameValue()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Prime with data
|
||||
for (int i = 0; i < 70; i++)
|
||||
{
|
||||
ht.Update(new TValue(now.AddMinutes(i), 100 + (Math.Sin(i * 0.1) * 10)));
|
||||
}
|
||||
|
||||
Assert.True(ht.IsHot);
|
||||
|
||||
// First update (new bar)
|
||||
var result1 = ht.Update(new TValue(now.AddMinutes(70), 105), isNew: true);
|
||||
|
||||
// Same bar update with the same price — must yield identical result
|
||||
var result2 = ht.Update(new TValue(now.AddMinutes(70), 105), isNew: false);
|
||||
|
||||
Assert.Equal(result1.Value, result2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BarCorrection_MultipleCorrections_StableState()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < 70; i++)
|
||||
{
|
||||
ht.Update(new TValue(now.AddMinutes(i), 100 + (i * 0.5)));
|
||||
}
|
||||
|
||||
// New bar
|
||||
ht.Update(new TValue(now.AddMinutes(70), 150), isNew: true);
|
||||
var afterNew = ht.Last;
|
||||
|
||||
// Multiple corrections
|
||||
ht.Update(new TValue(now.AddMinutes(70), 160), isNew: false);
|
||||
ht.Update(new TValue(now.AddMinutes(70), 140), isNew: false);
|
||||
ht.Update(new TValue(now.AddMinutes(70), 150), isNew: false);
|
||||
var afterCorrections = ht.Last;
|
||||
|
||||
Assert.Equal(afterNew.Value, afterCorrections.Value);
|
||||
}
|
||||
|
||||
// ── NaN handling ────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Update_NaN_BeforeValidData_ReturnsZero()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var result = ht.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
Assert.Equal(0.0, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_NaN_AfterValidData_UsesLastValid()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < 80; i++)
|
||||
{
|
||||
ht.Update(new TValue(now.AddMinutes(i), 100 + (Math.Sin(i * 0.2) * 5)));
|
||||
}
|
||||
|
||||
Assert.True(ht.IsHot);
|
||||
var result = ht.Update(new TValue(now.AddMinutes(80), double.NaN));
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_Infinity_UsesLastValid()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < 80; i++)
|
||||
{
|
||||
ht.Update(new TValue(now.AddMinutes(i), 100 + (i * 0.5)));
|
||||
}
|
||||
|
||||
var result = ht.Update(new TValue(now.AddMinutes(80), double.PositiveInfinity));
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
// ── Reset ───────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Reset_ClearsState()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 80; i++)
|
||||
{
|
||||
ht.Update(new TValue(now.AddMinutes(i), 100 + i));
|
||||
}
|
||||
|
||||
Assert.True(ht.IsHot);
|
||||
ht.Reset();
|
||||
Assert.False(ht.IsHot);
|
||||
Assert.Equal(default, ht.Last);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_AllowsReuse()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < 80; i++)
|
||||
{
|
||||
ht.Update(new TValue(now.AddMinutes(i), 100 + (Math.Sin(i * 0.2) * 5)));
|
||||
}
|
||||
var firstResult = ht.Last.Value;
|
||||
|
||||
ht.Reset();
|
||||
for (int i = 0; i < 80; i++)
|
||||
{
|
||||
ht.Update(new TValue(now.AddMinutes(i), 100 + (Math.Sin(i * 0.2) * 5)));
|
||||
}
|
||||
Assert.Equal(firstResult, ht.Last.Value);
|
||||
}
|
||||
|
||||
// ── Batch ───────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Batch_TSeries_MatchesStreaming()
|
||||
{
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var series = bars.Close;
|
||||
|
||||
var batchResult = HtDcphase.Batch(series);
|
||||
|
||||
var streaming = new HtDcphase();
|
||||
var streamingResults = new TSeries();
|
||||
foreach (var item in series)
|
||||
{
|
||||
streamingResults.Add(streaming.Update(item));
|
||||
}
|
||||
|
||||
Assert.Equal(batchResult.Count, streamingResults.Count);
|
||||
for (int i = 0; i < batchResult.Count; i++)
|
||||
{
|
||||
Assert.Equal(batchResult[i].Value, streamingResults[i].Value, 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_Span_MatchesStreaming()
|
||||
{
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var values = bars.Close.Values.ToArray();
|
||||
|
||||
var spanOutput = new double[values.Length];
|
||||
HtDcphase.Batch(values, spanOutput);
|
||||
|
||||
var streaming = new HtDcphase();
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
var result = streaming.Update(new TValue(DateTime.UtcNow.AddTicks(i), values[i]));
|
||||
Assert.Equal(result.Value, spanOutput[i], 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_Span_OutputTooShort_Throws()
|
||||
{
|
||||
var source = new double[10];
|
||||
var output = new double[5];
|
||||
Assert.Throws<ArgumentException>(() => HtDcphase.Batch(source, output));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_EmptyTSeries_ReturnsEmpty()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var result = ht.Update(new TSeries());
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_TSeries_ReturnsCorrectCount()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var result = ht.Update(bars.Close);
|
||||
Assert.Equal(100, result.Count);
|
||||
}
|
||||
|
||||
// ── Calculate ───────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Calculate_ReturnsBothResultsAndIndicator()
|
||||
{
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var (results, indicator) = HtDcphase.Calculate(bars.Close);
|
||||
Assert.Equal(100, results.Count);
|
||||
Assert.True(indicator.IsHot);
|
||||
}
|
||||
|
||||
// ── Prime ───────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Prime_WarmsUpIndicator()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var values = bars.Close.Values.ToArray();
|
||||
|
||||
ht.Prime(values);
|
||||
Assert.True(ht.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prime_WithStepParameter()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var values = new double[80];
|
||||
for (int i = 0; i < 80; i++)
|
||||
{
|
||||
values[i] = 100 + (Math.Sin(i * 0.2) * 5);
|
||||
}
|
||||
|
||||
ht.Prime(values, TimeSpan.FromMinutes(5));
|
||||
Assert.True(ht.IsHot);
|
||||
}
|
||||
|
||||
// ── Determinism ─────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void TwoInstances_SameInput_SameOutput()
|
||||
{
|
||||
var ht1 = new HtDcphase();
|
||||
var ht2 = new HtDcphase();
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
var tv = new TValue(bar.Time, bar.Close);
|
||||
var r1 = ht1.Update(tv);
|
||||
var r2 = ht2.Update(tv);
|
||||
Assert.Equal(r1.Value, r2.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Constant price ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ConstantPrice_ProducesFiniteOutput()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var result = ht.Update(new TValue(now.AddMinutes(i), 100.0));
|
||||
Assert.True(double.IsFinite(result.Value),
|
||||
$"Bar {i}: Expected finite, got {result.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dispose ─────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Dispose_DoesNotThrow()
|
||||
{
|
||||
var ht = new HtDcphase();
|
||||
ht.Update(new TValue(DateTime.UtcNow, 100));
|
||||
ht.Dispose();
|
||||
Assert.True(true); // S2699: explicit assertion for dispose-only test
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuanTAlib;
|
||||
using TALib;
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public sealed class HtDcphaseValidationTests : IDisposable
|
||||
{
|
||||
private readonly ValidationTestData _data;
|
||||
private bool _disposed;
|
||||
|
||||
public HtDcphaseValidationTests()
|
||||
{
|
||||
_data = new ValidationTestData(10000);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
if (disposing)
|
||||
{
|
||||
_data?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_TaLib_Static()
|
||||
{
|
||||
var input = _data.RawData.Span;
|
||||
var outPhase = new double[input.Length];
|
||||
var rc = TALib.Functions.HtDcPhase(input, 0..^0, outPhase, out var outRange);
|
||||
|
||||
Assert.Equal(TALib.Core.RetCode.Success, rc);
|
||||
|
||||
var q = new HtDcphase();
|
||||
var qSeries = q.Update(_data.Data);
|
||||
|
||||
int outLength = outRange.End.Value - outRange.Start.Value;
|
||||
for (int i = qSeries.Count - 200; i < qSeries.Count; i++)
|
||||
{
|
||||
int talibIdx = i - outRange.Start.Value;
|
||||
if (talibIdx >= 0 && talibIdx < outLength)
|
||||
{
|
||||
Assert.Equal(outPhase[talibIdx], qSeries.Values[i], ValidationHelper.TalibTolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_TaLib_Streaming()
|
||||
{
|
||||
var input = _data.RawData.Span;
|
||||
var outPhase = new double[input.Length];
|
||||
var rc = TALib.Functions.HtDcPhase(input, 0..^0, outPhase, out var outRange);
|
||||
|
||||
Assert.Equal(TALib.Core.RetCode.Success, rc);
|
||||
|
||||
var streaming = new List<double>(_data.Data.Count);
|
||||
var q = new HtDcphase();
|
||||
foreach (var tv in _data.Data)
|
||||
{
|
||||
streaming.Add(q.Update(tv).Value);
|
||||
}
|
||||
|
||||
int outLength = outRange.End.Value - outRange.Start.Value;
|
||||
for (int i = streaming.Count - 200; i < streaming.Count; i++)
|
||||
{
|
||||
int talibIdx = i - outRange.Start.Value;
|
||||
if (talibIdx >= 0 && talibIdx < outLength)
|
||||
{
|
||||
Assert.Equal(outPhase[talibIdx], streaming[i], ValidationHelper.TalibTolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Lookback_MatchesTaLib()
|
||||
{
|
||||
int talibLookback = TALib.Functions.HtDcPhaseLookback();
|
||||
var q = new HtDcphase();
|
||||
Assert.Equal(talibLookback, q.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphase_Correction_Recomputes()
|
||||
{
|
||||
var ind = new HtDcphase();
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
ind.Update(new TValue(t0.AddMinutes(i),
|
||||
100.0 + (10.0 * Math.Sin(2.0 * Math.PI * i / 20.0))), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorPrice = 105.5;
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: true);
|
||||
double anchorResult = ind.Last.Value;
|
||||
|
||||
// Correction with a dramatically different price — recompute must yield different result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice * 10.0), isNew: false);
|
||||
Assert.NotEqual(anchorResult, ind.Last.Value);
|
||||
|
||||
// Correction back to original price — must exactly restore original result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: false);
|
||||
Assert.Equal(anchorResult, ind.Last.Value, 1e-9);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user