Refactor IndicatorExtensions: Remove unused methods and optimize price retrieval

This commit is contained in:
Miha Kralj
2025-12-24 13:50:19 -08:00
parent c47b106597
commit 8917575994
101 changed files with 1311 additions and 450292 deletions
+3 -3
View File
@@ -25,8 +25,8 @@ public class AlmaIndicatorTests
{
var indicator = new AlmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, AlmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -165,6 +165,6 @@ public class AlmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, AlmaIndicator.MinHistoryDepths);
}
}
+11 -17
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class AlmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -23,12 +25,13 @@ public class AlmaIndicator : Indicator, IWatchlistIndicator
private Alma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ALMA {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/alma/Alma.Quantower.cs";
public AlmaIndicator()
{
@@ -45,26 +48,17 @@ public class AlmaIndicator : Indicator, IWatchlistIndicator
{
ma = new Alma(Period, Offset, Sigma);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+3 -3
View File
@@ -23,8 +23,8 @@ public class BesselIndicatorTests
{
var indicator = new BesselIndicator { Length = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, BesselIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -157,6 +157,6 @@ public class BesselIndicatorTests
indicator.Length = 20;
Assert.Equal(20, indicator.Length);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, BesselIndicator.MinHistoryDepths);
}
}
+10 -18
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class BesselIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Length", sortIndex: 1, 1, 1000, 1, 0)]
@@ -17,9 +19,9 @@ public class BesselIndicator : Indicator, IWatchlistIndicator
private Bessel? _filter;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Length;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"BESSEL {Length}:{SourceName}";
@@ -39,27 +41,17 @@ public class BesselIndicator : Indicator, IWatchlistIndicator
{
_filter = new Bessel(Length);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _filter!.Update(input, isNew);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _filter!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && _filter!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, _filter.IsHot, ShowColdValues);
}
}
@@ -25,8 +25,8 @@ public class BilateralIndicatorTests
{
var indicator = new BilateralIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, BilateralIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -113,16 +113,6 @@ public class BilateralIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void BilateralIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new BilateralIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(BilateralIndicator), method.DeclaringType);
}
[Fact]
public void BilateralIndicator_MultipleUpdates_ProducesCorrectSequence()
@@ -181,6 +171,6 @@ public class BilateralIndicatorTests
Assert.Equal(20, indicator.Period);
Assert.Equal(1.0, indicator.SigmaSRatio);
Assert.Equal(2.0, indicator.SigmaRMult);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, BilateralIndicator.MinHistoryDepths);
}
}
+10 -21
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class BilateralIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -23,9 +25,9 @@ public class BilateralIndicator : Indicator, IWatchlistIndicator
private Bilateral? _bilateral;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Bilateral {Period}:{SourceName}";
@@ -46,30 +48,17 @@ public class BilateralIndicator : Indicator, IWatchlistIndicator
{
_bilateral = new Bilateral(Period, SigmaSRatio, SigmaRMult);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _bilateral!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _bilateral!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (_warmupBarIndex < 0 && _bilateral!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
var savedColor = Series!.Color;
Series.Color = Color.Transparent;
base.OnPaintChart(args);
Series.Color = savedColor;
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintLine(args, Series!, warmupPeriod, showColdValues: ShowColdValues);
Series!.SetValue(result.Value, _bilateral.IsHot, ShowColdValues);
}
}
+40 -12
View File
@@ -5,6 +5,13 @@ namespace QuanTAlib;
public class BilateralTests
{
private readonly GBM _gbm;
public BilateralTests()
{
_gbm = new GBM();
}
[Fact]
public void Constructor_ValidatesInput()
{
@@ -101,22 +108,43 @@ public class BilateralTests
}
[Fact]
public void TSeries_Update_Matches_Iterative()
public void AllModes_ProduceSameResult()
{
var indicator = new Bilateral(5);
var series = new TSeries();
for (int i = 0; i < 20; i++)
int period = 10;
var bars = _gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = new Bilateral(period).Update(series);
double expected = batchSeries.Last.Value;
// 2. Span Mode
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Bilateral.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Bilateral(period);
for (int i = 0; i < series.Count; i++)
{
series.Add(new TValue(DateTime.UtcNow.AddMinutes(i), i));
streamingInd.Update(series[i]);
}
var resultSeries = indicator.Update(series);
var indicatorIterative = new Bilateral(5);
for (int i = 0; i < 20; i++)
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Bilateral(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
indicatorIterative.Update(series[i]);
Assert.Equal(indicatorIterative.Last.Value, resultSeries[i].Value);
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, 1e-9);
Assert.Equal(expected, streamingResult, 1e-9);
Assert.Equal(expected, eventingResult, 1e-9);
}
}
+102
View File
@@ -260,4 +260,106 @@ public sealed class Bilateral : AbstractBase
_p_state = default;
Last = default;
}
public static void Calculate(ReadOnlySpan<double> source, Span<double> destination, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
{
if (period <= 0)
throw new ArgumentException("Period must be greater than 0", nameof(period));
// Precalculate spatial weights
double sigmaS = Math.Max(period * sigmaSRatio, 1e-10);
double twoSigmaSSq = 2.0 * sigmaS * sigmaS;
Span<double> spatialWeights = period <= 256 ? stackalloc double[period] : new double[period];
for (int i = 0; i < period; i++)
{
double diffSpatial = i;
spatialWeights[i] = Math.Exp(-(diffSpatial * diffSpatial) / twoSigmaSSq);
}
// Handle NaNs by tracking last valid value
double lastValid = double.NaN;
// Find initial valid value
for (int i = 0; i < source.Length; i++)
{
if (double.IsFinite(source[i]))
{
lastValid = source[i];
break;
}
}
// If all NaNs, fill with NaN
if (double.IsNaN(lastValid))
{
destination.Fill(double.NaN);
return;
}
Span<double> window = period <= 256 ? stackalloc double[period] : new double[period];
int windowIdx = 0;
int count = 0;
double sum = 0;
double sumSq = 0;
for (int i = 0; i < source.Length; i++)
{
double val = source[i];
if (double.IsNaN(val))
{
val = lastValid;
}
else
{
lastValid = val;
}
// Add to window
double removed = 0;
if (count >= period)
{
removed = window[windowIdx];
sum -= removed;
sumSq -= removed * removed;
}
window[windowIdx] = val;
sum += val;
sumSq += val * val;
int currentNewestIdx = windowIdx;
windowIdx = (windowIdx + 1) % period;
if (count < period) count++;
// Calculate StDev
double variance = Math.Max(0, (sumSq - (sum * sum) / count) / count);
double stdev = Math.Sqrt(variance);
double sigmaR = Math.Max(stdev * sigmaRMult, 1e-10);
double twoSigmaRSq = 2.0 * sigmaR * sigmaR;
double sumWeights = 0.0;
double sumWeightedSrc = 0.0;
double centerVal = val; // Newest value
// Iterate backwards through the window
for (int k = 0; k < count; k++)
{
// k=0 is newest (currentNewestIdx)
// k=1 is previous...
int idx = currentNewestIdx - k;
if (idx < 0) idx += period;
double wVal = window[idx];
double diffRange = centerVal - wVal;
double weightRange = Math.Exp(-(diffRange * diffRange) / twoSigmaRSq);
double weight = spatialWeights[k] * weightRange;
sumWeights += weight;
sumWeightedSrc += weight * wVal;
}
destination[i] = sumWeights == 0.0 ? centerVal : sumWeightedSrc / sumWeights;
}
}
}
+2 -2
View File
@@ -23,9 +23,9 @@ public class BlmaIndicatorTests
{
var indicator = new BlmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, BlmaIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
+16 -21
View File
@@ -1,9 +1,10 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class BlmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
@@ -17,47 +18,41 @@ public class BlmaIndicator : Indicator, IWatchlistIndicator
private Blma? _ma;
protected LineSeries? _series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"BLMA {Period}";
public override string ShortName => $"BLMA {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/blma/Blma.Quantower.cs";
public BlmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "BLMA - Blackman Window Moving Average";
Description = "A moving average using the Blackman window function for superior noise suppression.";
SeparateWindow = false;
_series = new(name: "BLMA", color: Color.Yellow, width: 2, style: LineStyle.Solid);
_series = new(name: $"BLMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_ma = new Blma(Period);
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _ma!.Update(input, isNew);
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (!_ma.IsHot && !ShowColdValues)
{
return;
}
_series!.SetValue(result.Value);
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, _series!, _ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
}
}
+16 -14
View File
@@ -150,12 +150,22 @@ public sealed class Blma : AbstractBase
private static double CalculateWeightedSum(RingBuffer buffer, ReadOnlySpan<double> weights)
{
double sum = 0;
for (int i = 0; i < buffer.Count; i++)
int start = buffer.StartIndex;
int count = buffer.Count;
int capacity = buffer.Capacity;
if (start + count <= capacity)
{
sum += buffer[i] * weights[i];
return buffer.InternalBuffer.Slice(start, count).DotProduct(weights);
}
return sum;
int firstPartLength = capacity - start;
int secondPartLength = count - firstPartLength;
double sum1 = buffer.InternalBuffer.Slice(start, firstPartLength).DotProduct(weights[..firstPartLength]);
double sum2 = buffer.InternalBuffer.Slice(0, secondPartLength).DotProduct(weights[firstPartLength..]);
return sum1 + sum2;
}
public static void Calculate(ReadOnlySpan<double> source, Span<double> destination, int period)
@@ -200,11 +210,7 @@ public sealed class Blma : AbstractBase
}
else
{
double sum = 0;
for (int j = 0; j < count; j++)
{
sum += source[i - count + 1 + j] * currentWeights[j];
}
double sum = source.Slice(i - count + 1, count).DotProduct(currentWeights);
destination[i] = sum / currentWeightSum;
}
}
@@ -212,11 +218,7 @@ public sealed class Blma : AbstractBase
else
{
// Full period
double sum = 0;
for (int j = 0; j < period; j++)
{
sum += source[i - period + 1 + j] * weights[j];
}
double sum = source.Slice(i - period + 1, period).DotProduct(weights);
destination[i] = sum / weightSum;
}
}
+2 -2
View File
@@ -24,9 +24,9 @@ public class ButterIndicatorTests
{
var indicator = new ButterIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, ButterIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
+16 -21
View File
@@ -1,9 +1,10 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class ButterIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)]
@@ -17,47 +18,41 @@ public class ButterIndicator : Indicator, IWatchlistIndicator
private Butter? _ma;
protected LineSeries? _series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"BUTTER {Period}";
public override string ShortName => $"BUTTER {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/butter/Butter.Quantower.cs";
public ButterIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "BUTTER - Butterworth Filter";
Description = "A 2nd-order low-pass filter with maximally flat frequency response in the passband.";
SeparateWindow = false;
_series = new(name: "BUTTER", color: Color.Orange, width: 2, style: LineStyle.Solid);
_series = new(name: $"BUTTER {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_ma = new Butter(Period);
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _ma!.Update(input, isNew);
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (!_ma.IsHot && !ShowColdValues)
{
return;
}
_series!.SetValue(result.Value);
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, _series!, _ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
}
}
+2 -12
View File
@@ -24,8 +24,8 @@ public class ConvIndicatorTests
var indicator = new ConvIndicator { WeightsInput = "1, 2, 3, 4, 5" };
indicator.Initialize(); // Initialize to parse weights
Assert.Equal(5, indicator.MinHistoryDepths);
Assert.Equal(5, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, ConvIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -112,16 +112,6 @@ public class ConvIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void ConvIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new ConvIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(ConvIndicator), method.DeclaringType);
}
[Fact]
public void ConvIndicator_MultipleUpdates_ProducesCorrectSequence()
+10 -17
View File
@@ -1,10 +1,10 @@
using System;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class ConvIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Weights (comma separated)", sortIndex: 1)]
@@ -17,11 +17,11 @@ public class ConvIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Conv? _conv;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => _conv != null ? WeightsInput.Split(',').Length : 0;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"CONV:{SourceName}";
@@ -60,25 +60,18 @@ public class ConvIndicator : Indicator, IWatchlistIndicator
_conv = new Conv([1.0]);
}
_warmupBarIndex = -1;
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _conv!.Update(input, isNew);
if (_warmupBarIndex < 0 && _conv!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _conv!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, _conv.IsHot, ShowColdValues);
}
}
+2 -24
View File
@@ -23,8 +23,8 @@ public class DemaIndicatorTests
{
var indicator = new DemaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, DemaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -111,18 +111,6 @@ public class DemaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void DemaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new DemaIndicator();
indicator.Initialize();
// We can't easily mock PaintChartEventArgs fully, but we can verify the method exists and is callable
// if we could mock the args. Since we can't, we skip the actual call but verify the method is overridden.
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(DemaIndicator), method.DeclaringType);
}
[Fact]
public void DemaIndicator_MultipleUpdates_ProducesCorrectDemaSequence()
@@ -166,14 +154,4 @@ public class DemaIndicatorTests
}
}
[Fact]
public void DemaIndicator_Period_CanBeChanged()
{
var indicator = new DemaIndicator { Period = 5 };
Assert.Equal(5, indicator.Period);
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
}
}
+10 -17
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class DemaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -17,9 +19,9 @@ public class DemaIndicator : Indicator, IWatchlistIndicator
private Dema? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"DEMA {Period}:{SourceName}";
@@ -40,26 +42,17 @@ public class DemaIndicator : Indicator, IWatchlistIndicator
{
ma = new Dema(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+2 -2
View File
@@ -23,8 +23,8 @@ public class DwmaIndicatorTests
{
var indicator = new DwmaIndicator { Period = 20 };
Assert.Equal(40, indicator.MinHistoryDepths);
Assert.Equal(40, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, DwmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
+10 -15
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class DwmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -15,11 +17,11 @@ public class DwmaIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Dwma? ma;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period * 2; // DWMA needs roughly 2x period to warm up
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"DWMA {Period}:{SourceName}";
@@ -39,25 +41,18 @@ public class DwmaIndicator : Indicator, IWatchlistIndicator
protected override void OnInit()
{
ma = new Dwma(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+4 -4
View File
@@ -19,12 +19,12 @@ public class EmaIndicatorTests
}
[Fact]
public void EmaIndicator_MinHistoryDepths_EqualsPeriod()
public void EmaIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new EmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, EmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -164,6 +164,6 @@ public class EmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, EmaIndicator.MinHistoryDepths);
}
}
+9 -19
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class EmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -17,9 +19,9 @@ public class EmaIndicator : Indicator, IWatchlistIndicator
private Ema? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"EMA {Period}:{SourceName}";
@@ -39,27 +41,15 @@ public class EmaIndicator : Indicator, IWatchlistIndicator
{
ma = new Ema(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1; // Reset warmup tracking when period changes
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
// Track when IsHot becomes true for the first time
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+3 -13
View File
@@ -24,8 +24,8 @@ public class HmaIndicatorTests
var indicator = new HmaIndicator { Period = 16 };
// HMA warmup is roughly Period + Sqrt(Period)
// 16 + Sqrt(16) = 16 + 4 = 20
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, HmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -112,16 +112,6 @@ public class HmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void HmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new HmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(HmaIndicator), method.DeclaringType);
}
[Fact]
public void HmaIndicator_MultipleUpdates_ProducesCorrectHmaSequence()
@@ -174,6 +164,6 @@ public class HmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
// 20 + sqrt(20) = 20 + 4 = 24
Assert.Equal(24, indicator.MinHistoryDepths);
Assert.Equal(0, HmaIndicator.MinHistoryDepths);
}
}
+10 -15
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class HmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 2, 1000, 1, 0)]
@@ -15,11 +17,11 @@ public class HmaIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Hma? ma;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period + (int)Math.Sqrt(Period); // Approximate warmup
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"HMA {Period}:{SourceName}";
@@ -39,25 +41,18 @@ public class HmaIndicator : Indicator, IWatchlistIndicator
protected override void OnInit()
{
ma = new Hma(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+4 -2
View File
@@ -11,9 +11,11 @@ public class HtitIndicatorTests
public void Indicator_Initializes_Correctly()
{
var indicator = new HtitIndicator();
indicator.Initialize();
Assert.Equal("HTIT - Ehlers Hilbert Transform Instantaneous Trend", indicator.Name);
Assert.Equal("HTIT:Close", indicator.ShortName);
Assert.Equal(50, HtitIndicator.MinHistoryDepths);
Assert.StartsWith("HTIT", indicator.ShortName);
Assert.Contains("Close", indicator.ShortName);
Assert.Equal(0, HtitIndicator.MinHistoryDepths);
Assert.Single(indicator.LinesSeries);
}
+18 -28
View File
@@ -1,13 +1,15 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class HtitIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class HtitIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 50; // Not used in calculation but kept for consistency if needed
public int Period { get; set; } = 50; // Not used in calculation but kept for consistency
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
@@ -16,51 +18,39 @@ public class HtitIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Htit? _htit;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public static int MinHistoryDepths => 50;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"HTIT:{SourceName}";
public override string ShortName => $"HTIT:{_sourceName}";
public HtitIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "HTIT - Ehlers Hilbert Transform Instantaneous Trend";
Description = "Ehlers Hilbert Transform Instantaneous Trend";
Series = new(name: "HTIT", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: "HTIT", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_htit = new Htit();
SourceName = Source.ToString();
_warmupBarIndex = -1;
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _htit!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && _htit.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _htit!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _htit.IsHot, ShowColdValues);
}
}
+5 -20
View File
@@ -12,7 +12,6 @@ public class JmaIndicatorTests
Assert.Equal(10, indicator.Period);
Assert.Equal(0, indicator.Phase);
Assert.Equal(0.45, indicator.Power);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("JMA - Jurik Moving Average", indicator.Name);
@@ -25,19 +24,18 @@ public class JmaIndicatorTests
{
var indicator = new JmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, JmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void JmaIndicator_ShortName_IncludesParameters()
{
var indicator = new JmaIndicator { Period = 15, Phase = 50, Power = 0.8 };
var indicator = new JmaIndicator { Period = 15, Phase = 50 };
Assert.Contains("JMA", indicator.ShortName);
Assert.Contains("15", indicator.ShortName);
Assert.Contains("50", indicator.ShortName);
Assert.Contains("0.8", indicator.ShortName);
}
[Fact]
@@ -115,16 +113,6 @@ public class JmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void JmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new JmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(JmaIndicator), method.DeclaringType);
}
[Fact]
public void JmaIndicator_MultipleUpdates_ProducesCorrectSequence()
@@ -171,18 +159,15 @@ public class JmaIndicatorTests
[Fact]
public void JmaIndicator_Parameters_CanBeChanged()
{
var indicator = new JmaIndicator { Period = 5, Phase = 10, Power = 0.5 };
var indicator = new JmaIndicator { Period = 5, Phase = 10 };
Assert.Equal(5, indicator.Period);
Assert.Equal(10, indicator.Phase);
Assert.Equal(0.5, indicator.Power);
indicator.Period = 20;
indicator.Phase = -10;
indicator.Power = 0.9;
Assert.Equal(20, indicator.Period);
Assert.Equal(-10, indicator.Phase);
Assert.Equal(0.9, indicator.Power);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, JmaIndicator.MinHistoryDepths);
}
}
+20 -22
View File
@@ -1,8 +1,10 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class JmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
@@ -11,24 +13,27 @@ public class JmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Phase", sortIndex: 2, -100, 100, 1, 0)]
public int Phase { get; set; } = 0;
[InputParameter("Power", sortIndex: 3, 0.1, 10.0, 0.1, 1)]
public double Power { get; set; } = 0.45;
[IndicatorExtensions.DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
[InputParameter("Color", sortIndex: 22)]
public Color LineColor { get; set; } = IndicatorExtensions.Averages;
[InputParameter("Width", sortIndex: 23)]
public int LineWidth { get; set; } = 2;
private Jma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"JMA {Period}:{Phase}:{Power}:{SourceName}";
public override string ShortName => $"JMA {Period}:{Phase}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/jma/Jma.Quantower.cs";
public JmaIndicator()
@@ -44,28 +49,21 @@ public class JmaIndicator : Indicator, IWatchlistIndicator
protected override void OnInit()
{
ma = new Jma(Period, Phase, Power);
ma = new Jma(Period, Phase);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
Series!.Color = LineColor;
Series!.Width = LineWidth;
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
Series!.SetValue(result.Value, ma.IsHot, ShowColdValues);
}
}
+4 -4
View File
@@ -15,7 +15,7 @@ public class KamaIndicatorTests
Assert.Equal(30, indicator.SlowPeriod);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("KAMA - Kaufman Adaptive Moving Average", indicator.Name);
Assert.Equal("KAMA - Kaufman's Adaptive Moving Average", indicator.Name);
Assert.False(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
@@ -25,8 +25,8 @@ public class KamaIndicatorTests
{
var indicator = new KamaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, KamaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -165,6 +165,6 @@ public class KamaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, KamaIndicator.MinHistoryDepths);
}
}
+25 -33
View File
@@ -1,17 +1,20 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class KamaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class KamaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[InputParameter("Fast Period", sortIndex: 2, 1, 1000, 1, 0)]
[InputParameter("Fast Period", sortIndex: 2, 1, 200, 1, 0)]
public int FastPeriod { get; set; } = 2;
[InputParameter("Slow Period", sortIndex: 3, 1, 1000, 1, 0)]
[InputParameter("Slow Period", sortIndex: 3, 1, 200, 1, 0)]
public int SlowPeriod { get; set; } = 30;
[IndicatorExtensions.DataSourceInput]
@@ -20,51 +23,40 @@ public class KamaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Kama? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Kama? _kama;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"KAMA {Period}:{SourceName}";
public override string ShortName => $"KAMA {Period}:{_sourceName}";
public KamaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "KAMA - Kaufman Adaptive Moving Average";
Description = "Kaufman Adaptive Moving Average";
Series = new(name: $"KAMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
Name = "KAMA - Kaufman's Adaptive Moving Average";
Description = "Kaufman's Adaptive Moving Average";
_series = new(name: $"KAMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Kama(Period, FastPeriod, SlowPeriod);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_kama = new Kama(Period, FastPeriod, SlowPeriod);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _kama!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _kama.IsHot, ShowColdValues);
}
}
+4 -15
View File
@@ -10,7 +10,7 @@ public class LsmaIndicatorTests
{
var indicator = new LsmaIndicator();
Assert.Equal(14, indicator.Period);
Assert.Equal(25, indicator.Period);
Assert.Equal(0, indicator.Offset);
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
@@ -24,8 +24,8 @@ public class LsmaIndicatorTests
{
var indicator = new LsmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, LsmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -35,7 +35,6 @@ public class LsmaIndicatorTests
Assert.Contains("LSMA", indicator.ShortName);
Assert.Contains("15", indicator.ShortName);
Assert.Contains("2", indicator.ShortName);
}
[Fact]
@@ -113,16 +112,6 @@ public class LsmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void LsmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new LsmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(LsmaIndicator), method.DeclaringType);
}
[Fact]
public void LsmaIndicator_MultipleUpdates_ProducesCorrectSequence()
@@ -177,6 +166,6 @@ public class LsmaIndicatorTests
indicator.Offset = 2;
Assert.Equal(20, indicator.Period);
Assert.Equal(2, indicator.Offset);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, LsmaIndicator.MinHistoryDepths);
}
}
+22 -31
View File
@@ -1,12 +1,15 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class LsmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class LsmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 25;
[InputParameter("Offset", sortIndex: 2, -1000, 1000, 1, 0)]
public int Offset { get; set; } = 0;
@@ -17,53 +20,41 @@ public class LsmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Lsma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Lsma? _lsma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"LSMA {Period}:{Offset}:{SourceName}";
public override string ShortName => $"LSMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/lsma/Lsma.Quantower.cs";
public LsmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "LSMA - Least Squares Moving Average";
Description = "Least Squares Moving Average";
Series = new(name: $"LSMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"LSMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Lsma(Period, Offset);
SourceName = Source.ToString();
_warmupBarIndex = -1; // Reset warmup tracking when period changes
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_lsma = new Lsma(Period, Offset);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
// Track when IsHot becomes true for the first time
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _lsma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _lsma.IsHot, ShowColdValues);
}
}
+5 -5
View File
@@ -20,22 +20,22 @@ public class MamaIndicatorTests
}
[Fact]
public void MamaIndicator_MinHistoryDepths_Equals6()
public void MamaIndicator_MinHistoryDepths_Equals50()
{
var indicator = new MamaIndicator();
Assert.Equal(6, MamaIndicator.MinHistoryDepths);
Assert.Equal(6, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, MamaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void MamaIndicator_ShortName_IncludesLimitsAndSource()
{
var indicator = new MamaIndicator { FastLimit = 0.5, SlowLimit = 0.05 };
indicator.Initialize();
Assert.Contains("MAMA", indicator.ShortName);
Assert.Contains("0.50", indicator.ShortName);
Assert.Contains("0.05", indicator.ShortName);
Assert.Contains("Close", indicator.ShortName);
}
[Fact]
+24 -39
View File
@@ -1,9 +1,12 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class MamaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class MamaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Fast Limit", sortIndex: 1, 0.01, 0.99, 0.01, 2)]
public double FastLimit { get; set; } = 0.5;
@@ -17,62 +20,44 @@ public class MamaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Mama? _ma;
protected LineSeries? MamaSeries;
protected LineSeries? FamaSeries;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Mama? _mama;
private readonly LineSeries? _series;
private readonly LineSeries? _famaSeries;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public static int MinHistoryDepths => 6;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"MAMA({FastLimit:F2}, {SlowLimit:F2}):{SourceName}";
public override string ShortName => $"MAMA:{_sourceName}";
public MamaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "MAMA - MESA Adaptive Moving Average";
Description = "MESA Adaptive Moving Average";
MamaSeries = new(name: "MAMA", color: Color.Red, width: 2, style: LineStyle.Solid);
FamaSeries = new(name: "FAMA", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(MamaSeries);
AddLineSeries(FamaSeries);
_series = new(name: "MAMA", color: Color.Orange, width: 2, style: LineStyle.Solid);
_famaSeries = new(name: "FAMA", color: Color.Red, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
AddLineSeries(_famaSeries);
}
protected override void OnInit()
{
_ma = new Mama(FastLimit, SlowLimit);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_mama = new Mama(FastLimit, SlowLimit);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _ma!.Update(input, isNew);
MamaSeries!.SetValue(result.Value);
FamaSeries!.SetValue(_ma.Fama.Value);
MamaSeries!.SetMarker(0, Color.Transparent);
FamaSeries!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && _ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, MamaSeries!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
this.PaintSmoothCurve(args, FamaSeries!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _mama!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _mama.IsHot, ShowColdValues);
_famaSeries!.SetValue(_mama.Fama.Value, _mama.IsHot, ShowColdValues);
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ public class MgdiIndicatorTests
var indicator = new MgdiIndicator();
Assert.Equal("MGDI - McGinley Dynamic Indicator", indicator.Name);
Assert.Equal("MGDI(14,0.6):Close", indicator.ShortName);
Assert.Equal(14, indicator.MinHistoryDepths);
Assert.Equal(0, MgdiIndicator.MinHistoryDepths);
Assert.Single(indicator.LinesSeries);
}
+19 -28
View File
@@ -1,15 +1,17 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class MgdiIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class MgdiIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("K Factor", sortIndex: 2, 0.1, 10, 0.1, 1)]
[InputParameter("K Factor", sortIndex: 2, 0.1, 10.0, 0.1, 1)]
public double K { get; set; } = 0.6;
[IndicatorExtensions.DataSourceInput]
@@ -19,51 +21,40 @@ public class MgdiIndicator : Indicator, IWatchlistIndicator
public bool ShowColdValues { get; set; } = true;
private Mgdi? _mgdi;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"MGDI({Period},{K}):{SourceName}";
public override string ShortName => $"MGDI({Period},{K}):{_sourceName}";
public MgdiIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "MGDI - McGinley Dynamic Indicator";
Description = "McGinley Dynamic Indicator";
Series = new(name: "MGDI", color: Color.Orange, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"MGDI {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_mgdi = new Mgdi(Period, K);
SourceName = Source.ToString();
_warmupBarIndex = -1;
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _mgdi!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && _mgdi.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _mgdi!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _mgdi.IsHot, ShowColdValues);
}
}
+3 -14
View File
@@ -24,9 +24,9 @@ public class PwmaIndicatorTests
{
var indicator = new PwmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, PwmaIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -113,17 +113,6 @@ public class PwmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void PwmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new PwmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(PwmaIndicator), method.DeclaringType);
}
[Fact]
public void PwmaIndicator_MultipleUpdates_ProducesCorrectPwmaSequence()
{
@@ -174,6 +163,6 @@ public class PwmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, PwmaIndicator.MinHistoryDepths);
}
}
+21 -26
View File
@@ -1,9 +1,12 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class PwmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class PwmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
@@ -14,50 +17,42 @@ public class PwmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Pwma? _ma;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Pwma? _pwma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"PWMA {Period}:{SourceName}";
public override string ShortName => $"PWMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/pwma/Pwma.Quantower.cs";
public PwmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "PWMA - Parabolic Weighted Moving Average";
Description = "Weighted Moving Average with parabolic weighting";
Series = new(name: $"PWMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
Description = "Parabolic Weighted Moving Average";
_series = new(name: $"PWMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
_ma = new Pwma(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_pwma = new Pwma(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = _ma!.Update(input, isNew);
if (_warmupBarIndex < 0 && _ma!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _pwma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _pwma.IsHot, ShowColdValues);
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
}
}
+3 -3
View File
@@ -23,8 +23,8 @@ public class RmaIndicatorTests
{
var indicator = new RmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, RmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -164,6 +164,6 @@ public class RmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, RmaIndicator.MinHistoryDepths);
}
}
+21 -30
View File
@@ -1,11 +1,14 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class RmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class RmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 14;
[IndicatorExtensions.DataSourceInput]
@@ -14,52 +17,40 @@ public class RmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Rma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Rma? _rma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"RMA {Period}:{SourceName}";
public override string ShortName => $"RMA {Period}:{_sourceName}";
public RmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "RMA - Running Moving Average";
Description = "Running Moving Average (Wilder's Smoothing)";
Series = new(name: $"RMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"RMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Rma(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1; // Reset warmup tracking when period changes
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_rma = new Rma(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
// Track when IsHot becomes true for the first time
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _rma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _rma.IsHot, ShowColdValues);
}
}
+4 -22
View File
@@ -19,12 +19,12 @@ public class SmaIndicatorTests
}
[Fact]
public void SmaIndicator_MinHistoryDepths_EqualsPeriod()
public void SmaIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new SmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, SmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -36,14 +36,6 @@ public class SmaIndicatorTests
Assert.Contains("15", indicator.ShortName);
}
[Fact]
public void SmaIndicator_SourceCodeLink_IsValid()
{
var indicator = new SmaIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink);
Assert.Contains("Sma.Quantower.cs", indicator.SourceCodeLink);
}
[Fact]
public void SmaIndicator_Initialize_CreatesInternalSma()
@@ -111,16 +103,6 @@ public class SmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void SmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new SmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(SmaIndicator), method.DeclaringType);
}
[Fact]
public void SmaIndicator_MultipleUpdates_ProducesCorrectSmaSequence()
@@ -177,6 +159,6 @@ public class SmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, SmaIndicator.MinHistoryDepths);
}
}
+21 -35
View File
@@ -1,11 +1,14 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class SmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class SmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[IndicatorExtensions.DataSourceInput]
@@ -14,57 +17,40 @@ public class SmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Sma? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Sma? _sma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"SMA {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/sma/Sma.Quantower.cs";
public override string ShortName => $"SMA {Period}:{_sourceName}";
public SmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "SMA - Simple Moving Average";
Description = "Simple Moving Average";
Series = new(name: $"SMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"SMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Sma(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1; // Reset warmup tracking when period changes
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_sma = new Sma(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
// Track when IsHot becomes true for the first time
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
var savedColor = Series!.Color;
Series.Color = Color.Transparent;
base.OnPaintChart(args);
Series.Color = savedColor;
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintLine(args, Series!, warmupPeriod, showColdValues: ShowColdValues);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _sma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _sma.IsHot, ShowColdValues);
}
}
+3 -3
View File
@@ -23,8 +23,8 @@ public class SsfIndicatorTests
{
var indicator = new SsfIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, SsfIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -164,6 +164,6 @@ public class SsfIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, SsfIndicator.MinHistoryDepths);
}
}
+21 -29
View File
@@ -1,11 +1,14 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class SsfIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class SsfIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[IndicatorExtensions.DataSourceInput]
@@ -14,51 +17,40 @@ public class SsfIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Ssf? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Ssf? _ssf;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"SSF {Period}:{SourceName}";
public override string ShortName => $"SSF {Period}:{_sourceName}";
public SsfIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
Name = "SSF - Super Smooth Filter";
Description = "Ehlers Super Smooth Filter";
Series = new(name: $"SSF {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"SSF {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
protected override void OnInit()
{
ma = new Ssf(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_priceSelector = Source.GetPriceSelector();
_sourceName = Source.ToString();
_ssf = new Ssf(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
bool isNew = args.IsNewBar();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
double value = _ssf!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew).Value;
_series!.SetValue(value, _ssf.IsHot, ShowColdValues);
}
}
+6 -6
View File
@@ -20,13 +20,13 @@ public class SuperIndicatorTests
}
[Fact]
public void SuperIndicator_MinHistoryDepths_EqualsPeriod()
public void SuperIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new SuperIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, SuperIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
@@ -57,8 +57,8 @@ public class SuperIndicatorTests
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist (Up and Down)
Assert.Equal(2, indicator.LinesSeries.Count);
// After init, line series should exist (SuperTrend, Upper, Lower)
Assert.Equal(3, indicator.LinesSeries.Count);
}
[Fact]
@@ -119,6 +119,6 @@ public class SuperIndicatorTests
Assert.Equal(20, indicator.Period);
Assert.Equal(4.0, indicator.Multiplier);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, SuperIndicator.MinHistoryDepths);
}
}
+29 -32
View File
@@ -1,41 +1,45 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class SuperIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class SuperIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 10;
[InputParameter("Multiplier", sortIndex: 2, 0.1, 100, 0.1, 1)]
[InputParameter("Multiplier", sortIndex: 2, 0.1, 100.0, 0.1, 1)]
public double Multiplier { get; set; } = 3.0;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Super? _super;
protected LineSeries? UpSeries;
protected LineSeries? DownSeries;
private readonly LineSeries? _series;
private readonly LineSeries? _upperBand;
private readonly LineSeries? _lowerBand;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Super {Period}:{Multiplier}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/super/Super.Quantower.cs";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/master/lib/trends/super/Super.Quantower.cs";
public SuperIndicator()
{
OnBackGround = true;
SeparateWindow = false;
Name = "SuperTrend";
Description = "Trend-following indicator using ATR";
UpSeries = new(name: "SuperTrend Up", color: Color.Green, width: 2, style: LineStyle.Solid);
DownSeries = new(name: "SuperTrend Down", color: Color.Red, width: 2, style: LineStyle.Solid);
AddLineSeries(UpSeries);
AddLineSeries(DownSeries);
Description = "SuperTrend Indicator";
_series = new(name: "SuperTrend", color: Color.Orange, width: 2, style: LineStyle.Solid);
_upperBand = new(name: "Upper Band", color: Color.Red, width: 1, style: LineStyle.Dot);
_lowerBand = new(name: "Lower Band", color: Color.Green, width: 1, style: LineStyle.Dot);
AddLineSeries(_series);
AddLineSeries(_upperBand);
AddLineSeries(_lowerBand);
}
protected override void OnInit()
@@ -44,28 +48,21 @@ public class SuperIndicator : Indicator, IWatchlistIndicator
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
bool isNew = args.IsNewBar();
var bar = this.GetInputBar(args);
double value = _super!.Update(bar, isNew).Value;
_series!.SetValue(value, _super.IsHot, ShowColdValues);
_upperBand!.SetValue(_super.UpperBand.Value, _super.IsHot, ShowColdValues);
_lowerBand!.SetValue(_super.LowerBand.Value, _super.IsHot, ShowColdValues);
TBar bar = this.GetInputBar(args);
TValue result = _super!.Update(bar, isNew);
if (!_super.IsHot && !ShowColdValues)
// Color logic
if (_super.IsHot)
{
return;
}
if (_super.IsBullish)
{
UpSeries!.SetValue(result.Value);
DownSeries!.SetValue(double.NaN);
}
else
{
UpSeries!.SetValue(double.NaN);
DownSeries!.SetValue(result.Value);
_series!.SetMarker(0, _super.IsBullish ? Color.Green : Color.Red);
}
}
}
+3 -3
View File
@@ -25,8 +25,8 @@ public class T3IndicatorTests
var indicator = new T3Indicator { Period = 10 };
// MinHistoryDepths is Period * 6 for T3 due to 6 stages
Assert.Equal(60, indicator.MinHistoryDepths);
Assert.Equal(60, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, T3Indicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -168,6 +168,6 @@ public class T3IndicatorTests
indicator.VolumeFactor = 0.9;
Assert.Equal(20, indicator.Period);
Assert.Equal(0.9, indicator.VolumeFactor);
Assert.Equal(120, indicator.MinHistoryDepths); // 20 * 6
Assert.Equal(0, T3Indicator.MinHistoryDepths); // 20 * 6
}
}
+22 -27
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class T3Indicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class T3Indicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 10;
@@ -17,51 +19,44 @@ public class T3Indicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private T3? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private T3? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period * 6; // Approx warmup for 6 stages
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"T3({Period}, {VolumeFactor:F2}):{SourceName}";
public override string ShortName => $"T3({Period}, {VolumeFactor:F2}):{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/t3/T3.Quantower.cs";
public T3Indicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "T3 - Tillson T3 Moving Average";
Description = "Tillson T3 Moving Average";
Series = new(name: $"T3 {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"T3 {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new T3(Period, VolumeFactor);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_ma = new T3(Period, VolumeFactor);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+17 -22
View File
@@ -23,8 +23,8 @@ public class TemaIndicatorTests
{
var indicator = new TemaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, TemaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -65,14 +65,17 @@ public class TemaIndicatorTests
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(indicator.LinesSeries[0].Count > 0);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
@@ -99,9 +102,12 @@ public class TemaIndicatorTests
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
@@ -111,17 +117,6 @@ public class TemaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void TemaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new TemaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(TemaIndicator), method.DeclaringType);
}
[Fact]
public void TemaIndicator_MultipleUpdates_ProducesCorrectTemaSequence()
{
@@ -172,6 +167,6 @@ public class TemaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, TemaIndicator.MinHistoryDepths);
}
}
+23 -26
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class TemaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class TemaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 10;
@@ -14,52 +16,47 @@ public class TemaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Tema? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Tema? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"TEMA {Period}:{SourceName}";
public override string ShortName => $"TEMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/tema/Tema.Quantower.cs";
public TemaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "TEMA - Triple Exponential Moving Average";
Description = "Triple Exponential Moving Average";
Series = new(name: $"TEMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"TEMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Tema(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_ma = new Tema(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
return;
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+16 -21
View File
@@ -23,8 +23,8 @@ public class TrimaIndicatorTests
{
var indicator = new TrimaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, TrimaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -65,14 +65,17 @@ public class TrimaIndicatorTests
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(indicator.LinesSeries[0].Count > 0);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
@@ -99,9 +102,12 @@ public class TrimaIndicatorTests
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
@@ -111,17 +117,6 @@ public class TrimaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void TrimaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new TrimaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(TrimaIndicator), method.DeclaringType);
}
[Fact]
public void TrimaIndicator_MultipleUpdates_ProducesCorrectTrimaSequence()
{
@@ -176,7 +171,7 @@ public class TrimaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, TrimaIndicator.MinHistoryDepths);
}
[Fact]
+23 -26
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class TrimaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class TrimaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 10;
@@ -14,52 +16,47 @@ public class TrimaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Trima? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Trima? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"TRIMA {Period}:{SourceName}";
public override string ShortName => $"TRIMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/trima/Trima.Quantower.cs";
public TrimaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "TRIMA - Triangular Moving Average";
Description = "Triangular Moving Average";
Series = new(name: $"TRIMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"TRIMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Trima(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_ma = new Trima(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
return;
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+24 -27
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class UsfIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class UsfIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
public int Period { get; set; } = 20;
@@ -14,52 +16,47 @@ public class UsfIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Usf? ma;
protected LineSeries? Series;
protected string? SourceName;
private int _warmupBarIndex = -1;
private Usf? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"USF {Period}:{SourceName}";
public override string ShortName => $"USF {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/usf/Usf.Quantower.cs";
public UsfIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "USF - Ultimate Smoother Filter";
Description = "Ehlers Ultimate Smoother Filter";
Series = new(name: $"USF {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"USF {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Usf(Period);
SourceName = Source.ToString();
_warmupBarIndex = -1;
_ma = new Usf(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent);
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar)
return;
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+17 -12
View File
@@ -23,8 +23,8 @@ public class VidyaIndicatorTests
{
var indicator = new VidyaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, VidyaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -56,14 +56,17 @@ public class VidyaIndicatorTests
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(indicator.LinesSeries[0].Count > 0);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
@@ -94,10 +97,12 @@ public class VidyaIndicatorTests
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
// Process historical bar first
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
// Update with new tick (same bar data - simulates intrabar update)
@@ -164,6 +169,6 @@ public class VidyaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, VidyaIndicator.MinHistoryDepths);
}
}
+25 -21
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class VidyaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class VidyaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
@@ -14,45 +16,47 @@ public class VidyaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Vidya? ma;
protected LineSeries? Series;
protected string? SourceName;
private Vidya? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"VIDYA {Period}:{SourceName}";
public override string ShortName => $"VIDYA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/vidya/Vidya.Quantower.cs";
public VidyaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "VIDYA - Variable Index Dynamic Average";
Description = "Variable Index Dynamic Average (Chande)";
Series = new(name: $"VIDYA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"VIDYA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Vidya(Period);
SourceName = Source.ToString();
_ma = new Vidya(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
return;
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, Period, showColdValues: ShowColdValues, tension: 0.2);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}
+17 -22
View File
@@ -23,8 +23,8 @@ public class WmaIndicatorTests
{
var indicator = new WmaIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
Assert.Equal(0, WmaIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
@@ -65,14 +65,17 @@ public class WmaIndicatorTests
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(indicator.LinesSeries[0].Count > 0);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
@@ -99,9 +102,12 @@ public class WmaIndicatorTests
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
for (int i = 0; i < 50; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double firstValue = indicator.LinesSeries[0].GetValue(0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
@@ -111,17 +117,6 @@ public class WmaIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
[Fact]
public void WmaIndicator_OnPaintChart_DoesNotThrow()
{
var indicator = new WmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(WmaIndicator), method.DeclaringType);
}
[Fact]
public void WmaIndicator_MultipleUpdates_ProducesCorrectWmaSequence()
{
@@ -177,7 +172,7 @@ public class WmaIndicatorTests
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
Assert.Equal(20, indicator.MinHistoryDepths);
Assert.Equal(0, WmaIndicator.MinHistoryDepths);
}
[Fact]
+24 -25
View File
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class WmaIndicator : Indicator, IWatchlistIndicator
[SkipLocalsInit]
public sealed class WmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 10;
@@ -14,50 +16,47 @@ public class WmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Wma? ma;
private int _warmupBarIndex = -1;
protected LineSeries? Series;
protected string? SourceName;
private Wma? _ma;
private readonly LineSeries? _series;
private string? _sourceName;
private Func<IHistoryItem, double>? _priceSelector;
public int MinHistoryDepths => Period;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"WMA {Period}:{SourceName}";
public override string ShortName => $"WMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/wma/Wma.Quantower.cs";
public WmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "WMA - Weighted Moving Average";
Description = "Weighted Moving Average with linear weighting";
Series = new(name: $"WMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
_series = new(name: $"WMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Wma(Period);
_warmupBarIndex = -1;
SourceName = Source.ToString();
_ma = new Wma(Period);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TValue input = this.GetInputValue(args, Source);
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TValue result = ma!.Update(input, isNew);
if (_warmupBarIndex < 0 && ma!.IsHot)
_warmupBarIndex = Count;
Series!.SetValue(result.Value);
Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
}
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
return;
public override void OnPaintChart(PaintChartEventArgs args)
{
base.OnPaintChart(args);
this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2);
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), args.IsNewBar());
_series!.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series!.SetMarker(0, Color.Transparent);
}
}