mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-16 17:48:05 +00:00
- Implemented Prime method in Vel, Ao, Apo, Frama, Adl, Adosc, Aobv, Cmf, Efi, Eom, Iii, Kvo, Mfi, Nvi, Obv, Pvd, Pvi, Pvo, Pvr, Pvt, Tvi, Twap, Va, Vf, Vo, Vroc, Vwad, Vwap, and Vwma classes. - The Prime method resets the indicator state and processes the provided historical bar data to initialize the indicator. - Added warmup period property to Adl and Wad classes to define the minimum number of data points required for validity. - Updated benchmark tests to use Batch methods for performance evaluation.
358 lines
12 KiB
C#
358 lines
12 KiB
C#
using TradingPlatform.BusinessLayer;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
/// <summary>
|
|
/// Tests for HtDcperiodIndicator Quantower adapter.
|
|
/// Covers: constructor, properties (Source, ShowColdValues, ShortName, MinHistoryDepths),
|
|
/// OnInit, OnUpdate (HistoricalBar, NewBar, NewTick filtered), multiple bars,
|
|
/// ShowColdValues false, reinitialize, source variants.
|
|
/// </summary>
|
|
public class HtDcperiodIndicatorTests
|
|
{
|
|
// ═══════════════════════════════ Constructor ═══════════════════════════════
|
|
|
|
[Fact]
|
|
public void Constructor_InitializesName()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.Contains("HT_DCPERIOD", indicator.Name, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_InitializesDescription()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.False(string.IsNullOrEmpty(indicator.Description));
|
|
Assert.Contains("Hilbert", indicator.Description, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_SeparateWindowTrue()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.True(indicator.SeparateWindow);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_HasLineSeries()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.True(indicator.LinesSeries.Count >= 1);
|
|
}
|
|
|
|
// ═══════════════════════════════ Properties ═══════════════════════════════
|
|
|
|
[Fact]
|
|
public void Source_DefaultsToClose()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.Equal(SourceType.Close, indicator.Source);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowColdValues_DefaultsToTrue()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.True(indicator.ShowColdValues);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShortName_IsHtDcperiod()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.Equal("HT_DCPERIOD", indicator.ShortName);
|
|
}
|
|
|
|
[Fact]
|
|
public void MinHistoryDepths_Static_Is32()
|
|
{
|
|
Assert.Equal(32, HtDcperiodIndicator.MinHistoryDepths);
|
|
}
|
|
|
|
[Fact]
|
|
public void MinHistoryDepths_Interface_Is32()
|
|
{
|
|
IWatchlistIndicator indicator = new HtDcperiodIndicator();
|
|
Assert.Equal(32, indicator.MinHistoryDepths);
|
|
}
|
|
|
|
[Fact]
|
|
public void SourceCodeLink_IsNotEmpty()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.False(string.IsNullOrEmpty(indicator.SourceCodeLink));
|
|
Assert.Contains("HtDcperiod", indicator.SourceCodeLink, StringComparison.Ordinal);
|
|
}
|
|
|
|
// ═══════════════════════════════ OnInit ═══════════════════════════════════
|
|
|
|
[Fact]
|
|
public void OnInit_CreatesInternalIndicator()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Initialize();
|
|
// Should not throw - internal indicator created successfully
|
|
Assert.True(true);
|
|
}
|
|
|
|
// ═══════════════════════════════ OnUpdate ═════════════════════════════════
|
|
|
|
[Fact]
|
|
public void OnUpdate_HistoricalBar_Processes()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 40; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(i),
|
|
open: 100 + i,
|
|
high: 105 + i,
|
|
low: 95 + i,
|
|
close: 102 + i,
|
|
volume: 1000);
|
|
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
Assert.Equal(40, indicator.LinesSeries[0].Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void OnUpdate_NewBar_Processes()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
// Feed historical bars first
|
|
for (int i = 0; i < 35; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(i),
|
|
open: 100 + i,
|
|
high: 105 + i,
|
|
low: 95 + i,
|
|
close: 102 + i,
|
|
volume: 1000);
|
|
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// Then process a new bar
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(35),
|
|
open: 135, high: 140, low: 130, close: 137, volume: 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
|
|
|
Assert.Equal(36, indicator.LinesSeries[0].Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void OnUpdate_NewTick_DoesNotThrow()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
// NewTick should be filtered (early return) - no exception
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
|
|
Assert.True(true);
|
|
}
|
|
|
|
// ═══════════════════════════════ Multiple Bars ════════════════════════════
|
|
|
|
[Fact]
|
|
public void OnUpdate_MultipleBars_ProducesFiniteValues()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
// HT_DCPERIOD needs significant warmup - feed sinusoidal data
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
double price = 100 + 10 * Math.Sin(i * 0.3);
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(i),
|
|
open: price - 1,
|
|
high: price + 2,
|
|
low: price - 2,
|
|
close: price,
|
|
volume: 1000);
|
|
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
Assert.Equal(100, indicator.LinesSeries[0].Count);
|
|
|
|
// After warmup, values should be finite
|
|
double lastValue = indicator.LinesSeries[0].GetValue(0);
|
|
Assert.True(double.IsFinite(lastValue));
|
|
}
|
|
|
|
[Fact]
|
|
public void OnUpdate_SingleBar_ProducesValue()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
Assert.Equal(1, indicator.LinesSeries[0].Count);
|
|
}
|
|
|
|
// ═══════════════════════════════ ShowColdValues ═══════════════════════════
|
|
|
|
[Fact]
|
|
public void ShowColdValues_CanBeSetFalse()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.ShowColdValues = false;
|
|
Assert.False(indicator.ShowColdValues);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowColdValues_False_ProcessesWithoutError()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.ShowColdValues = false;
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 40; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(i),
|
|
open: 100 + i, high: 105 + i, low: 95 + i,
|
|
close: 102 + i, volume: 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
Assert.True(true);
|
|
}
|
|
|
|
// ═══════════════════════════════ Reinitialize ═════════════════════════════
|
|
|
|
[Fact]
|
|
public void Reinitialize_ResetsState()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(i),
|
|
open: 100 + i, high: 105 + i, low: 95 + i, close: 102 + i, volume: 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// Reinitialize
|
|
indicator.Initialize();
|
|
|
|
// Should process fresh data without error
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(100 + i),
|
|
open: 200 + i, high: 205 + i, low: 195 + i, close: 202 + i, volume: 2000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
Assert.True(true);
|
|
}
|
|
|
|
// ═══════════════════════════════ Source Variants ══════════════════════════
|
|
|
|
[Fact]
|
|
public void Source_SetToOpen_Accepted()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Source = SourceType.Open;
|
|
Assert.Equal(SourceType.Open, indicator.Source);
|
|
}
|
|
|
|
[Fact]
|
|
public void Source_SetToHigh_Accepted()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Source = SourceType.High;
|
|
Assert.Equal(SourceType.High, indicator.Source);
|
|
}
|
|
|
|
[Fact]
|
|
public void Source_SetToLow_Accepted()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Source = SourceType.Low;
|
|
Assert.Equal(SourceType.Low, indicator.Source);
|
|
}
|
|
|
|
[Fact]
|
|
public void Source_DifferentSources_ProcessWithoutError()
|
|
{
|
|
foreach (var source in new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close })
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Source = source;
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 40; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(i),
|
|
open: 100 + i, high: 105 + i, low: 95 + i,
|
|
close: 102 + i, volume: 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
}
|
|
Assert.True(true);
|
|
}
|
|
|
|
// ═══════════════════════════════ OnBackGround ═════════════════════════════
|
|
|
|
[Fact]
|
|
public void OnBackGround_IsTrue()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
Assert.True(indicator.OnBackGround);
|
|
}
|
|
|
|
// ═══════════════════════════════ Value Assertions ═════════════════════════
|
|
|
|
[Fact]
|
|
public void Values_AfterWarmup_ArePositive()
|
|
{
|
|
var indicator = new HtDcperiodIndicator();
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
// Feed sinusoidal data with known period (~21 bars)
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
double price = 100 + 10 * Math.Sin(2 * Math.PI * i / 21.0);
|
|
indicator.HistoricalData.AddBar(
|
|
time: now.AddMinutes(i),
|
|
open: price - 0.5,
|
|
high: price + 1,
|
|
low: price - 1,
|
|
close: price,
|
|
volume: 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// Dominant cycle period should be positive after warmup
|
|
double lastValue = indicator.LinesSeries[0].GetValue(0);
|
|
Assert.True(lastValue > 0, $"Expected positive period, got {lastValue}");
|
|
}
|
|
}
|