python wrapper

This commit is contained in:
Miha Kralj
2026-02-28 14:14:35 -08:00
parent 82e0248eb0
commit 83e9511261
521 changed files with 62395 additions and 15669 deletions
-297
View File
@@ -1,297 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Quantower.Tests;
public class AcfIndicatorTests
{
[Fact]
public void AcfIndicator_Constructor_SetsDefaults()
{
var indicator = new AcfIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(1, indicator.Lag);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("ACF - Autocorrelation Function", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void AcfIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AcfIndicator();
Assert.Equal(0, AcfIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void AcfIndicator_ShortName_IncludesPeriodAndLag()
{
var indicator = new AcfIndicator { Period = 14, Lag = 2 };
Assert.True(indicator.ShortName.Contains("ACF", StringComparison.Ordinal));
Assert.True(indicator.ShortName.Contains("14", StringComparison.Ordinal));
Assert.True(indicator.ShortName.Contains("2", StringComparison.Ordinal));
}
[Fact]
public void AcfIndicator_Initialize_CreatesInternalAcf()
{
var indicator = new AcfIndicator { Period = 10, Lag = 1 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void AcfIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new AcfIndicator { Period = 5, Lag = 1 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
[Fact]
public void AcfIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new AcfIndicator { Period = 5, Lag = 1 };
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 AcfIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new AcfIndicator { Period = 5, Lag = 1 };
indicator.Initialize();
// Should not throw an exception
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
// Assert that the indicator still exists (method completed without exception)
Assert.NotNull(indicator);
}
[Fact]
public void AcfIndicator_MultipleUpdates_ProducesCorrectSequence()
{
var indicator = new AcfIndicator { Period = 5, Lag = 1 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 100, 102, 105, 103, 107, 110 };
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);
}
// All values should be finite
for (int i = 0; i < closes.Length; i++)
{
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(closes.Length - 1 - i)));
}
}
[Fact]
public void AcfIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new AcfIndicator { Period = 5, Lag = 1, Source = source };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)),
$"Source {source} should produce finite value");
}
}
[Fact]
public void AcfIndicator_Period_CanBeChanged()
{
var indicator = new AcfIndicator { Period = 10 };
Assert.Equal(10, indicator.Period);
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
}
[Fact]
public void AcfIndicator_Lag_CanBeChanged()
{
var indicator = new AcfIndicator { Lag = 1 };
Assert.Equal(1, indicator.Lag);
indicator.Lag = 5;
Assert.Equal(5, indicator.Lag);
}
[Fact]
public void AcfIndicator_Source_CanBeChanged()
{
var indicator = new AcfIndicator { Source = SourceType.Close };
Assert.Equal(SourceType.Close, indicator.Source);
indicator.Source = SourceType.Open;
Assert.Equal(SourceType.Open, indicator.Source);
}
[Fact]
public void AcfIndicator_ShowColdValues_CanBeChanged()
{
var indicator = new AcfIndicator { ShowColdValues = true };
Assert.True(indicator.ShowColdValues);
indicator.ShowColdValues = false;
Assert.False(indicator.ShowColdValues);
}
[Fact]
public void AcfIndicator_ShortName_UpdatesWhenPeriodChanges()
{
var indicator = new AcfIndicator { Period = 10 };
string initialName = indicator.ShortName;
Assert.True(initialName.Contains("10", StringComparison.Ordinal));
indicator.Period = 20;
string updatedName = indicator.ShortName;
Assert.True(updatedName.Contains("20", StringComparison.Ordinal));
}
[Fact]
public void AcfIndicator_ShortName_UpdatesWhenLagChanges()
{
var indicator = new AcfIndicator { Lag = 1 };
string initialName = indicator.ShortName;
Assert.True(initialName.Contains("1", StringComparison.Ordinal));
indicator.Lag = 3;
string updatedName = indicator.ShortName;
Assert.True(updatedName.Contains("3", StringComparison.Ordinal));
}
[Fact]
public void AcfIndicator_ProcessUpdate_IgnoresNonBarUpdates()
{
var indicator = new AcfIndicator { Period = 5, Lag = 1 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process historical bar first
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Process other update reasons - should not throw
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
// Assert that the indicator still exists (method completed without exception)
Assert.NotNull(indicator);
}
[Fact]
public void AcfIndicator_LineSeries_HasCorrectProperties()
{
var indicator = new AcfIndicator { Period = 10 };
indicator.Initialize();
var lineSeries = indicator.LinesSeries[0];
Assert.Equal("ACF", lineSeries.Name);
Assert.Equal(2, lineSeries.Width);
Assert.Equal(LineStyle.Solid, lineSeries.Style);
}
[Fact]
public void AcfIndicator_DifferentLagValues_Work()
{
var lags = new[] { 1, 2, 3, 5, 10 };
foreach (var lag in lags)
{
// Period must be > lag + 1
int period = Math.Max(20, lag + 5);
var indicator = new AcfIndicator { Period = period, Lag = lag };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add enough bars to fill the buffer
for (int i = 0; i < period + 5; i++)
{
double close = 100 + (i % 10);
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 2, close - 2, close);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
// Last value should be finite and bounded
double acfValue = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(acfValue), $"Lag {lag} should produce finite value");
Assert.True(acfValue >= -1 && acfValue <= 1, $"ACF at lag {lag} should be bounded [-1, 1]");
}
}
[Fact]
public void AcfIndicator_AcfValuesAreBounded()
{
var indicator = new AcfIndicator { Period = 10, Lag = 1 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 100, 102, 98, 105, 97, 110, 95, 108, 92, 115, 90, 120 };
foreach (var close in closes)
{
indicator.HistoricalData.AddBar(now, close, close + 5, close - 5, close);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
now = now.AddMinutes(1);
}
// All ACF values should be bounded between -1 and 1
for (int i = 0; i < closes.Length; i++)
{
double value = indicator.LinesSeries[0].GetValue(closes.Length - 1 - i);
Assert.True(value >= -1 && value <= 1, $"ACF value at index {i} should be bounded [-1, 1], got {value}");
}
}
}
-68
View File
@@ -1,68 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class AcfIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 3, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Lag", sortIndex: 2, 1, 100, 1, 0)]
public int Lag { get; set; } = 1;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Acf _acf = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ACF ({Period},{Lag})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/acf/Acf.Quantower.cs";
public AcfIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "ACF - Autocorrelation Function";
Description = "Measures the correlation of a time series with a lagged copy of itself";
_series = new LineSeries(name: "ACF", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_acf = new Acf(Period, Lag);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar)
{
return;
}
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _acf.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _acf.IsHot, ShowColdValues);
}
}
-127
View File
@@ -1,127 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class BetaIndicatorTests
{
[Fact]
public void BetaIndicator_Constructor_SetsDefaults()
{
var indicator = new BetaIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(SourceType.Close, indicator.AssetSource);
Assert.Equal(SourceType.Close, indicator.MarketSource);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Beta Coefficient", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void BetaIndicator_MinHistoryDepths_EqualsTwo()
{
var indicator = new BetaIndicator { Period = 20 };
Assert.Equal(2, BetaIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(2, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void BetaIndicator_ShortName_IncludesParameters()
{
var indicator = new BetaIndicator { Period = 14 };
Assert.Contains("14", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("Beta", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void BetaIndicator_Initialize_CreatesInternalBeta()
{
var indicator = new BetaIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Beta", indicator.LinesSeries[0].Name);
}
[Fact]
public void BetaIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new BetaIndicator { Period = 5 };
indicator.Initialize();
// Add historical data - need enough bars for warmup
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);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double beta = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(beta));
}
[Fact]
public void BetaIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new BetaIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add initial bars
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
// Add a new bar
indicator.HistoricalData.AddBar(now.AddMinutes(10), 110, 120, 100, 115);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(11, indicator.LinesSeries[0].Count);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
[Fact]
public void BetaIndicator_DifferentSourceTypes_Work()
{
var assetSources = new[]
{
SourceType.Open,
SourceType.High,
SourceType.Low,
SourceType.Close,
};
foreach (var source in assetSources)
{
var indicator = new BetaIndicator { Period = 5, AssetSource = source, MarketSource = SourceType.Close };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)),
$"AssetSource {source} should produce finite value");
}
}
}
-68
View File
@@ -1,68 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class BetaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Asset Source", sortIndex: 2)]
public SourceType AssetSource { get; set; } = SourceType.Close;
[InputParameter("Market Source", sortIndex: 3)]
public SourceType MarketSource { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Beta _beta = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _assetSelector = null!;
private Func<IHistoryItem, double> _marketSelector = null!;
public static int MinHistoryDepths => 2;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Beta({Period})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/beta/Beta.Quantower.cs";
public BetaIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Beta Coefficient";
Description = "Measures the volatility of an asset in relation to the overall market.";
_series = new LineSeries(name: "Beta", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_beta = new Beta(Period);
_assetSelector = AssetSource.GetPriceSelector();
_marketSelector = MarketSource.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double assetVal = _assetSelector(item);
double marketVal = _marketSelector(item);
var time = this.HistoricalData.Time();
var assetInput = new TValue(time, assetVal);
var marketInput = new TValue(time, marketVal);
TValue result = _beta.Update(assetInput, marketInput, args.IsNewBar());
_series.SetValue(result.Value, _beta.IsHot, ShowColdValues);
}
}
-169
View File
@@ -1,169 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class CmaIndicatorTests
{
[Fact]
public void CmaIndicator_Constructor_SetsDefaults()
{
var indicator = new CmaIndicator();
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("CMA - Cumulative Moving Average", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void CmaIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new CmaIndicator();
Assert.Equal(0, CmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void CmaIndicator_ShortName_IncludesSource()
{
var indicator = new CmaIndicator();
Assert.Contains("CMA", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void CmaIndicator_Initialize_CreatesInternalCma()
{
var indicator = new CmaIndicator();
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void CmaIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new CmaIndicator();
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
[Fact]
public void CmaIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new CmaIndicator();
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 CmaIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new CmaIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
double secondValue = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(firstValue));
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void CmaIndicator_MultipleUpdates_ProducesCorrectCmaSequence()
{
var indicator = new CmaIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 100, 102, 104, 103, 105 };
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);
}
// All values should be finite
for (int i = 0; i < closes.Length; i++)
{
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(closes.Length - 1 - i)));
}
// Last CMA should be average of all values: (100 + 102 + 104 + 103 + 105) / 5 = 102.8
double lastCma = indicator.LinesSeries[0].GetValue(0);
Assert.Equal(102.8, lastCma, 1e-10);
}
[Fact]
public void CmaIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new CmaIndicator { Source = source };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)),
$"Source {source} should produce finite value");
}
}
[Fact]
public void CmaIndicator_CalculatesRunningAverage()
{
var indicator = new CmaIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
// Add bars with known close prices: 10, 20, 30
indicator.HistoricalData.AddBar(now, 10, 10, 10, 10);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(10.0, indicator.LinesSeries[0].GetValue(0), 1e-10); // CMA = 10
indicator.HistoricalData.AddBar(now.AddMinutes(1), 20, 20, 20, 20);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(15.0, indicator.LinesSeries[0].GetValue(0), 1e-10); // CMA = (10+20)/2 = 15
indicator.HistoricalData.AddBar(now.AddMinutes(2), 30, 30, 30, 30);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(20.0, indicator.LinesSeries[0].GetValue(0), 1e-10); // CMA = (10+20+30)/3 = 20
}
}
-52
View File
@@ -1,52 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class CmaIndicator : Indicator, IWatchlistIndicator
{
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Cma _cma = null!;
private readonly LineSeries _series;
private string _sourceName = null!;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"CMA:{_sourceName}";
public CmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "CMA - Cumulative Moving Average";
Description = "Cumulative Moving Average (Running Average)";
_series = new LineSeries(name: "CMA", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_cma = new Cma();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _cma.Update(new TValue(item.TimeLeft.Ticks, _priceSelector(item)), isNew).Value;
_series.SetValue(value, _cma.IsHot, ShowColdValues);
}
}
@@ -1,267 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class CointegrationIndicatorTests
{
[Fact]
public void CointegrationIndicator_Constructor_SetsDefaults()
{
var indicator = new CointegrationIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.Equal(SourceType.Open, indicator.Source2);
Assert.True(indicator.ShowColdValues);
Assert.Equal("COINT - Cointegration (Engle-Granger)", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void CointegrationIndicator_MinHistoryDepths_EqualsTwo()
{
var indicator = new CointegrationIndicator();
Assert.Equal(2, CointegrationIndicator.MinHistoryDepths);
Assert.Equal(2, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void CointegrationIndicator_ShortName_IncludesPeriodAndSources()
{
var indicator = new CointegrationIndicator { Period = 20 };
Assert.Contains("COINT", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void CointegrationIndicator_Initialize_CreatesInternalCointegration()
{
var indicator = new CointegrationIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void CointegrationIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new CointegrationIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have a value (may be NaN during warmup)
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
[Fact]
public void CointegrationIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new CointegrationIndicator { Period = 5 };
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 CointegrationIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new CointegrationIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
// NewTick should not throw
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
double secondValue = indicator.LinesSeries[0].GetValue(0);
// Values should be produced (may be NaN during warmup, but should not throw)
Assert.True(double.IsNaN(firstValue) || double.IsFinite(firstValue));
Assert.True(double.IsNaN(secondValue) || double.IsFinite(secondValue));
}
[Fact]
public void CointegrationIndicator_MultipleUpdates_ProducesSequence()
{
var indicator = new CointegrationIndicator { Period = 3 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add bars with different O/C patterns to create cointegration signals
double[] opens = { 100, 101, 102, 103, 104, 105 };
double[] closes = { 100, 101, 102, 103, 104, 105 };
for (int i = 0; i < opens.Length; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), opens[i], opens[i] + 5, opens[i] - 5, closes[i]);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
// All values should exist
Assert.Equal(opens.Length, indicator.LinesSeries[0].Count);
}
[Fact]
public void CointegrationIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new CointegrationIndicator { Period = 5, Source = source, Source2 = SourceType.Close };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Should have computed a value (may be NaN during warmup, but should not throw)
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
}
[Fact]
public void CointegrationIndicator_CointegrationInterpretation()
{
// This test verifies the indicator produces meaningful cointegration values
// when given perfectly correlated data (Open = Close), we expect strong cointegration
var indicator = new CointegrationIndicator { Period = 5, Source = SourceType.Close, Source2 = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add perfectly proportional bars: Open always equals Close
for (int i = 0; i < 20; i++)
{
double price = 100 + i;
indicator.HistoricalData.AddBar(now.AddMinutes(i), price, price + 2, price - 2, price);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
// After warmup, should have finite values
// (Note: when Close == Open exactly, residuals have zero variance, may produce NaN)
Assert.Equal(20, indicator.LinesSeries[0].Count);
}
[Fact]
public void CointegrationIndicator_DifferentSource2Types_Work()
{
var source2Types = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.HL2 };
foreach (var source2 in source2Types)
{
var indicator = new CointegrationIndicator { Period = 5, Source = SourceType.Close, Source2 = source2 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
}
[Fact]
public void CointegrationIndicator_Period_CanBeChanged()
{
var indicator = new CointegrationIndicator { Period = 50 };
Assert.Equal(50, indicator.Period);
indicator.Period = 100;
Assert.Equal(100, indicator.Period);
}
[Fact]
public void CointegrationIndicator_Source2_CanBeChanged()
{
var indicator = new CointegrationIndicator { Source2 = SourceType.High };
Assert.Equal(SourceType.High, indicator.Source2);
indicator.Source2 = SourceType.Low;
Assert.Equal(SourceType.Low, indicator.Source2);
}
[Fact]
public void CointegrationIndicator_ReInitialize_ResetsState()
{
var indicator = new CointegrationIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 105 + i, 95 + i, 102 + i);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
Assert.Equal(10, indicator.LinesSeries[0].Count);
// Re-initialize should work without errors
var indicator2 = new CointegrationIndicator { Period = 5 };
indicator2.Initialize();
indicator2.HistoricalData.AddBar(now.AddMinutes(100), 200, 210, 190, 205);
indicator2.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(1, indicator2.LinesSeries[0].Count);
}
[Fact]
public void CointegrationIndicator_HighLow_ProducesValues()
{
// Test with High vs Low as a practical use case
var indicator = new CointegrationIndicator { Period = 10, Source = SourceType.High, Source2 = SourceType.Low };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add bars with varying spread between high and low
for (int i = 0; i < 15; i++)
{
double mid = 100 + (i * 0.5);
double spread = 5 + (i % 3); // Varying spread
indicator.HistoricalData.AddBar(now.AddMinutes(i), mid, mid + spread, mid - spread, mid);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
Assert.Equal(15, indicator.LinesSeries[0].Count);
// After warmup period, should have finite values
double lastValue = indicator.LinesSeries[0].GetValue(0);
// High and Low should be cointegrated (they move together)
Assert.True(double.IsFinite(lastValue) || double.IsNaN(lastValue));
}
[Fact]
public void CointegrationIndicator_Description_IsSet()
{
var indicator = new CointegrationIndicator();
Assert.Contains("cointegration", indicator.Description, StringComparison.OrdinalIgnoreCase);
Assert.Contains("ADF", indicator.Description, StringComparison.Ordinal);
}
}
@@ -1,81 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
/// <summary>
/// Quantower adapter for Cointegration indicator.
/// Measures the statistical equilibrium relationship between two price series
/// using the Engle-Granger two-step method with ADF test.
/// </summary>
/// <remarks>
/// This adapter compares two different price sources from the same symbol (e.g., Close vs Open,
/// Close vs Volume, High vs Low). For cross-symbol cointegration analysis, use the core
/// Cointegration class directly with data from multiple symbols.
///
/// The output is the ADF test statistic. More negative values indicate stronger cointegration.
/// Critical values: -3.43 (1%), -2.86 (5%), -2.57 (10%)
/// </remarks>
[SkipLocalsInit]
public sealed class CointegrationIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 0, minimum: 2, maximum: 10000)]
public int Period { get; set; } = 20;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Source 2 Type", sortIndex: 2)]
public SourceType Source2 { get; set; } = SourceType.Open;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Cointegration _cointegration = null!;
private readonly LineSeries _series;
private string _sourceName = null!;
private Func<IHistoryItem, double> _priceSelector = null!;
private Func<IHistoryItem, double> _priceSelector2 = null!;
public static int MinHistoryDepths => 2;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"COINT({Period}):{_sourceName}/{Source2}";
public CointegrationIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "COINT - Cointegration (Engle-Granger)";
Description = "Measures statistical equilibrium between two price sources using ADF test. More negative = stronger cointegration.";
_series = new LineSeries(name: "ADF", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_priceSelector2 = Source2.GetPriceSelector();
_sourceName = Source.ToString();
_cointegration = new Cointegration(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.IsNewBar();
// Get both price sources from the same bar
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double valueA = _priceSelector(item);
double valueB = _priceSelector2(item);
var tvalA = new TValue(item.TimeLeft.Ticks, valueA);
var tvalB = new TValue(item.TimeLeft.Ticks, valueB);
double value = _cointegration.Update(tvalA, tvalB, isNew).Value;
_series.SetValue(value, _cointegration.IsHot, ShowColdValues);
}
}
@@ -1,307 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class CorrelationIndicatorTests
{
[Fact]
public void CorrelationIndicator_Constructor_SetsDefaults()
{
var indicator = new CorrelationIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.Equal(SourceType.Open, indicator.Source2);
Assert.True(indicator.ShowColdValues);
Assert.Equal("CORR - Pearson Correlation Coefficient", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void CorrelationIndicator_MinHistoryDepths_EqualsTwo()
{
var indicator = new CorrelationIndicator();
Assert.Equal(2, CorrelationIndicator.MinHistoryDepths);
Assert.Equal(2, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void CorrelationIndicator_ShortName_IncludesPeriodAndSources()
{
var indicator = new CorrelationIndicator { Period = 20 };
Assert.Contains("CORR", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void CorrelationIndicator_Initialize_CreatesInternalCorrelation()
{
var indicator = new CorrelationIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void CorrelationIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new CorrelationIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have a value (may be NaN during warmup)
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
[Fact]
public void CorrelationIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new CorrelationIndicator { Period = 5 };
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 CorrelationIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new CorrelationIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
// NewTick should not throw
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
double secondValue = indicator.LinesSeries[0].GetValue(0);
// Values should be produced (may be NaN during warmup, but should not throw)
Assert.True(double.IsNaN(firstValue) || double.IsFinite(firstValue));
Assert.True(double.IsNaN(secondValue) || double.IsFinite(secondValue));
}
[Fact]
public void CorrelationIndicator_MultipleUpdates_ProducesSequence()
{
var indicator = new CorrelationIndicator { Period = 3 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add bars with different O/C patterns to create varying correlation
double[] opens = { 100, 101, 102, 103, 104, 105 };
double[] closes = { 100, 101, 102, 103, 104, 105 };
for (int i = 0; i < opens.Length; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), opens[i], opens[i] + 5, opens[i] - 5, closes[i]);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
// All values should exist
Assert.Equal(opens.Length, indicator.LinesSeries[0].Count);
}
[Fact]
public void CorrelationIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new CorrelationIndicator { Period = 5, Source = source, Source2 = SourceType.Close };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Should have computed a value (may be NaN during warmup, but should not throw)
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
}
[Fact]
public void CorrelationIndicator_CorrelationBounds()
{
// This test verifies the indicator produces values in valid range [-1, +1]
var indicator = new CorrelationIndicator { Period = 5, Source = SourceType.Close, Source2 = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add bars with varying patterns
for (int i = 0; i < 20; i++)
{
double open = 100 + i;
double close = 100 + i + (i % 2 == 0 ? 2 : -1); // Varying relationship
indicator.HistoricalData.AddBar(now.AddMinutes(i), open, open + 5, open - 5, close);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
// After warmup, should have values in valid range
Assert.Equal(20, indicator.LinesSeries[0].Count);
// Check that values are bounded
for (int i = 0; i < 20; i++)
{
double value = indicator.LinesSeries[0].GetValue(i);
if (double.IsFinite(value))
{
Assert.InRange(value, -1.0, 1.0);
}
}
}
[Fact]
public void CorrelationIndicator_DifferentSource2Types_Work()
{
var source2Types = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.HL2 };
foreach (var source2 in source2Types)
{
var indicator = new CorrelationIndicator { Period = 5, Source = SourceType.Close, Source2 = source2 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
}
[Fact]
public void CorrelationIndicator_Period_CanBeChanged()
{
var indicator = new CorrelationIndicator { Period = 50 };
Assert.Equal(50, indicator.Period);
indicator.Period = 100;
Assert.Equal(100, indicator.Period);
}
[Fact]
public void CorrelationIndicator_Source2_CanBeChanged()
{
var indicator = new CorrelationIndicator { Source2 = SourceType.High };
Assert.Equal(SourceType.High, indicator.Source2);
indicator.Source2 = SourceType.Low;
Assert.Equal(SourceType.Low, indicator.Source2);
}
[Fact]
public void CorrelationIndicator_ReInitialize_ResetsState()
{
var indicator = new CorrelationIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 105 + i, 95 + i, 102 + i);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
Assert.Equal(10, indicator.LinesSeries[0].Count);
// Re-initialize should work without errors
var indicator2 = new CorrelationIndicator { Period = 5 };
indicator2.Initialize();
indicator2.HistoricalData.AddBar(now.AddMinutes(100), 200, 210, 190, 205);
indicator2.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(1, indicator2.LinesSeries[0].Count);
}
[Fact]
public void CorrelationIndicator_HighLow_ProducesPositiveCorrelation()
{
// Test with High vs Low - they should be positively correlated
var indicator = new CorrelationIndicator { Period = 10, Source = SourceType.High, Source2 = SourceType.Low };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add bars with typical High > Low relationship
for (int i = 0; i < 15; i++)
{
double mid = 100 + (i * 0.5);
double spread = 5;
indicator.HistoricalData.AddBar(now.AddMinutes(i), mid, mid + spread, mid - spread, mid);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
Assert.Equal(15, indicator.LinesSeries[0].Count);
// After warmup period, High and Low should show positive correlation
// (they both trend together as price moves)
double lastValue = indicator.LinesSeries[0].GetValue(0);
if (double.IsFinite(lastValue))
{
Assert.True(lastValue > 0, $"Expected positive correlation for High vs Low, got {lastValue}");
}
}
[Fact]
public void CorrelationIndicator_Description_IsSet()
{
var indicator = new CorrelationIndicator();
Assert.Contains("linear", indicator.Description, StringComparison.OrdinalIgnoreCase);
Assert.Contains("-1", indicator.Description, StringComparison.Ordinal);
Assert.Contains("+1", indicator.Description, StringComparison.Ordinal);
}
[Fact]
public void CorrelationIndicator_PerfectCorrelation_ReturnsOne()
{
// When Close == Open for all bars, correlation should be 1.0 (or NaN if zero variance)
var indicator = new CorrelationIndicator { Period = 5, Source = SourceType.Close, Source2 = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add bars where Close always equals Open (perfect linear relationship)
for (int i = 0; i < 10; i++)
{
double price = 100 + i * 2; // Trending up
indicator.HistoricalData.AddBar(now.AddMinutes(i), price, price + 5, price - 5, price);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
Assert.Equal(10, indicator.LinesSeries[0].Count);
// When Open == Close exactly, we get perfect correlation = 1.0
double lastValue = indicator.LinesSeries[0].GetValue(0);
if (double.IsFinite(lastValue))
{
Assert.Equal(1.0, lastValue, precision: 6);
}
}
}
@@ -1,80 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
/// <summary>
/// Quantower adapter for Correlation indicator.
/// Measures the Pearson correlation coefficient between two price series.
/// </summary>
/// <remarks>
/// This adapter compares two different price sources from the same symbol (e.g., Close vs Open,
/// Close vs Volume, High vs Low). For cross-symbol correlation analysis, use the core
/// Correlation class directly with data from multiple symbols.
///
/// The output is the Pearson correlation coefficient, ranging from -1 to +1.
/// Values near +1 indicate strong positive correlation, near -1 indicate strong negative correlation.
/// </remarks>
[SkipLocalsInit]
public sealed class CorrelationIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 0, minimum: 2, maximum: 10000)]
public int Period { get; set; } = 20;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Source 2 Type", sortIndex: 2)]
public SourceType Source2 { get; set; } = SourceType.Open;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Correlation _correlation = null!;
private readonly LineSeries _series;
private string _sourceName = null!;
private Func<IHistoryItem, double> _priceSelector = null!;
private Func<IHistoryItem, double> _priceSelector2 = null!;
public static int MinHistoryDepths => 2;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"CORR({Period}):{_sourceName}/{Source2}";
public CorrelationIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "CORR - Pearson Correlation Coefficient";
Description = "Measures linear relationship between two price sources. Range: -1 (inverse) to +1 (perfect positive).";
_series = new LineSeries(name: "Correlation", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_priceSelector2 = Source2.GetPriceSelector();
_sourceName = Source.ToString();
_correlation = new Correlation(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.IsNewBar();
// Get both price sources from the same bar
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double valueA = _priceSelector(item);
double valueB = _priceSelector2(item);
var tvalA = new TValue(item.TimeLeft.Ticks, valueA);
var tvalB = new TValue(item.TimeLeft.Ticks, valueB);
double value = _correlation.Update(tvalA, tvalB, isNew).Value;
_series.SetValue(value, _correlation.IsHot, ShowColdValues);
}
}
@@ -1,69 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class CovarianceIndicatorTests
{
[Fact]
public void CovarianceIndicator_Constructor_SetsDefaults()
{
var indicator = new CovarianceIndicator();
Assert.Equal(20, indicator.Period);
Assert.False(indicator.IsPopulation);
Assert.Equal(SourceType.Close, indicator.Source1);
Assert.Equal(SourceType.Open, indicator.Source2);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Covariance", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void CovarianceIndicator_MinHistoryDepths_EqualsTwo()
{
var indicator = new CovarianceIndicator { Period = 20 };
Assert.Equal(2, CovarianceIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(2, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void CovarianceIndicator_Initialize_CreatesInternalCovariance()
{
var indicator = new CovarianceIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Covariance", indicator.LinesSeries[0].Name);
}
[Fact]
public void CovarianceIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new CovarianceIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double cov = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(cov));
}
}
@@ -1,71 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class CovarianceIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Population", sortIndex: 2)]
public bool IsPopulation { get; set; } = false;
[InputParameter("Source 1", sortIndex: 3)]
public SourceType Source1 { get; set; } = SourceType.Close;
[InputParameter("Source 2", sortIndex: 4)]
public SourceType Source2 { get; set; } = SourceType.Open;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Covariance _cov = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector1 = null!;
private Func<IHistoryItem, double> _priceSelector2 = null!;
public static int MinHistoryDepths => 2;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Cov({Period})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/covariance/Covariance.Quantower.cs";
public CovarianceIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Covariance";
Description = "Measures the joint variability of two random variables.";
_series = new LineSeries(name: "Covariance", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_cov = new Covariance(Period, IsPopulation);
_priceSelector1 = Source1.GetPriceSelector();
_priceSelector2 = Source2.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double val1 = _priceSelector1(item);
double val2 = _priceSelector2(item);
var time = this.HistoricalData.Time();
var input1 = new TValue(time, val1);
var input2 = new TValue(time, val2);
TValue result = _cov.Update(input1, input2, args.IsNewBar());
_series.SetValue(result.Value, _cov.IsHot, ShowColdValues);
}
}
@@ -1,122 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class EntropyIndicatorTests
{
[Fact]
public void EntropyIndicator_Constructor_SetsDefaults()
{
var indicator = new EntropyIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Entropy - Shannon Entropy", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void EntropyIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new EntropyIndicator { Period = 14 };
Assert.Equal(0, EntropyIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void EntropyIndicator_Initialize_CreatesInternalEntropy()
{
var indicator = new EntropyIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Entropy", indicator.LinesSeries[0].Name);
}
[Fact]
public void EntropyIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new EntropyIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
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);
}
// Line series should have a value
double entropy = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(entropy));
// Allow tiny floating-point overshoot above 1.0
Assert.True(entropy >= -1e-10 && entropy <= 1.0 + 1e-10,
$"Expected entropy in [0, 1], got {entropy}");
}
[Fact]
public void EntropyIndicator_DifferentSourceTypes()
{
var indicator = new EntropyIndicator { Period = 5, Source = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double entropy = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(entropy));
}
[Fact]
public void EntropyIndicator_ShortName_IncludesPeriod()
{
var indicator = new EntropyIndicator { Period = 20 };
Assert.Equal("Entropy 20", indicator.ShortName);
}
[Fact]
public void EntropyIndicator_NewBar_UpdatesValue()
{
var indicator = new EntropyIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add enough bars to warm up
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
_ = indicator.LinesSeries[0].GetValue(0);
// Add a new bar with a very different value
indicator.HistoricalData.AddBar(now.AddMinutes(10), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
// Value should change after adding a significantly different bar
Assert.True(double.IsFinite(valueAfter));
}
}
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class EntropyIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Entropy _entropy = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Entropy {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/entropy/Entropy.Quantower.cs";
public EntropyIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Entropy - Shannon Entropy";
Description = "Measures the randomness/predictability of price data using normalized Shannon entropy";
_series = new LineSeries(name: "Entropy", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_entropy = new Entropy(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _entropy.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _entropy.IsHot, ShowColdValues);
}
}
@@ -1,122 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class GeomeanIndicatorTests
{
[Fact]
public void GeomeanIndicator_Constructor_SetsDefaults()
{
var indicator = new GeomeanIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("GEOMEAN - Geometric Mean", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void GeomeanIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new GeomeanIndicator { Period = 14 };
Assert.Equal(0, GeomeanIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void GeomeanIndicator_Initialize_CreatesInternalGeomean()
{
var indicator = new GeomeanIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Geomean", indicator.LinesSeries[0].Name);
}
[Fact]
public void GeomeanIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new GeomeanIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
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);
}
// Line series should have a value
double geomean = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(geomean));
Assert.True(geomean > 0, $"Geometric mean should be positive, got {geomean}");
}
[Fact]
public void GeomeanIndicator_DifferentSourceTypes()
{
var indicator = new GeomeanIndicator { Period = 5, Source = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double geomean = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(geomean));
Assert.True(geomean > 0);
}
[Fact]
public void GeomeanIndicator_ShortName_IncludesPeriod()
{
var indicator = new GeomeanIndicator { Period = 20 };
Assert.Equal("Geomean 20", indicator.ShortName);
}
[Fact]
public void GeomeanIndicator_NewBar_UpdatesValue()
{
var indicator = new GeomeanIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add enough bars to warm up
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
_ = indicator.LinesSeries[0].GetValue(0);
// Add a new bar with a very different value
indicator.HistoricalData.AddBar(now.AddMinutes(10), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
// Value should change after adding a significantly different bar
Assert.True(double.IsFinite(valueAfter));
Assert.True(valueAfter > 0);
}
}
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class GeomeanIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Geomean _geomean = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Geomean {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/geomean/Geomean.Quantower.cs";
public GeomeanIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "GEOMEAN - Geometric Mean";
Description = "Rolling geometric mean of price data using log-sum approach";
_series = new LineSeries(name: "Geomean", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_geomean = new Geomean(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _geomean.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _geomean.IsHot, ShowColdValues);
}
}
@@ -1,135 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public sealed class GrangerIndicatorTests
{
[Fact]
public void GrangerIndicator_Constructor_SetsDefaults()
{
var indicator = new GrangerIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.Equal(SourceType.Open, indicator.Source2);
Assert.True(indicator.ShowColdValues);
Assert.Equal("GRANGER - Granger Causality F-Statistic", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void GrangerIndicator_MinHistoryDepths_EqualsTwo()
{
var indicator = new GrangerIndicator();
Assert.Equal(2, GrangerIndicator.MinHistoryDepths);
Assert.Equal(2, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void GrangerIndicator_ShortName_IncludesPeriodAndSources()
{
var indicator = new GrangerIndicator { Period = 20 };
Assert.Contains("GRANGER", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void GrangerIndicator_Initialize_CreatesInternalGranger()
{
var indicator = new GrangerIndicator { Period = 10 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void GrangerIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new GrangerIndicator { Period = 5 };
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);
}
[Fact]
public void GrangerIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new GrangerIndicator { Period = 5 };
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 GrangerIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new GrangerIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
double secondValue = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsNaN(firstValue) || double.IsFinite(firstValue));
Assert.True(double.IsNaN(secondValue) || double.IsFinite(secondValue));
}
[Fact]
public void GrangerIndicator_MultipleUpdates_ProducesSequence()
{
var indicator = new GrangerIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] opens = { 100, 101, 102, 103, 104, 105 };
double[] closes = { 100, 101, 102, 103, 104, 105 };
for (int i = 0; i < opens.Length; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), opens[i], opens[i] + 5, opens[i] - 5, closes[i]);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
Assert.Equal(opens.Length, indicator.LinesSeries[0].Count);
}
[Fact]
public void GrangerIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new GrangerIndicator { Period = 5, Source = source, Source2 = SourceType.Close };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
}
}
@@ -1,77 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
/// <summary>
/// Quantower adapter for Granger Causality indicator.
/// Tests whether one price source Granger-causes another using F-statistic.
/// </summary>
/// <remarks>
/// This adapter compares two different price sources from the same symbol (e.g., Close vs Volume).
/// For cross-symbol Granger causality analysis, use the core Granger class directly.
///
/// Higher F-statistic values indicate stronger evidence that Source 2 Granger-causes Source 1.
/// </remarks>
[SkipLocalsInit]
public sealed class GrangerIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 0, minimum: 4, maximum: 10000)]
public int Period { get; set; } = 20;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Source 2 Type", sortIndex: 2)]
public SourceType Source2 { get; set; } = SourceType.Open;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Granger _granger = null!;
private readonly LineSeries _series;
private string _sourceName = null!;
private Func<IHistoryItem, double> _priceSelector = null!;
private Func<IHistoryItem, double> _priceSelector2 = null!;
public static int MinHistoryDepths => 2;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"GRANGER({Period}):{_sourceName}/{Source2}";
public GrangerIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "GRANGER - Granger Causality F-Statistic";
Description = "Tests whether one price source helps predict another. Higher F-statistic = stronger evidence of Granger causality.";
_series = new LineSeries(name: "F-Stat", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_priceSelector2 = Source2.GetPriceSelector();
_sourceName = Source.ToString();
_granger = new Granger(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double valueY = _priceSelector(item);
double valueX = _priceSelector2(item);
var tvalY = new TValue(item.TimeLeft.Ticks, valueY);
var tvalX = new TValue(item.TimeLeft.Ticks, valueX);
double value = _granger.Update(tvalY, tvalX, isNew).Value;
_series.SetValue(value, _granger.IsHot, ShowColdValues);
}
}
@@ -1,122 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class HarmeanIndicatorTests
{
[Fact]
public void HarmeanIndicator_Constructor_SetsDefaults()
{
var indicator = new HarmeanIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("HARMEAN - Harmonic Mean", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void HarmeanIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new HarmeanIndicator { Period = 14 };
Assert.Equal(0, HarmeanIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void HarmeanIndicator_Initialize_CreatesInternalHarmean()
{
var indicator = new HarmeanIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Harmean", indicator.LinesSeries[0].Name);
}
[Fact]
public void HarmeanIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new HarmeanIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
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);
}
// Line series should have a value
double harmean = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(harmean));
Assert.True(harmean > 0, $"Harmonic mean should be positive, got {harmean}");
}
[Fact]
public void HarmeanIndicator_DifferentSourceTypes()
{
var indicator = new HarmeanIndicator { Period = 5, Source = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double harmean = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(harmean));
Assert.True(harmean > 0);
}
[Fact]
public void HarmeanIndicator_ShortName_IncludesPeriod()
{
var indicator = new HarmeanIndicator { Period = 20 };
Assert.Equal("Harmean 20", indicator.ShortName);
}
[Fact]
public void HarmeanIndicator_NewBar_UpdatesValue()
{
var indicator = new HarmeanIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add enough bars to warm up
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
_ = indicator.LinesSeries[0].GetValue(0);
// Add a new bar with a very different value
indicator.HistoricalData.AddBar(now.AddMinutes(10), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
// Value should change after adding a significantly different bar
Assert.True(double.IsFinite(valueAfter));
Assert.True(valueAfter > 0);
}
}
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class HarmeanIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Harmean _harmean = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Harmean {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/harmean/Harmean.Quantower.cs";
public HarmeanIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "HARMEAN - Harmonic Mean";
Description = "Rolling harmonic mean of price data using reciprocal-sum approach";
_series = new LineSeries(name: "Harmean", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_harmean = new Harmean(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _harmean.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _harmean.IsHot, ShowColdValues);
}
}
@@ -1,114 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class HurstIndicatorTests
{
[Fact]
public void HurstIndicator_Constructor_SetsDefaults()
{
var indicator = new HurstIndicator();
Assert.Equal(100, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Hurst - Hurst Exponent", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void HurstIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new HurstIndicator { Period = 100 };
Assert.Equal(0, HurstIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void HurstIndicator_Initialize_CreatesInternalHurst()
{
var indicator = new HurstIndicator { Period = 20 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist (Hurst line + 0.5 reference line)
Assert.Equal(2, indicator.LinesSeries.Count);
Assert.Equal("Hurst", indicator.LinesSeries[0].Name);
Assert.Equal("0.5", indicator.LinesSeries[1].Name);
}
[Fact]
public void HurstIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new HurstIndicator { Period = 20 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double hurst = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(hurst));
}
[Fact]
public void HurstIndicator_DifferentSourceTypes()
{
var indicator = new HurstIndicator { Period = 20, Source = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 30; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double hurst = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(hurst));
}
[Fact]
public void HurstIndicator_ShortName_IncludesPeriod()
{
var indicator = new HurstIndicator { Period = 50 };
Assert.Equal("Hurst 50", indicator.ShortName);
}
[Fact]
public void HurstIndicator_NewBar_UpdatesValue()
{
var indicator = new HurstIndicator { Period = 20 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 30; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
_ = indicator.LinesSeries[0].GetValue(0);
// Add a new bar with a very different value
indicator.HistoricalData.AddBar(now.AddMinutes(30), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(valueAfter));
}
}
-64
View File
@@ -1,64 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class HurstIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 20, 2000, 1, 0)]
public int Period { get; set; } = 100;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Hurst _hurst = null!;
private readonly LineSeries _series;
private readonly LineSeries _halfLine;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Hurst {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/hurst/Hurst.Quantower.cs";
public HurstIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Hurst - Hurst Exponent";
Description = "Measures long-range dependence using Rescaled Range (R/S) analysis. H > 0.5 = trending, H < 0.5 = mean-reverting, H ≈ 0.5 = random walk";
_series = new LineSeries(name: "Hurst", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
_halfLine = new LineSeries(name: "0.5", color: Color.Gray, width: 1, style: LineStyle.Dash);
AddLineSeries(_series);
AddLineSeries(_halfLine);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_hurst = new Hurst(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _hurst.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _hurst.IsHot, ShowColdValues);
_halfLine.SetValue(0.5);
}
}
@@ -1,4 +1,7 @@
using Skender.Stock.Indicators;
using QuanTAlib.Tests;
// HURST Validation Tests - Hurst Exponent via Rescaled Range (R/S) Analysis
// Validated against self-consistency and known mathematical properties
// No external library provides a direct R/S-based Hurst exponent equivalent
@@ -181,4 +184,40 @@ public sealed class HurstValidationTests
Assert.Equal(h1.Last.Value, h2.Last.Value, 1e-15);
}
/// <summary>
/// Structural comparison with Skender GetHurst — both compute Hurst exponent
/// but may use different R/S subdivision strategies and regression methods.
/// Validates that Skender produces finite results in the same range.
/// </summary>
[Fact]
public void Validate_Skender_Hurst_Structural()
{
const int period = 20;
using var data = new ValidationTestData(10000);
// QuanTAlib streaming
var indicator = new Hurst(period);
foreach (var tv in data.Data)
{
indicator.Update(tv);
}
// Skender
var sResult = data.SkenderQuotes.GetHurst(period).ToList();
// QuanTAlib produces finite output
Assert.True(double.IsFinite(indicator.Last.Value), "QuanTAlib Hurst last must be finite");
// Skender produces finite Hurst exponents
int sFinite = sResult.Count(r => r.HurstExponent is not null && double.IsFinite(r.HurstExponent.Value));
Assert.True(sFinite > 50, $"Skender produced only {sFinite} finite Hurst values");
// Both Hurst exponents should be finite
foreach (var r in sResult.Where(r => r.HurstExponent is not null))
{
Assert.True(double.IsFinite(r.HurstExponent!.Value),
$"Skender Hurst value {r.HurstExponent.Value} is not finite");
}
}
}
-113
View File
@@ -1,113 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class IqrIndicatorTests
{
[Fact]
public void IqrIndicator_Constructor_SetsDefaults()
{
var indicator = new IqrIndicator();
Assert.Equal(20, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("IQR - Interquartile Range", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void IqrIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new IqrIndicator { Period = 20 };
Assert.Equal(0, IqrIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void IqrIndicator_Initialize_CreatesInternalIqr()
{
var indicator = new IqrIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("IQR", indicator.LinesSeries[0].Name);
}
[Fact]
public void IqrIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new IqrIndicator { Period = 5 };
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);
}
double iqr = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(iqr));
}
[Fact]
public void IqrIndicator_DifferentSourceTypes()
{
var indicator = new IqrIndicator { Period = 5, Source = SourceType.Open };
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);
}
double iqr = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(iqr));
}
[Fact]
public void IqrIndicator_ShortName_IncludesPeriod()
{
var indicator = new IqrIndicator { Period = 30 };
Assert.Equal("IQR 30", indicator.ShortName);
}
[Fact]
public void IqrIndicator_NewBar_UpdatesValue()
{
var indicator = new IqrIndicator { Period = 5 };
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);
}
_ = indicator.LinesSeries[0].GetValue(0);
// Add a new bar with a very different value
indicator.HistoricalData.AddBar(now.AddMinutes(20), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(valueAfter));
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class IqrIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
public int Period { get; set; } = 20;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Iqr _iqr = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"IQR {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/iqr/Iqr.Quantower.cs";
public IqrIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "IQR - Interquartile Range";
Description = "Measures spread of the middle 50% of data (Q3 - Q1)";
_series = new LineSeries(name: "IQR", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_iqr = new Iqr(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _iqr.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _iqr.IsHot, ShowColdValues);
}
}
-130
View File
@@ -1,130 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class JbIndicatorTests
{
[Fact]
public void JbIndicator_Constructor_SetsDefaults()
{
var indicator = new JbIndicator();
Assert.Equal(20, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("JB - Jarque-Bera Test", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void JbIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new JbIndicator { Period = 20 };
Assert.Equal(0, JbIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void JbIndicator_Initialize_CreatesInternalJb()
{
var indicator = new JbIndicator { Period = 10 };
indicator.Initialize();
Assert.Equal(4, indicator.LinesSeries.Count);
Assert.Equal("JB", indicator.LinesSeries[0].Name);
Assert.Equal("10%", indicator.LinesSeries[1].Name);
Assert.Equal("5%", indicator.LinesSeries[2].Name);
Assert.Equal("1%", indicator.LinesSeries[3].Name);
}
[Fact]
public void JbIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new JbIndicator { Period = 5 };
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);
}
double jb = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(jb));
}
[Fact]
public void JbIndicator_DifferentSourceTypes()
{
var indicator = new JbIndicator { Period = 5, Source = SourceType.Open };
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);
}
double jb = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(jb));
}
[Fact]
public void JbIndicator_ShortName_IncludesPeriod()
{
var indicator = new JbIndicator { Period = 30 };
Assert.Equal("JB 30", indicator.ShortName);
}
[Fact]
public void JbIndicator_NewBar_UpdatesValue()
{
var indicator = new JbIndicator { Period = 5 };
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);
}
_ = indicator.LinesSeries[0].GetValue(0);
indicator.HistoricalData.AddBar(now.AddMinutes(20), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(valueAfter));
}
[Fact]
public void JbIndicator_CriticalValueLines_AreSet()
{
var indicator = new JbIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Critical value lines should be set
Assert.Equal(4.605, indicator.LinesSeries[1].GetValue(0), 3);
Assert.Equal(5.991, indicator.LinesSeries[2].GetValue(0), 3);
Assert.Equal(9.210, indicator.LinesSeries[3].GetValue(0), 3);
}
}
-72
View File
@@ -1,72 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class JbIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 3, 2000, 1, 0)]
public int Period { get; set; } = 20;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Jb _jb = null!;
private readonly LineSeries _series;
private readonly LineSeries _crit10;
private readonly LineSeries _crit05;
private readonly LineSeries _crit01;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"JB {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/jb/Jb.Quantower.cs";
public JbIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "JB - Jarque-Bera Test";
Description = "Normality test using skewness and kurtosis. Large values reject normality.";
_series = new LineSeries(name: "JB", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
_crit10 = new LineSeries(name: "10%", color: Color.Gray, width: 1, style: LineStyle.Dash);
_crit05 = new LineSeries(name: "5%", color: Color.Orange, width: 1, style: LineStyle.Dash);
_crit01 = new LineSeries(name: "1%", color: Color.Red, width: 1, style: LineStyle.Solid);
AddLineSeries(_series);
AddLineSeries(_crit10);
AddLineSeries(_crit05);
AddLineSeries(_crit01);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_jb = new Jb(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _jb.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _jb.IsHot, ShowColdValues);
_crit10.SetValue(4.605);
_crit05.SetValue(5.991);
_crit01.SetValue(9.210);
}
}
@@ -1,136 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public sealed class KendallIndicatorTests
{
[Fact]
public void KendallIndicator_Constructor_SetsDefaults()
{
var indicator = new KendallIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.Equal(SourceType.Open, indicator.Source2);
Assert.True(indicator.ShowColdValues);
Assert.Equal("KENDALL - Kendall Tau-a Rank Correlation", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void KendallIndicator_MinHistoryDepths_EqualsTwo()
{
var indicator = new KendallIndicator();
Assert.Equal(2, KendallIndicator.MinHistoryDepths);
Assert.Equal(2, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void KendallIndicator_ShortName_IncludesPeriodAndSources()
{
var indicator = new KendallIndicator { Period = 20 };
Assert.Contains("KENDALL", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void KendallIndicator_Initialize_CreatesInternalKendall()
{
var indicator = new KendallIndicator { Period = 10 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void KendallIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new KendallIndicator { Period = 5 };
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);
}
[Fact]
public void KendallIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new KendallIndicator { Period = 5 };
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 KendallIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new KendallIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
double secondValue = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsNaN(firstValue) || double.IsFinite(firstValue));
Assert.True(double.IsNaN(secondValue) || double.IsFinite(secondValue));
}
[Fact]
public void KendallIndicator_MultipleUpdates_ProducesSequence()
{
var indicator = new KendallIndicator { Period = 3 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] opens = [100, 101, 102, 103, 104, 105];
double[] closes = [100, 101, 102, 103, 104, 105];
for (int i = 0; i < opens.Length; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), opens[i], opens[i] + 5, opens[i] - 5, closes[i]);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
Assert.Equal(opens.Length, indicator.LinesSeries[0].Count);
}
[Fact]
public void KendallIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new KendallIndicator { Period = 5, Source = source, Source2 = SourceType.Close };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Should not throw and should produce output
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
}
}
@@ -1,79 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
/// <summary>
/// Quantower adapter for Kendall Tau-a Rank Correlation indicator.
/// Measures ordinal association between two price sources from the same symbol.
/// </summary>
/// <remarks>
/// This adapter compares two different price sources from the same symbol (e.g., Close vs Open,
/// Close vs Volume, High vs Low). For cross-symbol correlation, use the core
/// Kendall class directly.
///
/// Output is the Kendall Tau-a coefficient, ranging from -1 to +1.
/// Values near +1 indicate strong concordance, near -1 strong discordance.
/// </remarks>
[SkipLocalsInit]
public sealed class KendallIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 0, minimum: 2, maximum: 10000)]
public int Period { get; set; } = 20;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Source 2 Type", sortIndex: 2)]
public SourceType Source2 { get; set; } = SourceType.Open;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Kendall _kendall = null!;
private readonly LineSeries _series;
private string _sourceName = null!;
private Func<IHistoryItem, double> _priceSelector = null!;
private Func<IHistoryItem, double> _priceSelector2 = null!;
public static int MinHistoryDepths => 2;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"KENDALL({Period}):{_sourceName}/{Source2}";
public KendallIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "KENDALL - Kendall Tau-a Rank Correlation";
Description = "Measures ordinal association between two price sources. Range: -1 (discordant) to +1 (concordant).";
_series = new LineSeries(name: "Kendall", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_priceSelector2 = Source2.GetPriceSelector();
_sourceName = Source.ToString();
_kendall = new Kendall(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double valueA = _priceSelector(item);
double valueB = _priceSelector2(item);
var tvalA = new TValue(item.TimeLeft.Ticks, valueA);
var tvalB = new TValue(item.TimeLeft.Ticks, valueB);
double value = _kendall.Update(tvalA, tvalB, isNew).Value;
_series.SetValue(value, _kendall.IsHot, ShowColdValues);
}
}
@@ -1,67 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class KurtosisIndicatorTests
{
[Fact]
public void KurtosisIndicator_Constructor_SetsDefaults()
{
var indicator = new KurtosisIndicator();
Assert.Equal(20, indicator.Period);
Assert.False(indicator.IsPopulation);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Kurtosis - Excess Kurtosis", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void KurtosisIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new KurtosisIndicator { Period = 20 };
Assert.Equal(0, KurtosisIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void KurtosisIndicator_Initialize_CreatesInternalKurtosis()
{
var indicator = new KurtosisIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Kurtosis", indicator.LinesSeries[0].Name);
}
[Fact]
public void KurtosisIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new KurtosisIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
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);
}
// Line series should have a value
double kurtosis = indicator.LinesSeries[0].GetValue(0);
// Kurtosis of a linear trend should be finite
Assert.True(double.IsFinite(kurtosis));
}
}
@@ -1,62 +0,0 @@
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class KurtosisIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 4, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Population Kurtosis", sortIndex: 2)]
public bool IsPopulation { get; set; } = false;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Kurtosis _kurtosis = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Kurtosis {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/kurtosis/Kurtosis.Quantower.cs";
public KurtosisIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Kurtosis - Excess Kurtosis";
Description = "Measures the tailedness of the probability distribution. Positive = fat tails, Negative = thin tails.";
_series = new LineSeries(name: "Kurtosis", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_kurtosis = new Kurtosis(Period, IsPopulation);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _kurtosis.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _kurtosis.IsHot, ShowColdValues);
}
}
@@ -1,257 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class LinRegIndicatorTests
{
[Fact]
public void LinRegIndicator_Constructor_SetsDefaults()
{
var indicator = new LinRegIndicator();
Assert.Equal(14, indicator.Period);
Assert.Equal(0, indicator.Offset);
Assert.True(indicator.ShowColdValues);
Assert.Equal("LinReg - Linear Regression Curve", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void LinRegIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new LinRegIndicator { Period = 20 };
Assert.Equal(0, LinRegIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void LinRegIndicator_Initialize_CreatesInternalLinReg()
{
var indicator = new LinRegIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("LinReg", indicator.LinesSeries[0].Name);
}
[Fact]
public void LinRegIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new LinRegIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double linreg = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(linreg));
}
}
public class LinRegSlopeIndicatorTests
{
[Fact]
public void LinRegSlopeIndicator_Constructor_SetsDefaults()
{
var indicator = new LinRegSlopeIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("LinReg Slope", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void LinRegSlopeIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new LinRegSlopeIndicator { Period = 20 };
Assert.Equal(0, LinRegSlopeIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void LinRegSlopeIndicator_Initialize_CreatesInternalLinReg()
{
var indicator = new LinRegSlopeIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Slope", indicator.LinesSeries[0].Name);
}
[Fact]
public void LinRegSlopeIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new LinRegSlopeIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double slope = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(slope));
}
}
public class LinRegInterceptIndicatorTests
{
[Fact]
public void LinRegInterceptIndicator_Constructor_SetsDefaults()
{
var indicator = new LinRegInterceptIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("LinReg Intercept", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void LinRegInterceptIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new LinRegInterceptIndicator { Period = 20 };
Assert.Equal(0, LinRegInterceptIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void LinRegInterceptIndicator_Initialize_CreatesInternalLinReg()
{
var indicator = new LinRegInterceptIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Intercept", indicator.LinesSeries[0].Name);
}
[Fact]
public void LinRegInterceptIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new LinRegInterceptIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double intercept = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(intercept));
}
}
public class LinRegRSquaredIndicatorTests
{
[Fact]
public void LinRegRSquaredIndicator_Constructor_SetsDefaults()
{
var indicator = new LinRegRSquaredIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("LinReg R-Squared", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void LinRegRSquaredIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new LinRegRSquaredIndicator { Period = 20 };
Assert.Equal(0, LinRegRSquaredIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void LinRegRSquaredIndicator_Initialize_CreatesInternalLinReg()
{
var indicator = new LinRegRSquaredIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("RSquared", indicator.LinesSeries[0].Name);
}
[Fact]
public void LinRegRSquaredIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new LinRegRSquaredIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double r2 = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(r2));
}
}
-228
View File
@@ -1,228 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class LinRegIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("Offset", sortIndex: 2, -2000, 2000, 1, 0)]
public int Offset { get; set; } = 0;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private LinReg _linreg = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"LinReg({Period})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/linreg/LinReg.Quantower.cs";
public LinRegIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "LinReg - Linear Regression Curve";
Description = "Plots the end point of the linear regression line for each bar.";
_series = new LineSeries(name: "LinReg", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_linreg = new LinReg(Period, Offset);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _linreg.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _linreg.IsHot, ShowColdValues);
}
}
[SkipLocalsInit]
public sealed class LinRegSlopeIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private LinReg _linreg = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"LinRegSlope({Period})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/linreg/LinReg.Quantower.cs";
public LinRegSlopeIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "LinReg Slope";
Description = "Plots the slope of the linear regression line.";
_series = new LineSeries(name: "Slope", color: IndicatorExtensions.Momentum, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_linreg = new LinReg(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
_linreg.Update(input, args.IsNewBar());
_series.SetValue(_linreg.Slope, _linreg.IsHot, ShowColdValues);
}
}
[SkipLocalsInit]
public sealed class LinRegInterceptIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private LinReg _linreg = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"LinRegIntercept({Period})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/linreg/LinReg.Quantower.cs";
public LinRegInterceptIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "LinReg Intercept";
Description = "Plots the intercept of the linear regression line.";
_series = new LineSeries(name: "Intercept", color: IndicatorExtensions.Experiments, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_linreg = new LinReg(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
_linreg.Update(input, args.IsNewBar());
_series.SetValue(_linreg.Intercept, _linreg.IsHot, ShowColdValues);
}
}
[SkipLocalsInit]
public sealed class LinRegRSquaredIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private LinReg _linreg = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"LinRegR2({Period})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/linreg/LinReg.Quantower.cs";
public LinRegRSquaredIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "LinReg R-Squared";
Description = "Plots the R-Squared (coefficient of determination) of the linear regression line.";
_series = new LineSeries(name: "RSquared", color: IndicatorExtensions.Oscillators, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_linreg = new LinReg(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
_linreg.Update(input, args.IsNewBar());
_series.SetValue(_linreg.RSquared, _linreg.IsHot, ShowColdValues);
}
}
@@ -1,96 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class MeanDevIndicatorTests
{
[Fact]
public void MeanDevIndicator_Constructor_SetsDefaults()
{
var indicator = new MeanDevIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("MeanDev - Mean Absolute Deviation", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void MeanDevIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new MeanDevIndicator { Period = 14 };
Assert.Equal(0, MeanDevIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void MeanDevIndicator_Initialize_CreatesInternalMeanDev()
{
var indicator = new MeanDevIndicator { Period = 10 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
Assert.Equal("MeanDev", indicator.LinesSeries[0].Name);
}
[Fact]
public void MeanDevIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new MeanDevIndicator { Period = 5 };
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);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
Assert.True(value >= 0.0);
}
[Fact]
public void MeanDevIndicator_DifferentSourceTypes()
{
var indicator = new MeanDevIndicator { Period = 5, Source = SourceType.Open };
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);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
}
[Fact]
public void MeanDevIndicator_ConstantData_ReturnsZero()
{
var indicator = new MeanDevIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 105.0, 95.0, 100.0);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.Equal(0.0, value, precision: 6);
}
}
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class MeanDevIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private MeanDev _meandev = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"MeanDev {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/meandev/MeanDev.Quantower.cs";
public MeanDevIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "MeanDev - Mean Absolute Deviation";
Description = "Average absolute deviation from the mean over a rolling window.";
_series = new LineSeries(name: "MeanDev", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_meandev = new MeanDev(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _meandev.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _meandev.IsHot, ShowColdValues);
}
}
@@ -1,67 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class MedianIndicatorTests
{
[Fact]
public void MedianIndicator_Constructor_SetsDefaults()
{
var indicator = new MedianIndicator();
Assert.Equal(10, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Median - Rolling Median", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void MedianIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new MedianIndicator { Period = 20 };
Assert.Equal(0, MedianIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void MedianIndicator_Initialize_CreatesInternalMedian()
{
var indicator = new MedianIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Median", indicator.LinesSeries[0].Name);
}
[Fact]
public void MedianIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new MedianIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double median = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(median));
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class MedianIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Median _median = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Median {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/median/Median.Quantower.cs";
public MedianIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "Median - Rolling Median";
Description = "The middle value of a sorted dataset";
_series = new LineSeries(name: "Median", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_median = new Median(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _median.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _median.IsHot, ShowColdValues);
}
}
@@ -1,66 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class ModeIndicatorTests
{
[Fact]
public void ModeIndicator_Constructor_SetsDefaults()
{
var indicator = new ModeIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Mode - Statistical Mode (Most Frequent Value)", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void ModeIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new ModeIndicator { Period = 14 };
Assert.Equal(0, ModeIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void ModeIndicator_Initialize_CreatesInternalMode()
{
var indicator = new ModeIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Mode", indicator.LinesSeries[0].Name);
}
[Fact]
public void ModeIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new ModeIndicator { Period = 5 };
indicator.Initialize();
// Add historical data with repeating close prices to produce a mode
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
double close = 100 + (i % 3); // cycles 100, 101, 102, 100, 101, ...
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 5, close - 5, close);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double mode = indicator.LinesSeries[0].GetValue(0);
// Mode of cycling values should be finite
Assert.True(double.IsFinite(mode));
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class ModeIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Mode _mode = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Mode {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/mode/Mode.Quantower.cs";
public ModeIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "Mode - Statistical Mode (Most Frequent Value)";
Description = "The most frequently occurring value in a rolling window";
_series = new LineSeries(name: "Mode", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_mode = new Mode(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _mode.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _mode.IsHot, ShowColdValues);
}
}
-328
View File
@@ -1,328 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Quantower.Tests;
public class PacfIndicatorTests
{
[Fact]
public void PacfIndicator_Constructor_SetsDefaults()
{
var indicator = new PacfIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(1, indicator.Lag);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("PACF - Partial Autocorrelation Function", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void PacfIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new PacfIndicator();
Assert.Equal(0, PacfIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void PacfIndicator_ShortName_IncludesPeriodAndLag()
{
var indicator = new PacfIndicator { Period = 14, Lag = 2 };
Assert.True(indicator.ShortName.Contains("PACF", StringComparison.Ordinal));
Assert.True(indicator.ShortName.Contains("14", StringComparison.Ordinal));
Assert.True(indicator.ShortName.Contains("2", StringComparison.Ordinal));
}
[Fact]
public void PacfIndicator_Initialize_CreatesInternalPacf()
{
var indicator = new PacfIndicator { Period = 10, Lag = 1 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void PacfIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new PacfIndicator { Period = 5, Lag = 1 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
[Fact]
public void PacfIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new PacfIndicator { Period = 5, Lag = 1 };
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 PacfIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new PacfIndicator { Period = 5, Lag = 1 };
indicator.Initialize();
// Should not throw an exception
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
// Assert that the indicator still exists (method completed without exception)
Assert.NotNull(indicator);
}
[Fact]
public void PacfIndicator_MultipleUpdates_ProducesCorrectSequence()
{
var indicator = new PacfIndicator { Period = 5, Lag = 1 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 100, 102, 105, 103, 107, 110 };
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);
}
// All values should be finite
for (int i = 0; i < closes.Length; i++)
{
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(closes.Length - 1 - i)));
}
}
[Fact]
public void PacfIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new PacfIndicator { Period = 5, Lag = 1, Source = source };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)),
$"Source {source} should produce finite value");
}
}
[Fact]
public void PacfIndicator_Period_CanBeChanged()
{
var indicator = new PacfIndicator { Period = 10 };
Assert.Equal(10, indicator.Period);
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
}
[Fact]
public void PacfIndicator_Lag_CanBeChanged()
{
var indicator = new PacfIndicator { Lag = 1 };
Assert.Equal(1, indicator.Lag);
indicator.Lag = 5;
Assert.Equal(5, indicator.Lag);
}
[Fact]
public void PacfIndicator_Source_CanBeChanged()
{
var indicator = new PacfIndicator { Source = SourceType.Close };
Assert.Equal(SourceType.Close, indicator.Source);
indicator.Source = SourceType.Open;
Assert.Equal(SourceType.Open, indicator.Source);
}
[Fact]
public void PacfIndicator_ShowColdValues_CanBeChanged()
{
var indicator = new PacfIndicator { ShowColdValues = true };
Assert.True(indicator.ShowColdValues);
indicator.ShowColdValues = false;
Assert.False(indicator.ShowColdValues);
}
[Fact]
public void PacfIndicator_ShortName_UpdatesWhenPeriodChanges()
{
var indicator = new PacfIndicator { Period = 10 };
string initialName = indicator.ShortName;
Assert.True(initialName.Contains("10", StringComparison.Ordinal));
indicator.Period = 20;
string updatedName = indicator.ShortName;
Assert.True(updatedName.Contains("20", StringComparison.Ordinal));
}
[Fact]
public void PacfIndicator_ShortName_UpdatesWhenLagChanges()
{
var indicator = new PacfIndicator { Lag = 1 };
string initialName = indicator.ShortName;
Assert.True(initialName.Contains("1", StringComparison.Ordinal));
indicator.Lag = 3;
string updatedName = indicator.ShortName;
Assert.True(updatedName.Contains("3", StringComparison.Ordinal));
}
[Fact]
public void PacfIndicator_ProcessUpdate_IgnoresNonBarUpdates()
{
var indicator = new PacfIndicator { Period = 5, Lag = 1 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process historical bar first
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Process other update reasons - should not throw
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
// Assert that the indicator still exists (method completed without exception)
Assert.NotNull(indicator);
}
[Fact]
public void PacfIndicator_LineSeries_HasCorrectProperties()
{
var indicator = new PacfIndicator { Period = 10 };
indicator.Initialize();
var lineSeries = indicator.LinesSeries[0];
Assert.Equal("PACF", lineSeries.Name);
Assert.Equal(2, lineSeries.Width);
Assert.Equal(LineStyle.Solid, lineSeries.Style);
}
[Fact]
public void PacfIndicator_DifferentLagValues_Work()
{
var lags = new[] { 1, 2, 3, 5, 10 };
foreach (var lag in lags)
{
// Period must be > lag + 1
int period = Math.Max(20, lag + 5);
var indicator = new PacfIndicator { Period = period, Lag = lag };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add enough bars to fill the buffer
for (int i = 0; i < period + 5; i++)
{
double close = 100 + (i % 10);
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 2, close - 2, close);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
// Last value should be finite and bounded
double pacfValue = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(pacfValue), $"Lag {lag} should produce finite value");
Assert.True(pacfValue >= -1 && pacfValue <= 1, $"PACF at lag {lag} should be bounded [-1, 1]");
}
}
[Fact]
public void PacfIndicator_PacfValuesAreBounded()
{
var indicator = new PacfIndicator { Period = 10, Lag = 1 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 100, 102, 98, 105, 97, 110, 95, 108, 92, 115, 90, 120 };
foreach (var close in closes)
{
indicator.HistoricalData.AddBar(now, close, close + 5, close - 5, close);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
now = now.AddMinutes(1);
}
// All PACF values should be bounded between -1 and 1
for (int i = 0; i < closes.Length; i++)
{
double value = indicator.LinesSeries[0].GetValue(closes.Length - 1 - i);
Assert.True(value >= -1 && value <= 1, $"PACF value at index {i} should be bounded [-1, 1], got {value}");
}
}
[Fact]
public void PacfIndicator_AtLagOne_EqualsAcf()
{
// PACF at lag 1 should equal ACF at lag 1 (key mathematical property)
var pacfIndicator = new PacfIndicator { Period = 10, Lag = 1 };
var acfIndicator = new AcfIndicator { Period = 10, Lag = 1 };
pacfIndicator.Initialize();
acfIndicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 100, 102, 98, 105, 97, 110, 95, 108, 92, 115, 90, 120 };
foreach (var close in closes)
{
pacfIndicator.HistoricalData.AddBar(now, close, close + 5, close - 5, close);
acfIndicator.HistoricalData.AddBar(now, close, close + 5, close - 5, close);
pacfIndicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
acfIndicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
now = now.AddMinutes(1);
}
// At lag 1, PACF should equal ACF
double pacfValue = pacfIndicator.LinesSeries[0].GetValue(0);
double acfValue = acfIndicator.LinesSeries[0].GetValue(0);
Assert.Equal(acfValue, pacfValue, 6); // Allow for minor floating-point differences
}
}
-68
View File
@@ -1,68 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class PacfIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 3, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Lag", sortIndex: 2, 1, 100, 1, 0)]
public int Lag { get; set; } = 1;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Pacf _pacf = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"PACF ({Period},{Lag})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/pacf/Pacf.Quantower.cs";
public PacfIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "PACF - Partial Autocorrelation Function";
Description = "Measures the correlation of a time series with a lagged copy after removing effects of shorter lags";
_series = new LineSeries(name: "PACF", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_pacf = new Pacf(Period, Lag);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar)
{
return;
}
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _pacf.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _pacf.IsHot, ShowColdValues);
}
}
@@ -1,66 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class PercentileIndicatorTests
{
[Fact]
public void PercentileIndicator_Constructor_SetsDefaults()
{
var indicator = new PercentileIndicator();
Assert.Equal(14, indicator.Period);
Assert.Equal(50.0, indicator.Percent);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Percentile - Rolling Percentile", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void PercentileIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new PercentileIndicator { Period = 14 };
Assert.Equal(0, PercentileIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void PercentileIndicator_Initialize_CreatesInternalPercentile()
{
var indicator = new PercentileIndicator { Period = 10, Percent = 25.0 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Percentile", indicator.LinesSeries[0].Name);
}
[Fact]
public void PercentileIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new PercentileIndicator { Period = 5, Percent = 75.0 };
indicator.Initialize();
// Add historical data
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);
}
// Line series should have a value
double percentile = indicator.LinesSeries[0].GetValue(0);
// Percentile of a trending series should be finite
Assert.True(double.IsFinite(percentile));
}
}
@@ -1,63 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class PercentileIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("Percentile (0-100)", sortIndex: 2, 0, 100, 0.1, 1)]
public double Percent { get; set; } = 50.0;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Percentile _percentile = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Percentile {Period} ({Percent}%)";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/percentile/Percentile.Quantower.cs";
public PercentileIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "Percentile - Rolling Percentile";
Description = "Value below which a given percentage of observations fall in a rolling window";
_series = new LineSeries(name: "Percentile", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_percentile = new Percentile(Period, Percent);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _percentile.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _percentile.IsHot, ShowColdValues);
}
}
@@ -1,193 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class PolyfitIndicatorTests
{
// ── 1. Constructor defaults ───────────────────────────────────────────────
[Fact]
public void Constructor_DefaultValues()
{
var ind = new PolyfitIndicator();
Assert.Equal(20, ind.Period);
Assert.Equal(2, ind.Degree);
Assert.True(ind.ShowColdValues);
Assert.Equal("Polyfit - Polynomial Fitting", ind.Name);
Assert.False(ind.SeparateWindow);
Assert.True(ind.OnBackGround);
Assert.Equal(SourceType.Close, ind.Source);
}
[Fact]
public void Constructor_ShortName_IncludesPeriodDegree()
{
var ind = new PolyfitIndicator { Period = 10, Degree = 3 };
Assert.Equal("Polyfit 10,3", ind.ShortName);
}
// ── 2. MinHistoryDepths ───────────────────────────────────────────────────
[Fact]
public void MinHistoryDepths_IsZero()
{
Assert.Equal(0, PolyfitIndicator.MinHistoryDepths);
}
[Fact]
public void MinHistoryDepths_InterfaceImplementation()
{
IWatchlistIndicator ind = new PolyfitIndicator();
Assert.Equal(0, ind.MinHistoryDepths);
}
// ── 3. Initialize creates internal indicator and line series ──────────────
[Fact]
public void Initialize_CreatesLineSeries()
{
var ind = new PolyfitIndicator { Period = 10 };
ind.Initialize();
Assert.Single(ind.LinesSeries);
Assert.Equal("Polyfit", ind.LinesSeries[0].Name);
}
[Fact]
public void Initialize_CustomPeriodDegree()
{
var ind = new PolyfitIndicator { Period = 8, Degree = 3 };
ind.Initialize();
Assert.Equal("Polyfit 8,3", ind.ShortName);
}
// ── 4. ProcessUpdate — historical data ────────────────────────────────────
[Fact]
public void ProcessUpdate_HistoricalBars_ProducesFiniteValues()
{
var ind = new PolyfitIndicator { Period = 5, Degree = 2 };
ind.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
ind.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
ind.ProcessUpdate(args);
}
double val = ind.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val));
}
[Fact]
public void ProcessUpdate_NewBar_UpdatesValue()
{
var ind = new PolyfitIndicator { Period = 5, Degree = 2 };
ind.Initialize();
var now = DateTime.UtcNow;
// Fill warmup with historical bars
for (int i = 0; i < 5; i++)
{
ind.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
ind.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
double val1 = ind.LinesSeries[0].GetValue(0);
// Add one more new bar
ind.HistoricalData.AddBar(now.AddMinutes(5), 110, 120, 100, 115);
ind.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
double val2 = ind.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val1));
Assert.True(double.IsFinite(val2));
}
[Fact]
public void ProcessUpdate_SameBarUpdate_ProducesFiniteValue()
{
var ind = new PolyfitIndicator { Period = 5, Degree = 2 };
ind.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 5; i++)
{
ind.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
ind.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
// Non-new bar update (bar correction)
ind.HistoricalData.AddBar(now.AddMinutes(4), 108, 118, 98, 112);
ind.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double val = ind.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val));
}
// ── 5. Different source types ─────────────────────────────────────────────
[Theory]
[InlineData(SourceType.Close)]
[InlineData(SourceType.Open)]
[InlineData(SourceType.High)]
[InlineData(SourceType.Low)]
[InlineData(SourceType.HL2)]
public void DifferentSourceTypes_ProducesFiniteValues(SourceType sourceType)
{
var ind = new PolyfitIndicator { Period = 5, Degree = 2, Source = sourceType };
ind.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
ind.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
ind.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
double val = ind.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val));
}
// ── 6. Different degree variants ─────────────────────────────────────────
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void DifferentDegrees_ProducesFiniteValues(int degree)
{
var ind = new PolyfitIndicator { Period = 10, Degree = degree };
ind.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
ind.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
ind.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
double val = ind.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val));
Assert.True(val > 0, "Expected positive overlay value");
}
// ── 7. SeparateWindow and SourceCodeLink ──────────────────────────────────
[Fact]
public void SeparateWindow_IsFalse_Overlay()
{
var ind = new PolyfitIndicator();
Assert.False(ind.SeparateWindow);
}
[Fact]
public void SourceCodeLink_ContainsPolyfit()
{
var ind = new PolyfitIndicator();
Assert.Contains("Polyfit", ind.SourceCodeLink, StringComparison.Ordinal);
}
}
@@ -1,63 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class PolyfitIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Degree", sortIndex: 2, 1, 6, 1, 0)]
public int Degree { get; set; } = 2;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Polyfit _polyfit = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Polyfit {Period},{Degree}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/polyfit/Polyfit.Quantower.cs";
public PolyfitIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "Polyfit - Polynomial Fitting";
Description = "Rolling polynomial regression of configurable degree; returns fitted value at current bar";
_series = new LineSeries(name: "Polyfit", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_polyfit = new Polyfit(Period, Degree);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _polyfit.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _polyfit.IsHot, ShowColdValues);
}
}
@@ -1,54 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class QuantileIndicatorTests
{
[Fact]
public void QuantileIndicator_Constructor_DefaultValues()
{
var indicator = new QuantileIndicator();
Assert.Equal(14, indicator.Period);
Assert.Equal(0.5, indicator.QuantileLevel);
Assert.False(indicator.SeparateWindow);
}
[Fact]
public void QuantileIndicator_MinHistoryDepths()
{
var indicator = new QuantileIndicator { Period = 20 };
Assert.Equal(20, indicator.Period);
}
[Fact]
public void QuantileIndicator_Initialize_CreatesInternalQuantile()
{
var indicator = new QuantileIndicator { Period = 10, QuantileLevel = 0.75 };
indicator.Initialize();
Assert.Equal("Quantile 10 (0.75)", indicator.ShortName);
}
[Fact]
public void QuantileIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new QuantileIndicator { Period = 5, QuantileLevel = 0.75 };
indicator.Initialize();
// Add historical data
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);
}
// Line series should have a value
double quantile = indicator.LinesSeries[0].GetValue(0);
// Quantile of a trending series should be finite
Assert.True(double.IsFinite(quantile));
}
}
@@ -1,63 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class QuantileIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("Quantile Level (0.0-1.0)", sortIndex: 2, 0.0, 1.0, 0.01, 2)]
public double QuantileLevel { get; set; } = 0.5;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Quantile _quantile = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Quantile {Period} ({QuantileLevel})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/quantile/Quantile.Quantower.cs";
public QuantileIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "Quantile - Rolling Quantile";
Description = "Fraction of observations that fall below a given value in a rolling window";
_series = new LineSeries(name: "Quantile", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_quantile = new Quantile(Period, QuantileLevel);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _quantile.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _quantile.IsHot, ShowColdValues);
}
}
@@ -1,70 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class SkewIndicatorTests
{
[Fact]
public void SkewIndicator_Constructor_SetsDefaults()
{
var indicator = new SkewIndicator();
Assert.Equal(20, indicator.Period);
Assert.False(indicator.IsPopulation);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Skew - Skewness", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void SkewIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new SkewIndicator { Period = 20 };
Assert.Equal(0, SkewIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void SkewIndicator_Initialize_CreatesInternalSkew()
{
var indicator = new SkewIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Skew", indicator.LinesSeries[0].Name);
}
[Fact]
public void SkewIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new SkewIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double skew = indicator.LinesSeries[0].GetValue(0);
// Skew of a linear trend (100, 101, 102...) is 0 (symmetric)
Assert.True(double.IsFinite(skew));
Assert.Equal(0, skew, 9);
}
}
-63
View File
@@ -1,63 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class SkewIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 3, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Population Skewness", sortIndex: 2)]
public bool IsPopulation { get; set; } = false;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Skew _skew = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Skew {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/skew/Skew.Quantower.cs";
public SkewIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Skew - Skewness";
Description = "Measures the asymmetry of the probability distribution of a real-valued random variable about its mean";
_series = new LineSeries(name: "Skew", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_skew = new Skew(Period, IsPopulation);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _skew.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _skew.IsHot, ShowColdValues);
}
}
@@ -1,136 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public sealed class SpearmanIndicatorTests
{
[Fact]
public void SpearmanIndicator_Constructor_SetsDefaults()
{
var indicator = new SpearmanIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.Equal(SourceType.Open, indicator.Source2);
Assert.True(indicator.ShowColdValues);
Assert.Equal("SPEARMAN - Spearman Rank Correlation", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void SpearmanIndicator_MinHistoryDepths_EqualsTwo()
{
var indicator = new SpearmanIndicator();
Assert.Equal(2, SpearmanIndicator.MinHistoryDepths);
Assert.Equal(2, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void SpearmanIndicator_ShortName_IncludesPeriodAndSources()
{
var indicator = new SpearmanIndicator { Period = 20 };
Assert.Contains("SPEARMAN", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void SpearmanIndicator_Initialize_CreatesInternalSpearman()
{
var indicator = new SpearmanIndicator { Period = 10 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void SpearmanIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new SpearmanIndicator { Period = 5 };
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);
}
[Fact]
public void SpearmanIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new SpearmanIndicator { Period = 5 };
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 SpearmanIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new SpearmanIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
double secondValue = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsNaN(firstValue) || double.IsFinite(firstValue));
Assert.True(double.IsNaN(secondValue) || double.IsFinite(secondValue));
}
[Fact]
public void SpearmanIndicator_MultipleUpdates_ProducesSequence()
{
var indicator = new SpearmanIndicator { Period = 3 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] opens = [100, 101, 102, 103, 104, 105];
double[] closes = [100, 101, 102, 103, 104, 105];
for (int i = 0; i < opens.Length; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), opens[i], opens[i] + 5, opens[i] - 5, closes[i]);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
Assert.Equal(opens.Length, indicator.LinesSeries[0].Count);
}
[Fact]
public void SpearmanIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new SpearmanIndicator { Period = 5, Source = source, Source2 = SourceType.Close };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Should not throw and should produce output
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(1, indicator.LinesSeries[0].Count);
}
}
}
@@ -1,79 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
/// <summary>
/// Quantower adapter for Spearman Rank Correlation indicator.
/// Measures monotonic association between two price sources from the same symbol.
/// </summary>
/// <remarks>
/// This adapter compares two different price sources from the same symbol (e.g., Close vs Open,
/// Close vs Volume, High vs Low). For cross-symbol correlation, use the core
/// Spearman class directly.
///
/// Output is Spearman's ρ coefficient, ranging from -1 to +1.
/// Values near +1 indicate strong positive monotonic association, near -1 strong negative.
/// </remarks>
[SkipLocalsInit]
public sealed class SpearmanIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 0, minimum: 2, maximum: 10000)]
public int Period { get; set; } = 20;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Source 2 Type", sortIndex: 2)]
public SourceType Source2 { get; set; } = SourceType.Open;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Spearman _spearman = null!;
private readonly LineSeries _series;
private string _sourceName = null!;
private Func<IHistoryItem, double> _priceSelector = null!;
private Func<IHistoryItem, double> _priceSelector2 = null!;
public static int MinHistoryDepths => 2;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"SPEARMAN({Period}):{_sourceName}/{Source2}";
public SpearmanIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "SPEARMAN - Spearman Rank Correlation";
Description = "Measures monotonic association between two price sources. Range: -1 to +1.";
_series = new LineSeries(name: "Spearman", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_priceSelector2 = Source2.GetPriceSelector();
_sourceName = Source.ToString();
_spearman = new Spearman(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double valueA = _priceSelector(item);
double valueB = _priceSelector2(item);
var tvalA = new TValue(item.TimeLeft.Ticks, valueA);
var tvalB = new TValue(item.TimeLeft.Ticks, valueB);
double value = _spearman.Update(tvalA, tvalB, isNew).Value;
_series.SetValue(value, _spearman.IsHot, ShowColdValues);
}
}
@@ -1,68 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class StdDevIndicatorTests
{
[Fact]
public void StdDevIndicator_Constructor_SetsDefaults()
{
var indicator = new StdDevIndicator();
Assert.Equal(20, indicator.Period);
Assert.False(indicator.IsPopulation);
Assert.True(indicator.ShowColdValues);
Assert.Equal("StdDev - Standard Deviation", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void StdDevIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new StdDevIndicator { Period = 20 };
Assert.Equal(0, StdDevIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void StdDevIndicator_Initialize_CreatesInternalStdDev()
{
var indicator = new StdDevIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("StdDev", indicator.LinesSeries[0].Name);
}
[Fact]
public void StdDevIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new StdDevIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double stdDev = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(stdDev));
}
}
-63
View File
@@ -1,63 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class StdDevIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Population StdDev", sortIndex: 2)]
public bool IsPopulation { get; set; } = false;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private StdDev _stdDev = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"StdDev {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/stddev/StdDev.Quantower.cs";
public StdDevIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "StdDev - Standard Deviation";
Description = "Measures the amount of variation or dispersion of a set of values";
_series = new LineSeries(name: "StdDev", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_stdDev = new StdDev(Period, IsPopulation);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _stdDev.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _stdDev.IsHot, ShowColdValues);
}
}
@@ -1,99 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class StderrIndicatorTests
{
[Fact]
public void StderrIndicator_Constructor_SetsDefaults()
{
var indicator = new StderrIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Stderr - Standard Error of Regression", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void StderrIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new StderrIndicator { Period = 14 };
Assert.Equal(0, StderrIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void StderrIndicator_Initialize_CreatesInternalStderr()
{
var indicator = new StderrIndicator { Period = 10 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
Assert.Equal("Stderr", indicator.LinesSeries[0].Name);
}
[Fact]
public void StderrIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new StderrIndicator { Period = 5 };
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);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
Assert.True(value >= 0.0);
}
[Fact]
public void StderrIndicator_DifferentSourceTypes()
{
var indicator = new StderrIndicator { Period = 5, Source = SourceType.Open };
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);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
}
[Fact]
public void StderrIndicator_LinearData_ReturnsNearZero()
{
var indicator = new StderrIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Perfectly linear close prices → residuals = 0 → Stderr ≈ 0
for (int i = 0; i < 20; i++)
{
double price = 100.0 + i * 2.0;
indicator.HistoricalData.AddBar(now.AddMinutes(i), price - 1, price + 1, price - 2, price);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
Assert.Equal(0.0, value, precision: 6);
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class StderrIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 3, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Stderr _stderr = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Stderr {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/stderr/Stderr.Quantower.cs";
public StderrIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Stderr - Standard Error of Regression";
Description = "Average distance of observed values from the linear regression line.";
_series = new LineSeries(name: "Stderr", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_stderr = new Stderr(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _stderr.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _stderr.IsHot, ShowColdValues);
}
}
-186
View File
@@ -1,186 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class SumIndicatorTests
{
[Fact]
public void SumIndicator_Constructor_SetsDefaults()
{
var indicator = new SumIndicator();
Assert.Equal(14, indicator.Period);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("SUM - Rolling Sum", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void SumIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new SumIndicator();
Assert.Equal(0, SumIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void SumIndicator_ShortName_IncludesPeriodAndSource()
{
var indicator = new SumIndicator { Period = 20 };
Assert.Contains("SUM", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void SumIndicator_Initialize_CreatesInternalSum()
{
var indicator = new SumIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void SumIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new SumIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
[Fact]
public void SumIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new SumIndicator { Period = 5 };
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 SumIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new SumIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
double secondValue = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(firstValue));
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void SumIndicator_MultipleUpdates_ProducesCorrectSumSequence()
{
var indicator = new SumIndicator { Period = 3 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 10, 20, 30, 40, 50 };
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);
}
// All values should be finite
for (int i = 0; i < closes.Length; i++)
{
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(closes.Length - 1 - i)));
}
// Last SUM(3) should be sum of last 3 values: 30 + 40 + 50 = 120
double lastSum = indicator.LinesSeries[0].GetValue(0);
Assert.Equal(120.0, lastSum, 1e-10);
}
[Fact]
public void SumIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new SumIndicator { Period = 5, Source = source };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)),
$"Source {source} should produce finite value");
}
}
[Fact]
public void SumIndicator_CalculatesRollingSum()
{
var indicator = new SumIndicator { Period = 3 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add bars with known close prices: 10, 20, 30, 40
indicator.HistoricalData.AddBar(now, 10, 10, 10, 10);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(10.0, indicator.LinesSeries[0].GetValue(0), 1e-10); // Sum = 10
indicator.HistoricalData.AddBar(now.AddMinutes(1), 20, 20, 20, 20);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(30.0, indicator.LinesSeries[0].GetValue(0), 1e-10); // Sum = 10+20 = 30
indicator.HistoricalData.AddBar(now.AddMinutes(2), 30, 30, 30, 30);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(60.0, indicator.LinesSeries[0].GetValue(0), 1e-10); // Sum = 10+20+30 = 60
indicator.HistoricalData.AddBar(now.AddMinutes(3), 40, 40, 40, 40);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(90.0, indicator.LinesSeries[0].GetValue(0), 1e-10); // Sum = 20+30+40 = 90 (10 dropped)
}
[Fact]
public void SumIndicator_Period_CanBeChanged()
{
var indicator = new SumIndicator { Period = 50 };
Assert.Equal(50, indicator.Period);
indicator.Period = 100;
Assert.Equal(100, indicator.Period);
}
}
-55
View File
@@ -1,55 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class SumIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 0, minimum: 1, maximum: 10000)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Sum _sum = null!;
private readonly LineSeries _series;
private string _sourceName = null!;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"SUM({Period}):{_sourceName}";
public SumIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "SUM - Rolling Sum";
Description = "Rolling Sum with Kahan-Babuška summation for numerical stability";
_series = new LineSeries(name: "SUM", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_sum = new Sum(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _sum.Update(new TValue(item.TimeLeft.Ticks, _priceSelector(item)), isNew).Value;
_series.SetValue(value, _sum.IsHot, ShowColdValues);
}
}
@@ -1,108 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class TheilIndicatorTests
{
[Fact]
public void TheilIndicator_Constructor_SetsDefaults()
{
var indicator = new TheilIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Theil - Theil T Index", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void TheilIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new TheilIndicator { Period = 14 };
Assert.Equal(0, TheilIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void TheilIndicator_ShortName_IncludesPeriod()
{
var indicator = new TheilIndicator { Period = 20 };
Assert.Equal("Theil 20", indicator.ShortName);
}
[Fact]
public void TheilIndicator_Initialize_CreatesInternalTheil()
{
var indicator = new TheilIndicator { Period = 10 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
Assert.Equal("Theil", indicator.LinesSeries[0].Name);
}
[Fact]
public void TheilIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new TheilIndicator { Period = 5 };
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);
}
double theil = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(theil));
Assert.True(theil >= -1e-10, $"Expected non-negative Theil, got {theil}");
}
[Fact]
public void TheilIndicator_NewBar_UpdatesValue()
{
var indicator = new TheilIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
_ = indicator.LinesSeries[0].GetValue(0);
indicator.HistoricalData.AddBar(now.AddMinutes(10), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(valueAfter));
}
[Fact]
public void TheilIndicator_DifferentSourceTypes()
{
var indicator = new TheilIndicator { Period = 5, Source = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double theil = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(theil));
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class TheilIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Theil _theil = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Theil {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/theil/Theil.Quantower.cs";
public TheilIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Theil - Theil T Index";
Description = "Measures inequality/concentration of values using generalized entropy";
_series = new LineSeries(name: "Theil", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_theil = new Theil(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _theil.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _theil.IsHot, ShowColdValues);
}
}
@@ -1,61 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class TrimIndicatorTests
{
[Fact]
public void TrimIndicator_Constructor_SetsDefaults()
{
var indicator = new TrimIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(10.0, indicator.TrimPct);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Trim - Trimmed Mean Moving Average", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void TrimIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new TrimIndicator { Period = 20 };
Assert.Equal(0, TrimIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void TrimIndicator_Initialize_CreatesInternalTrim()
{
var indicator = new TrimIndicator { Period = 10, TrimPct = 10.0 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
Assert.Equal("Trim", indicator.LinesSeries[0].Name);
}
[Fact]
public void TrimIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new TrimIndicator { Period = 5, TrimPct = 10.0 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
double close = 100 + Math.Sin(i * 0.5);
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 2, close - 2, close);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
}
}
-63
View File
@@ -1,63 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class TrimIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 3, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Trim %", sortIndex: 2, 0, 49, 1, 0)]
public double TrimPct { get; set; } = 10.0;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Trim _trim = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Trim {Period}/{TrimPct}%";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/trim/Trim.Quantower.cs";
public TrimIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "Trim - Trimmed Mean Moving Average";
Description = "Rolling mean after discarding extreme values from each tail";
_series = new LineSeries(name: "Trim", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_trim = new Trim(Period, TrimPct);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _trim.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _trim.IsHot, ShowColdValues);
}
}
@@ -1,230 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Quantower.Tests;
public class VarianceIndicatorTests
{
[Fact]
public void VarianceIndicator_Constructor_SetsDefaults()
{
var indicator = new VarianceIndicator();
Assert.Equal(20, indicator.Period);
Assert.False(indicator.IsPopulation);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Variance - Rolling Variance", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void VarianceIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new VarianceIndicator();
Assert.Equal(0, VarianceIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void VarianceIndicator_ShortName_IncludesPeriod()
{
var indicator = new VarianceIndicator { Period = 14 };
Assert.True(indicator.ShortName.Contains("Variance", StringComparison.Ordinal));
Assert.True(indicator.ShortName.Contains("14", StringComparison.Ordinal));
}
[Fact]
public void VarianceIndicator_Initialize_CreatesInternalVariance()
{
var indicator = new VarianceIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void VarianceIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new VarianceIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
[Fact]
public void VarianceIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new VarianceIndicator { Period = 5 };
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 VarianceIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new VarianceIndicator { Period = 5 };
indicator.Initialize();
// Should not throw an exception
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
// Assert that the indicator still exists (method completed without exception)
Assert.NotNull(indicator);
}
[Fact]
public void VarianceIndicator_MultipleUpdates_ProducesCorrectSequence()
{
var indicator = new VarianceIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 100, 102, 105, 103, 107, 110 };
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);
}
// All values should be finite
for (int i = 0; i < closes.Length; i++)
{
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(closes.Length - 1 - i)));
}
}
[Fact]
public void VarianceIndicator_DifferentSourceTypes_Work()
{
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
foreach (var source in sources)
{
var indicator = new VarianceIndicator { Period = 5, Source = source };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)),
$"Source {source} should produce finite value");
}
}
[Fact]
public void VarianceIndicator_Period_CanBeChanged()
{
var indicator = new VarianceIndicator { Period = 10 };
Assert.Equal(10, indicator.Period);
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
}
[Fact]
public void VarianceIndicator_IsPopulation_CanBeChanged()
{
var indicator = new VarianceIndicator { IsPopulation = false };
Assert.False(indicator.IsPopulation);
indicator.IsPopulation = true;
Assert.True(indicator.IsPopulation);
}
[Fact]
public void VarianceIndicator_Source_CanBeChanged()
{
var indicator = new VarianceIndicator { Source = SourceType.Close };
Assert.Equal(SourceType.Close, indicator.Source);
indicator.Source = SourceType.Open;
Assert.Equal(SourceType.Open, indicator.Source);
}
[Fact]
public void VarianceIndicator_ShowColdValues_CanBeChanged()
{
var indicator = new VarianceIndicator { ShowColdValues = true };
Assert.True(indicator.ShowColdValues);
indicator.ShowColdValues = false;
Assert.False(indicator.ShowColdValues);
}
[Fact]
public void VarianceIndicator_ShortName_UpdatesWhenPeriodChanges()
{
var indicator = new VarianceIndicator { Period = 10 };
string initialName = indicator.ShortName;
Assert.True(initialName.Contains("10", StringComparison.Ordinal));
indicator.Period = 20;
string updatedName = indicator.ShortName;
Assert.True(updatedName.Contains("20", StringComparison.Ordinal));
}
[Fact]
public void VarianceIndicator_ProcessUpdate_IgnoresNonBarUpdates()
{
var indicator = new VarianceIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process historical bar first
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Process other update reasons - should not throw
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
// Assert that the indicator still exists (method completed without exception)
Assert.NotNull(indicator);
}
[Fact]
public void VarianceIndicator_LineSeries_HasCorrectProperties()
{
var indicator = new VarianceIndicator { Period = 10 };
indicator.Initialize();
var lineSeries = indicator.LinesSeries[0];
Assert.Equal("Variance", lineSeries.Name);
Assert.Equal(2, lineSeries.Width);
Assert.Equal(LineStyle.Solid, lineSeries.Style);
}
}
@@ -1,68 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class VarianceIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Population Variance", sortIndex: 2)]
public bool IsPopulation { get; set; } = false;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Variance _variance = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Variance {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/variance/Variance.Quantower.cs";
public VarianceIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Variance - Rolling Variance";
Description = "Measures the dispersion of a set of data points around their mean";
_series = new LineSeries(name: "Variance", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_variance = new Variance(Period, IsPopulation);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar)
{
return;
}
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _variance.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _variance.IsHot, ShowColdValues);
}
}
+40
View File
@@ -717,4 +717,44 @@ public class VarianceTests
Assert.Equal(0, output[0]); // N=1
Assert.Equal(50, output[1]); // Var([10,20]) = 50
}
[Fact]
public void Batch_AllNonFinite_UsesScalarFallbackAndReturnsFinite()
{
double[] source = [double.NaN, double.PositiveInfinity, double.NegativeInfinity, double.NaN];
double[] output = new double[source.Length];
Variance.Batch(source.AsSpan(), output.AsSpan(), 2);
foreach (double value in output)
{
Assert.True(double.IsFinite(value));
Assert.True(value >= 0);
}
}
[Fact]
public void Calculate_ReturnsConfiguredIndicatorAndMatchingResults()
{
const int period = 5;
var source = new TSeries();
var now = DateTime.UtcNow;
for (int i = 0; i < 25; i++)
{
source.Add(now.AddSeconds(i), 100 + i);
}
var (results, indicator) = Variance.Calculate(source, period, isPopulation: true);
var batch = Variance.Batch(source, period, isPopulation: true);
Assert.NotNull(indicator);
Assert.Equal(period, indicator.WarmupPeriod);
Assert.Equal(batch.Count, results.Count);
for (int i = 0; i < results.Count; i++)
{
Assert.Equal(batch[i].Value, results[i].Value, 10);
}
}
}
@@ -1,60 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class WavgIndicatorTests
{
[Fact]
public void WavgIndicator_Constructor_SetsDefaults()
{
var indicator = new WavgIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Wavg - Linearly Weighted Average", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void WavgIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new WavgIndicator { Period = 14 };
Assert.Equal(0, WavgIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void WavgIndicator_Initialize_CreatesInternalWavg()
{
var indicator = new WavgIndicator { Period = 10 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
Assert.Equal("Wavg", indicator.LinesSeries[0].Name);
}
[Fact]
public void WavgIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new WavgIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
double close = 100 + Math.Sin(i * 0.5);
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 2, close - 2, close);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class WavgIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Wavg _wavg = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Wavg {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/wavg/Wavg.Quantower.cs";
public WavgIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "Wavg - Linearly Weighted Average";
Description = "Rolling linearly-weighted average (identical to WMA) categorized as statistics";
_series = new LineSeries(name: "Wavg", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_wavg = new Wavg(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _wavg.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _wavg.IsHot, ShowColdValues);
}
}
@@ -1,61 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class WinsIndicatorTests
{
[Fact]
public void WinsIndicator_Constructor_SetsDefaults()
{
var indicator = new WinsIndicator();
Assert.Equal(20, indicator.Period);
Assert.Equal(10.0, indicator.WinPct);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Wins - Winsorized Mean Moving Average", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void WinsIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new WinsIndicator { Period = 20 };
Assert.Equal(0, WinsIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void WinsIndicator_Initialize_CreatesInternalWins()
{
var indicator = new WinsIndicator { Period = 10, WinPct = 10.0 };
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
Assert.Equal("Wins", indicator.LinesSeries[0].Name);
}
[Fact]
public void WinsIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new WinsIndicator { Period = 5, WinPct = 10.0 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
double close = 100 + Math.Sin(i * 0.5);
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 2, close - 2, close);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
}
}
-63
View File
@@ -1,63 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class WinsIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 3, 2000, 1, 0)]
public int Period { get; set; } = 20;
[InputParameter("Winsorize %", sortIndex: 2, 0, 49, 1, 0)]
public double WinPct { get; set; } = 10.0;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Wins _wins = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Wins {Period}/{WinPct}%";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/wins/Wins.Quantower.cs";
public WinsIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "Wins - Winsorized Mean Moving Average";
Description = "Rolling mean after replacing extreme tail values with boundary values";
_series = new LineSeries(name: "Wins", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_wins = new Wins(Period, WinPct);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _wins.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _wins.IsHot, ShowColdValues);
}
}
@@ -1,116 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class ZscoreIndicatorTests
{
[Fact]
public void ZscoreIndicator_Constructor_SetsDefaults()
{
var indicator = new ZscoreIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Contains("ZSCORE", indicator.Name, StringComparison.Ordinal);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void ZscoreIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new ZscoreIndicator { Period = 14 };
Assert.Equal(0, ZscoreIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void ZscoreIndicator_Initialize_CreatesInternalZscore()
{
var indicator = new ZscoreIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("Z-Score", indicator.LinesSeries[0].Name);
}
[Fact]
public void ZscoreIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new ZscoreIndicator { Period = 5 };
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);
}
double zscore = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(zscore));
}
[Fact]
public void ZscoreIndicator_DifferentSourceTypes()
{
var indicator = new ZscoreIndicator { Period = 5, Source = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double zscore = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(zscore));
}
[Fact]
public void ZscoreIndicator_ShortName_IncludesPeriod()
{
var indicator = new ZscoreIndicator { Period = 20 };
Assert.Equal("ZSCORE(20)", indicator.ShortName);
}
[Fact]
public void ZscoreIndicator_NewBar_UpdatesValue()
{
var indicator = new ZscoreIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add enough bars to warm up
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
_ = indicator.LinesSeries[0].GetValue(0);
// Add a new bar with a very different value
indicator.HistoricalData.AddBar(now.AddMinutes(10), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
// Value should change after adding a significantly different bar
Assert.True(double.IsFinite(valueAfter));
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class ZscoreIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Zscore _zscore = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ZSCORE({Period})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/zscore/Zscore.Quantower.cs";
public ZscoreIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "ZSCORE - Z-Score (Population Standard Score)";
Description = "Measures how many population standard deviations a value is from the mean";
_series = new LineSeries(name: "Z-Score", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_zscore = new Zscore(Period);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _zscore.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _zscore.IsHot, ShowColdValues);
}
}
@@ -1,6 +1,8 @@
using OoplesFinance.StockIndicators;
using OoplesFinance.StockIndicators.Models;
using Skender.Stock.Indicators;
namespace QuanTAlib.Validation;
/// <summary>
@@ -139,4 +141,37 @@ public sealed class ZscoreValidationTests
int finiteCount = values.Count(v => double.IsFinite(v));
Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}");
}
/// <summary>
/// Structural validation using Skender <c>GetStdDev</c> as a related metric.
/// Z-score = (value - mean) / stddev. Skender provides GetStdDev which computes
/// the denominator of the z-score formula. We verify that QuanTAlib z-score
/// is consistent with the relationship: z * stddev + mean ≈ value.
/// Skender v2 does not have a direct GetZScore method.
/// </summary>
[Fact]
public void Validate_Skender_StdDev_RelatedToZscore()
{
using var data = new QuanTAlib.Tests.ValidationTestData();
const int period = 20;
// QuanTAlib Zscore (streaming)
var zs = new Zscore(period);
foreach (var tv in data.Data)
{
zs.Update(tv);
}
// Skender StdDev
var sResult = data.SkenderQuotes.GetStdDev(period).ToList();
// Structural: Skender StdDev produces finite output
int finiteCount = sResult.Count(r => r.StdDev is not null && double.IsFinite(r.StdDev.Value));
Assert.True(finiteCount > 100, $"Skender StdDev should produce >100 finite values, got {finiteCount}");
// QuanTAlib Zscore must be finite and bounded
Assert.True(double.IsFinite(zs.Last.Value), "QuanTAlib Zscore last must be finite");
Assert.True(zs.Last.Value > -10 && zs.Last.Value < 10,
$"Zscore {zs.Last.Value} outside expected [-10,10] range for GBM data");
}
}
@@ -1,117 +0,0 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public sealed class ZtestIndicatorTests
{
[Fact]
public void ZtestIndicator_Constructor_SetsDefaults()
{
var indicator = new ZtestIndicator();
Assert.Equal(30, indicator.Period);
Assert.Equal(0.0, indicator.Mu0);
Assert.True(indicator.ShowColdValues);
Assert.Contains("ZTEST", indicator.Name, StringComparison.Ordinal);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(SourceType.Close, indicator.Source);
}
[Fact]
public void ZtestIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new ZtestIndicator { Period = 30 };
Assert.Equal(0, ZtestIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void ZtestIndicator_Initialize_CreatesInternalZtest()
{
var indicator = new ZtestIndicator { Period = 10 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
Assert.Equal("t-stat", indicator.LinesSeries[0].Name);
}
[Fact]
public void ZtestIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new ZtestIndicator { Period = 5 };
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);
}
double tStat = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(tStat));
}
[Fact]
public void ZtestIndicator_DifferentSourceTypes()
{
var indicator = new ZtestIndicator { Period = 5, Source = SourceType.Open };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double tStat = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(tStat));
}
[Fact]
public void ZtestIndicator_ShortName_IncludesPeriod()
{
var indicator = new ZtestIndicator { Period = 20 };
Assert.Equal("ZTEST(20)", indicator.ShortName);
}
[Fact]
public void ZtestIndicator_NewBar_UpdatesValue()
{
var indicator = new ZtestIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
// Add enough bars to warm up
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
_ = indicator.LinesSeries[0].GetValue(0);
// Add a new bar with a very different value
indicator.HistoricalData.AddBar(now.AddMinutes(10), 200, 210, 190, 205);
var newArgs = new UpdateArgs(UpdateReason.NewBar);
indicator.ProcessUpdate(newArgs);
double valueAfter = indicator.LinesSeries[0].GetValue(0);
// Value should change after adding a significantly different bar
Assert.True(double.IsFinite(valueAfter));
}
}
-63
View File
@@ -1,63 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class ZtestIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
public int Period { get; set; } = 30;
[InputParameter("Hypothesized Mean (μ₀)", sortIndex: 2)]
public double Mu0 { get; set; } = 0.0;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Ztest _ztest = null!;
private readonly LineSeries _series;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ZTEST({Period})";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/statistics/ztest/Ztest.Quantower.cs";
public ZtestIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "ZTEST - One-Sample t-Test Statistic";
Description = "Computes the t-statistic for a one-sample hypothesis test against a hypothesized mean";
_series = new LineSeries(name: "t-stat", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_ztest = new Ztest(Period, Mu0);
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
double value = _priceSelector(item);
var time = this.HistoricalData.Time();
var input = new TValue(time, value);
TValue result = _ztest.Update(input, args.IsNewBar());
_series.SetValue(result.Value, _ztest.IsHot, ShowColdValues);
}
}