Refactor IndicatorExtensions: Remove unused methods and optimize price retrieval

This commit is contained in:
Miha Kralj
2025-12-24 13:50:19 -08:00
parent c47b106597
commit 8917575994
101 changed files with 1311 additions and 450292 deletions
+3 -21
View File
@@ -19,33 +19,15 @@ public class AdxIndicatorTests
}
[Fact]
public void AdxIndicator_MinHistoryDepths_EqualsPeriod()
public void AdxIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AdxIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, AdxIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void AdxIndicator_ShortName_IncludesParameters()
{
var indicator = new AdxIndicator { Period = 20 };
indicator.Initialize();
Assert.Contains("ADX", indicator.ShortName);
Assert.Contains("20", indicator.ShortName);
}
[Fact]
public void AdxIndicator_SourceCodeLink_IsValid()
{
var indicator = new AdxIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink);
Assert.Contains("Adx.Quantower.cs", indicator.SourceCodeLink);
}
[Fact]
public void AdxIndicator_Initialize_CreatesInternalAdx()
+20 -25
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class AdxIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class AdxIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
@@ -12,15 +14,15 @@ public class AdxIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Adx? _adx;
protected LineSeries? AdxSeries;
protected LineSeries? DiPlusSeries;
protected LineSeries? DiMinusSeries;
private readonly LineSeries? _adxSeries;
private readonly LineSeries? _diPlusSeries;
private readonly LineSeries? _diMinusSeries;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ADX {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/adx/Adx.Quantower.cs";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/momentum/adx/Adx.Quantower.cs";
public AdxIndicator()
{
@@ -29,36 +31,29 @@ public class AdxIndicator : Indicator, IWatchlistIndicator
Name = "ADX - Average Directional Index";
Description = "Measures the strength of a trend";
AdxSeries = new(name: "ADX", color: Color.Blue, width: 2, style: LineStyle.Solid);
DiPlusSeries = new(name: "+DI", color: Color.Green, width: 1, style: LineStyle.Solid);
DiMinusSeries = new(name: "-DI", color: Color.Red, width: 1, style: LineStyle.Solid);
_adxSeries = new(name: "ADX", color: Color.Blue, width: 2, style: LineStyle.Solid);
_diPlusSeries = new(name: "+DI", color: Color.Green, width: 1, style: LineStyle.Solid);
_diMinusSeries = new(name: "-DI", color: Color.Red, width: 1, style: LineStyle.Solid);
AddLineSeries(AdxSeries);
AddLineSeries(DiPlusSeries);
AddLineSeries(DiMinusSeries);
AddLineSeries(_adxSeries);
AddLineSeries(_diPlusSeries);
AddLineSeries(_diMinusSeries);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_adx = new Adx(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _adx!.Update(this.GetInputBar(args), args.IsNewBar());
TBar bar = this.GetInputBar(args);
TValue result = _adx!.Update(bar, isNew);
if (!_adx.IsHot && !ShowColdValues)
{
return;
}
AdxSeries!.SetValue(result.Value);
DiPlusSeries!.SetValue(_adx.DiPlus.Value);
DiMinusSeries!.SetValue(_adx.DiMinus.Value);
_adxSeries!.SetValue(result.Value, _adx.IsHot, ShowColdValues);
_diPlusSeries!.SetValue(_adx.DiPlus.Value, _adx.IsHot, ShowColdValues);
_diMinusSeries!.SetValue(_adx.DiMinus.Value, _adx.IsHot, ShowColdValues);
}
}
+3 -21
View File
@@ -19,33 +19,15 @@ public class AdxrIndicatorTests
}
[Fact]
public void AdxrIndicator_MinHistoryDepths_EqualsPeriod()
public void AdxrIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AdxrIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, AdxrIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void AdxrIndicator_ShortName_IncludesParameters()
{
var indicator = new AdxrIndicator { Period = 20 };
indicator.Initialize();
Assert.Contains("ADXR", indicator.ShortName);
Assert.Contains("20", indicator.ShortName);
}
[Fact]
public void AdxrIndicator_SourceCodeLink_IsValid()
{
var indicator = new AdxrIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink);
Assert.Contains("Adxr.Quantower.cs", indicator.SourceCodeLink);
}
[Fact]
public void AdxrIndicator_Initialize_CreatesInternalAdxr()
+11 -16
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class AdxrIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class AdxrIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
@@ -12,9 +14,9 @@ public class AdxrIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Adxr? _adxr;
protected LineSeries? AdxrSeries;
private readonly LineSeries? _adxrSeries;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ADXR {Period}";
@@ -27,29 +29,22 @@ public class AdxrIndicator : Indicator, IWatchlistIndicator
Name = "ADXR - Average Directional Movement Rating";
Description = "Quantifies the change in momentum of the ADX";
AdxrSeries = new(name: "ADXR", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(AdxrSeries);
_adxrSeries = new(name: "ADXR", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(_adxrSeries);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_adxr = new Adxr(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _adxr!.Update(this.GetInputBar(args), args.IsNewBar());
TBar bar = this.GetInputBar(args);
TValue result = _adxr!.Update(bar, isNew);
if (!_adxr.IsHot && !ShowColdValues)
{
return;
}
AdxrSeries!.SetValue(result.Value);
_adxrSeries!.SetValue(result.Value, _adxr.IsHot, ShowColdValues);
}
}
+17
View File
@@ -128,4 +128,21 @@ public class AdxrTests
Assert.Throws<ArgumentException>(() => new Adxr(0));
Assert.Throws<ArgumentException>(() => new Adxr(-1));
}
[Fact]
public void Chainability_Works()
{
var adxr = new Adxr(14);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Test TBarSeries chain
var result = adxr.Update(bars);
Assert.NotNull(result);
Assert.IsType<TSeries>(result);
// Test TBar chain (returns TValue)
var result2 = adxr.Update(bars[0]);
Assert.IsType<TValue>(result2);
}
}
+4 -4
View File
@@ -20,13 +20,13 @@ public class AoIndicatorTests
}
[Fact]
public void AoIndicator_MinHistoryDepths_EqualsSlowPeriod()
public void AoIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AoIndicator { SlowPeriod = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, AoIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -120,6 +120,6 @@ public class AoIndicatorTests
Assert.Equal(10, indicator.FastPeriod);
Assert.Equal(40, indicator.SlowPeriod);
Assert.Equal(40, indicator.MinHistoryDepths);
Assert.Equal(0, AoIndicator.MinHistoryDepths);
}
}
+19 -39
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class AoIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class AoIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Fast Period", sortIndex: 1, 1, 1000, 1, 0)]
public int FastPeriod { get; set; } = 5;
@@ -15,10 +17,10 @@ public class AoIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Ao? _ao;
protected LineSeries? UpSeries;
protected LineSeries? DownSeries;
private readonly LineSeries? _upSeries;
private readonly LineSeries? _downSeries;
public int MinHistoryDepths => SlowPeriod;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"AO {FastPeriod}:{SlowPeriod}";
@@ -31,69 +33,47 @@ public class AoIndicator : Indicator, IWatchlistIndicator
Name = "AO - Awesome Oscillator";
Description = "Momentum indicator measuring market momentum";
UpSeries = new(name: "AO Up", color: Color.Green, width: 2, style: LineStyle.Solid);
DownSeries = new(name: "AO Down", color: Color.Red, width: 2, style: LineStyle.Solid);
_upSeries = new(name: "AO Up", color: Color.Green, width: 2, style: LineStyle.Solid);
_downSeries = new(name: "AO Down", color: Color.Red, width: 2, style: LineStyle.Solid);
AddLineSeries(UpSeries);
AddLineSeries(DownSeries);
AddLineSeries(_upSeries);
AddLineSeries(_downSeries);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_ao = new Ao(FastPeriod, SlowPeriod);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TBar bar = this.GetInputBar(args);
TValue result = _ao!.Update(bar, isNew);
TValue result = _ao!.Update(this.GetInputBar(args), args.IsNewBar());
if (!_ao.IsHot && !ShowColdValues)
{
return;
}
// Determine color based on momentum
// Green if rising, Red if falling
// We need previous value to compare.
// Since OnUpdate is called multiple times for the same bar (ticks),
// we need to be careful about "previous value".
// Ideally, we compare with the value of the *previous bar*.
// But AO coloring is usually: Current > Previous Bar's AO => Green.
// Or Current > Previous Value (intra-bar)?
// Standard is: "Green bar if the bar is higher than the previous bar. Red bar if the bar is lower than the previous bar."
// "Previous bar" usually means the AO value of the previous period.
// We can get the previous value from the indicator history if we stored it,
// or just use _ao.Last (which is current) and we need the previous one.
// But _ao doesn't expose history directly unless we use TSeries.
// However, Quantower stores history in the Series.
// Get previous value from series
double prevAo = double.NaN;
if (Count > 1)
{
// Try to get from UpSeries
prevAo = UpSeries!.GetValue(1);
prevAo = _upSeries!.GetValue(1);
if (double.IsNaN(prevAo))
{
prevAo = DownSeries!.GetValue(1);
prevAo = _downSeries!.GetValue(1);
}
}
// If first bar, just pick a color (e.g. Green) or NaN
if (double.IsNaN(prevAo) || result.Value > prevAo)
{
UpSeries!.SetValue(result.Value);
DownSeries!.SetValue(double.NaN);
_upSeries!.SetValue(result.Value);
_downSeries!.SetValue(double.NaN);
}
else
{
UpSeries!.SetValue(double.NaN);
DownSeries!.SetValue(result.Value);
_upSeries!.SetValue(double.NaN);
_downSeries!.SetValue(result.Value);
}
}
}
+1 -1
View File
@@ -101,7 +101,7 @@ public class AoTests
}
[Fact]
public void StaticBatch_Matches_Streaming()
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
+4 -4
View File
@@ -20,13 +20,13 @@ public class ApoIndicatorTests
}
[Fact]
public void ApoIndicator_MinHistoryDepths_EqualsSlowPeriod()
public void ApoIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new ApoIndicator { SlowPeriod = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, ApoIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -117,6 +117,6 @@ public class ApoIndicatorTests
Assert.Equal(10, indicator.FastPeriod);
Assert.Equal(40, indicator.SlowPeriod);
Assert.Equal(40, indicator.MinHistoryDepths);
Assert.Equal(0, ApoIndicator.MinHistoryDepths);
}
}
+11 -15
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class ApoIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class ApoIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Fast Period", sortIndex: 1, 1, 1000, 1, 0)]
public int FastPeriod { get; set; } = 12;
@@ -15,9 +17,9 @@ public class ApoIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Apo? _apo;
protected LineSeries? Series;
private readonly LineSeries? _series;
public int MinHistoryDepths => SlowPeriod;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"APO {FastPeriod}:{SlowPeriod}";
@@ -30,28 +32,22 @@ public class ApoIndicator : Indicator, IWatchlistIndicator
Name = "APO - Absolute Price Oscillator";
Description = "Momentum indicator showing the difference between two EMAs";
Series = new(name: "APO", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: "APO", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_apo = new Apo(FastPeriod, SlowPeriod);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _apo!.Update(this.GetInputBar(args), args.IsNewBar());
TBar bar = this.GetInputBar(args);
TValue result = _apo!.Update(bar, isNew);
if (!_apo.IsHot && !ShowColdValues)
{
return;
}
Series!.SetValue(result.Value);
_series!.SetValue(result.Value, _apo.IsHot, ShowColdValues);
}
}
+120 -38
View File
@@ -1,67 +1,149 @@
using Xunit;
using System;
using System.Collections.Generic;
using Xunit;
namespace QuanTAlib.Tests;
namespace QuanTAlib;
public class ApoTests
{
private readonly GBM _gbm;
public ApoTests()
{
_gbm = new GBM();
}
[Fact]
public void Constructor_ValidatesInput()
{
Assert.Throws<ArgumentException>(() => new Apo(fastPeriod: 0));
Assert.Throws<ArgumentException>(() => new Apo(slowPeriod: 0));
Assert.Throws<ArgumentException>(() => new Apo(fastPeriod: 26, slowPeriod: 12)); // Fast >= Slow
}
[Fact]
public void Update_ReturnsValidValue()
public void BasicCalculation_DoesNotCrash()
{
var apo = new Apo(12, 26);
var result = apo.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(0, result.Value); // First value: EMA(100) - EMA(100) = 0
var gbm = new GBM();
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
apo.Update(bars[i]);
}
Assert.True(double.IsFinite(apo.Last.Value));
}
[Fact]
public void IsHot_BecomesTrue()
public void IsNew_Consistency()
{
var apo = new Apo(12, 26);
for (int i = 0; i < 100; i++)
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Feed first 99
for (int i = 0; i < 99; i++)
{
apo.Update(new TValue(DateTime.UtcNow, 100));
apo.Update(bars[i]);
}
Assert.True(apo.IsHot);
// Update with 100th point (isNew=true)
apo.Update(bars[99], true);
// Update with modified 100th point (isNew=false)
var modifiedBar = new TBar(bars[99].Time, bars[99].Open, bars[99].High + 1.0, bars[99].Low - 1.0, bars[99].Close, bars[99].Volume);
var val2 = apo.Update(modifiedBar, false);
// Create new instance and feed up to modified
var apo2 = new Apo(12, 26);
for (int i = 0; i < 99; i++)
{
apo2.Update(bars[i]);
}
var val3 = apo2.Update(modifiedBar, true);
Assert.Equal(val3.Value, val2.Value, 1e-9);
}
[Fact]
public void Batch_Matches_Streaming()
public void Reset_Works()
{
var source = _gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var tSeries = new TSeries(source.Close.Count);
for (int i = 0; i < source.Close.Count; i++)
var apo = new Apo(12, 26);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
tSeries.Add(source.Close[i]);
apo.Update(bars[i]);
}
var apoBatch = Apo.Batch(tSeries, 12, 26);
var apoStream = new Apo(12, 26);
var streamResults = new List<double>();
for (int i = 0; i < tSeries.Count; i++)
apo.Reset();
Assert.Equal(0, apo.Last.Value);
Assert.False(apo.IsHot);
// Feed again
for (int i = 0; i < bars.Count; i++)
{
streamResults.Add(apoStream.Update(tSeries[i]).Value);
apo.Update(bars[i]);
}
Assert.True(double.IsFinite(apo.Last.Value));
}
[Fact]
public void TBarSeries_Update_Matches_Streaming()
{
var apo = new Apo(12, 26);
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var streamingResults = new List<double>();
for (int i = 0; i < bars.Count; i++)
{
streamingResults.Add(apo.Update(bars[i]).Value);
}
Assert.Equal(apoBatch.Count, streamResults.Count);
for (int i = 0; i < apoBatch.Count; i++)
var apo2 = new Apo(12, 26);
var seriesResults = apo2.Update(bars.Close);
Assert.Equal(streamingResults.Count, seriesResults.Count);
for (int i = 0; i < seriesResults.Count; i++)
{
Assert.Equal(apoBatch[i].Value, streamResults[i], precision: 9);
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var apo = new Apo(12, 26);
var streamingResults = new List<double>();
for (int i = 0; i < bars.Count; i++)
{
streamingResults.Add(apo.Update(bars[i]).Value);
}
var staticResults = Apo.Batch(bars.Close, 12, 26);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
{
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
}
}
[Fact]
public void Chainability_Works()
{
var apo = new Apo(12, 26);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Test TBarSeries chain
var result = apo.Update(bars.Close);
Assert.NotNull(result);
Assert.IsType<TSeries>(result);
// Test TBar chain (returns TValue)
var result2 = apo.Update(bars[0]);
Assert.IsType<TValue>(result2);
}
[Fact]
public void Constructor_InvalidParameters_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new Apo(0, 26));
Assert.Throws<ArgumentException>(() => new Apo(12, 0));
Assert.Throws<ArgumentException>(() => new Apo(26, 12)); // Fast >= Slow
}
}
+3 -3
View File
@@ -19,13 +19,13 @@ public class AroonIndicatorTests
}
[Fact]
public void AroonIndicator_MinHistoryDepths_EqualsPeriod()
public void AroonIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AroonIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, AroonIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
+19 -24
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class AroonIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class AroonIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
@@ -12,11 +14,11 @@ public class AroonIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Aroon? _aroon;
protected LineSeries? UpSeries;
protected LineSeries? DownSeries;
protected LineSeries? OscSeries;
private readonly LineSeries? _upSeries;
private readonly LineSeries? _downSeries;
private readonly LineSeries? _oscSeries;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Aroon {Period}";
@@ -29,36 +31,29 @@ public class AroonIndicator : Indicator, IWatchlistIndicator
Name = "Aroon";
Description = "Identifies trend changes and strength";
UpSeries = new(name: "Aroon Up", color: Color.Green, width: 1, style: LineStyle.Solid);
DownSeries = new(name: "Aroon Down", color: Color.Red, width: 1, style: LineStyle.Solid);
OscSeries = new(name: "Aroon Osc", color: Color.Blue, width: 2, style: LineStyle.Solid);
_upSeries = new(name: "Aroon Up", color: Color.Green, width: 1, style: LineStyle.Solid);
_downSeries = new(name: "Aroon Down", color: Color.Red, width: 1, style: LineStyle.Solid);
_oscSeries = new(name: "Aroon Osc", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(UpSeries);
AddLineSeries(DownSeries);
AddLineSeries(OscSeries);
AddLineSeries(_upSeries);
AddLineSeries(_downSeries);
AddLineSeries(_oscSeries);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_aroon = new Aroon(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _aroon!.Update(this.GetInputBar(args), args.IsNewBar());
TBar bar = this.GetInputBar(args);
TValue result = _aroon!.Update(bar, isNew);
if (!_aroon.IsHot && !ShowColdValues)
{
return;
}
UpSeries!.SetValue(_aroon.Up.Value);
DownSeries!.SetValue(_aroon.Down.Value);
OscSeries!.SetValue(result.Value);
_upSeries!.SetValue(_aroon.Up.Value, _aroon.IsHot, ShowColdValues);
_downSeries!.SetValue(_aroon.Down.Value, _aroon.IsHot, ShowColdValues);
_oscSeries!.SetValue(result.Value, _aroon.IsHot, ShowColdValues);
}
}
@@ -19,13 +19,13 @@ public class AroonOscIndicatorTests
}
[Fact]
public void AroonOscIndicator_MinHistoryDepths_EqualsPeriod()
public void AroonOscIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AroonOscIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, AroonOscIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
+11 -16
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class AroonOscIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class AroonOscIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
@@ -12,9 +14,9 @@ public class AroonOscIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private AroonOsc? _aroonOsc;
protected LineSeries? OscSeries;
private readonly LineSeries? _oscSeries;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"AroonOsc {Period}";
@@ -27,30 +29,23 @@ public class AroonOscIndicator : Indicator, IWatchlistIndicator
Name = "Aroon Oscillator";
Description = "Aroon Oscillator";
OscSeries = new(name: "Aroon Osc", color: Color.Blue, width: 2, style: LineStyle.Solid);
_oscSeries = new(name: "Aroon Osc", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(OscSeries);
AddLineSeries(_oscSeries);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_aroonOsc = new AroonOsc(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _aroonOsc!.Update(this.GetInputBar(args), args.IsNewBar());
TBar bar = this.GetInputBar(args);
TValue result = _aroonOsc!.Update(bar, isNew);
if (!_aroonOsc.IsHot && !ShowColdValues)
{
return;
}
OscSeries!.SetValue(result.Value);
_oscSeries!.SetValue(result.Value, _aroonOsc.IsHot, ShowColdValues);
}
}
+10 -10
View File
@@ -1,12 +1,14 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class BopIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class BopIndicator : Indicator, IWatchlistIndicator
{
private Bop? _bop;
protected LineSeries? BopSeries;
private readonly LineSeries? _bopSeries;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
@@ -21,24 +23,22 @@ public class BopIndicator : Indicator, IWatchlistIndicator
Name = "BOP - Balance of Power";
Description = "Measures the strength of buyers vs sellers";
BopSeries = new(name: "BOP", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(BopSeries);
_bopSeries = new(name: "BOP", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(_bopSeries);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_bop = new Bop();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _bop!.Update(this.GetInputBar(args), args.IsNewBar());
TBar bar = this.GetInputBar(args);
TValue result = _bop!.Update(bar, isNew);
BopSeries!.SetValue(result.Value);
_bopSeries!.SetValue(result.Value);
}
}
+3 -14
View File
@@ -26,9 +26,9 @@ public class CfbIndicatorTests
{
var indicator = new CfbIndicator { MaxLength = 50 };
Assert.Equal(50, indicator.MinHistoryDepths);
Assert.Equal(0, CfbIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(50, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -132,17 +132,6 @@ public class CfbIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void CfbIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new CfbIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(CfbIndicator), method.DeclaringType);
}
[Fact]
public void CfbIndicator_DifferentSourceTypes_Work()
{
@@ -182,6 +171,6 @@ public class CfbIndicatorTests
Assert.Equal(10, indicator.MinLength);
Assert.Equal(40, indicator.MaxLength);
Assert.Equal(10, indicator.Step);
Assert.Equal(40, indicator.MinHistoryDepths);
Assert.Equal(0, CfbIndicator.MinHistoryDepths);
}
}
+18 -23
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class CfbIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class CfbIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Min Length", sortIndex: 1, 2, 1000, 1, 0)]
public int MinLength { get; set; } = 2;
@@ -21,27 +23,28 @@ public class CfbIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Cfb? _cfb;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => MaxLength;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"CFB {MinLength}-{MaxLength}:{SourceName}";
public override string ShortName => $"CFB {MinLength}-{MaxLength}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/momentum/cfb/Cfb.Quantower.cs";
public CfbIndicator()
{
OnBackGround = true;
SeparateWindow = true;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "CFB - Jurik Composite Fractal Behavior";
Description = "Trend Duration Index using fractal efficiency";
Series = new(name: "CFB", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: "CFB", color: IndicatorExtensions.Statistics, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
// Generate lengths array
@@ -53,25 +56,17 @@ public class CfbIndicator : Indicator, IWatchlistIndicator
}
_cfb = new Cfb(lengths);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _cfb!.Update(input, isNew);
if (_warmupBarIndex < 0 && _cfb!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
TValue result = _cfb!.Update(new TValue(this.GetInputBar(args).Time, _priceSelector!(HistoricalData[Count - 1, SeekOriginHistory.Begin])), args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _cfb.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+3 -14
View File
@@ -23,9 +23,9 @@ public class DmxIndicatorTests
{
var indicator = new DmxIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, DmxIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -126,17 +126,6 @@ public class DmxIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void DmxIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new DmxIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(DmxIndicator), method.DeclaringType);
}
[Fact]
public void DmxIndicator_Parameters_CanBeChanged()
{
@@ -146,6 +135,6 @@ public class DmxIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, DmxIndicator.MinHistoryDepths);
}
}
+12 -24
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class DmxIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class DmxIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
@@ -12,10 +14,9 @@ public class DmxIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Dmx? _dmx;
protected LineSeries? Series;
private int _warmupBarIndex = -1;
private readonly LineSeries? _series;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"DMX {Period}";
@@ -27,36 +28,23 @@ public class DmxIndicator : Indicator, IWatchlistIndicator
SeparateWindow = true;
Name = "DMX - Jurik Directional Movement Index";
Description = "Jurik's smoother, lower-lag alternative to DMI/ADX";
Series = new(name: $"DMX {Period}", color: IndicatorExtensions.Momentum, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"DMX {Period}", color: IndicatorExtensions.Momentum, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_dmx = new Dmx(Period);
_warmupBarIndex = -1;
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _dmx!.Update(this.GetInputBar(args), args.IsNewBar());
TBar bar = this.GetInputBar(args);
TValue result = _dmx!.Update(bar, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
// DMX doesn't expose IsHot directly, but we can infer warmup
if (_warmupBarIndex < 0 && Count > Period * 2) // Rough estimate for JMA warmup
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value);
_series!.SetMarker(0, Color.Transparent);
}
}
+3 -3
View File
@@ -30,9 +30,9 @@ public class MacdIndicatorTests
};
// 26 + 9 = 35
Assert.Equal(35, indicator.MinHistoryDepths);
Assert.Equal(0, MacdIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(35, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -41,7 +41,7 @@ public class MacdIndicatorTests
var indicator = new MacdIndicator();
indicator.Initialize();
Assert.Equal("MACD(12,26,9)", indicator.ShortName);
Assert.Equal("MACD(12,26,9):Close", indicator.ShortName);
}
[Fact]
+32 -21
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class MacdIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class MacdIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Fast Period", sortIndex: 1, 1, 2000, 1, 0)]
public int FastPeriod { get; set; } = 12;
@@ -14,49 +16,58 @@ public class MacdIndicator : Indicator, IWatchlistIndicator
[InputParameter("Signal Period", sortIndex: 3, 1, 2000, 1, 0)]
public int SignalPeriod { get; set; } = 9;
private Macd? _macd;
protected LineSeries? MacdSeries;
protected LineSeries? SignalSeries;
protected LineSeries? HistSeries;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
public int MinHistoryDepths => Math.Max(FastPeriod, SlowPeriod) + SignalPeriod;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Macd? _macd;
private readonly LineSeries? _macdSeries;
private readonly LineSeries? _signalSeries;
private readonly LineSeries? _histSeries;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"MACD({FastPeriod},{SlowPeriod},{SignalPeriod})";
public override string ShortName => $"MACD({FastPeriod},{SlowPeriod},{SignalPeriod}):{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/momentum/macd/Macd.Quantower.cs";
public MacdIndicator()
{
OnBackGround = true;
SeparateWindow = true;
_sourceName = Source.ToString();
Name = "MACD - Moving Average Convergence Divergence";
Description = "Trend-following momentum indicator";
MacdSeries = new(name: "MACD", color: Color.Blue, width: 2, style: LineStyle.Solid);
SignalSeries = new(name: "Signal", color: Color.Red, width: 2, style: LineStyle.Solid);
HistSeries = new(name: "Histogram", color: Color.Green, width: 2, style: LineStyle.Solid); // Quantower LineStyle doesn't have Histogram, use Solid and we'll paint it manually if needed, or just use Solid for now. Actually, Quantower usually handles Histogram via a different series type or style, but LineSeries only supports lines. Let's stick to Solid for now to fix compilation.
_macdSeries = new(name: "MACD", color: Color.Blue, width: 2, style: LineStyle.Solid);
_signalSeries = new(name: "Signal", color: Color.Red, width: 2, style: LineStyle.Solid);
_histSeries = new(name: "Histogram", color: Color.Green, width: 2, style: LineStyle.Solid);
AddLineSeries(MacdSeries);
AddLineSeries(SignalSeries);
AddLineSeries(HistSeries);
AddLineSeries(_macdSeries);
AddLineSeries(_signalSeries);
AddLineSeries(_histSeries);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_macd = new Macd(FastPeriod, SlowPeriod, SignalPeriod);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _macd!.Update(new TValue(this.GetInputBar(args).Time, _priceSelector!(HistoricalData[Count - 1, SeekOriginHistory.Begin])), args.IsNewBar());
TValue input = this.GetInputValue(args, SourceType.Close);
_macd!.Update(input, isNew);
MacdSeries!.SetValue(_macd.Last.Value);
SignalSeries!.SetValue(_macd.Signal.Value);
HistSeries!.SetValue(_macd.Histogram.Value);
_macdSeries!.SetValue(result.Value, _macd.IsHot, ShowColdValues);
_signalSeries!.SetValue(_macd.Signal.Value, _macd.IsHot, ShowColdValues);
_histSeries!.SetValue(_macd.Histogram.Value, _macd.IsHot, ShowColdValues);
}
}
+3 -3
View File
@@ -25,9 +25,9 @@ public class RsiIndicatorTests
Period = 20
};
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, RsiIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -39,7 +39,7 @@ public class RsiIndicatorTests
};
indicator.Initialize();
Assert.Equal("RSI(20)", indicator.ShortName);
Assert.Contains("RSI(20)", indicator.ShortName);
}
[Fact]
+25 -13
View File
@@ -1,47 +1,59 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class RsiIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class RsiIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
private Rsi? _rsi;
protected LineSeries? RsiSeries;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
public int MinHistoryDepths => Period;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Rsi? _rsi;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"RSI({Period})";
public override string ShortName => $"RSI({Period}):{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/momentum/rsi/Rsi.Quantower.cs";
public RsiIndicator()
{
OnBackGround = true;
SeparateWindow = true;
_sourceName = Source.ToString();
Name = "RSI - Relative Strength Index";
Description = "Measures the speed and change of price movements";
RsiSeries = new(name: "RSI", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(RsiSeries);
_series = new(name: "RSI", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_rsi = new Rsi(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _rsi!.Update(new TValue(this.GetInputBar(args).Time, _priceSelector!(HistoricalData[Count - 1, SeekOriginHistory.Begin])), args.IsNewBar());
TValue input = this.GetInputValue(args, SourceType.Close);
TValue result = _rsi!.Update(input, isNew);
RsiSeries!.SetValue(result.Value);
_series!.SetValue(result.Value, _rsi.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+3 -14
View File
@@ -24,9 +24,9 @@ public class RsxIndicatorTests
{
var indicator = new RsxIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, RsxIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -113,17 +113,6 @@ public class RsxIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void RsxIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new RsxIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(RsxIndicator), method.DeclaringType);
}
[Fact]
public void RsxIndicator_MultipleUpdates_ProducesCorrectRsxSequence()
{
@@ -174,6 +163,6 @@ public class RsxIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, RsxIndicator.MinHistoryDepths);
}
}
+18 -25
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class RsxIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class RsxIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
@@ -15,51 +17,42 @@ public class RsxIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Rsx? _rsx;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"RSX {Period}:{SourceName}";
public override string ShortName => $"RSX {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/momentum/rsx/Rsx.Quantower.cs";
public RsxIndicator()
{
OnBackGround = true;
SeparateWindow = true;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "RSX - Jurik Relative Strength Index";
Description = "Jurik's RSI: A noise-free, zero-lag version of RSI";
Series = new(name: $"RSX {Period}", color: IndicatorExtensions.Momentum, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"RSX {Period}", color: IndicatorExtensions.Momentum, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_rsx = new Rsx(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _rsx!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
TValue result = _rsx!.Update(new TValue(this.GetInputBar(args).Time, _priceSelector!(HistoricalData[Count - 1, SeekOriginHistory.Begin])), args.IsNewBar());
if (_warmupBarIndex < 0 && _rsx!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _rsx.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+3 -14
View File
@@ -24,9 +24,9 @@ public class VelIndicatorTests
{
var indicator = new VelIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, VelIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -113,17 +113,6 @@ public class VelIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void VelIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new VelIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(VelIndicator), method.DeclaringType);
}
[Fact]
public void VelIndicator_MultipleUpdates_ProducesCorrectVelSequence()
{
@@ -174,6 +163,6 @@ public class VelIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, VelIndicator.MinHistoryDepths);
}
}
+18 -23
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class VelIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class VelIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
@@ -15,49 +17,42 @@ public class VelIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Vel? _vel;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"VEL {Period}:{SourceName}";
public override string ShortName => $"VEL {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/momentum/vel/Vel.Quantower.cs";
public VelIndicator()
{
OnBackGround = true;
SeparateWindow = true;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "VEL - Jurik Velocity";
Description = "Momentum oscillator calculated as PWMA - WMA";
Series = new(name: $"VEL {Period}", color: IndicatorExtensions.Momentum, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"VEL {Period}", color: IndicatorExtensions.Momentum, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_vel = new Vel(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _vel!.Update(input, isNew);
if (_warmupBarIndex < 0 && _vel!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
TValue result = _vel!.Update(new TValue(this.GetInputBar(args).Time, _priceSelector!(HistoricalData[Count - 1, SeekOriginHistory.Begin])), args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _vel.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+3 -3
View File
@@ -25,8 +25,8 @@ public class AlmaIndicatorTests
{
var indicator = new AlmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, AlmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -165,6 +165,6 @@ public class AlmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, AlmaIndicator.MinHistoryDepths);
}
}
+11 -17
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class AlmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -23,12 +25,13 @@ public class AlmaIndicator : Indicator, IWatchlistIndicator
private Alma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ALMA {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/alma/Alma.Quantower.cs";
public AlmaIndicator()
{
@@ -45,26 +48,17 @@ public class AlmaIndicator : Indicator, IWatchlistIndicator
{
ma = new Alma(Period, Offset, Sigma);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+3 -3
View File
@@ -23,8 +23,8 @@ public class BesselIndicatorTests
{
var indicator = new BesselIndicator { Length = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, BesselIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -157,6 +157,6 @@ public class BesselIndicatorTests
indicator.Length = 20;
Assert.Equal(20, indicator.Length);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, BesselIndicator.MinHistoryDepths);
}
}
+10 -18
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class BesselIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Length", sortIndex: 1, 1, 1000, 1, 0)]
@@ -17,9 +19,9 @@ public class BesselIndicator : Indicator, IWatchlistIndicator
private Bessel? _filter;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Length;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"BESSEL {Length}:{SourceName}";
@@ -39,27 +41,17 @@ public class BesselIndicator : Indicator, IWatchlistIndicator
{
_filter = new Bessel(Length);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _filter!.Update(input, isNew);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _filter!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && _filter!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, _filter.IsHot, ShowColdValues);
}
}
@@ -25,8 +25,8 @@ public class BilateralIndicatorTests
{
var indicator = new BilateralIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, BilateralIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -113,16 +113,6 @@ public class BilateralIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void BilateralIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new BilateralIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(BilateralIndicator), method.DeclaringType);
}
[Fact]
public void BilateralIndicator_MultipleUpdates_ProducesCorrectSequence()
@@ -181,6 +171,6 @@ public class BilateralIndicatorTests
Assert.Equal(20, indicator.Period);
Assert.Equal(1.0, indicator.SigmaSRatio);
Assert.Equal(2.0, indicator.SigmaRMult);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, BilateralIndicator.MinHistoryDepths);
}
}
+10 -21
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class BilateralIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -23,9 +25,9 @@ public class BilateralIndicator : Indicator, IWatchlistIndicator
private Bilateral? _bilateral;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Bilateral {Period}:{SourceName}";
@@ -46,30 +48,17 @@ public class BilateralIndicator : Indicator, IWatchlistIndicator
{
_bilateral = new Bilateral(Period, SigmaSRatio, SigmaRMult);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _bilateral!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _bilateral!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (_warmupBarIndex < 0 && _bilateral!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
var savedColor = Series!.Color;
Series.Color = Color.Transparent;
base.OnPaintChart(args);
Series.Color = savedColor;
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintLine(args, Series!, warmupPeriod, showColdValues: ShowColdValues);
Series!.SetValue(result.Value, _bilateral.IsHot, ShowColdValues);
}
}
+40 -12
View File
@@ -5,6 +5,13 @@ namespace QuanTAlib;
public class BilateralTests
{
private readonly GBM _gbm;
public BilateralTests()
{
_gbm = new GBM();
}
[Fact]
public void Constructor_ValidatesInput()
{
@@ -101,22 +108,43 @@ public class BilateralTests
}
[Fact]
public void TSeries_Update_Matches_Iterative()
public void AllModes_ProduceSameResult()
{
var indicator = new Bilateral(5);
var series = new TSeries();
for (int i = 0; i < 20; i++)
int period = 10;
var bars = _gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = new Bilateral(period).Update(series);
double expected = batchSeries.Last.Value;
// 2. Span Mode
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Bilateral.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Bilateral(period);
for (int i = 0; i < series.Count; i++)
{
series.Add(new TValue(DateTime.UtcNow.AddMinutes(i), i));
streamingInd.Update(series[i]);
}
var resultSeries = indicator.Update(series);
var indicatorIterative = new Bilateral(5);
for (int i = 0; i < 20; i++)
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Bilateral(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
indicatorIterative.Update(series[i]);
Assert.Equal(indicatorIterative.Last.Value, resultSeries[i].Value);
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, 1e-9);
Assert.Equal(expected, streamingResult, 1e-9);
Assert.Equal(expected, eventingResult, 1e-9);
}
}
+102
View File
@@ -260,4 +260,106 @@ public sealed class Bilateral : AbstractBase
_p_state = default;
Last = default;
}
public static void Calculate(ReadOnlySpan<double> source, Span<double> destination, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
{
if (period <= 0)
throw new ArgumentException("Period must be greater than 0", nameof(period));
// Precalculate spatial weights
double sigmaS = Math.Max(period * sigmaSRatio, 1e-10);
double twoSigmaSSq = 2.0 * sigmaS * sigmaS;
Span<double> spatialWeights = period <= 256 ? stackalloc double[period] : new double[period];
for (int i = 0; i < period; i++)
{
double diffSpatial = i;
spatialWeights[i] = Math.Exp(-(diffSpatial * diffSpatial) / twoSigmaSSq);
}
// Handle NaNs by tracking last valid value
double lastValid = double.NaN;
// Find initial valid value
for (int i = 0; i < source.Length; i++)
{
if (double.IsFinite(source[i]))
{
lastValid = source[i];
break;
}
}
// If all NaNs, fill with NaN
if (double.IsNaN(lastValid))
{
destination.Fill(double.NaN);
return;
}
Span<double> window = period <= 256 ? stackalloc double[period] : new double[period];
int windowIdx = 0;
int count = 0;
double sum = 0;
double sumSq = 0;
for (int i = 0; i < source.Length; i++)
{
double val = source[i];
if (double.IsNaN(val))
{
val = lastValid;
}
else
{
lastValid = val;
}
// Add to window
double removed = 0;
if (count >= period)
{
removed = window[windowIdx];
sum -= removed;
sumSq -= removed * removed;
}
window[windowIdx] = val;
sum += val;
sumSq += val * val;
int currentNewestIdx = windowIdx;
windowIdx = (windowIdx + 1) % period;
if (count < period) count++;
// Calculate StDev
double variance = Math.Max(0, (sumSq - (sum * sum) / count) / count);
double stdev = Math.Sqrt(variance);
double sigmaR = Math.Max(stdev * sigmaRMult, 1e-10);
double twoSigmaRSq = 2.0 * sigmaR * sigmaR;
double sumWeights = 0.0;
double sumWeightedSrc = 0.0;
double centerVal = val; // Newest value
// Iterate backwards through the window
for (int k = 0; k < count; k++)
{
// k=0 is newest (currentNewestIdx)
// k=1 is previous...
int idx = currentNewestIdx - k;
if (idx < 0) idx += period;
double wVal = window[idx];
double diffRange = centerVal - wVal;
double weightRange = Math.Exp(-(diffRange * diffRange) / twoSigmaRSq);
double weight = spatialWeights[k] * weightRange;
sumWeights += weight;
sumWeightedSrc += weight * wVal;
}
destination[i] = sumWeights == 0.0 ? centerVal : sumWeightedSrc / sumWeights;
}
}
}
+2 -2
View File
@@ -23,9 +23,9 @@ public class BlmaIndicatorTests
{
var indicator = new BlmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, BlmaIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
+16 -21
View File
@@ -1,9 +1,10 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class BlmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
@@ -17,47 +18,41 @@ public class BlmaIndicator : Indicator, IWatchlistIndicator
private Blma? _ma;
protected LineSeries? _series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"BLMA {Period}";
public override string ShortName => $"BLMA {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/blma/Blma.Quantower.cs";
public BlmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "BLMA - Blackman Window Moving Average";
Description = "A moving average using the Blackman window function for superior noise suppression.";
SeparateWindow = false;
_series = new(name: "BLMA", color: Color.Yellow, width: 2, style: LineStyle.Solid);
_series = new(name: $"BLMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_ma = new Blma(Period);
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _ma!.Update(input, isNew);
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (!_ma.IsHot && !ShowColdValues)
{
return;
}
_series!.SetValue(result.Value);
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, _series!, _ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
}
}
+16 -14
View File
@@ -150,12 +150,22 @@ public sealed class Blma : AbstractBase
private static double CalculateWeightedSum(RingBuffer buffer, ReadOnlySpan<double> weights)
{
double sum = 0;
for (int i = 0; i < buffer.Count; i++)
int start = buffer.StartIndex;
int count = buffer.Count;
int capacity = buffer.Capacity;
if (start + count <= capacity)
{
sum += buffer[i] * weights[i];
return buffer.InternalBuffer.Slice(start, count).DotProduct(weights);
}
return sum;
int firstPartLength = capacity - start;
int secondPartLength = count - firstPartLength;
double sum1 = buffer.InternalBuffer.Slice(start, firstPartLength).DotProduct(weights[..firstPartLength]);
double sum2 = buffer.InternalBuffer.Slice(0, secondPartLength).DotProduct(weights[firstPartLength..]);
return sum1 + sum2;
}
public static void Calculate(ReadOnlySpan<double> source, Span<double> destination, int period)
@@ -200,11 +210,7 @@ public sealed class Blma : AbstractBase
}
else
{
double sum = 0;
for (int j = 0; j < count; j++)
{
sum += source[i - count + 1 + j] * currentWeights[j];
}
double sum = source.Slice(i - count + 1, count).DotProduct(currentWeights);
destination[i] = sum / currentWeightSum;
}
}
@@ -212,11 +218,7 @@ public sealed class Blma : AbstractBase
else
{
// Full period
double sum = 0;
for (int j = 0; j < period; j++)
{
sum += source[i - period + 1 + j] * weights[j];
}
double sum = source.Slice(i - period + 1, period).DotProduct(weights);
destination[i] = sum / weightSum;
}
}
+2 -2
View File
@@ -24,9 +24,9 @@ public class ButterIndicatorTests
{
var indicator = new ButterIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, ButterIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
+16 -21
View File
@@ -1,9 +1,10 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class ButterIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
@@ -17,47 +18,41 @@ public class ButterIndicator : Indicator, IWatchlistIndicator
private Butter? _ma;
protected LineSeries? _series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"BUTTER {Period}";
public override string ShortName => $"BUTTER {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/butter/Butter.Quantower.cs";
public ButterIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "BUTTER - Butterworth Filter";
Description = "A 2nd-order low-pass filter with maximally flat frequency response in the passband.";
SeparateWindow = false;
_series = new(name: "BUTTER", color: Color.Orange, width: 2, style: LineStyle.Solid);
_series = new(name: $"BUTTER {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_ma = new Butter(Period);
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _ma!.Update(input, isNew);
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (!_ma.IsHot && !ShowColdValues)
{
return;
}
_series!.SetValue(result.Value);
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, _series!, _ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
}
}
+2 -12
View File
@@ -24,8 +24,8 @@ public class ConvIndicatorTests
var indicator = new ConvIndicator { WeightsInput = "1, 2, 3, 4, 5" };
indicator.Initialize(); // Initialize to parse weights
Assert.Equal(5, indicator.MinHistoryDepths);
Assert.Equal(5, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, ConvIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -112,16 +112,6 @@ public class ConvIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void ConvIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new ConvIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(ConvIndicator), method.DeclaringType);
}
[Fact]
public void ConvIndicator_MultipleUpdates_ProducesCorrectSequence()
+10 -17
View File
@@ -1,10 +1,10 @@
using System;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class ConvIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Weights (comma separated)", sortIndex: 1)]
@@ -17,11 +17,11 @@ public class ConvIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Conv? _conv;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => _conv != null ? WeightsInput.Split(',').Length : 0;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"CONV:{SourceName}";
@@ -60,25 +60,18 @@ public class ConvIndicator : Indicator, IWatchlistIndicator
_conv = new Conv([1.0]);
}
_warmupBarIndex = -1;
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _conv!.Update(input, isNew);
if (_warmupBarIndex < 0 && _conv!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _conv!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, _conv.IsHot, ShowColdValues);
}
}
+2 -24
View File
@@ -23,8 +23,8 @@ public class DemaIndicatorTests
{
var indicator = new DemaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, DemaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -111,18 +111,6 @@ public class DemaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void DemaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new DemaIndicator();
indicator.Initialize();
// We can't easily mock PaintChartEventArgs fully, but we can verify the method exists and is callable
// if we could mock the args. Since we can't, we skip the actual call but verify the method is overridden.
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(DemaIndicator), method.DeclaringType);
}
[Fact]
public void DemaIndicator_MultipleUpdates_ProducesCorrectDemaSequence()
@@ -166,14 +154,4 @@ public class DemaIndicatorTests
}
}
[Fact]
public void DemaIndicator_Period_CanBeChanged()
{
var indicator = new DemaIndicator { Period = 5 };
Assert.Equal(5, indicator.Period);
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
}
}
+10 -17
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class DemaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -17,9 +19,9 @@ public class DemaIndicator : Indicator, IWatchlistIndicator
private Dema? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"DEMA {Period}:{SourceName}";
@@ -40,26 +42,17 @@ public class DemaIndicator : Indicator, IWatchlistIndicator
{
ma = new Dema(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+2 -2
View File
@@ -23,8 +23,8 @@ public class DwmaIndicatorTests
{
var indicator = new DwmaIndicator { Period = 20 };
Assert.Equal(40, indicator.MinHistoryDepths);
Assert.Equal(40, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, DwmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
+10 -15
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class DwmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -15,11 +17,11 @@ public class DwmaIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Dwma? ma;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period * 2; // DWMA needs roughly 2x period to warm up
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"DWMA {Period}:{SourceName}";
@@ -39,25 +41,18 @@ public class DwmaIndicator : Indicator, IWatchlistIndicator
protected override void OnInit()
{
ma = new Dwma(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+4 -4
View File
@@ -19,12 +19,12 @@ public class EmaIndicatorTests
}
[Fact]
public void EmaIndicator_MinHistoryDepths_EqualsPeriod()
public void EmaIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new EmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, EmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -164,6 +164,6 @@ public class EmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, EmaIndicator.MinHistoryDepths);
}
}
+9 -19
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class EmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -17,9 +19,9 @@ public class EmaIndicator : Indicator, IWatchlistIndicator
private Ema? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"EMA {Period}:{SourceName}";
@@ -39,27 +41,15 @@ public class EmaIndicator : Indicator, IWatchlistIndicator
{
ma = new Ema(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1; // Reset warmup tracking when period changes
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
// Track when IsHot becomes true for the first time
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+3 -13
View File
@@ -24,8 +24,8 @@ public class HmaIndicatorTests
var indicator = new HmaIndicator { Period = 16 };
// HMA warmup is roughly Period + Sqrt(Period)
// 16 + Sqrt(16) = 16 + 4 = 20
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, HmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -112,16 +112,6 @@ public class HmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void HmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new HmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(HmaIndicator), method.DeclaringType);
}
[Fact]
public void HmaIndicator_MultipleUpdates_ProducesCorrectHmaSequence()
@@ -174,6 +164,6 @@ public class HmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
// 20 + sqrt(20) = 20 + 4 = 24
Assert.Equal(24, indicator.MinHistoryDepths);
Assert.Equal(0, HmaIndicator.MinHistoryDepths);
}
}
+10 -15
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class HmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 1000, 1, 0)]
@@ -15,11 +17,11 @@ public class HmaIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Hma? ma;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period + (int)Math.Sqrt(Period); // Approximate warmup
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"HMA {Period}:{SourceName}";
@@ -39,25 +41,18 @@ public class HmaIndicator : Indicator, IWatchlistIndicator
protected override void OnInit()
{
ma = new Hma(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+4 -2
View File
@@ -11,9 +11,11 @@ public class HtitIndicatorTests
public void Indicator_Initializes_Correctly()
{
var indicator = new HtitIndicator();
indicator.Initialize();
Assert.Equal("HTIT - Ehlers Hilbert Transform Instantaneous Trend", indicator.Name);
Assert.Equal("HTIT:Close", indicator.ShortName);
Assert.Equal(50, HtitIndicator.MinHistoryDepths);
Assert.StartsWith("HTIT", indicator.ShortName);
Assert.Contains("Close", indicator.ShortName);
Assert.Equal(0, HtitIndicator.MinHistoryDepths);
Assert.Single(indicator.LinesSeries);
}
+18 -28
View File
@@ -1,13 +1,15 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class HtitIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class HtitIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 50; // Not used in calculation but kept for consistency if needed
public int Period { get; set; } = 50; // Not used in calculation but kept for consistency
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
@@ -16,51 +18,39 @@ public class HtitIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Htit? _htit;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public static int MinHistoryDepths => 50;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"HTIT:{SourceName}";
public override string ShortName => $"HTIT:{_sourceName}";
public HtitIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "HTIT - Ehlers Hilbert Transform Instantaneous Trend";
Description = "Ehlers Hilbert Transform Instantaneous Trend";
Series = new(name: "HTIT", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: "HTIT", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_htit = new Htit();
SourceName = Source.ToString();
_warmupBarIndex = -1;
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _htit!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && _htit.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _htit!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _htit.IsHot, ShowColdValues);
}
}
+5 -20
View File
@@ -12,7 +12,6 @@ public class JmaIndicatorTests
Assert.Equal(10, indicator.Period);
Assert.Equal(0, indicator.Phase);
Assert.Equal(0.45, indicator.Power);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("JMA - Jurik Moving Average", indicator.Name);
@@ -25,19 +24,18 @@ public class JmaIndicatorTests
{
var indicator = new JmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, JmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void JmaIndicator_ShortName_IncludesParameters()
{
var indicator = new JmaIndicator { Period = 15, Phase = 50, Power = 0.8 };
var indicator = new JmaIndicator { Period = 15, Phase = 50 };
Assert.Contains("JMA", indicator.ShortName);
Assert.Contains("15", indicator.ShortName);
Assert.Contains("50", indicator.ShortName);
Assert.Contains("0.8", indicator.ShortName);
}
[Fact]
@@ -115,16 +113,6 @@ public class JmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void JmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new JmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(JmaIndicator), method.DeclaringType);
}
[Fact]
public void JmaIndicator_MultipleUpdates_ProducesCorrectSequence()
@@ -171,18 +159,15 @@ public class JmaIndicatorTests
[Fact]
public void JmaIndicator_Parameters_CanBeChanged()
{
var indicator = new JmaIndicator { Period = 5, Phase = 10, Power = 0.5 };
var indicator = new JmaIndicator { Period = 5, Phase = 10 };
Assert.Equal(5, indicator.Period);
Assert.Equal(10, indicator.Phase);
Assert.Equal(0.5, indicator.Power);
indicator.Period = 20;
indicator.Phase = -10;
indicator.Power = 0.9;
Assert.Equal(20, indicator.Period);
Assert.Equal(-10, indicator.Phase);
Assert.Equal(0.9, indicator.Power);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, JmaIndicator.MinHistoryDepths);
}
}
+20 -22
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class JmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -11,24 +13,27 @@ public class JmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Phase", sortIndex: 2, -100, 100, 1, 0)]
public int Phase { get; set; } = 0;
[InputParameter("Power", sortIndex: 3, 0.1, 10.0, 0.1, 1)]
public double Power { get; set; } = 0.45;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
[InputParameter("Color", sortIndex: 22)]
public Color LineColor { get; set; } = IndicatorExtensions.Averages;
[InputParameter("Width", sortIndex: 23)]
public int LineWidth { get; set; } = 2;
private Jma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"JMA {Period}:{Phase}:{Power}:{SourceName}";
public override string ShortName => $"JMA {Period}:{Phase}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/jma/Jma.Quantower.cs";
public JmaIndicator()
@@ -44,28 +49,21 @@ public class JmaIndicator : Indicator, IWatchlistIndicator
protected override void OnInit()
{
ma = new Jma(Period, Phase, Power);
ma = new Jma(Period, Phase);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
Series!.Color = LineColor;
Series!.Width = LineWidth;
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+4 -4
View File
@@ -15,7 +15,7 @@ public class KamaIndicatorTests
Assert.Equal(30, indicator.SlowPeriod);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("KAMA - Kaufman Adaptive Moving Average", indicator.Name);
Assert.Equal("KAMA - Kaufman's Adaptive Moving Average", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
@@ -25,8 +25,8 @@ public class KamaIndicatorTests
{
var indicator = new KamaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, KamaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -165,6 +165,6 @@ public class KamaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, KamaIndicator.MinHistoryDepths);
}
}
+25 -33
View File
@@ -1,17 +1,20 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class KamaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class KamaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[InputParameter("Fast Period", sortIndex: 2, 1, 1000, 1, 0)]
[InputParameter("Fast Period", sortIndex: 2, 1, 200, 1, 0)]
public int FastPeriod { get; set; } = 2;
[InputParameter("Slow Period", sortIndex: 3, 1, 1000, 1, 0)]
[InputParameter("Slow Period", sortIndex: 3, 1, 200, 1, 0)]
public int SlowPeriod { get; set; } = 30;
[IndicatorExtensions.DataSourceInput]
@@ -20,51 +23,40 @@ public class KamaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Kama? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Kama? _kama;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"KAMA {Period}:{SourceName}";
public override string ShortName => $"KAMA {Period}:{_sourceName}";
public KamaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "KAMA - Kaufman Adaptive Moving Average";
Description = "Kaufman Adaptive Moving Average";
Series = new(name: $"KAMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
Name = "KAMA - Kaufman's Adaptive Moving Average";
Description = "Kaufman's Adaptive Moving Average";
_series = new(name: $"KAMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Kama(Period, FastPeriod, SlowPeriod);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_kama = new Kama(Period, FastPeriod, SlowPeriod);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _kama!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _kama.IsHot, ShowColdValues);
}
}
+4 -15
View File
@@ -10,7 +10,7 @@ public class LsmaIndicatorTests
{
var indicator = new LsmaIndicator();
Assert.Equal(14, indicator.Period);
Assert.Equal(25, indicator.Period);
Assert.Equal(0, indicator.Offset);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
@@ -24,8 +24,8 @@ public class LsmaIndicatorTests
{
var indicator = new LsmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, LsmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -35,7 +35,6 @@ public class LsmaIndicatorTests
Assert.Contains("LSMA", indicator.ShortName);
Assert.Contains("15", indicator.ShortName);
Assert.Contains("2", indicator.ShortName);
}
[Fact]
@@ -113,16 +112,6 @@ public class LsmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void LsmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new LsmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(LsmaIndicator), method.DeclaringType);
}
[Fact]
public void LsmaIndicator_MultipleUpdates_ProducesCorrectSequence()
@@ -177,6 +166,6 @@ public class LsmaIndicatorTests
indicator.Offset = 2;
Assert.Equal(20, indicator.Period);
Assert.Equal(2, indicator.Offset);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, LsmaIndicator.MinHistoryDepths);
}
}
+22 -31
View File
@@ -1,12 +1,15 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class LsmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class LsmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 25;
[InputParameter("Offset", sortIndex: 2, -1000, 1000, 1, 0)]
public int Offset { get; set; } = 0;
@@ -17,53 +20,41 @@ public class LsmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Lsma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Lsma? _lsma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"LSMA {Period}:{Offset}:{SourceName}";
public override string ShortName => $"LSMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/lsma/Lsma.Quantower.cs";
public LsmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "LSMA - Least Squares Moving Average";
Description = "Least Squares Moving Average";
Series = new(name: $"LSMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"LSMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Lsma(Period, Offset);
SourceName = Source.ToString();
_warmupBarIndex = -1; // Reset warmup tracking when period changes
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_lsma = new Lsma(Period, Offset);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
// Track when IsHot becomes true for the first time
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _lsma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _lsma.IsHot, ShowColdValues);
}
}
+5 -5
View File
@@ -20,22 +20,22 @@ public class MamaIndicatorTests
}
[Fact]
public void MamaIndicator_MinHistoryDepths_Equals6()
public void MamaIndicator_MinHistoryDepths_Equals50()
{
var indicator = new MamaIndicator();
Assert.Equal(6, MamaIndicator.MinHistoryDepths);
Assert.Equal(6, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, MamaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void MamaIndicator_ShortName_IncludesLimitsAndSource()
{
var indicator = new MamaIndicator { FastLimit = 0.5, SlowLimit = 0.05 };
indicator.Initialize();
Assert.Contains("MAMA", indicator.ShortName);
Assert.Contains("0.50", indicator.ShortName);
Assert.Contains("0.05", indicator.ShortName);
Assert.Contains("Close", indicator.ShortName);
}
[Fact]
+24 -39
View File
@@ -1,9 +1,12 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class MamaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class MamaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Fast Limit", sortIndex: 1, 0.01, 0.99, 0.01, 2)]
public double FastLimit { get; set; } = 0.5;
@@ -17,62 +20,44 @@ public class MamaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Mama? _ma;
protected LineSeries? MamaSeries;
protected LineSeries? FamaSeries;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Mama? _mama;
private readonly LineSeries? _series;
private readonly LineSeries? _famaSeries;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public static int MinHistoryDepths => 6;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"MAMA({FastLimit:F2}, {SlowLimit:F2}):{SourceName}";
public override string ShortName => $"MAMA:{_sourceName}";
public MamaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "MAMA - MESA Adaptive Moving Average";
Description = "MESA Adaptive Moving Average";
MamaSeries = new(name: "MAMA", color: Color.Red, width: 2, style: LineStyle.Solid);
FamaSeries = new(name: "FAMA", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(MamaSeries);
AddLineSeries(FamaSeries);
_series = new(name: "MAMA", color: Color.Orange, width: 2, style: LineStyle.Solid);
_famaSeries = new(name: "FAMA", color: Color.Red, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
AddLineSeries(_famaSeries);
}
protected override void OnInit()
{
_ma = new Mama(FastLimit, SlowLimit);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_mama = new Mama(FastLimit, SlowLimit);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _ma!.Update(input, isNew);
MamaSeries!.SetValue(result.Value);
FamaSeries!.SetValue(_ma.Fama.Value);
MamaSeries!.SetMarker(0, Color.Transparent);
FamaSeries!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && _ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, MamaSeries!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
this.PaintSmoothCurve(args, FamaSeries!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _mama!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _mama.IsHot, ShowColdValues);
_famaSeries!.SetValue(_mama.Fama.Value, _mama.IsHot, ShowColdValues);
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ public class MgdiIndicatorTests
var indicator = new MgdiIndicator();
Assert.Equal("MGDI - McGinley Dynamic Indicator", indicator.Name);
Assert.Equal("MGDI(14,0.6):Close", indicator.ShortName);
Assert.Equal(14, indicator.MinHistoryDepths);
Assert.Equal(0, MgdiIndicator.MinHistoryDepths);
Assert.Single(indicator.LinesSeries);
}
+19 -28
View File
@@ -1,15 +1,17 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class MgdiIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class MgdiIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("K Factor", sortIndex: 2, 0.1, 10, 0.1, 1)]
[InputParameter("K Factor", sortIndex: 2, 0.1, 10.0, 0.1, 1)]
public double K { get; set; } = 0.6;
[IndicatorExtensions.DataSourceInput]
@@ -19,51 +21,40 @@ public class MgdiIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Mgdi? _mgdi;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"MGDI({Period},{K}):{SourceName}";
public override string ShortName => $"MGDI({Period},{K}):{_sourceName}";
public MgdiIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "MGDI - McGinley Dynamic Indicator";
Description = "McGinley Dynamic Indicator";
Series = new(name: "MGDI", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"MGDI {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_mgdi = new Mgdi(Period, K);
SourceName = Source.ToString();
_warmupBarIndex = -1;
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _mgdi!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && _mgdi.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _mgdi!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _mgdi.IsHot, ShowColdValues);
}
}
+3 -14
View File
@@ -24,9 +24,9 @@ public class PwmaIndicatorTests
{
var indicator = new PwmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, PwmaIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -113,17 +113,6 @@ public class PwmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void PwmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new PwmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(PwmaIndicator), method.DeclaringType);
}
[Fact]
public void PwmaIndicator_MultipleUpdates_ProducesCorrectPwmaSequence()
{
@@ -174,6 +163,6 @@ public class PwmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, PwmaIndicator.MinHistoryDepths);
}
}
+21 -26
View File
@@ -1,9 +1,12 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class PwmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class PwmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
@@ -14,50 +17,42 @@ public class PwmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Pwma? _ma;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Pwma? _pwma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"PWMA {Period}:{SourceName}";
public override string ShortName => $"PWMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/pwma/Pwma.Quantower.cs";
public PwmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "PWMA - Parabolic Weighted Moving Average";
Description = "Weighted Moving Average with parabolic weighting";
Series = new(name: $"PWMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
Description = "Parabolic Weighted Moving Average";
_series = new(name: $"PWMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_ma = new Pwma(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_pwma = new Pwma(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _ma!.Update(input, isNew);
if (_warmupBarIndex < 0 && _ma!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _pwma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _pwma.IsHot, ShowColdValues);
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
}
}
+3 -3
View File
@@ -23,8 +23,8 @@ public class RmaIndicatorTests
{
var indicator = new RmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, RmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -164,6 +164,6 @@ public class RmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, RmaIndicator.MinHistoryDepths);
}
}
+21 -30
View File
@@ -1,11 +1,14 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class RmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class RmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
@@ -14,52 +17,40 @@ public class RmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Rma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Rma? _rma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"RMA {Period}:{SourceName}";
public override string ShortName => $"RMA {Period}:{_sourceName}";
public RmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "RMA - Running Moving Average";
Description = "Running Moving Average (Wilder's Smoothing)";
Series = new(name: $"RMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"RMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Rma(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1; // Reset warmup tracking when period changes
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_rma = new Rma(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
// Track when IsHot becomes true for the first time
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _rma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _rma.IsHot, ShowColdValues);
}
}
+4 -22
View File
@@ -19,12 +19,12 @@ public class SmaIndicatorTests
}
[Fact]
public void SmaIndicator_MinHistoryDepths_EqualsPeriod()
public void SmaIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new SmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, SmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -36,14 +36,6 @@ public class SmaIndicatorTests
Assert.Contains("15", indicator.ShortName);
}
[Fact]
public void SmaIndicator_SourceCodeLink_IsValid()
{
var indicator = new SmaIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink);
Assert.Contains("Sma.Quantower.cs", indicator.SourceCodeLink);
}
[Fact]
public void SmaIndicator_Initialize_CreatesInternalSma()
@@ -111,16 +103,6 @@ public class SmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void SmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new SmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(SmaIndicator), method.DeclaringType);
}
[Fact]
public void SmaIndicator_MultipleUpdates_ProducesCorrectSmaSequence()
@@ -177,6 +159,6 @@ public class SmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, SmaIndicator.MinHistoryDepths);
}
}
+21 -35
View File
@@ -1,11 +1,14 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class SmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class SmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[IndicatorExtensions.DataSourceInput]
@@ -14,57 +17,40 @@ public class SmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Sma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Sma? _sma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"SMA {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/sma/Sma.Quantower.cs";
public override string ShortName => $"SMA {Period}:{_sourceName}";
public SmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "SMA - Simple Moving Average";
Description = "Simple Moving Average";
Series = new(name: $"SMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"SMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Sma(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1; // Reset warmup tracking when period changes
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_sma = new Sma(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
// Track when IsHot becomes true for the first time
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
var savedColor = Series!.Color;
Series.Color = Color.Transparent;
base.OnPaintChart(args);
Series.Color = savedColor;
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintLine(args, Series!, warmupPeriod, showColdValues: ShowColdValues);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _sma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _sma.IsHot, ShowColdValues);
}
}
+3 -3
View File
@@ -23,8 +23,8 @@ public class SsfIndicatorTests
{
var indicator = new SsfIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, SsfIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -164,6 +164,6 @@ public class SsfIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, SsfIndicator.MinHistoryDepths);
}
}
+21 -29
View File
@@ -1,11 +1,14 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class SsfIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class SsfIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[IndicatorExtensions.DataSourceInput]
@@ -14,51 +17,40 @@ public class SsfIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Ssf? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Ssf? _ssf;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"SSF {Period}:{SourceName}";
public override string ShortName => $"SSF {Period}:{_sourceName}";
public SsfIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "SSF - Super Smooth Filter";
Description = "Ehlers Super Smooth Filter";
Series = new(name: $"SSF {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"SSF {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Ssf(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_ssf = new Ssf(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _ssf!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _ssf.IsHot, ShowColdValues);
}
}
+6 -6
View File
@@ -20,13 +20,13 @@ public class SuperIndicatorTests
}
[Fact]
public void SuperIndicator_MinHistoryDepths_EqualsPeriod()
public void SuperIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new SuperIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, SuperIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -57,8 +57,8 @@ public class SuperIndicatorTests
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist (Up and Down)
Assert.Equal(2, indicator.LinesSeries.Count);
// After init, line series should exist (SuperTrend, Upper, Lower)
Assert.Equal(3, indicator.LinesSeries.Count);
}
[Fact]
@@ -119,6 +119,6 @@ public class SuperIndicatorTests
Assert.Equal(20, indicator.Period);
Assert.Equal(4.0, indicator.Multiplier);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, SuperIndicator.MinHistoryDepths);
}
}
+29 -32
View File
@@ -1,41 +1,45 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class SuperIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class SuperIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[InputParameter("Multiplier", sortIndex: 2, 0.1, 100, 0.1, 1)]
[InputParameter("Multiplier", sortIndex: 2, 0.1, 100.0, 0.1, 1)]
public double Multiplier { get; set; } = 3.0;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Super? _super;
protected LineSeries? UpSeries;
protected LineSeries? DownSeries;
private readonly LineSeries? _series;
private readonly LineSeries? _upperBand;
private readonly LineSeries? _lowerBand;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Super {Period}:{Multiplier}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/super/Super.Quantower.cs";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/master/lib/trends/super/Super.Quantower.cs";
public SuperIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "SuperTrend";
Description = "Trend-following indicator using ATR";
UpSeries = new(name: "SuperTrend Up", color: Color.Green, width: 2, style: LineStyle.Solid);
DownSeries = new(name: "SuperTrend Down", color: Color.Red, width: 2, style: LineStyle.Solid);
AddLineSeries(UpSeries);
AddLineSeries(DownSeries);
Description = "SuperTrend Indicator";
_series = new(name: "SuperTrend", color: Color.Orange, width: 2, style: LineStyle.Solid);
_upperBand = new(name: "Upper Band", color: Color.Red, width: 1, style: LineStyle.Dot);
_lowerBand = new(name: "Lower Band", color: Color.Green, width: 1, style: LineStyle.Dot);
AddLineSeries(_series);
AddLineSeries(_upperBand);
AddLineSeries(_lowerBand);
}
protected override void OnInit()
@@ -44,28 +48,21 @@ public class SuperIndicator : Indicator, IWatchlistIndicator
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
bool isNew = args.IsNewBar();
var bar = this.GetInputBar(args);
double value = _super!.Update(bar, isNew).Value;
_series!.SetValue(value, _super.IsHot, ShowColdValues);
_upperBand!.SetValue(_super.UpperBand.Value, _super.IsHot, ShowColdValues);
_lowerBand!.SetValue(_super.LowerBand.Value, _super.IsHot, ShowColdValues);
TBar bar = this.GetInputBar(args);
TValue result = _super!.Update(bar, isNew);
if (!_super.IsHot && !ShowColdValues)
// Color logic
if (_super.IsHot)
{
return;
}
if (_super.IsBullish)
{
UpSeries!.SetValue(result.Value);
DownSeries!.SetValue(double.NaN);
}
else
{
UpSeries!.SetValue(double.NaN);
DownSeries!.SetValue(result.Value);
_series!.SetMarker(0, _super.IsBullish ? Color.Green : Color.Red);
}
}
}
+3 -3
View File
@@ -25,8 +25,8 @@ public class T3IndicatorTests
var indicator = new T3Indicator { Period = 10 };
// MinHistoryDepths is Period * 6 for T3 due to 6 stages
Assert.Equal(60, indicator.MinHistoryDepths);
Assert.Equal(60, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, T3Indicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -168,6 +168,6 @@ public class T3IndicatorTests
indicator.VolumeFactor = 0.9;
Assert.Equal(20, indicator.Period);
Assert.Equal(0.9, indicator.VolumeFactor);
Assert.Equal(120, indicator.MinHistoryDepths); // 20 * 6
Assert.Equal(0, T3Indicator.MinHistoryDepths); // 20 * 6
}
}
+22 -27
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class T3Indicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class T3Indicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 10;
@@ -17,51 +19,44 @@ public class T3Indicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private T3? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private T3? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period * 6; // Approx warmup for 6 stages
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"T3({Period}, {VolumeFactor:F2}):{SourceName}";
public override string ShortName => $"T3({Period}, {VolumeFactor:F2}):{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/t3/T3.Quantower.cs";
public T3Indicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "T3 - Tillson T3 Moving Average";
Description = "Tillson T3 Moving Average";
Series = new(name: $"T3 {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"T3 {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new T3(Period, VolumeFactor);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_ma = new T3(Period, VolumeFactor);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+17 -22
View File
@@ -23,8 +23,8 @@ public class TemaIndicatorTests
{
var indicator = new TemaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, TemaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -65,14 +65,17 @@ public class TemaIndicatorTests
// 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);
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 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(indicator.LinesSeries[0].Count > 0);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
@@ -99,9 +102,12 @@ public class TemaIndicatorTests
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
@@ -111,17 +117,6 @@ public class TemaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void TemaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new TemaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(TemaIndicator), method.DeclaringType);
}
[Fact]
public void TemaIndicator_MultipleUpdates_ProducesCorrectTemaSequence()
{
@@ -172,6 +167,6 @@ public class TemaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, TemaIndicator.MinHistoryDepths);
}
}
+23 -26
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class TemaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class TemaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 10;
@@ -14,52 +16,47 @@ public class TemaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Tema? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Tema? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"TEMA {Period}:{SourceName}";
public override string ShortName => $"TEMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/tema/Tema.Quantower.cs";
public TemaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "TEMA - Triple Exponential Moving Average";
Description = "Triple Exponential Moving Average";
Series = new(name: $"TEMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"TEMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Tema(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_ma = new Tema(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
return;
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+16 -21
View File
@@ -23,8 +23,8 @@ public class TrimaIndicatorTests
{
var indicator = new TrimaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, TrimaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -65,14 +65,17 @@ public class TrimaIndicatorTests
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// 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(indicator.LinesSeries[0].Count > 0);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
@@ -99,9 +102,12 @@ public class TrimaIndicatorTests
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
@@ -111,17 +117,6 @@ public class TrimaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void TrimaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new TrimaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(TrimaIndicator), method.DeclaringType);
}
[Fact]
public void TrimaIndicator_MultipleUpdates_ProducesCorrectTrimaSequence()
{
@@ -176,7 +171,7 @@ public class TrimaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, TrimaIndicator.MinHistoryDepths);
}
[Fact]
+23 -26
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class TrimaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class TrimaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 10;
@@ -14,52 +16,47 @@ public class TrimaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Trima? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Trima? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"TRIMA {Period}:{SourceName}";
public override string ShortName => $"TRIMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/trima/Trima.Quantower.cs";
public TrimaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "TRIMA - Triangular Moving Average";
Description = "Triangular Moving Average";
Series = new(name: $"TRIMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"TRIMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Trima(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_ma = new Trima(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
return;
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+24 -27
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class UsfIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class UsfIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 20;
@@ -14,52 +16,47 @@ public class UsfIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Usf? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Usf? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"USF {Period}:{SourceName}";
public override string ShortName => $"USF {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/usf/Usf.Quantower.cs";
public UsfIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "USF - Ultimate Smoother Filter";
Description = "Ehlers Ultimate Smoother Filter";
Series = new(name: $"USF {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"USF {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Usf(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_ma = new Usf(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar)
return;
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+17 -12
View File
@@ -23,8 +23,8 @@ public class VidyaIndicatorTests
{
var indicator = new VidyaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, VidyaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -56,14 +56,17 @@ public class VidyaIndicatorTests
// 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);
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 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(indicator.LinesSeries[0].Count > 0);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
@@ -94,10 +97,12 @@ public class VidyaIndicatorTests
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
// Process historical bar first
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
// Update with new tick (same bar data - simulates intrabar update)
@@ -164,6 +169,6 @@ public class VidyaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, VidyaIndicator.MinHistoryDepths);
}
}
+25 -21
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class VidyaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class VidyaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
@@ -14,45 +16,47 @@ public class VidyaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Vidya? ma;
protected LineSeries? Series;
protected string? SourceName;
private Vidya? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"VIDYA {Period}:{SourceName}";
public override string ShortName => $"VIDYA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/vidya/Vidya.Quantower.cs";
public VidyaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "VIDYA - Variable Index Dynamic Average";
Description = "Variable Index Dynamic Average (Chande)";
Series = new(name: $"VIDYA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"VIDYA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Vidya(Period);
SourceName = Source.ToString();
_ma = new Vidya(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
return;
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, Period, showColdValues: ShowColdValues, tension: 0.2);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+17 -22
View File
@@ -23,8 +23,8 @@ public class WmaIndicatorTests
{
var indicator = new WmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, WmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -65,14 +65,17 @@ public class WmaIndicatorTests
// 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);
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 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(indicator.LinesSeries[0].Count > 0);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
@@ -99,9 +102,12 @@ public class WmaIndicatorTests
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
@@ -111,17 +117,6 @@ public class WmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void WmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new WmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(WmaIndicator), method.DeclaringType);
}
[Fact]
public void WmaIndicator_MultipleUpdates_ProducesCorrectWmaSequence()
{
@@ -177,7 +172,7 @@ public class WmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, WmaIndicator.MinHistoryDepths);
}
[Fact]
+24 -25
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class WmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class WmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 10;
@@ -14,50 +16,47 @@ public class WmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Wma? ma;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Wma? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"WMA {Period}:{SourceName}";
public override string ShortName => $"WMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/wma/Wma.Quantower.cs";
public WmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "WMA - Weighted Moving Average";
Description = "Weighted Moving Average with linear weighting";
Series = new(name: $"WMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"WMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Wma(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_ma = new Wma(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
return;
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+51
View File
@@ -0,0 +1,51 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class AtrIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Atr? _atr;
private readonly LineSeries? _series;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ATR {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/volatility/atr/Atr.Quantower.cs";
public AtrIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "ATR - Average True Range";
Description = "Measures the volatility of an asset";
_series = new(name: "ATR", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_atr = new Atr(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TBar bar = this.GetInputBar(args);
TValue result = _atr!.Update(bar, args.IsNewBar());
_series!.SetValue(result.Value, _atr.IsHot, ShowColdValues);
}
}
+3 -3
View File
@@ -25,12 +25,12 @@ public class AdlIndicatorTests
}
[Fact]
public void AdlIndicator_SourceCodeLink_IsValid()
public void AdlIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AdlIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink);
Assert.Contains("Adl.Quantower.cs", indicator.SourceCodeLink);
Assert.Equal(0, AdlIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
+13 -8
View File
@@ -1,12 +1,17 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class AdlIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class AdlIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Adl? _adl;
protected LineSeries? AdlSeries;
private readonly LineSeries? _series;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
@@ -21,23 +26,23 @@ public class AdlIndicator : Indicator, IWatchlistIndicator
Name = "ADL - Accumulation/Distribution Line";
Description = "Accumulation/Distribution Line";
AdlSeries = new(name: "ADL", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(AdlSeries);
_series = new(name: "ADL", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_adl = new Adl();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TBar bar = this.GetInputBar(args);
TValue result = _adl!.Update(bar, isNew);
TValue result = _adl!.Update(bar, args.IsNewBar());
AdlSeries!.SetValue(result.Value);
_series!.SetValue(result.Value, _adl.IsHot, ShowColdValues);
}
}
+15 -11
View File
@@ -20,24 +20,28 @@ public class AdoscIndicatorTests
}
[Fact]
public void AdoscIndicator_MinHistoryDepths_EqualsSlowPeriod()
public void AdoscIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AdoscIndicator { SlowPeriod = 20 };
var indicator = new AdoscIndicator
{
SlowPeriod = 20
};
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, AdoscIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void AdoscIndicator_ShortName_IncludesParameters()
public void AdoscIndicator_SlowPeriod_CanBeChanged()
{
var indicator = new AdoscIndicator { FastPeriod = 10, SlowPeriod = 40 };
indicator.Initialize();
var indicator = new AdoscIndicator
{
SlowPeriod = 40
};
Assert.Contains("ADOSC", indicator.ShortName);
Assert.Contains("10", indicator.ShortName);
Assert.Contains("40", indicator.ShortName);
Assert.Equal(40, indicator.SlowPeriod);
Assert.Equal(0, AdoscIndicator.MinHistoryDepths);
}
[Fact]
@@ -117,6 +121,6 @@ public class AdoscIndicatorTests
Assert.Equal(10, indicator.FastPeriod);
Assert.Equal(40, indicator.SlowPeriod);
Assert.Equal(40, indicator.MinHistoryDepths);
Assert.Equal(0, AdoscIndicator.MinHistoryDepths);
}
}
+11 -14
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class AdoscIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class AdoscIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Fast Period", sortIndex: 1, 1, 1000, 1, 0)]
public int FastPeriod { get; set; } = 3;
@@ -15,9 +17,9 @@ public class AdoscIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Adosc? _adosc;
protected LineSeries? Series;
private readonly LineSeries? _series;
public int MinHistoryDepths => SlowPeriod;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ADOSC {FastPeriod}:{SlowPeriod}";
@@ -30,28 +32,23 @@ public class AdoscIndicator : Indicator, IWatchlistIndicator
Name = "ADOSC - Accumulation/Distribution Oscillator";
Description = "Momentum indicator for the Accumulation/Distribution Line";
Series = new(name: "ADOSC", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: "ADOSC", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_adosc = new Adosc(FastPeriod, SlowPeriod);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TBar bar = this.GetInputBar(args);
TValue result = _adosc!.Update(bar, isNew);
TValue result = _adosc!.Update(bar, args.IsNewBar());
if (!_adosc.IsHot && !ShowColdValues)
{
return;
}
Series!.SetValue(result.Value);
_series!.SetValue(result.Value, _adosc.IsHot, ShowColdValues);
}
}
-60
View File
@@ -1,60 +0,0 @@
{
"runs": [
{
"automationDetails": {
"guid": "68355419-3619-441c-a921-548ead8e8712",
"id": "QDNETC/qodana/2025-12-21",
"properties": {
"jobUrl": ""
}
},
"columnKind": "utf16CodeUnits",
"invocations": [
{
"endTimeUtc": "0001-01-01T00:00:00Z",
"executionSuccessful": true,
"startTimeUtc": "0001-01-01T00:00:00Z"
}
],
"language": "en-US",
"newlineSequences": [
"\r\n",
"\n"
],
"properties": {
"deviceId": "200820300000000-2e23-80fc-7565-86ffbc01e7c2"
},
"results": [],
"tool": {
"driver": {
"contents": [
"localizedData",
"nonLocalizedData"
],
"fullName": "Qodana Community for .NET",
"informationUri": "http://www.jetbrains.com/resharper/features/command-line.html",
"language": "en-US",
"name": "QDNETC",
"organization": "JetBrains, Inc",
"semanticVersion": "261.0.20251210.63140-eap01d",
"version": "2025.3.842301551.39"
}
},
"versionControlProvenance": [
{
"branch": "simd-dev",
"properties": {
"lastAuthorEmail": "miha.kralj@outlook.com",
"lastAuthorName": "Miha Kralj",
"repoUrl": "https://github.com/mihakralj/QuanTAlib.git",
"vcsType": "Git"
},
"repositoryUri": "https://github.com/mihakralj/QuanTAlib.git",
"revisionId": "a7b7207801ad31a4837444345612ebe99cb3dd9d"
}
]
}
],
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.6.json",
"version": "2.1.0"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,21 +0,0 @@
{
"version": "1.0",
"total": 1884,
"tools inspection": {
"Code Inspection": 1884
},
"linter": "QDNETC",
"attributes": {
"deviceId": "200820300000000-2e23-80fc-7565-86ffbc01e7c2",
"jobUrl": "",
"vcs": {
"sarifIdea": {
"repositoryUri": "https://github.com/mihakralj/QuanTAlib.git",
"revisionId": "a7b7207801ad31a4837444345612ebe99cb3dd9d",
"branch": "simd-dev"
}
},
"repoUrl": "https://github.com/mihakralj/QuanTAlib.git"
},
"linterVersion": "2025.3.842301551.39"
}
-4
View File
@@ -1,4 +0,0 @@
{
"version": "3",
"listProblem": []
}
File diff suppressed because it is too large Load Diff
-114
View File
@@ -1,114 +0,0 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
version: '1.0'
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
exclude:
# Disable XML documentation comment validation (allows unescaped < > in comments)
- name: XmlDocAnalyzer
- name: InvalidXmlDocComment
# Library project - public API properties are intentionally unused internally
- name: UnusedAutoPropertyAccessor.Global
# Style preference - fully qualified names used intentionally for clarity
- name: RedundantNameQualifier
- name: RCS1036 # Roslyn: Remove redundant empty line
- name: IDE0001 # Simplify name
- name: IDE0002 # Simplify member access
# HIGH-PERFORMANCE LIBRARY EXCLUSIONS
# ------------------------------------
# Flat namespace structure is intentional for this library
- name: CheckNamespace
# Float comparisons are intentional in financial calculations (checking 0.0, NaN, sentinel values)
- name: CompareOfFloatsByEqualityOperator
# Explicit default args improve code clarity and self-documentation
- name: RedundantArgumentDefaultValue
# Platform-specific optimizations (SIMD, intrinsics) are intentional
- name: CA1416 # Validate platform compatibility
# Public API unused internally - this is a library
- name: UnusedMember.Global
- name: MemberCanBePrivate.Global
- name: MemberCanBePrivate.Local
- name: ClassNeverInstantiated.Global
- name: UnusedType.Global
- name: UnusedMethodReturnValue.Global
- name: AutoPropertyCanBeMadeGetOnly.Global
- name: MemberCanBeMadeStatic.Global
- name: MemberCanBeMadeStatic.Local
# Redundant using directives - managed by IDE/build, not critical for library
- name: RedundantUsingDirective
- name: IDE0005 # Remove unnecessary using directives
- name: CS8019 # Unnecessary using directive
# Nullable warning suppressions - used intentionally for null safety patterns
- name: RedundantSuppressNullableWarningExpression
# Redundant type specifications - explicit types used for clarity/documentation
- name: RedundantTypeArgumentsOfMethod
- name: RedundantCast
- name: RedundantExplicitArrayCreation
# Unused local variables - often used in test setup or placeholder code (includes false positives for tuple deconstruction)
- name: UnusedVariable
- name: UnusedVariable.Compiler # False positive for tuple deconstruction
- name: CS0219 # Variable is assigned but never used
- name: RedundantAssignment # Value assigned is not used in any execution path
- name: UnusedAssignment # Assignment is not used
- name: IDE0059 # Unnecessary assignment of a value
# Object initializer in using statement - false positive for simple property setters
- name: CA2000 # Dispose objects before losing scope (overly cautious for simple cases)
- name: UseObjectOrCollectionInitializerWhenPossible
- name: DoNotUseObjectInitializerForUsingVariable
- name: ObjectInitializerMightCauseException
- name: ObjectCreationAsStatement
- name: UseObjectOrCollectionInitializer
- name: UsingStatementResourceInitialization # "Do not use object initializer for 'using' variable"
# Private field can be local variable - test fixtures often use fields for clarity/organization
- name: PrivateFieldCanBeConvertedToLocalVariable
- name: ConvertToLocalFunction
# Code coverage checks - SonarCloud handles coverage, Qodana coverage unreliable
- name: CoverageCheck
- name: ClassCoverageCheck
- name: MethodCoverageCheck
- name: CodeCoverageCheck
# Library-specific exclusions
- name: UnusedMember.Global # Suppresses "Method/Class is never used"
- name: UnusedMethodReturnValue.Global # Suppresses "Return value is never used"
- name: AutoPropertyCanBeMadeGetOnly.Global # Optional: common in libraries
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
#linter: jetbrains/qodana-dotnet:2025.3
solution: QuanTAlib.sln
linter: qodana-cdnet-EAP
dotnet:
msbuild:
properties:
Qodana: true
File diff suppressed because it is too large Load Diff
-4
View File
@@ -1,4 +0,0 @@
{
"version": "3",
"listProblem": []
}
-1
View File
@@ -1 +0,0 @@
[{"groupId":"qd.cl.system.os","eventName":"os.name","time":1766359483444,"state":true,"eventData":{"arch":"amd64","name":"linux","version":"2025.3"},"sessionId":"ee478045-63bd-4988-a07c-59a4c0aca61a"},{"groupId":"qd.cl.lifecycle","eventName":"project.opened","time":1766359483444,"state":false,"eventData":{"version":"2025.3"},"sessionId":"ee478045-63bd-4988-a07c-59a4c0aca61a"},{"groupId":"qd.cl.lifecycle","eventName":"project.closed","time":1766359561631,"state":false,"eventData":{"version":"2025.3"},"sessionId":"ee478045-63bd-4988-a07c-59a4c0aca61a"}]
+1 -117
View File
@@ -37,52 +37,6 @@ public class IndicatorExtensionsTests
Assert.NotEmpty(attr.Variants);
}
[Fact]
public void GetInputValue_ReturnsCorrectValues_ForSourceTypes()
{
TestIndicator indicator = new();
DateTime now = new(2024, 1, 1, 12, 0, 0, DateTimeKind.Utc);
// Open=100, High=110, Low=90, Close=105, Volume=1000
const double open = 100;
const double high = 110;
const double low = 90;
const double close = 105;
const double volume = 1000;
indicator.HistoricalData.AddBar(now, open, high, low, close, volume);
// Ensure Count is updated (mock implementation detail)
// The mock HistoricalData.Count reflects added items.
// Indicator.Count => HistoricalData.Count.
UpdateArgs args = new(UpdateReason.NewBar);
// Test each SourceType
Assert.Equal(open, IndicatorExtensions.GetInputValue(indicator, args, SourceType.Open).Value);
Assert.Equal(high, IndicatorExtensions.GetInputValue(indicator, args, SourceType.High).Value);
Assert.Equal(low, IndicatorExtensions.GetInputValue(indicator, args, SourceType.Low).Value);
Assert.Equal(close, IndicatorExtensions.GetInputValue(indicator, args, SourceType.Close).Value);
// HL2 = (110 + 90) / 2 = 100
Assert.Equal(100, IndicatorExtensions.GetInputValue(indicator, args, SourceType.HL2).Value);
// OC2 = (100 + 105) / 2 = 102.5
Assert.Equal(102.5, IndicatorExtensions.GetInputValue(indicator, args, SourceType.OC2).Value);
// OHL3 = (100 + 110 + 90) / 3 = 100
Assert.Equal(100, IndicatorExtensions.GetInputValue(indicator, args, SourceType.OHL3).Value);
// HLC3 = (110 + 90 + 105) / 3 = 101.666...
Assert.Equal(101.66666666666667, IndicatorExtensions.GetInputValue(indicator, args, SourceType.HLC3).Value, 5);
// OHLC4 = (100 + 110 + 90 + 105) / 4 = 101.25
Assert.Equal(101.25, IndicatorExtensions.GetInputValue(indicator, args, SourceType.OHLC4).Value);
// HLCC4 = (110 + 90 + 105 + 105) / 4 = 102.5
Assert.Equal(102.5, IndicatorExtensions.GetInputValue(indicator, args, SourceType.HLCC4).Value);
}
[Fact]
public void GetInputBar_ReturnsCorrectBar()
{
@@ -130,11 +84,7 @@ public class IndicatorExtensionsTests
var clientRect = new Rectangle(0, 0, 100, 100);
// 1. Test GetHLineY
int y = IndicatorExtensions.GetHLineY(converter, 50.0);
Assert.Equal(50, y); // Since our mock returns value as Y
// 2. Test GetSmoothCurvePoints
// Test GetSmoothCurvePoints
var series = new LineSeries("Test", Color.Blue, 1, LineStyle.Solid);
for (int i = 0; i < 20; i++) series.AddValue();
for (int i = 0; i < 20; i++) series.SetValue(100 + i, i);
@@ -145,34 +95,6 @@ public class IndicatorExtensionsTests
// MockChart.BarsWidth defaults to something? Let's assume 0 or check logic.
// In GetSmoothCurvePoints: barX + halfBarWidth.
// Our mock GetChartX returns 10.
// 3. Test GetHistogramRectangles
var histSeries = new LineSeries("Hist", Color.Blue, 1, LineStyle.Solid);
for (int i = 0; i < 20; i++) histSeries.AddValue();
for (int i = 0; i < 20; i++)
{
double val = (i % 2 == 0) ? 10.0 : -10.0;
histSeries.SetValue(val, i);
}
var rects = IndicatorExtensions.GetHistogramRectangles(indicator, converter, clientRect, histSeries);
Assert.NotEmpty(rects);
// Check value at offset 9 (i=9 in setup loop)
// i=9 is odd -> -10.0 (Negative)
// Color should be Red (150, 255, 0, 0)
var first = rects[0];
Assert.Equal(Color.FromArgb(150, 255, 0, 0), first.Color);
// Verify geometry
// Value is -10. GetChartY(-10) -> -10.
// GetChartY(0) -> 0.
// Height = Abs(0 - (-10)) = 10.
// Y = 0 (since negative bars start at 0 and go down? No, GDI+ coords usually go down.
// But here we are testing the logic in GetHistogramRectangles:
// else new Rectangle(barX, barY0, ...) -> Y = barY0 = 0.
Assert.Equal(0, first.Rect.Y);
Assert.Equal(10, first.Rect.Height);
}
[Fact]
@@ -225,10 +147,6 @@ public class IndicatorExtensionsTests
indicator.CurrentChart.MainWindow.CoordinatesConverter = new TestCoordinatesConverter(validTime);
var args = new PaintChartEventArgs(graphics, new Rectangle(0, 0, 100, 100));
using var pen = new Pen(Color.Red);
// Test PaintHLine
IndicatorExtensions.PaintHLine(indicator, args, 100, pen);
// Test PaintSmoothCurve with different LineStyles and Warmup
foreach (LineStyle style in Enum.GetValues(typeof(LineStyle)))
@@ -242,40 +160,6 @@ public class IndicatorExtensionsTests
// Test without cold values
IndicatorExtensions.PaintSmoothCurve(indicator, args, series, warmupPeriod: 5, showColdValues: false);
// Test PaintLine
IndicatorExtensions.PaintLine(indicator, args, series, warmupPeriod: 5, showColdValues: true);
}
// Test PaintHistogram with Positive and Negative values
var histSeries = new LineSeries("Hist", Color.Blue, 1, LineStyle.Solid);
for (int i = 0; i < 20; i++) histSeries.AddValue();
for (int i = 0; i < 20; i++)
{
// Alternate positive and negative
double val = (i % 2 == 0) ? 10.0 : -10.0;
histSeries.SetValue(val, i);
}
IndicatorExtensions.PaintHistogram(indicator, args, histSeries, 0);
// Test DrawText
IndicatorExtensions.DrawText(indicator, args, "Test Text");
}
[Fact]
public void GetInputValue_DefaultCase_ReturnsClose()
{
TestIndicator indicator = new();
DateTime now = new(2024, 1, 1, 12, 0, 0, DateTimeKind.Utc);
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105, 1000);
UpdateArgs args = new(UpdateReason.NewBar);
// Cast to an invalid SourceType to trigger default case
SourceType invalidType = (SourceType)999;
var result = IndicatorExtensions.GetInputValue(indicator, args, invalidType);
Assert.Equal(105, result.Value); // Should default to Close (105)
}
}

Some files were not shown because too many files have changed in this diff Show More