mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-05 20:47:43 +00:00
Refactor IndicatorExtensions: Remove unused methods and optimize price retrieval
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"version": "3",
|
||||
"listProblem": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"version": "3",
|
||||
"listProblem": []
|
||||
}
|
||||
@@ -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"}]
|
||||
@@ -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
Reference in New Issue
Block a user