mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-28 09:47:43 +00:00
653aafacd8
- Implemented Prime method in Vel, Ao, Apo, Frama, Adl, Adosc, Aobv, Cmf, Efi, Eom, Iii, Kvo, Mfi, Nvi, Obv, Pvd, Pvi, Pvo, Pvr, Pvt, Tvi, Twap, Va, Vf, Vo, Vroc, Vwad, Vwap, and Vwma classes. - The Prime method resets the indicator state and processes the provided historical bar data to initialize the indicator. - Added warmup period property to Adl and Wad classes to define the minimum number of data points required for validity. - Updated benchmark tests to use Batch methods for performance evaluation.
406 lines
15 KiB
C#
406 lines
15 KiB
C#
using BenchmarkDotNet.Attributes;
|
|
using BenchmarkDotNet.Columns;
|
|
using BenchmarkDotNet.Configs;
|
|
using BenchmarkDotNet.Environments;
|
|
using BenchmarkDotNet.Jobs;
|
|
using BenchmarkDotNet.Running;
|
|
using QuanTAlib;
|
|
using QuanTAlib.Benchmarks;
|
|
using Skender.Stock.Indicators;
|
|
using TALib;
|
|
using Tulip;
|
|
using OoplesFinance.StockIndicators;
|
|
using OoplesFinance.StockIndicators.Models;
|
|
using OoplesFinance.StockIndicators.Enums;
|
|
|
|
namespace QuanTAlib.Benchmarks;
|
|
|
|
public static class Program
|
|
{
|
|
public static void Main(string[] args)
|
|
{
|
|
// Run: dotnet run -c Release -- --filter *Sma* *Ema* *Wma*
|
|
var config = ManualConfig.Create(DefaultConfig.Instance)
|
|
.AddJob(Job.ShortRun
|
|
.WithRuntime(CoreRuntime.Core10_0)
|
|
.WithId("NET10-JIT"))
|
|
.AddColumn(StatisticColumn.Mean)
|
|
.AddColumn(StatisticColumn.StdDev)
|
|
.HideColumns(Column.Job, Column.Error, Column.RatioSD);
|
|
|
|
if (args.Length == 0)
|
|
{
|
|
BenchmarkRunner.Run<IndicatorBenchmarks>(config);
|
|
}
|
|
else
|
|
{
|
|
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
|
}
|
|
}
|
|
}
|
|
|
|
[MemoryDiagnoser]
|
|
[MarkdownExporter, HtmlExporter]
|
|
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
|
|
public class IndicatorBenchmarks
|
|
{
|
|
private const int BarCount = 500_000;
|
|
private const int Period = 220;
|
|
|
|
private double[] _closeValues = null!;
|
|
private TSeries _closeTseries = null!;
|
|
private List<Quote> _quotes = null!;
|
|
private List<TickerData> _ooplesData = null!;
|
|
|
|
// Pre-allocated outputs for TA-Lib
|
|
private double[] _talibOutput = null!;
|
|
|
|
// Pre-allocated outputs for Tulip
|
|
private double[][] _tulipSmaInputs = null!;
|
|
private double[] _tulipSmaOptions = null!;
|
|
private double[][] _tulipSmaOutputs = null!;
|
|
private double[][] _tulipEmaInputs = null!;
|
|
private double[] _tulipEmaOptions = null!;
|
|
private double[][] _tulipEmaOutputs = null!;
|
|
private double[][] _tulipWmaInputs = null!;
|
|
private double[] _tulipWmaOptions = null!;
|
|
private double[][] _tulipWmaOutputs = null!;
|
|
private double[][] _tulipHmaInputs = null!;
|
|
private double[] _tulipHmaOptions = null!;
|
|
private double[][] _tulipHmaOutputs = null!;
|
|
|
|
// Pre-allocated outputs for ADOSC
|
|
private double[] _highValues = null!;
|
|
private double[] _lowValues = null!;
|
|
private double[] _volumeValues = null!;
|
|
private TBarSeries _bars = null!;
|
|
private double[][] _tulipAdoscInputs = null!;
|
|
private double[] _tulipAdoscOptions = null!;
|
|
private double[][] _tulipAdoscOutputs = null!;
|
|
|
|
// Pre-allocated outputs for QuanTAlib Span API
|
|
private double[] _quantalibOutput = null!;
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
// Generate data using GBM
|
|
var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2, seed: 42);
|
|
var bars = gbm.Fetch(BarCount, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
|
|
_bars = bars;
|
|
_closeValues = bars.Close.Values.ToArray();
|
|
_highValues = bars.High.Values.ToArray();
|
|
_lowValues = bars.Low.Values.ToArray();
|
|
_volumeValues = bars.Volume.Values.ToArray();
|
|
_closeTseries = bars.Close;
|
|
|
|
// Create Skender Quote format
|
|
_quotes = new List<Quote>(BarCount);
|
|
for (int i = 0; i < BarCount; i++)
|
|
{
|
|
_quotes.Add(new Quote
|
|
{
|
|
Date = new DateTime(_closeTseries.Times[i], DateTimeKind.Utc),
|
|
Open = (decimal)bars.Open.Values[i],
|
|
High = (decimal)bars.High.Values[i],
|
|
Low = (decimal)bars.Low.Values[i],
|
|
Close = (decimal)_closeValues[i],
|
|
Volume = (decimal)bars.Volume.Values[i]
|
|
});
|
|
}
|
|
|
|
// Create Ooples TickerData format
|
|
_ooplesData = new List<TickerData>(BarCount);
|
|
for (int i = 0; i < BarCount; i++)
|
|
{
|
|
_ooplesData.Add(new TickerData
|
|
{
|
|
Date = new DateTime(_closeTseries.Times[i], DateTimeKind.Utc),
|
|
Open = bars.Open.Values[i],
|
|
High = bars.High.Values[i],
|
|
Low = bars.Low.Values[i],
|
|
Close = _closeValues[i],
|
|
Volume = bars.Volume.Values[i]
|
|
});
|
|
}
|
|
|
|
// Pre-allocate TA-Lib output
|
|
_talibOutput = new double[BarCount];
|
|
|
|
// Pre-allocate Tulip arrays
|
|
int smaLookback = Period - 1;
|
|
_tulipSmaInputs = new[] { _closeValues };
|
|
_tulipSmaOptions = new double[] { Period };
|
|
_tulipSmaOutputs = new[] { new double[BarCount - smaLookback] };
|
|
|
|
_tulipEmaInputs = new[] { _closeValues };
|
|
_tulipEmaOptions = new double[] { Period };
|
|
_tulipEmaOutputs = new[] { new double[BarCount] };
|
|
|
|
_tulipWmaInputs = new[] { _closeValues };
|
|
_tulipWmaOptions = new double[] { Period };
|
|
_tulipWmaOutputs = new[] { new double[BarCount - smaLookback] };
|
|
|
|
int hmaLookback = Period + (int)Math.Sqrt(Period) - 2;
|
|
_tulipHmaInputs = new[] { _closeValues };
|
|
_tulipHmaOptions = new double[] { Period };
|
|
_tulipHmaOutputs = new[] { new double[BarCount - hmaLookback] };
|
|
|
|
// Pre-allocate Tulip ADOSC
|
|
_tulipAdoscInputs = new[] { _highValues, _lowValues, _closeValues, _volumeValues };
|
|
_tulipAdoscOptions = new double[] { 3, 10 }; // Fast=3, Slow=10
|
|
_tulipAdoscOutputs = new[] { new double[BarCount - 1] }; // Tulip ADOSC starts at index 1?
|
|
|
|
// Pre-allocate QuanTAlib output
|
|
_quantalibOutput = new double[BarCount];
|
|
}
|
|
|
|
// ==================== ADOSC ====================
|
|
[BenchmarkCategory("ADOSC")]
|
|
[Benchmark(Description = "QuanTAlib ADOSC (Span)")]
|
|
public void QuanTAlib_Adosc_Span() => Adosc.Batch(_highValues.AsSpan(), _lowValues.AsSpan(), _closeValues.AsSpan(), _volumeValues.AsSpan(), _quantalibOutput.AsSpan(), 3, 10);
|
|
|
|
[BenchmarkCategory("ADOSC")]
|
|
[Benchmark(Description = "QuanTAlib ADOSC (Batch)")]
|
|
public TSeries QuanTAlib_Adosc_TSeries() => Adosc.Batch(_bars, 3, 10);
|
|
|
|
[BenchmarkCategory("ADOSC")]
|
|
[Benchmark(Description = "QuanTAlib ADOSC (Streaming)")]
|
|
public void QuanTAlib_Adosc_Streaming()
|
|
{
|
|
var adosc = new Adosc(3, 10);
|
|
for (int i = 0; i < _bars.Count; i++)
|
|
{
|
|
_quantalibOutput[i] = adosc.Update(_bars[i]).Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("ADOSC")]
|
|
[Benchmark(Description = "Tulip ADOSC")]
|
|
public void Tulip_Adosc() => Tulip.Indicators.adosc.Run(_tulipAdoscInputs, _tulipAdoscOptions, _tulipAdoscOutputs);
|
|
|
|
[BenchmarkCategory("ADOSC")]
|
|
[Benchmark(Description = "TALib ADOSC")]
|
|
public Core.RetCode TALib_Adosc() => TALib.Functions.AdOsc(_highValues, _lowValues, _closeValues, _volumeValues, 0..^0, _quantalibOutput, out _, 3, 10);
|
|
|
|
[BenchmarkCategory("ADOSC")]
|
|
[Benchmark(Description = "Skender ADOSC")]
|
|
public object Skender_Adosc() => _quotes.GetChaikinOsc(3, 10);
|
|
|
|
[BenchmarkCategory("ADOSC")]
|
|
[Benchmark(Description = "Ooples ADOSC")]
|
|
public object Ooples_Adosc() => new StockData(_ooplesData).CalculateChaikinOscillator(MovingAvgType.ExponentialMovingAverage, 3, 10);
|
|
|
|
// ==================== SMA ====================
|
|
[BenchmarkCategory("SMA")]
|
|
[Benchmark(Description = "QuanTAlib SMA (Span)")]
|
|
public void QuanTAlib_Sma_Span() => Sma.Batch(_closeValues.AsSpan(), _quantalibOutput.AsSpan(), Period);
|
|
|
|
[BenchmarkCategory("SMA")]
|
|
[Benchmark(Description = "QuanTAlib SMA (Batch)")]
|
|
public TSeries QuanTAlib_Sma_TSeries() => Sma.Calculate(_closeTseries, Period).Results;
|
|
|
|
[BenchmarkCategory("SMA")]
|
|
[Benchmark(Description = "QuanTAlib SMA (Streaming)")]
|
|
public void QuanTAlib_Sma_Streaming()
|
|
{
|
|
var sma = new Sma(Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
_quantalibOutput[i] = sma.Update(new TValue(_closeTseries.Times[i], _closeValues[i])).Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("SMA")]
|
|
[Benchmark(Description = "QuanTAlib SMA (Eventing)")]
|
|
public void QuanTAlib_Sma_Eventing()
|
|
{
|
|
var source = new TSeries();
|
|
var sma = new Sma(source, Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
source.Add(new TValue(_closeTseries.Times[i], _closeValues[i]));
|
|
_quantalibOutput[i] = sma.Last.Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("SMA")]
|
|
[Benchmark(Description = "Tulip SMA")]
|
|
public void Tulip_Sma() => Tulip.Indicators.sma.Run(_tulipSmaInputs, _tulipSmaOptions, _tulipSmaOutputs);
|
|
|
|
[BenchmarkCategory("SMA")]
|
|
[Benchmark(Description = "TALib SMA")]
|
|
public Core.RetCode TALib_Sma() => TALib.Functions.Sma<double>(_closeValues, 0..^0, _talibOutput, out _, Period);
|
|
|
|
[BenchmarkCategory("SMA")]
|
|
[Benchmark(Description = "Skender SMA")]
|
|
public object Skender_Sma() => _quotes.GetSma(Period);
|
|
|
|
[BenchmarkCategory("SMA")]
|
|
[Benchmark(Description = "Ooples SMA")]
|
|
public object Ooples_Sma() => new StockData(_ooplesData).CalculateSimpleMovingAverage(Period);
|
|
|
|
// ==================== EMA ====================
|
|
[BenchmarkCategory("EMA")]
|
|
[Benchmark(Description = "QuanTAlib EMA (Span)")]
|
|
public void QuanTAlib_Ema_Span() => Ema.Batch(_closeValues.AsSpan(), _quantalibOutput.AsSpan(), Period);
|
|
|
|
[BenchmarkCategory("EMA")]
|
|
[Benchmark(Description = "QuanTAlib EMA (Batch)")]
|
|
public TSeries QuanTAlib_Ema_TSeries() => Ema.Calculate(_closeTseries, Period).Results;
|
|
|
|
[BenchmarkCategory("EMA")]
|
|
[Benchmark(Description = "QuanTAlib EMA (Streaming)")]
|
|
public void QuanTAlib_Ema_Streaming()
|
|
{
|
|
var ema = new Ema(Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
_quantalibOutput[i] = ema.Update(new TValue(_closeTseries.Times[i], _closeValues[i])).Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("EMA")]
|
|
[Benchmark(Description = "QuanTAlib EMA (Eventing)")]
|
|
public void QuanTAlib_Ema_Eventing()
|
|
{
|
|
var source = new TSeries();
|
|
var ema = new Ema(source, Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
source.Add(new TValue(_closeTseries.Times[i], _closeValues[i]));
|
|
_quantalibOutput[i] = ema.Last.Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("EMA")]
|
|
[Benchmark(Description = "Tulip EMA")]
|
|
public void Tulip_Ema() => Tulip.Indicators.ema.Run(_tulipEmaInputs, _tulipEmaOptions, _tulipEmaOutputs);
|
|
|
|
[BenchmarkCategory("EMA")]
|
|
[Benchmark(Description = "TALib EMA")]
|
|
public Core.RetCode TALib_Ema() => TALib.Functions.Ema<double>(_closeValues, 0..^0, _talibOutput, out _, Period);
|
|
|
|
[BenchmarkCategory("EMA")]
|
|
[Benchmark(Description = "Skender EMA")]
|
|
public object Skender_Ema() => _quotes.GetEma(Period);
|
|
|
|
[BenchmarkCategory("EMA")]
|
|
[Benchmark(Description = "Ooples EMA")]
|
|
public object Ooples_Ema() => new StockData(_ooplesData).CalculateExponentialMovingAverage(Period);
|
|
|
|
// ==================== WMA ====================
|
|
[BenchmarkCategory("WMA")]
|
|
[Benchmark(Description = "QuanTAlib WMA (Span)")]
|
|
public void QuanTAlib_Wma_Span() => Wma.Batch(_closeValues.AsSpan(), _quantalibOutput.AsSpan(), Period);
|
|
|
|
[BenchmarkCategory("WMA")]
|
|
[Benchmark(Description = "QuanTAlib WMA (Batch)")]
|
|
public TSeries QuanTAlib_Wma_TSeries() => Wma.Batch(_closeTseries, Period);
|
|
|
|
[BenchmarkCategory("WMA")]
|
|
[Benchmark(Description = "QuanTAlib WMA (Streaming)")]
|
|
public void QuanTAlib_Wma_Streaming()
|
|
{
|
|
var wma = new Wma(Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
_quantalibOutput[i] = wma.Update(new TValue(_closeTseries.Times[i], _closeValues[i])).Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("WMA")]
|
|
[Benchmark(Description = "QuanTAlib WMA (Eventing)")]
|
|
public void QuanTAlib_Wma_Eventing()
|
|
{
|
|
var source = new TSeries();
|
|
var wma = new Wma(source, Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
source.Add(new TValue(_closeTseries.Times[i], _closeValues[i]));
|
|
_quantalibOutput[i] = wma.Last.Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("WMA")]
|
|
[Benchmark(Description = "Tulip WMA")]
|
|
public void Tulip_Wma() => Tulip.Indicators.wma.Run(_tulipWmaInputs, _tulipWmaOptions, _tulipWmaOutputs);
|
|
|
|
[BenchmarkCategory("WMA")]
|
|
[Benchmark(Description = "TALib WMA")]
|
|
public Core.RetCode TALib_Wma() => TALib.Functions.Wma<double>(_closeValues, 0..^0, _talibOutput, out _, Period);
|
|
|
|
[BenchmarkCategory("WMA")]
|
|
[Benchmark(Description = "Skender WMA")]
|
|
public object Skender_Wma() => _quotes.GetWma(Period);
|
|
|
|
[BenchmarkCategory("WMA")]
|
|
[Benchmark(Description = "Ooples WMA")]
|
|
public object Ooples_Wma() => new StockData(_ooplesData).CalculateWeightedMovingAverage(Period);
|
|
|
|
// ==================== HMA ====================
|
|
[BenchmarkCategory("HMA")]
|
|
[Benchmark(Description = "QuanTAlib HMA (Span)")]
|
|
public void QuanTAlib_Hma_Span() => Hma.Batch(_closeValues.AsSpan(), _quantalibOutput.AsSpan(), Period);
|
|
|
|
[BenchmarkCategory("HMA")]
|
|
[Benchmark(Description = "QuanTAlib HMA (Batch)")]
|
|
public TSeries QuanTAlib_Hma_TSeries() => Hma.Batch(_closeTseries, Period);
|
|
|
|
[BenchmarkCategory("HMA")]
|
|
[Benchmark(Description = "QuanTAlib HMA (Streaming)")]
|
|
public void QuanTAlib_Hma_Streaming()
|
|
{
|
|
var hma = new Hma(Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
_quantalibOutput[i] = hma.Update(new TValue(_closeTseries.Times[i], _closeValues[i])).Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("HMA")]
|
|
[Benchmark(Description = "QuanTAlib HMA (Eventing)")]
|
|
public void QuanTAlib_Hma_Eventing()
|
|
{
|
|
var source = new TSeries();
|
|
var hma = new Hma(source, Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
source.Add(new TValue(_closeTseries.Times[i], _closeValues[i]));
|
|
_quantalibOutput[i] = hma.Last.Value;
|
|
}
|
|
}
|
|
|
|
[BenchmarkCategory("HMA")]
|
|
[Benchmark(Description = "Tulip HMA")]
|
|
public void Tulip_Hma() => Tulip.Indicators.hma.Run(_tulipHmaInputs, _tulipHmaOptions, _tulipHmaOutputs);
|
|
|
|
[BenchmarkCategory("HMA")]
|
|
[Benchmark(Description = "Skender HMA")]
|
|
public object Skender_Hma() => _quotes.GetHma(Period);
|
|
|
|
[BenchmarkCategory("HMA")]
|
|
[Benchmark(Description = "Ooples HMA")]
|
|
public object Ooples_Hma() => new StockData(_ooplesData).CalculateHullMovingAverage(MovingAvgType.WeightedMovingAverage, Period);
|
|
|
|
// ==================== SKEW ====================
|
|
[BenchmarkCategory("SKEW")]
|
|
[Benchmark(Description = "QuanTAlib Skew (Span)")]
|
|
public void QuanTAlib_Skew_Span() => Skew.Batch(_closeValues.AsSpan(), _quantalibOutput.AsSpan(), Period);
|
|
|
|
[BenchmarkCategory("SKEW")]
|
|
[Benchmark(Description = "QuanTAlib Skew (Batch)")]
|
|
public TSeries QuanTAlib_Skew_TSeries() => Skew.Batch(_closeTseries, Period);
|
|
|
|
[BenchmarkCategory("SKEW")]
|
|
[Benchmark(Description = "QuanTAlib Skew (Streaming)")]
|
|
public void QuanTAlib_Skew_Streaming()
|
|
{
|
|
var skew = new Skew(Period);
|
|
for (int i = 0; i < _closeValues.Length; i++)
|
|
{
|
|
_quantalibOutput[i] = skew.Update(new TValue(_closeTseries.Times[i], _closeValues[i])).Value;
|
|
}
|
|
}
|
|
} |