Files
QuanTAlib/perf/Benchmark.cs
T

432 lines
16 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 double[] _openValues = 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();
_openValues = bars.Open.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);
// ==================== CORRELATION ====================
[BenchmarkCategory("CORRELATION")]
[Benchmark(Description = "QuanTAlib Correlation (Span)")]
public void QuanTAlib_Correlation_Span() => Correlation.Batch(_closeValues.AsSpan(), _openValues.AsSpan(), _quantalibOutput.AsSpan(), Period);
[BenchmarkCategory("CORRELATION")]
[Benchmark(Description = "QuanTAlib Correlation (Streaming)")]
public void QuanTAlib_Correlation_Streaming()
{
var corr = new Correlation(Period);
for (int i = 0; i < _closeValues.Length; i++)
{
_quantalibOutput[i] = corr.Update(_closeValues[i], _openValues[i]).Value;
}
}
[BenchmarkCategory("CORRELATION")]
[Benchmark(Description = "TALib Correlation")]
public Core.RetCode TALib_Correlation() => TALib.Functions.Correl<double>(_closeValues, _openValues, 0..^0, _talibOutput, out _, Period);
[BenchmarkCategory("CORRELATION")]
[Benchmark(Description = "Skender Correlation")]
public object Skender_Correlation() => _quotes.GetCorrelation(_quotes, 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;
}
}
}