2026-02-05 19:42:49 -08:00
|
|
|
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()
|
|
|
|
|
{
|
2026-02-28 14:14:35 -08:00
|
|
|
_data = new ValidationTestData(10000);
|
2026-02-05 19:42:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2026-02-27 12:50:05 -08:00
|
|
|
Assert.Equal(TALib.Core.RetCode.Success, rc);
|
2026-02-05 19:42:49 -08:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2026-02-27 12:50:05 -08:00
|
|
|
Assert.Equal(TALib.Core.RetCode.Success, rc);
|
2026-02-05 19:42:49 -08:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
feat: add new indicators (Decay, Edecay, MinusDi, MinusDm, PlusDi, PlusDm, Maxindex, Minindex, Sarext) and update pine scripts, core libs, validation tests, and python bindings
2026-03-09 13:45:46 -07:00
|
|
|
|
|
|
|
|
[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),
|
2026-03-11 03:35:12 +00:00
|
|
|
100.0 + (10.0 * Math.Sin(2.0 * Math.PI * i / 20.0))), isNew: true);
|
feat: add new indicators (Decay, Edecay, MinusDi, MinusDm, PlusDi, PlusDm, Maxindex, Minindex, Sarext) and update pine scripts, core libs, validation tests, and python bindings
2026-03-09 13:45:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
2026-02-05 19:42:49 -08:00
|
|
|
}
|