New version merge

This commit is contained in:
Miha Kralj
2024-09-22 17:31:24 -07:00
parent 1b719fa94e
commit d475bcd19a
405 changed files with 56573 additions and 12440 deletions
+121
View File
@@ -0,0 +1,121 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// ALMA: Arnaud Legoux Moving Average
/// Uses the curve of the Normal (Gauss) distribution. This moving average reduces lag
/// of the data in conjunction with smoothing to reduce noise.
/// </summary>
/// <remarks>
/// Smoothness: ★★★★☆ (4/5)
/// Sensitivity: ★★★★☆ (4/5)
/// Overshooting: ★★★★☆ (4/5)
/// Lag: ★★★★★ (5/5)
///
/// Validation:
/// Skender.Stock.Indicators
/// </remarks>
public class Alma : AbstractBase
{
private readonly int _period;
private readonly double _offset;
private readonly double _sigma;
private CircularBuffer? _buffer;
private CircularBuffer? _weight;
private double _norm;
/// <param name="period">The number of data points used in the ALMA calculation.</param>
/// <param name="offset">Controls the smoothness and high-frequency filtering. Default is 0.85.</param>
/// <param name="sigma">Controls the shape of the Gaussian distribution. Default is 6.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Alma(int period, double offset = 0.85, double sigma = 6) : base()
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_offset = offset;
_sigma = sigma;
WarmupPeriod = period;
Name = "Alma";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the ALMA calculation.</param>
/// <param name="offset">Controls the smoothness and high-frequency filtering. Default is 0.85.</param>
/// <param name="sigma">Controls the shape of the Gaussian distribution. Default is 6.</param>
public Alma(object source, int period, double offset = 0.85, double sigma = 6) : this(period, offset, sigma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer = new CircularBuffer(_period);
_weight = new CircularBuffer(_period);
_norm = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_index++;
}
}
/// <summary>
/// Performs the core ALMA calculation. Called from parent abstractBase Calc()
/// </summary>
/// <returns>The calculated ALMA value.</returns>
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer!.Add(Input.Value, Input.IsNew);
if (_weight!.Count < _buffer.Count)
{
for (var i = 0; i < _buffer.Count - _weight.Count; i++)
{
_weight.Add(0.0);
}
}
if (_buffer.Count <= _period)
{
UpdateWeights();
}
double weightedSum = 0;
for (var i = 0; i < _buffer.Count; i++)
{
weightedSum += _weight[i] * _buffer[i];
}
double result = weightedSum / _norm;
IsHot = _index >= WarmupPeriod;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateWeights()
{
int len = _buffer!.Count;
_norm = 0;
double m = _offset * (len - 1);
double s = len / _sigma;
for (int i = 0; i < len; i++)
{
double wt = Math.Exp(-((i - m) * (i - m)) / (2 * s * s));
_weight![i] = wt;
_norm += wt;
}
}
}
+103
View File
@@ -0,0 +1,103 @@
namespace QuanTAlib;
public class Convolution : AbstractBase
{
private readonly double[] _kernel;
private readonly int _kernelSize;
private CircularBuffer _buffer;
private double[] _normalizedKernel;
public Convolution(double[] kernel)
{
if (kernel == null || kernel.Length == 0)
{
throw new ArgumentException("Kernel must not be null or empty.", nameof(kernel));
}
_kernel = kernel;
_kernelSize = kernel.Length;
_buffer = new CircularBuffer(_kernelSize);
_normalizedKernel = new double[_kernelSize];
Init();
}
public Convolution(object source, double[] kernel) : this(kernel)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
private new void Init()
{
base.Init();
_buffer.Clear();
Array.Copy(_kernel, _normalizedKernel, _kernelSize);
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double GetLastValid()
{
return _lastValidValue;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
// Normalize kernel on each calculation until buffer is full
if (_index <= _kernelSize)
{
NormalizeKernel();
}
double result = ConvolveBuffer();
IsHot = _index >= _kernelSize;
return result;
}
private void NormalizeKernel()
{
int activeLength = Math.Min(_index, _kernelSize);
double sum = 0;
// Calculate the sum of the active kernel elements
for (int i = 0; i < activeLength; i++)
{
sum += _kernel[i];
}
// Normalize the kernel or set equal weights if the sum is zero
double normalizationFactor = (sum != 0) ? sum : activeLength;
for (int i = 0; i < activeLength; i++)
{
_normalizedKernel[i] = _kernel[i] / normalizationFactor;
}
// Set the rest of the normalized kernel to zero
Array.Clear(_normalizedKernel, activeLength, _kernelSize - activeLength);
}
private double ConvolveBuffer()
{
double sum = 0;
var bufferSpan = _buffer.GetSpan();
int activeLength = Math.Min(_index, _kernelSize);
for (int i = 0; i < activeLength; i++)
{
sum += bufferSpan[activeLength - 1 - i] * _normalizedKernel[i];
}
return sum;
}
}
+108
View File
@@ -0,0 +1,108 @@
namespace QuanTAlib;
/// <summary>
/// DEMA: Double Exponential Moving Average
/// DEMA reduces the lag of a traditional EMA by applying a second EMA over EMA.
/// It responds more quickly to price changes than a standard EMA while maintaining
/// smoothness, at the cost of overshooting the signal line.
/// </summary>
/// <remarks>
/// Smoothness: ★★★☆☆ (3/5)
/// Sensitivity: ★★★★☆ (4/5)
/// Overshooting: ★★★☆☆ (3/5)
/// Lag: ★★★★☆ (4/5)
///
/// Sources:
/// https://www.investopedia.com/terms/d/double-exponential-moving-average.asp
/// https://www.tradingview.com/support/solutions/43000502589-double-exponential-moving-average-dema/
///
/// Validation:
/// Skender.Stock.Indicators
/// </remarks>
public class Dema : AbstractBase
{
// inherited _index
// inherited _value
private readonly int _period;
private double _lastEma1, _p_lastEma1;
private double _lastEma2, _p_lastEma2;
private double _k, _e, _p_e;
public Dema(int period) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_period = period;
Name = "Dema";
double percentile = 0.85; //targeting 85th percentile of correctness of converging EMA
WarmupPeriod = (int)Math.Ceiling(-period * Math.Log(1 - percentile));
Init();
}
public Dema(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
//inhereted public void Sub(object source, in ValueEventArgs args)
public override void Init()
{
base.Init();
_k = 2.0 / (_period + 1);
_e = 1.0;
_lastEma1 = 0;
_lastEma2 = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastEma1 = _lastEma1;
_p_lastEma2 = _lastEma2;
_p_e = _e;
_index++;
}
else
{
_lastEma1 = _p_lastEma1;
_lastEma2 = _p_lastEma2;
_e = _p_e;
}
}
/// <summary>
/// Core DEMA calculation
/// </summary>
protected override double Calculation()
{
ManageState(Input.IsNew);
double result, _ema1, _ema2;
// dynamic k when within period; (index is zero-based, therefore +2)
//double _dk = (_index + 1 >= _period) ? _k : 2.0 / (_index + 2);
// compensator for early ema values
_e = (_e > 1e-10) ? (1 - _k) * _e : 0;
double _invE = (_e > 1e-10) ? 1 / (1 - _e) : 1;
// Calculate EMA1
_ema1 = _k * (Input.Value - _lastEma1) + _lastEma1;
// Calculate EMA2 using compensatedEma1
_ema2 = _k * (_ema1 * _invE - _lastEma2) + _lastEma2;
// Calculate DEMA
double _dema = 2 * _ema1 * _invE - (_ema2 * _invE);
result = _dema;
_lastEma1 = _ema1;
_lastEma2 = _ema2;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+144
View File
@@ -0,0 +1,144 @@
namespace QuanTAlib;
/// <summary>
/// DSMA: Deviation Scaled Moving Average
/// Adaptive moving average that adjusts its smoothing factor based on the volatility of the input data.
/// It aims to be more responsive during trending periods and more stable during ranging periods.
/// </summary>
/// <remarks>
/// Smoothness: ★★★★☆ (4/5)
/// Sensitivity: ★★★★☆ (4/5)
/// Overshooting: ★★★★☆ (4/5)
/// Lag: ★★★★☆ (4/5)
///
/// The DSMA uses a SuperSmoother filter to reduce noise and a dynamic alpha calculation based on the
/// scaled deviation of the input data. This allows it to adapt to changing market conditions.
///
/// The algorithm involves these main steps:
/// 1. Apply a SuperSmoother filter to the zero-mean input data.
/// 2. Calculate the Root Mean Square (RMS) of the filtered data.
/// 3. Scale the filtered data by the RMS to get a measure in terms of standard deviations.
/// 4. Use the scaled deviation to calculate an adaptive alpha for the moving average.
///
/// Source:
/// https://www.mesasoftware.com/papers/DEVIATION%20SCALED%20MOVING%20AVERAGE.pdf
/// </remarks>
public class Dsma : AbstractBase
{
private readonly int _period;
private readonly CircularBuffer _buffer;
private readonly double _a1, _b1, _c1, _c2, _c3;
private double _lastDsma, _p_lastDsma;
private double _filt, _filt1, _filt2, _zeros, _zeros1;
private double _p_filt, _p_filt1, _p_filt2, _p_zeros, _p_zeros1;
private bool _isInit, _p_isInit;
/// <summary>
/// Initializes a new instance of the <see cref="Dsma"/> class.
/// </summary>
/// <param name="period">The number of data points used in the DSMA calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
public Dsma(int period) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_period = period;
_buffer = new CircularBuffer(period);
// SuperSmoother filter coefficients
_a1 = Math.Exp(-1.414 * Math.PI / (0.5 * period));
_b1 = 2 * _a1 * Math.Cos(1.414 * Math.PI / (0.5 * period));
_c2 = _b1;
_c3 = -_a1 * _a1;
_c1 = 1 - _c2 - _c3;
Name = "Dsma";
WarmupPeriod = period * 2; // A conservative estimate
Init();
}
public Dsma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_lastDsma = 0;
_filt = _filt1 = _filt2 = 0;
_zeros = _zeros1 = 0;
_isInit = false;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastDsma = _lastDsma;
_p_isInit = _isInit;
_p_zeros = _zeros;
_p_zeros1 = _zeros1;
_p_filt = _filt;
_p_filt1 = _filt1;
_p_filt2 = _filt2;
_index++;
}
else
{
_lastDsma = _p_lastDsma;
_isInit = _p_isInit;
_zeros = _p_zeros;
_zeros1 = _p_zeros1;
_filt = _p_filt;
_filt1 = _p_filt1;
_filt2 = _p_filt2;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
if (!_isInit)
{
_lastDsma = Input.Value;
_isInit = true;
return _lastDsma;
}
// Produce nominal zero mean
_zeros = Input.Value - _lastDsma;
// SuperSmoother Filter
_filt = _c1 * (_zeros + _zeros1) / 2 + _c2 * _filt1 + _c3 * _filt2;
// Update buffer for RMS calculation
_buffer.Add(_filt * _filt, Input.IsNew);
// Compute RMS (Root Mean Square)
double rms = Math.Sqrt(_buffer.Sum() / _period);
// Rescale Filt in terms of Standard Deviations
double scaledFilt = rms != 0 ? _filt / rms : 0;
// Calculate adaptive alpha
double alpha = Math.Abs(scaledFilt) * 5 / _period;
// DSMA calculation
double dsma = alpha * Input.Value + (1 - alpha) * _lastDsma;
// Update state variables
_zeros1 = _zeros;
_filt2 = _filt1;
_filt1 = _filt;
_lastDsma = dsma;
IsHot = _index >= WarmupPeriod;
return dsma;
}
}
+86
View File
@@ -0,0 +1,86 @@
namespace QuanTAlib;
/// <summary>
/// DWMA: Double Weighted Moving Average
/// DWMA is a technical indicator that applies a Weighted Moving Average (WMA) twice to the input data.
/// The weights are decreasing over the period with p^2 decay, and the most recent data has the heaviest weight.
/// </summary>
/// <remarks>
/// Smoothness: ★★★★★ (5/5)
/// Sensitivity: ★★★☆☆ (3/5)
/// Overshooting: ★★★★☆ (4/5)
/// Lag: ★★☆☆☆ (2/5)
///
/// The DWMA is calculated by applying two WMAs in sequence:
/// 1. An inner WMA is applied to the input data.
/// 2. An outer WMA is then applied to the result of the inner WMA.
///
/// Key characteristics:
/// - The weight distribution follows a p^2 decay, where p is the position of the data point.
/// - More recent data points receive higher weights, emphasizing recent price movements.
/// - The double application of WMA results in a smoother indicator compared to a single WMA.
///
/// The formula for DWMA can be expressed as:
/// DWMA = WMA(WMA(price, period), period)
///
/// Where WMA is the Weighted Moving Average function and 'period' is the number of data points used in each WMA calculation.
/// </remarks>
public class Dwma : AbstractBase
{
private readonly int _period;
private readonly Wma _innerWma;
private readonly Wma _outerWma;
public Dwma(int period)
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_innerWma = new Wma(period);
_outerWma = new Wma(period);
Name = "Wma";
WarmupPeriod = 2 * _period - 1;
Init();
}
public Dwma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_innerWma.Init();
_outerWma.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Calculate inner WMA
TValue innerResult = _innerWma.Calc(Input);
// Calculate outer WMA using the result of inner WMA
TValue outerResult = _outerWma.Calc(innerResult);
double result = outerResult.Value;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+130
View File
@@ -0,0 +1,130 @@
namespace QuanTAlib;
/// <summary>
/// EMA: Exponential Moving Average
/// EMA needs very short history buffer and calculates the EMA value using just the
/// previous EMA value. The weight of the new datapoint (alpha) is alpha = 2 / (period + 1)
/// </summary>
/// <remarks>
/// Smoothness: ★★★☆☆ (3/5)
/// Sensitivity: ★★★★☆ (4/5)
/// Overshooting: ★★★★★ (5/5)
/// Lag: ★★★☆☆ (3/5)
///
/// Key characteristics:
/// - Uses no buffer, relying only on the previous EMA value.
/// - The weight of new data points is calculated as alpha = 2 / (period + 1).
/// - Provides a balance between responsiveness and smoothing. No overshooting. Significant lag
///
/// Calculation method:
/// This implementation can use SMA for the first Period bars as a seeding value for EMA when useSma is true.
///
/// Sources:
/// - https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages
/// - https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp
/// - https://blog.fugue88.ws/archives/2017-01/The-correct-way-to-start-an-Exponential-Moving-Average-EMA
/// </remarks>
public class Ema : AbstractBase
{
// inherited _index
// inherited _value
private readonly int _period;
private CircularBuffer _sma;
private double _lastEma, _p_lastEma;
private double _k, _e, _p_e;
private bool _isInit, _p_isInit, _useSma;
public Ema(int period, bool useSma = true) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_period = period;
_k = 2.0 / (_period + 1);
_useSma = useSma;
_sma = new(period);
Name = "Ema";
WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - _k)); //95th percentile
Init();
}
public Ema(double alpha) : base()
{
_k = alpha;
_useSma = false;
_sma = new(1);
_period = 1;
WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - _k)); //95th percentile
Init();
}
public Ema(object source, int period, bool useSma = true) : this(period, useSma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
//inhereted public void Sub(object source, in ValueEventArgs args)
public override void Init()
{
base.Init();
_e = 1.0;
_lastEma = 0;
_isInit = false;
_p_isInit = false;
_sma = new(_period);
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastEma = _lastEma;
_p_isInit = _isInit;
_p_e = _e;
_index++;
}
else
{
_lastEma = _p_lastEma;
_isInit = _p_isInit;
_e = _p_e;
}
}
/// <summary>
/// Core EMA calculation
/// </summary>
protected override double Calculation()
{
double result, _ema;
ManageState(Input.IsNew);
// when _UseSma == true, use SMA calculation until we have enough data points
if (!_isInit && _useSma)
{
_sma.Add(Input.Value, Input.IsNew);
_ema = _sma.Average();
result = _ema;
if (_index >= _period)
{
_isInit = true;
}
}
else
{
// compensator for early ema values
_e = (_e > 1e-10) ? (1 - _k) * _e : 0;
_ema = _k * (Input.Value - _lastEma) + _lastEma;
// _useSma decides if we use compensator or not
result = (_useSma || _e == 0) ? _ema : _ema / (1 - _e);
}
_lastEma = _ema;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+85
View File
@@ -0,0 +1,85 @@
namespace QuanTAlib;
public class Epma : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
public Epma(int period)
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_convolution = new Convolution(GenerateKernel(_period));
Name = "Epma";
WarmupPeriod = period;
Init();
}
public Epma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
private new void Init()
{
base.Init();
_convolution.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
TValue convolutionResult = _convolution.Calc(Input);
double result = convolutionResult.Value;
// Adjust for partial periods during warmup
if (_index < _period)
{
double[] partialKernel = GenerateKernel(_index);
result /= partialKernel.Sum();
}
IsHot = _index >= WarmupPeriod;
return result;
}
public static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
double weightSum = 0;
for (int i = 0; i < period; i++)
{
kernel[i] = (2 * period - 1) - 3 * i;
weightSum += kernel[i];
}
// Normalize the kernel
for (int i = 0; i < period; i++)
{
kernel[i] /= weightSum;
}
// Reverse the kernel for convolution
//Array.Reverse(kernel);
return kernel;
}
}
+99
View File
@@ -0,0 +1,99 @@
using System;
namespace QuanTAlib
{
public class Frama : AbstractBase
{
private readonly int _period;
private readonly double _fc;
private CircularBuffer _buffer;
private double _lastFrama;
private double _prevLastFrama;
public Frama(int period, double fc = 0.5) : base()
{
if (period < 2)
throw new ArgumentException("Period must be at least 2", nameof(period));
_period = period;
_fc = fc;
_buffer = new CircularBuffer(period);
WarmupPeriod = period;
}
public override void Init()
{
base.Init();
_buffer.Clear();
_lastFrama = 0;
_prevLastFrama = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_prevLastFrama = _lastFrama;
_index++;
}
else
{
_lastFrama = _prevLastFrama;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
if (_buffer.Count < _period)
{
_lastFrama = _buffer.Average();
return _lastFrama;
}
int half = _period / 2;
double hh = double.MinValue, ll = double.MaxValue;
double hh1 = double.MinValue, ll1 = double.MaxValue;
double hh2 = double.MinValue, ll2 = double.MaxValue;
for (int i = 0; i < _period; i++)
{
double price = _buffer[i];
hh = Math.Max(hh, price);
ll = Math.Min(ll, price);
if (i < half)
{
hh1 = Math.Max(hh1, price);
ll1 = Math.Min(ll1, price);
}
else
{
hh2 = Math.Max(hh2, price);
ll2 = Math.Min(ll2, price);
}
}
double n1 = (hh - ll) / _period;
double n2 = (hh1 - ll1 + hh2 - ll2) / (_period / 2);
double d = (Math.Log(n2 + double.Epsilon) - Math.Log(n1 + double.Epsilon)) / Math.Log(2);
double alpha = Math.Exp(-4.6 * (d - 1));
alpha = Math.Max(Math.Min(alpha, 1), 0.01); // Ensure alpha is between 0.01 and 1
_lastFrama = alpha * (Input.Value - _lastFrama) + _lastFrama;
IsHot = _index >= WarmupPeriod;
return _lastFrama;
}
protected override double GetLastValid()
{
return _lastFrama;
}
}
}
+83
View File
@@ -0,0 +1,83 @@
namespace QuanTAlib;
public class Fwma : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
public Fwma(int period)
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_convolution = new Convolution(GenerateKernel(_period));
Name = "Fwma";
WarmupPeriod = period;
Init();
}
public Fwma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
double[] fibSeries = new double[period];
double weightSum = 0;
// Generate Fibonacci series
fibSeries[0] = fibSeries[1] = 1;
for (int i = 2; i < period; i++)
{
fibSeries[i] = fibSeries[i - 1] + fibSeries[i - 2];
}
// Reverse the series to give more weight to recent prices
for (int i = 0; i < period; i++)
{
kernel[i] = fibSeries[period - 1 - i];
weightSum += kernel[i];
}
// Normalize the kernel
for (int i = 0; i < period; i++)
{
kernel[i] /= weightSum;
}
return kernel;
}
private new void Init()
{
base.Init();
_convolution.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
TValue convolutionResult = _convolution.Calc(Input);
double result = convolutionResult.Value;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+76
View File
@@ -0,0 +1,76 @@
namespace QuanTAlib;
public class Gma : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
public Gma(int period)
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_convolution = new Convolution(GenerateKernel(_period));
Name = "Gma";
WarmupPeriod = period;
Init();
}
public Gma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public static double[] GenerateKernel(int period, double sigma = 1.0)
{
double[] kernel = new double[period];
double weightSum = 0;
int center = period / 2;
for (int i = 0; i < period; i++)
{
double x = (i - center) / (double)center;
kernel[i] = Math.Exp(-(x * x) / (2 * sigma * sigma));
weightSum += kernel[i];
}
// Normalize the kernel
for (int i = 0; i < period; i++)
{
kernel[i] /= weightSum;
}
return kernel;
}
private new void Init()
{
base.Init();
_convolution.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
TValue convolutionResult = _convolution.Calc(Input);
double result = convolutionResult.Value;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+77
View File
@@ -0,0 +1,77 @@
namespace QuanTAlib;
public class Hma : AbstractBase
{
private readonly int _period, _sqrtPeriod;
private readonly Convolution _wmaHalf, _wmaFull, _wmaFinal;
public Hma(int period)
{
if (period < 2)
{
throw new ArgumentException("Period must be greater than or equal to 2.", nameof(period));
}
_period = period;
_sqrtPeriod = (int)Math.Sqrt(period);
_wmaHalf = new Convolution(GenerateWmaKernel(period / 2));
_wmaFull = new Convolution(GenerateWmaKernel(period));
_wmaFinal = new Convolution(GenerateWmaKernel(_sqrtPeriod));
Name = "Hma";
WarmupPeriod = _period + _sqrtPeriod - 1;
Init();
}
public Hma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
private static double[] GenerateWmaKernel(int period)
{
double[] kernel = new double[period];
double weightSum = period * (period + 1) / 2.0;
for (int i = 0; i < period; i++)
{
kernel[i] = (period - i) / weightSum;
}
return kernel;
}
private new void Init()
{
base.Init();
_wmaHalf.Init();
_wmaFull.Init();
_wmaFinal.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Calculate WMA(n/2) and WMA(n)
double wmaHalfResult = _wmaHalf.Calc(Input).Value;
double wmaFullResult = _wmaFull.Calc(Input).Value;
// Calculate 2*WMA(n/2) - WMA(n)
double intermediateResult = 2 * wmaHalfResult - wmaFullResult;
// Calculate final WMA
double result = _wmaFinal.Calc(new TValue(Input.Time, intermediateResult, Input.IsNew)).Value;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+146
View File
@@ -0,0 +1,146 @@
//not working yet
//TODO consistency test
using QuanTAlib;
public class Htit : AbstractBase
{
private readonly CircularBuffer _priceBuffer = new(7);
private readonly CircularBuffer _spBuffer = new(7);
private readonly CircularBuffer _dtBuffer = new(7);
private readonly CircularBuffer _i1Buffer = new(7);
private readonly CircularBuffer _q1Buffer = new(7);
private readonly CircularBuffer _i2Buffer = new(2);
private readonly CircularBuffer _q2Buffer = new(2);
private readonly CircularBuffer _reBuffer = new(2);
private readonly CircularBuffer _imBuffer = new(2);
private readonly CircularBuffer _pdBuffer = new(2);
private readonly CircularBuffer _sdBuffer = new(2);
private readonly CircularBuffer _itBuffer = new(4);
private double _lastPd = 0;
private double _p_lastPd = 0;
public Htit() : base()
{
Name = "Htit";
WarmupPeriod = 12;
}
public Htit(object source) : this()
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastPd = _lastPd;
_index++;
}
else
{
_lastPd = _p_lastPd;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double pr = Input.Value;
_priceBuffer.Add(pr, Input.IsNew);
if (_index <= 5)
{
_spBuffer.Add(0, Input.IsNew);
_dtBuffer.Add(0, Input.IsNew);
_i1Buffer.Add(0, Input.IsNew);
_q1Buffer.Add(0, Input.IsNew);
_i2Buffer.Add(0, Input.IsNew);
_q2Buffer.Add(0, Input.IsNew);
_reBuffer.Add(0, Input.IsNew);
_imBuffer.Add(0, Input.IsNew);
_pdBuffer.Add(0, Input.IsNew);
_sdBuffer.Add(0, Input.IsNew);
_itBuffer.Add(pr, Input.IsNew);
return pr;
}
double adj = (0.075 * _lastPd) + 0.54;
// Smooth and detrender
double sp = ((4 * _priceBuffer[0]) + (3 * _priceBuffer[1]) + (2 * _priceBuffer[2]) + _priceBuffer[3]) / 10;
_spBuffer.Add(sp, Input.IsNew);
double dt = ((0.0962 * _spBuffer[0]) + (0.5769 * _spBuffer[2]) - (0.5769 * _spBuffer[4]) - (0.0962 * _spBuffer[6])) * adj;
_dtBuffer.Add(dt, Input.IsNew);
// In-phase and quadrature
double q1 = ((0.0962 * _dtBuffer[0]) + (0.5769 * _dtBuffer[2]) - (0.5769 * _dtBuffer[4]) - (0.0962 * _dtBuffer[6])) * adj;
_q1Buffer.Add(q1, Input.IsNew);
double i1 = _dtBuffer[3];
_i1Buffer.Add(i1, Input.IsNew);
// Advance the phases by 90 degrees
double jI = ((0.0962 * _i1Buffer[0]) + (0.5769 * _i1Buffer[2]) - (0.5769 * _i1Buffer[4]) - (0.0962 * _i1Buffer[6])) * adj;
double jQ = ((0.0962 * _q1Buffer[0]) + (0.5769 * _q1Buffer[2]) - (0.5769 * _q1Buffer[4]) - (0.0962 * _q1Buffer[6])) * adj;
// Phasor addition for 3-bar averaging
double i2 = i1 - jQ;
double q2 = q1 + jI;
i2 = (0.2 * i2) + (0.8 * _i2Buffer[0]);
q2 = (0.2 * q2) + (0.8 * _q2Buffer[0]);
_i2Buffer.Add(i2, Input.IsNew);
_q2Buffer.Add(q2, Input.IsNew);
// Homodyne discriminator
double re = (i2 * _i2Buffer[1]) + (q2 * _q2Buffer[1]);
double im = (i2 * _q2Buffer[1]) - (q2 * _i2Buffer[1]);
re = (0.2 * re) + (0.8 * _reBuffer[0]);
im = (0.2 * im) + (0.8 * _imBuffer[0]);
_reBuffer.Add(re, Input.IsNew);
_imBuffer.Add(im, Input.IsNew);
// Calculate period
double pd = (im != 0 && re != 0) ? 2 * Math.PI / Math.Atan(im / re) : 0;
// Adjust period to thresholds
pd = (pd > 1.5 * _lastPd) ? 1.5 * _lastPd : pd;
pd = (pd < 0.67 * _lastPd) ? 0.67 * _lastPd : pd;
pd = (pd < 6) ? 6 : pd;
pd = (pd > 50) ? 50 : pd;
// Smooth the period
pd = (0.2 * pd) + (0.8 * _lastPd);
_pdBuffer.Add(pd, Input.IsNew);
double sd = (0.33 * pd) + (0.67 * _sdBuffer[0]);
_sdBuffer.Add(sd, Input.IsNew);
// Smooth dominant cycle period
int dcPeriods = (int)(sd + 0.5);
double sumPr = _priceBuffer.GetSpan().Slice(0, Math.Min(dcPeriods, _priceBuffer.Count)).ToArray().Sum();
double it = dcPeriods > 0 ? sumPr / dcPeriods : pr;
_itBuffer.Add(it, Input.IsNew);
_p_lastPd = _lastPd;
_lastPd = pd;
// Final indicator
if (_index >= 11) // 12th bar
{
return ((4 * _itBuffer[0]) + (3 * _itBuffer[1]) + (2 * _itBuffer[2]) + _itBuffer[3]) / 10;
}
else
{
return pr;
}
}
}
+96
View File
@@ -0,0 +1,96 @@
namespace QuanTAlib;
public class Hwma : AbstractBase
{
private readonly int _period;
private readonly double _nA, _nB, _nC;
private double _pF, _pV, _pA;
private double _ppF, _ppV, _ppA;
public Hwma(int period) : this(period, 2.0 / (1 + period), 1.0 / period, 1.0 / period)
{
}
public Hwma(double nA, double nB, double nC) : this((int)((2 - nA) / nA), nA, nB, nC)
{
}
public Hwma(int period, double nA, double nB, double nC) : base()
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_nA = nA;
_nB = nB;
_nC = nC;
WarmupPeriod = period;
Name = $"Hwma({_period})";
Init();
}
public Hwma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_pF = _pV = _pA = 0;
_ppF = _ppV = _ppA = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_ppF = _pF;
_ppV = _pV;
_ppA = _pA;
}
else
{
_pF = _ppF;
_pV = _ppV;
_pA = _ppA;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
if (_index == 1)
{
_pF = Input.Value;
_pA = _pV = 0;
}
double nA = _nA, nB = _nB, nC = _nC;
if (_period == 1)
{
nA = 1;
nB = 0;
nC = 0;
}
double F = (1 - nA) * (_pF + _pV + 0.5 * _pA) + nA * Input.Value;
double V = (1 - nB) * (_pV + _pA) + nB * (F - _pF);
double A = (1 - nC) * _pA + nC * (V - _pV);
double hwma = F + V + 0.5 * A;
_pF = F;
_pV = V;
_pA = A;
IsHot = _index >= WarmupPeriod;
return hwma;
}
}
+130
View File
@@ -0,0 +1,130 @@
using QuanTAlib;
//TODO consistency test
public class Jma : AbstractBase
{
public readonly int Period;
private readonly double _phase;
private readonly int _vshort, _vlong;
private CircularBuffer _values;
private CircularBuffer _voltyShort;
private CircularBuffer _vsumBuff;
private CircularBuffer _avoltyBuff;
private double _beta, _len1, _pow1;
private double _upperBand, _lowerBand, _prevMa1, _prevDet0, _prevDet1, _prevJma;
private double _p_UpperBand, _p_LowerBand, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma;
public Jma(int period, double phase = 0, int vshort = 10) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
Period = period;
_vshort = vshort;
_vlong = 65;
_phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5);
_values = new CircularBuffer(period);
_voltyShort = new CircularBuffer(vshort);
_vsumBuff = new CircularBuffer(_vlong);
_avoltyBuff = new CircularBuffer(2);
Name = "JMA";
WarmupPeriod = period * 2;
Init();
}
public override void Init()
{
_upperBand = _lowerBand = _prevMa1 = _prevDet0 = _prevDet1 = _prevJma = 0.0;
_p_UpperBand = _p_LowerBand = _p_prevMa1 = _p_prevDet0 = _p_prevDet1 = _p_prevJma = 0.0;
_beta = 0.45 * (Period - 1) / (0.45 * (Period - 1) + 2);
_len1 = Math.Max((Math.Log(Math.Sqrt(Period - 1)) / Math.Log(2.0)) + 2.0, 0);
_pow1 = Math.Max(_len1 - 2.0, 0.5);
_avoltyBuff.Clear();
_avoltyBuff.Add(0, true);
_avoltyBuff.Add(0, true);
base.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
// Save current state
_p_UpperBand = _upperBand;
_p_LowerBand = _lowerBand;
_p_prevMa1 = _prevMa1;
_p_prevDet0 = _prevDet0;
_p_prevDet1 = _prevDet1;
_p_prevJma = _prevJma;
}
else
{
// Restore previous state
_upperBand = _p_UpperBand;
_lowerBand = _p_LowerBand;
_prevMa1 = _p_prevMa1;
_prevDet0 = _p_prevDet0;
_prevDet1 = _p_prevDet1;
_prevJma = _p_prevJma;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_values.Add(Input.Value, Input.IsNew);
if (_index == 1)
{
_prevMa1 = _prevJma = Input.Value;
return Input.Value;
}
double hprice = _values.Max();
double lprice = _values.Min();
double del1 = hprice - _upperBand;
double del2 = lprice - _lowerBand;
double volty = Math.Max(Math.Abs(del1), Math.Abs(del2));
_voltyShort.Add(volty, Input.IsNew);
double vsum = _vsumBuff.Newest() + 0.1 * (volty - _voltyShort.Oldest());
_vsumBuff.Add(vsum, Input.IsNew);
double prevAvolty = _avoltyBuff.Newest();
double avolty = prevAvolty + 2.0 / (Math.Max(4.0 * Period, 30) + 1.0) * (vsum - prevAvolty);
_avoltyBuff.Add(avolty, Input.IsNew);
double dVolty = (avolty > 0) ? volty / avolty : 0;
dVolty = Math.Min(Math.Max(dVolty, 1.0), Math.Pow(_len1, 1.0 / _pow1));
double pow2 = Math.Pow(dVolty, _pow1);
double len2 = Math.Sqrt(0.5 * (Period - 1)) * _len1;
double _Kv = Math.Pow(len2 / (len2 + 1), Math.Sqrt(pow2));
_upperBand = (del1 > 0) ? hprice : hprice - (_Kv * del1);
_lowerBand = (del2 < 0) ? lprice : lprice - (_Kv * del2);
double alpha = Math.Pow(_beta, pow2);
double ma1 = (1 - alpha) * Input.Value + alpha * _prevMa1;
_prevMa1 = ma1;
double det0 = (1 - _beta) * (Input.Value - ma1) + _beta * _prevDet0;
_prevDet0 = det0;
double ma2 = ma1 + (_phase + 1) * det0;
double det1 = ((1 - alpha) * (1 - alpha) * (ma2 - _prevJma)) + (alpha * alpha * _prevDet1);
_prevDet1 = det1;
double jma = _prevJma + det1;
_prevJma = jma;
IsHot = _index >= WarmupPeriod;
return jma;
}
}
+85
View File
@@ -0,0 +1,85 @@
using System;
namespace QuanTAlib;
public class Kama : AbstractBase
{
private readonly int _period;
private readonly double _scFast, _scSlow;
private CircularBuffer? _buffer;
private double _lastKama, _p_lastKama;
public Kama(int period, int fast = 2, int slow = 30) : base()
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_scFast = 2.0 / (((period < fast) ? period : fast) + 1);
_scSlow = 2.0 / (slow + 1);
WarmupPeriod = period;
Name = $"Kama({_period}, {fast}, {slow})";
Init();
}
public Kama(object source, int period, int fast = 2, int slow = 30) : this(period, fast, slow)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer = new CircularBuffer(_period + 1);
_lastKama = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_p_lastKama = _lastKama;
}
else
{
_lastKama = _p_lastKama;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer!.Add(Input.Value, Input.IsNew);
double kama;
if (_index <= _period)
{
kama = Input.Value;
}
else
{
double change = Math.Abs(_buffer[^1] - _buffer[0]);
double volatility = 0;
for (int i = 1; i < _buffer.Count; i++)
{
volatility += Math.Abs(_buffer[i] - _buffer[i - 1]);
}
double er = volatility != 0 ? change / volatility : 0;
double sc = (er * (_scFast - _scSlow)) + _scSlow;
sc *= sc; // Square the smoothing constant
kama = _lastKama + (sc * (Input.Value - _lastKama));
}
_lastKama = kama;
IsHot = _index >= WarmupPeriod;
return kama;
}
}
+74
View File
@@ -0,0 +1,74 @@
namespace QuanTAlib;
// https://www.mesasoftware.com/papers/TimeWarp.pdf
public class Ltma : AbstractBase
{
private readonly double _gamma;
private double _prevL0, _prevL1, _prevL2, _prevL3;
private double _p_prevL0, _p_prevL1, _p_prevL2, _p_prevL3;
public double Gamma => _gamma;
public Ltma(double gamma = 0.1) : base()
{
if (gamma < 0 || gamma > 1)
throw new ArgumentOutOfRangeException(nameof(gamma), "Gamma must be between 0 and 1.");
_gamma = gamma;
Name = $"Laguerre({gamma:F2})";
WarmupPeriod = 4; // Minimum number of samples needed
Init();
}
public Ltma(object source, double gamma = 0.1) : this(gamma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_prevL0 = _prevL1 = _prevL2 = _prevL3 = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_prevL0 = _prevL0;
_p_prevL1 = _prevL1;
_p_prevL2 = _prevL2;
_p_prevL3 = _prevL3;
_index++;
}
else
{
_prevL0 = _p_prevL0;
_prevL1 = _p_prevL1;
_prevL2 = _p_prevL2;
_prevL3 = _p_prevL3;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Laguerre filter calculation
double _l0 = (1 - _gamma) * Input.Value + _gamma * _prevL0;
double _l1 = -_gamma * _l0 + _prevL0 + _gamma * _prevL1;
double _l2 = -_gamma * _l1 + _prevL1 + _gamma * _prevL2;
double _l3 = -_gamma * _l2 + _prevL2 + _gamma * _prevL3;
_prevL0 = _l0;
_prevL1 = _l1;
_prevL2 = _l2;
_prevL3 = _l3;
double filteredValue = (_l0 + 2 * _l1 + 2 * _l2 + _l3) / 6;
IsHot = _index >= WarmupPeriod;
return filteredValue;
}
}
+110
View File
@@ -0,0 +1,110 @@
//TODO: consistency test
namespace QuanTAlib;
// https://efs.kb.esignal.com/hc/en-us/articles/6362791434395-2005-Mar-The-Secret-Behind-The-Filter-MedianAdaptiveFilter-efs
public class Maaf : AbstractBase
{
private readonly CircularBuffer _priceBuffer;
private readonly CircularBuffer _smoothBuffer;
private double _prevFilter, _prevValue2, _threshold;
private double _p_prevFilter, _p_prevValue2;
private readonly int _period;
public Maaf(int Period = 39, double Threshold = 0.002) : base()
{
_period = Period;
_threshold = Threshold;
_priceBuffer = new CircularBuffer(4);
_smoothBuffer = new CircularBuffer(Period);
Name = "MAAF";
WarmupPeriod = Period;
Init();
}
public Maaf(object source, int Period = 39, double Threshold = 0.002) : this(Period, Threshold)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
_priceBuffer.Clear();
_smoothBuffer.Clear();
_prevFilter = 0;
_prevValue2 = 0;
base.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_p_prevFilter = _prevFilter;
_p_prevValue2 = _prevValue2;
}
else
{
_prevFilter = _p_prevFilter;
_prevValue2 = _p_prevValue2;
}
}
protected override double Calculation()
{
ManageState(IsNew);
_priceBuffer.Add(Input.Value, Input.IsNew);
if (_priceBuffer.Count < 4)
{
return Input.Value;
}
double smooth = (_priceBuffer[^1] + (2 * _priceBuffer[^2]) + (2 * _priceBuffer[^3]) + _priceBuffer[^4]) / 6;
_smoothBuffer.Add(smooth, Input.IsNew);
if (_smoothBuffer.Count < _period)
{
return smooth;
}
int length = _period;
double value3 = 0.2;
double value2 = _prevValue2;
while (value3 > _threshold && length > 0)
{
double alpha = 2.0 / (length + 1);
var sortedValues = _smoothBuffer.TakeLast(length).OrderBy(x => x).ToList();
double value1 = sortedValues[length / 2];
value2 = alpha * (smooth - _prevValue2) + _prevValue2;
if (value1 != 0)
{
value3 = Math.Abs(value1 - value2) / value1;
}
length -= 2;
}
if (length < 3) length = 3;
double finalAlpha = 2.0 / (length + 1);
double filter = finalAlpha * (smooth - _prevFilter) + _prevFilter;
_p_prevFilter = _prevFilter;
_prevFilter = filter;
_p_prevValue2 = _prevValue2;
_prevValue2 = value2;
IsHot = _index >= WarmupPeriod;
return filter;
}
}
+160
View File
@@ -0,0 +1,160 @@
using QuanTAlib;
using System;
public class Mama : AbstractBase
{
private readonly double _fastLimit, _slowLimit;
private CircularBuffer _pr, _sm, _dt, _i1, _q1, _i2, _q2, _re, _im, _pd, _ph;
private double _mama, _fama;
private double _prevMama, _prevFama, _sumPr;
private double _p_prevMama, _p_prevFama, _p_sumPr;
public TValue Fama { get; private set; }
public Mama(double fastLimit = 0.5, double slowLimit = 0.05) : base()
{
Fama = new TValue();
Name = $"Mama({_fastLimit:F2}, {_slowLimit:F2})";
_fastLimit = fastLimit;
_slowLimit = slowLimit;
_pr = new(7);
_sm = new(7);
_dt = new(7);
_q1 = new(7);
_i1 = new(7);
_i2 = new(2);
_q2 = new(2);
_re = new(2);
_im = new(2);
_pd = new(2);
_ph = new(2);
Init();
}
public Mama(object source, double fastLimit = 0.5, double slowLimit = 0.05) : this(fastLimit, slowLimit)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
Fama = new TValue();
base.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_prevMama = _prevMama;
_p_prevFama = _prevFama;
_p_sumPr = _sumPr;
_lastValidValue = Input.Value;
_index++;
}
else
{
_prevMama = _p_prevMama;
_prevFama = _p_prevFama;
_sumPr = _p_sumPr;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_pr.Add(Input.Value, Input.IsNew);
if (_index > 6)
{
double adj = (0.075 * _pd[^1]) + 0.54;
// Smooth
_sm.Add(((4 * _pr[^1]) + (3 * _pr[^2]) + (2 * _pr[^3]) + _pr[^4]) / 10, Input.IsNew);
// Detrender
_dt.Add(((0.0962 * _sm[^1]) + (0.5769 * _sm[^3]) - (0.5769 * _sm[^5]) - (0.0962 * _sm[^7])) * adj, Input.IsNew);
// In-phase and quadrature
_q1.Add(((0.0962 * _dt[^1]) + (0.5769 * _dt[^3]) - (0.5769 * _dt[^5]) - (0.0962 * _dt[^7])) * adj, Input.IsNew);
_i1.Add(_dt[^4], Input.IsNew);
// Advance the phases by 90 degrees
double jI = ((0.0962 * _i1[^1]) + (0.5769 * _i1[^3]) - (0.5769 * _i1[^5]) - (0.0962 * _i1[^7])) * adj;
double jQ = ((0.0962 * _q1[^1]) + (0.5769 * _q1[^3]) - (0.5769 * _q1[^5]) - (0.0962 * _q1[^7])) * adj;
// Phasor addition for 3-bar averaging
_i2.Add(_i1[^1] - jQ, Input.IsNew);
_q2.Add(_q1[^1] + jI, Input.IsNew);
_i2[^1] = 0.2 * _i2[^1] + 0.8 * _i2[^2];
_q2[^1] = 0.2 * _q2[^1] + 0.8 * _q2[^2];
// Homodyne discriminator
_re.Add((_i2[^1] * _i2[^2]) + (_q2[^1] * _q2[^2]), Input.IsNew);
_im.Add((_i2[^1] * _q2[^2]) - (_q2[^1] * _i2[^2]), Input.IsNew);
_re[^1] = (0.2 * _re[^1]) + (0.8 * _re[^2]);
_im[^1] = (0.2 * _im[^1]) + (0.8 * _im[^2]);
// Calculate period
if (_im[^1] != 0 && _re[^1] != 0)
{
_pd.Add(2 * Math.PI / Math.Atan(_im[^1] / _re[^1]), Input.IsNew);
}
else
{
_pd.Add(_pd[^2], Input.IsNew);
}
// Adjust period to thresholds
_pd[^1] = Math.Max(Math.Min(_pd[^1], 1.5 * _pd[^2]), 0.67 * _pd[^2]);
_pd[^1] = Math.Max(Math.Min(_pd[^1], 50), 6);
_pd[^1] = (0.2 * _pd[^1]) + (0.8 * _pd[^2]);
// Determine phase position
if (_i1[^1] != 0)
{
_ph.Add(Math.Atan(_q1[^1] / _i1[^1]) * 180 / Math.PI, Input.IsNew);
}
else
{
_ph.Add(_ph[^2], Input.IsNew);
}
// Change in phase
double delta = Math.Max(_ph[^2] - _ph[^1], 1);
// Adaptive alpha value
double alpha = Math.Max(_fastLimit / delta, _slowLimit);
// Final indicators
_mama = alpha * (_pr[^1] - _prevMama) + _prevMama;
_fama = 0.5 * alpha * (_mama - _prevFama) + _prevFama;
_prevMama = _mama;
_prevFama = _fama;
}
else
{
_pd.Add(0, Input.IsNew);
_sm.Add(0, Input.IsNew);
_dt.Add(0, Input.IsNew);
_i1.Add(0, Input.IsNew);
_q1.Add(0, Input.IsNew);
_i2.Add(0, Input.IsNew);
_q2.Add(0, Input.IsNew);
_re.Add(0, Input.IsNew);
_im.Add(0, Input.IsNew);
_ph.Add(0, Input.IsNew);
_sumPr += Input.Value;
_mama = _fama = _prevMama = _prevFama = _sumPr / _index;
}
Fama = new TValue(Time: Input.Time, Value: _fama, IsNew: Input.IsNew);
IsHot = _index >= 6;
return _mama;
}
}
+66
View File
@@ -0,0 +1,66 @@
namespace QuanTAlib;
public class Mgdi : AbstractBase
{
private readonly int _period;
private readonly double _kFactor;
private double _prevMd, _p_prevMd;
public Mgdi(int period, double kFactor = 0.6) : base()
{
if (period <= 0)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0.");
}
if (kFactor <= 0)
{
throw new ArgumentOutOfRangeException(nameof(kFactor), "K-Factor must be greater than 0.");
}
_period = period;
_kFactor = kFactor;
Name = "Mgdi";
WarmupPeriod = period;
Init();
}
public Mgdi(object source, int period, double kFactor = 1.0) : this(period, kFactor)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_prevMd = _p_prevMd = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_prevMd = _prevMd;
_index++;
} else {
_prevMd = _p_prevMd;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double value = Input.Value;
if (_index < 2){
_prevMd = value;
}
else
{
double md = _prevMd + ((value - _prevMd) /
(_kFactor * _period * Math.Pow(value / _prevMd, 4)));
_prevMd = md;
}
IsHot = _index >= _period;
return _prevMd;
}
}
+78
View File
@@ -0,0 +1,78 @@
using System;
using System.Linq;
namespace QuanTAlib
{
public class Mma : AbstractBase
{
private readonly int _period;
private readonly CircularBuffer _buffer;
private double _lastMma;
public Mma(int period) : base()
{
if (period < 2)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
_period = period;
_buffer = new CircularBuffer(period);
Name = "Mma";
WarmupPeriod = period;
Init();
}
public Mma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_lastMma = 0;
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
if (_index >= _period)
{
double T = _buffer.Sum();
double S = CalculateWeightedSum();
_lastMma = (T / _period) + (6 * S) / ((_period + 1) * _period);
}
else
{
// Use simple average until we have enough data points
_lastMma = _buffer.Average();
}
IsHot = _index >= _period;
return _lastMma;
}
private double CalculateWeightedSum()
{
double sum = 0;
for (int i = 0; i < _period; i++)
{
double weight = (_period - (2 * i + 1)) / 2.0;
sum += weight * _buffer[^(i + 1)];
}
return sum;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
namespace QuanTAlib;
public class Qema : AbstractBase
{
private readonly double _k1, _k2, _k3, _k4;
private readonly Ema _ema1, _ema2, _ema3, _ema4;
private double _lastQema, _p_lastQema;
public Qema(double k1=0.2, double k2=0.2, double k3=0.2, double k4=0.2) : base()
{
if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0 )
{
throw new ArgumentOutOfRangeException("All k values must be in the range (0, 1].");
}
_k1 = k1;
_k2 = k2;
_k3 = k3;
_k4 = k4;
_ema1 = new Ema(k1);
_ema2 = new Ema(k2);
_ema3 = new Ema(k3);
_ema4 = new Ema(k4);
Name = $"QEMA ({k1:F2},{k2:F2},{k3:F2},{k4:F2})";
double smK = Math.Min(Math.Min(_k1, _k2), Math.Min(_k3, _k4));
WarmupPeriod = (int) ((2 - smK) / smK);
Init();
}
public Qema(object source, double k1, double k2, double k3, double k4)
: this(k1, k2, k3, k4)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_lastQema = 0;
_p_lastQema = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastQema = _lastQema;
_index++;
}
else
{
_lastQema = _p_lastQema;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double ema1 = _ema1.Calc(new TValue(Input.Time, Input.Value, Input.IsNew));
double ema2 = _ema2.Calc(new TValue(Input.Time, ema1, Input.IsNew));
double ema3 = _ema3.Calc(new TValue(Input.Time, ema2, Input.IsNew));
double ema4 = _ema4.Calc(new TValue(Input.Time, ema3, Input.IsNew));
_lastQema = 4 * ema1 - 6 * ema2 + 4 * ema3 - ema4;
IsHot = _index >= WarmupPeriod;
return _lastQema;
}
}
+78
View File
@@ -0,0 +1,78 @@
using QuanTAlib;
//https://user42.tuxfamily.org/chart/manual/Regularized-Exponential-Moving-Average.html
public class Rema : AbstractBase
{
private readonly int _period;
private readonly double _lambda;
private double _lastRema, _prevRema;
private double _savedLastRema, _savedPrevRema;
public int Period => _period;
public double Lambda => _lambda;
public Rema(int period, double lambda = 0.5) : base()
{
if (period < 1)
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
if (lambda < 0)
throw new ArgumentOutOfRangeException(nameof(lambda), "Lambda must be non-negative.");
_period = period;
_lambda = lambda;
Name = $"REMA({period},{lambda:F2})";
WarmupPeriod = period;
Init();
}
public override void Init()
{
base.Init();
_lastRema = 0;
_prevRema = 0;
_savedLastRema = 0;
_savedPrevRema = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_savedLastRema = _lastRema;
_savedPrevRema = _prevRema;
_index++;
}
else
{
_lastRema = _savedLastRema;
_prevRema = _savedPrevRema;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double alpha = 2.0 / (Math.Min(_period, _index) + 1);
if (_index > 2)
{
double rema = (_lastRema + alpha * (Input.Value - _lastRema) + _lambda * (_lastRema + (_lastRema - _prevRema))) / (1 + _lambda);
_prevRema = _lastRema;
_lastRema = rema;
}
else if (_index == 2)
{
_prevRema = _lastRema;
_lastRema = Input.Value;
}
else
{ // _index == 1
_lastRema = Input.Value;
}
IsHot = _index >= WarmupPeriod;
return _lastRema;
}
}
+66
View File
@@ -0,0 +1,66 @@
using System;
using System.Runtime.CompilerServices;
namespace QuanTAlib {
public class Rma : AbstractBase {
private readonly int _period;
private double _alpha;
private double _lastRMA;
private double _savedLastRMA;
public Rma(int period) : base() {
if (period < 1) {
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
WarmupPeriod = period * 2;
_alpha = 1.0 / _period; // Wilder's smoothing factor
Name = $"Rma({_period})";
Init();
}
public Rma(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init() {
base.Init();
_lastRMA = 0;
_savedLastRMA = 0;
}
protected override void ManageState(bool isNew) {
if (isNew) {
_savedLastRMA = _lastRMA;
_lastValidValue = Input.Value;
_index++;
} else {
_lastRMA = _savedLastRMA;
}
}
protected override double Calculation() {
ManageState(Input.IsNew);
double rma;
if (_index == 1) {
rma = Input.Value;
} else if (_index <= _period) {
// Simple average during initial period
rma = (_lastRMA * (_index - 1) + Input.Value) / _index;
} else {
// Wilder's smoothing method
//rma = (_lastRMA * (_period - 1) + Input.Value) / _period;
rma = _alpha * (Input.Value - _lastRMA) + _lastRMA;
}
_lastRMA = rma;
IsHot = _index >= WarmupPeriod;
return rma;
}
}
}
+75
View File
@@ -0,0 +1,75 @@
namespace QuanTAlib;
public class Sinema : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
public Sinema(int period)
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_convolution = new Convolution(GenerateKernel(_period));
Name = "Sinema";
WarmupPeriod = period;
Init();
}
public Sinema(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
private new void Init()
{
base.Init();
_convolution.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
TValue convolutionResult = _convolution.Calc(Input);
double result = convolutionResult.Value;
IsHot = _index >= WarmupPeriod;
return result;
}
public static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
double weightSum = 0;
for (int i = 0; i < period; i++)
{
// Use sine function to generate weights
kernel[i] = Math.Sin((i + 1) * Math.PI / (period + 1));
weightSum += kernel[i];
}
// Normalize the kernel
for (int i = 0; i < period; i++)
{
kernel[i] /= weightSum;
}
return kernel;
}
}
+60
View File
@@ -0,0 +1,60 @@
namespace QuanTAlib;
public class Sma : AbstractBase
{
// inherited _index
// inherited _value
public readonly int Period;
private CircularBuffer _buffer;
public Sma(int period) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
Period = period;
WarmupPeriod = period;
_buffer = new CircularBuffer(period);
Name = "Sma";
WarmupPeriod = period;
Init();
}
public Sma(object source, int period) : this(period: period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
//inhereted public void Sub(object source, in ValueEventArgs args)
public override void Init()
{
//_buffer.Clear();
base.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
/// <summary>
/// Core SMA calculation - using _buffer.Average
/// </summary>
protected override double Calculation()
{
double result;
ManageState(IsNew);
_buffer.Add(Input.Value, Input.IsNew);
result = _buffer.Average();
IsHot = _index >= WarmupPeriod;
return result;
}
}
+78
View File
@@ -0,0 +1,78 @@
using System;
namespace QuanTAlib;
public class Smma : AbstractBase
{
private readonly int _period;
private CircularBuffer? _buffer;
private double _lastSmma, _p_lastSmma;
public Smma(int period) : base()
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
WarmupPeriod = period;
Name = $"Smma({_period})";
Init();
}
public Smma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer = new CircularBuffer(_period);
_lastSmma = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_p_lastSmma = _lastSmma;
_index++;
}
else
{
_lastSmma = _p_lastSmma;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer!.Add(Input.Value, Input.IsNew);
double smma;
if (_index <= _period)
{
smma = _buffer.Average();
if (_index == _period)
{
_lastSmma = smma; // Initialize _lastSmma for the transition
}
}
else
{
smma = ((_lastSmma * (_period - 1)) + Input.Value) / _period;
}
_lastSmma = smma;
IsHot = _index >= WarmupPeriod;
return smma;
}
}
+133
View File
@@ -0,0 +1,133 @@
namespace QuanTAlib;
public class T3 : AbstractBase
{
private readonly int _period;
private readonly double _vfactor;
private readonly bool _useSma;
private readonly double _k, _k1m, _c1, _c2, _c3, _c4;
private readonly CircularBuffer _buffer1, _buffer2, _buffer3, _buffer4, _buffer5, _buffer6;
private double _lastEma1, _lastEma2, _lastEma3, _lastEma4, _lastEma5, _lastEma6;
private double _p_lastEma1, _p_lastEma2, _p_lastEma3, _p_lastEma4, _p_lastEma5, _p_lastEma6;
public T3(int period, double vfactor = 0.7, bool useSma = true) : base()
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_vfactor = vfactor;
_useSma = useSma;
WarmupPeriod = period;
_k = 2.0 / (_period + 1);
_k1m = 1.0 - _k;
_c1 = -_vfactor * _vfactor * _vfactor;
_c2 = 3 * _vfactor * _vfactor + 3 * _vfactor * _vfactor * _vfactor;
_c3 = -6 * _vfactor * _vfactor - 3 * _vfactor - 3 * _vfactor * _vfactor * _vfactor;
_c4 = 1 + 3 * _vfactor + _vfactor * _vfactor * _vfactor + 3 * _vfactor * _vfactor;
_buffer1 = new(period);
_buffer2 = new(period);
_buffer3 = new(period);
_buffer4 = new(period);
_buffer5 = new(period);
_buffer6 = new(period);
Name = $"T3({_period}, {_vfactor})";
Init();
}
public T3(object source, int period, double vfactor = 0.7, bool useSma = true) : this(period, vfactor, useSma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_lastEma1 = _lastEma2 = _lastEma3 = _lastEma4 = _lastEma5 = _lastEma6 = 0;
_buffer1.Clear();
_buffer2.Clear();
_buffer3.Clear();
_buffer4.Clear();
_buffer5.Clear();
_buffer6.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_p_lastEma1 = _lastEma1;
_p_lastEma2 = _lastEma2;
_p_lastEma3 = _lastEma3;
_p_lastEma4 = _lastEma4;
_p_lastEma5 = _lastEma5;
_p_lastEma6 = _lastEma6;
}
else
{
_lastEma1 = _p_lastEma1;
_lastEma2 = _p_lastEma2;
_lastEma3 = _p_lastEma3;
_lastEma4 = _p_lastEma4;
_lastEma5 = _p_lastEma5;
_lastEma6 = _p_lastEma6;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double ema1, ema2, ema3, ema4, ema5, ema6;
if (_index == 1)
{
ema1 = ema2 = ema3 = ema4 = ema5 = ema6 = Input.Value;
}
else if (_index <= _period && _useSma)
{
_buffer1.Add(Input.Value, Input.IsNew);
ema1 = _buffer1.Average();
_buffer2.Add(ema1, Input.IsNew);
ema2 = _buffer2.Average();
_buffer3.Add(ema2, Input.IsNew);
ema3 = _buffer3.Average();
_buffer4.Add(ema3, Input.IsNew);
ema4 = _buffer4.Average();
_buffer5.Add(ema4, Input.IsNew);
ema5 = _buffer5.Average();
_buffer6.Add(ema5, Input.IsNew);
ema6 = _buffer6.Average();
}
else
{
ema1 = _k * (Input.Value - _lastEma1) + _lastEma1;
ema2 = _k * (ema1 - _lastEma2) + _lastEma2;
ema3 = _k * (ema2 - _lastEma3) + _lastEma3;
ema4 = _k * (ema3 - _lastEma4) + _lastEma4;
ema5 = _k * (ema4 - _lastEma5) + _lastEma5;
ema6 = _k * (ema5 - _lastEma6) + _lastEma6;
}
_lastEma1 = ema1;
_lastEma2 = ema2;
_lastEma3 = ema3;
_lastEma4 = ema4;
_lastEma5 = ema5;
_lastEma6 = ema6;
double t3 = _c1 * ema6 + _c2 * ema5 + _c3 * ema4 + _c4 * ema3;
IsHot = _index >= WarmupPeriod;
return t3;
}
}
+82
View File
@@ -0,0 +1,82 @@
namespace QuanTAlib;
public class Tema : AbstractBase
{
private readonly int _period;
private double _lastEma1, _p_lastEma1;
private double _lastEma2, _p_lastEma2;
private double _lastEma3, _p_lastEma3;
private double _k, _e, _p_e;
public Tema(int period) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_period = period;
Name = "Tema";
double percentile = 0.85; //targeting 85th percentile of correctness of converging EMA
WarmupPeriod = (int)Math.Ceiling(-period * Math.Log(1 - percentile));
Init();
}
public Tema(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_k = 2.0 / (_period + 1);
_e = 1.0;
_lastEma1 = _lastEma2 = _lastEma3 = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastEma1 = _lastEma1;
_p_lastEma2 = _lastEma2;
_p_lastEma3 = _lastEma3;
_p_e = _e;
_index++;
}
else
{
_lastEma1 = _p_lastEma1;
_lastEma2 = _p_lastEma2;
_lastEma3 = _p_lastEma3;
_e = _p_e;
}
}
protected override double Calculation()
{
double result, _ema1, _ema2, _ema3;
ManageState(Input.IsNew);
//double _dk = (_index + 1 >= _period) ? _k : 2.0 / (_index + 2);
_e = (_e > 1e-10) ? (1 - _k) * _e : 0;
double _invE = (_e > 1e-10) ? 1 / (1 - _e) : 1;
_ema1 = _k * (Input.Value - _lastEma1) + _lastEma1;
_ema2 = _k * (_ema1 * _invE - _lastEma2) + _lastEma2;
_ema3 = _k * (_ema2 * _invE - _lastEma3) + _lastEma3;
double _tema = 3 * _ema1 * _invE - 3 * _ema2 * _invE + _ema3 * _invE;
result = _tema;
_lastEma1 = _ema1;
_lastEma2 = _ema2;
_lastEma3 = _ema3;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+82
View File
@@ -0,0 +1,82 @@
namespace QuanTAlib;
public class Trima : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
public Trima(int period)
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_convolution = new Convolution(GenerateKernel(_period));
Name = "Trima";
WarmupPeriod = period;
Init();
}
public Trima(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
private static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
int halfPeriod = (period + 1) / 2;
double weightSum = 0;
for (int i = 0; i < period; i++)
{
if (i < halfPeriod)
{
kernel[i] = i + 1;
}
else
{
kernel[i] = period - i;
}
weightSum += kernel[i];
}
// Normalize the kernel
for (int i = 0; i < period; i++)
{
kernel[i] /= weightSum;
}
return kernel;
}
private new void Init()
{
base.Init();
_convolution.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
TValue convolutionResult = _convolution.Calc(Input);
double result = convolutionResult.Value;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+92
View File
@@ -0,0 +1,92 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
namespace QuanTAlib;
public class Vidya : AbstractBase
{
private readonly int _shortPeriod;
private readonly int _longPeriod;
private readonly double _alpha;
private double _lastVIDYA, _p_lastVIDYA;
private CircularBuffer? _shortBuffer;
private CircularBuffer? _longBuffer;
public Vidya(int shortPeriod, int longPeriod = 0, double alpha = 0.2) : base()
{
if (shortPeriod < 1)
{
throw new ArgumentException("Short period must be greater than or equal to 1.", nameof(shortPeriod));
}
_shortPeriod = shortPeriod;
_longPeriod = (longPeriod == 0) ? shortPeriod * 4 : longPeriod;
_alpha = alpha;
WarmupPeriod = _longPeriod;
Name = $"Vidya({_shortPeriod},{_longPeriod})";
Init();
}
public Vidya(object source, int shortPeriod, int longPeriod = 0, double alpha = 0.2)
: this(shortPeriod, longPeriod, alpha)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_lastVIDYA = 0;
_shortBuffer = new CircularBuffer(_shortPeriod);
_longBuffer = new CircularBuffer(_longPeriod);
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_p_lastVIDYA = _lastVIDYA;
}
else
{
_lastVIDYA = _p_lastVIDYA;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_shortBuffer!.Add(Input.Value, Input.IsNew);
_longBuffer!.Add(Input.Value, Input.IsNew);
double vidya;
if (_index <= _longPeriod)
{
vidya = _shortBuffer.Average();
}
else
{
double shortStdDev = CalculateStdDev(_shortBuffer);
double longStdDev = CalculateStdDev(_longBuffer);
double s = _alpha * (shortStdDev / longStdDev);
vidya = (s * Input.Value) + ((1 - s) * _lastVIDYA);
}
_lastVIDYA = vidya;
IsHot = _index >= WarmupPeriod;
return vidya;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateStdDev(CircularBuffer buffer)
{
double mean = buffer.Average();
double sumSquaredDiff = buffer.Sum(x => Math.Pow(x - mean, 2));
return Math.Sqrt(sumSquaredDiff / buffer.Count);
}
}
+67
View File
@@ -0,0 +1,67 @@
namespace QuanTAlib;
public class Wma : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
public Wma(int period)
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_convolution = new Convolution(GenerateWmaKernel(_period));
Name = "Wma";
WarmupPeriod = _period;
Init();
}
public Wma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
private static double[] GenerateWmaKernel(int period)
{
double[] kernel = new double[period];
double weightSum = period * (period + 1) / 2.0;
for (int i = 0; i < period; i++)
{
kernel[i] = (period - i) / weightSum;
}
return kernel;
}
private new void Init()
{
base.Init();
_convolution.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
TValue convolutionResult = _convolution.Calc(Input);
double result = convolutionResult.Value;
IsHot = _index >= WarmupPeriod;
return result;
}
}
+73
View File
@@ -0,0 +1,73 @@
using System;
using System.Runtime.CompilerServices;
namespace QuanTAlib;
public class Zlema : AbstractBase
{
private readonly int _period;
private CircularBuffer? _buffer;
private double _alpha;
private int _lag;
private double _lastZLEMA, _p_lastZLEMA;
public Zlema(int period) : base()
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
WarmupPeriod = period;
_alpha = 2.0 / (_period + 1);
_lag = (_period - 1) / 2;
Name = $"Zlema({_period})";
Init();
}
public Zlema(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer = new CircularBuffer(_period);
_lastZLEMA = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_p_lastZLEMA = _lastZLEMA;
}
else
{
_lastZLEMA = _p_lastZLEMA;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer!.Add(Input.Value, Input.IsNew);
int lag = Math.Max(Math.Min((int)((_period - 1) * 0.5), _buffer.Count - 1), 0) + 1;
double zlValue = 2 * Input.Value - _buffer[_buffer.Count - lag];
// Dynamic alpha factor for index <= period
double k = (_index <= _period) ? (2.0 / (_index + 1)) : _alpha;
double zlema = (zlValue - _lastZLEMA) * k + _lastZLEMA;
_lastZLEMA = zlema;
IsHot = _index >= WarmupPeriod;
return zlema;
}
}
+82
View File
@@ -0,0 +1,82 @@
namespace QuanTAlib;
/// <summary>
/// Provides a base implementation for financial indicators in the QuanTAlib library.
/// This abstract class implements the iTValue interface and defines common properties
/// and methods used by inheriting indicator types.
/// </summary>
public abstract class AbstractBase : iTValue
{
public DateTime Time { get; set; }
public double Value { get; set; }
public bool IsNew { get; set; }
public bool IsHot { get; set; }
public TValue Input { get; set; }
public String Name { get; set; } = "";
public int WarmupPeriod { get; set; }
public TValue Tick => new(Time, Value, IsNew, IsHot); // Stores the current value of indicator
public event ValueSignal Pub = delegate { }; // Publisher of generated values
protected int _index; //tracking the position of output
protected double _lastValidValue;
// other _internal vars defined here
protected AbstractBase()
{ //add parameters into constructor
}
/// <summary>
/// Subscribes to a data source and triggers calculations on new data.
/// </summary>
/// <param name="source">The class publishing the data.</param>
/// <param name="args">The argument containing the new data point.</param>
public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick);
public virtual void Init()
{
_index = 0;
_lastValidValue = 0;
}
/// <summary>
/// Calculates the indicator value based on the input; calls specific Calculation() method
/// where implementation is
/// </summary>
/// <param name="input">The input value for the calculation.</param>
/// <returns>A TValue representing the calculated indicator value.</returns>
public virtual TValue Calc(TValue input)
{
Input = input;
if (double.IsNaN(input.Value) || double.IsInfinity(input.Value))
{
return Process(new TValue(input.Time, GetLastValid(), input.IsNew, input.IsHot));
}
this.Value = Calculation();
return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot));
}
protected virtual double GetLastValid()
{
return this.Value;
}
protected abstract void ManageState(bool isNew);
protected abstract double Calculation();
/// <summary>
/// Processes the calculated value, updates the indicator's own state,
/// and publishes the result through an event.
/// </summary>
/// <param name="value">The calculated TValue to process.</param>
/// <returns>The processed TValue.</returns>
protected virtual TValue Process(TValue value)
{
this.Time = value.Time;
this.Value = value.Value;
this.IsNew = value.IsNew;
this.IsHot = value.IsHot;
Pub?.Invoke(this, new ValueEventArgs(value));
return value;
}
}
+332
View File
@@ -0,0 +1,332 @@
using System.Collections;
using System.Runtime.CompilerServices;
using System.Numerics;
namespace QuanTAlib;
public class CircularBuffer : IEnumerable<double>
{
private readonly double[] _buffer;
private int _start = 0;
private int _size = 0;
public int Capacity { get; }
public int Count => _size;
public CircularBuffer(int capacity)
{
Capacity = capacity;
_buffer = GC.AllocateArray<double>(capacity, pinned: true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(double item, bool isNew = true)
{
if (_size == 0 || isNew)
{
if (_size < Capacity)
{
_buffer[(_start + _size) % Capacity] = item;
_size++;
}
else
{
_buffer[_start] = item;
_start = (_start + 1) % Capacity;
}
}
else
{
_buffer[(_start + _size - 1) % Capacity] = item;
}
}
public double this[Index index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value;
actualIndex = Math.Clamp(actualIndex, 0, _size - 1);
return _buffer[(_start + actualIndex) % Capacity];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value;
actualIndex = Math.Clamp(actualIndex, 0, _size - 1);
_buffer[(_start + actualIndex) % Capacity] = value;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException("index", "Index is out of range.");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Newest()
{
if (_size == 0)
return 0;
return _buffer[(_start + _size - 1) % Capacity];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Oldest()
{
if (_size == 0)
ThrowInvalidOperationException();
return _buffer[_start];
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidOperationException()
{
throw new InvalidOperationException("Buffer is empty.");
}
public Enumerator GetEnumerator() => new(this);
IEnumerator<double> IEnumerable<double>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public struct Enumerator : IEnumerator<double>
{
private readonly CircularBuffer _buffer;
private int _index;
private double _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(CircularBuffer buffer)
{
_buffer = buffer;
_index = -1;
_current = default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (_index + 1 >= _buffer._size)
return false;
_index++;
_current = _buffer[_index];
return true;
}
public double Current => _current;
object IEnumerator.Current => Current;
public void Reset()
{
_index = -1;
_current = default;
}
public void Dispose() { }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CopyTo(double[] destination, int destinationIndex)
{
if (_size == 0)
return;
if (_start + _size <= Capacity)
{
Array.Copy(_buffer, _start, destination, destinationIndex, _size);
}
else
{
int firstPartLength = Capacity - _start;
Array.Copy(_buffer, _start, destination, destinationIndex, firstPartLength);
Array.Copy(_buffer, 0, destination, destinationIndex + firstPartLength, _size - firstPartLength);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<double> GetSpan()
{
if (_size == 0)
return ReadOnlySpan<double>.Empty;
if (_start + _size <= Capacity)
{
return new ReadOnlySpan<double>(_buffer, _start, _size);
}
else
{
return new ReadOnlySpan<double>(ToArray());
}
}
public double[] InternalBuffer => _buffer;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<double> GetInternalSpan() => _buffer.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
Array.Clear(_buffer, 0, _buffer.Length);
_start = 0;
_size = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Max()
{
if (_size == 0)
ThrowInvalidOperationException();
return MaxSimd();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Min()
{
if (_size == 0)
ThrowInvalidOperationException();
return MinSimd();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Sum()
{
return SumSimd();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Average()
{
if (_size == 0)
ThrowInvalidOperationException();
return SumSimd() / _size;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double MaxSimd()
{
var span = GetSpan();
var vectorSize = Vector<double>.Count;
var maxVector = new Vector<double>(double.MinValue);
int i = 0;
for (; i <= span.Length - vectorSize; i += vectorSize)
{
maxVector = Vector.Max(maxVector, new Vector<double>(span.Slice(i, vectorSize)));
}
double max = double.MinValue;
for (int j = 0; j < vectorSize; j++)
{
max = Math.Max(max, maxVector[j]);
}
for (; i < span.Length; i++)
{
max = Math.Max(max, span[i]);
}
return max;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double MinSimd()
{
var span = GetSpan();
var vectorSize = Vector<double>.Count;
var minVector = new Vector<double>(double.MaxValue);
int i = 0;
for (; i <= span.Length - vectorSize; i += vectorSize)
{
minVector = Vector.Min(minVector, new Vector<double>(span.Slice(i, vectorSize)));
}
double min = double.MaxValue;
for (int j = 0; j < vectorSize; j++)
{
min = Math.Min(min, minVector[j]);
}
for (; i < span.Length; i++)
{
min = Math.Min(min, span[i]);
}
return min;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double SumSimd()
{
var span = GetSpan();
var vectorSize = Vector<double>.Count;
var sumVector = Vector<double>.Zero;
int i = 0;
for (; i <= span.Length - vectorSize; i += vectorSize)
{
sumVector += new Vector<double>(span.Slice(i, vectorSize));
}
double sum = 0;
for (int j = 0; j < vectorSize; j++)
{
sum += sumVector[j];
}
for (; i < span.Length; i++)
{
sum += span[i];
}
return sum;
}
public double[] ToArray()
{
double[] array = new double[_size];
CopyTo(array, 0);
return array;
}
public void ParallelOperation(Func<double[], int, int, double> operation)
{
const int MinimumPartitionSize = 1024;
if (_size < MinimumPartitionSize)
{
var span = GetSpan();
var array = span.ToArray();
operation(array, 0, array.Length);
return;
}
int partitionCount = Environment.ProcessorCount;
int partitionSize = _size / partitionCount;
if (partitionSize < MinimumPartitionSize)
{
partitionCount = Math.Max(1, _size / MinimumPartitionSize);
partitionSize = _size / partitionCount;
}
var buffer = ToArray();
var results = new double[partitionCount];
Parallel.For(0, partitionCount, i =>
{
int start = i * partitionSize;
int length = (i == partitionCount - 1) ? _size - start : partitionSize;
results[i] = operation(buffer, start, length);
});
}
}
+97
View File
@@ -0,0 +1,97 @@
using Microsoft.DotNet.Interactive.Formatting;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace QuanTAlib;
public static class Formatters
{
const string smallfont = "smaller";
const string pad = "18";
public static void Initialize()
{
Formatter.Register<iTValue>((tick, writer) =>
{
var sb = new StringBuilder();
sb.Append("<table style='border-collapse: collapse; text-align: left;'><tr>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0; font-size: {smallfont};'>{tick.Time:yyyy-MM-dd HH:mm:ss}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{tick.Value:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{(tick.IsHot ? "🔥" : "")}</td>");
sb.Append("</tr></table>");
writer.Write(sb.ToString());
}, HtmlFormatter.MimeType);
Formatter.Register<TSeries>((series, writer) =>
{
var sb = new StringBuilder();
sb.Append("<table style='border-collapse: collapse; text-align: right;'></tr>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; text-align: left;'><b>{series.Name}</b></th>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; font-size: {smallfont};'><i>Index</i></th>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; font-size: {smallfont};'><i>Value</i></th>");
sb.Append("</tr>");
for (int i = 0; i < Math.Min(100, series.Count); i++)
{
TValue item = series[i];
sb.Append("<tr>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0; font-size: {smallfont};'>{item.Time:yyyy-MM-dd HH:mm:ss}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0; font-size: {smallfont};'>{i}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{item.Value:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{(item.IsHot ? "🔥" : "")}</td>");
sb.Append("</tr>");
}
sb.Append("</table>");
if (series.Count > 100)
{
sb.Append("<p>Showing first 100 items. Total items: " + series.Count + "</p>");
}
writer.Write(sb.ToString());
}, HtmlFormatter.MimeType);
Formatter.Register<TBar>((bar, writer) =>
{
var sb = new StringBuilder();
sb.Append("<table style='border-collapse: collapse; text-align: right;'><tr>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0; font-size: {smallfont};'>{bar.Time:yyyy-MM-dd HH:mm:ss}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{bar.Open:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{bar.High:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{bar.Low:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{bar.Close:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'> {bar.Volume:F2}</td>");
sb.Append("</tr></table>");
writer.Write(sb.ToString());
}, HtmlFormatter.MimeType);
Formatter.Register<TBarSeries>((series, writer) =>
{
var sb = new StringBuilder();
sb.Append("<table style='border-collapse: collapse; text-align: right;'></tr>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; text-align: left;'><b>{series.Name}</b></th>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; font-size: {smallfont};'><i>Index</i></th>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; font-size: {smallfont};'><i>Open</i></th>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; font-size: {smallfont};'><i>High</i></th>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; font-size: {smallfont};'><i>Low</i></th>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; font-size: {smallfont};'><i>Close</i></th>");
sb.Append($"<th style='padding-left: {pad}px; padding-right: {pad}px; font-size: {smallfont};'><i>Volume</i></th>");
sb.Append("</tr>");
for (int i = 0; i < Math.Min(100, series.Count); i++)
{
TBar item = series[i];
sb.Append("<tr>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0; font-size: {smallfont};'>{item.Time:yyyy-MM-dd HH:mm:ss}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0; font-size: {smallfont};'>{i}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{item.Open:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{item.High:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{item.Low:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{item.Close:F2}</td>");
sb.Append($"<td style='padding-left: {pad}px; padding-right: {pad}px; line-height: 1.0;'>{item.Volume:F2}</td>");
sb.Append("</tr>");
}
sb.Append("</table>");
if (series.Count > 100)
{
sb.Append("<p>Showing first 100 items. Total items: " + series.Count + "</p>");
}
writer.Write(sb.ToString());
}, HtmlFormatter.MimeType);
}
}
+128
View File
@@ -0,0 +1,128 @@
namespace QuanTAlib;
public interface iTBar
{
DateTime Time { get; }
double Open { get; }
double High { get; }
double Low { get; }
double Close { get; }
double Volume { get; }
bool IsNew { get; }
}
public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : iTBar
{
public DateTime Time { get; init; } = Time;
public double Open { get; init; } = Open;
public double High { get; init; } = High;
public double Low { get; init; } = Low;
public double Close { get; init; } = Close;
public double Volume { get; init; } = Volume;
public bool IsNew { get; init; } = IsNew;
public double HL2 => (High + Low) * 0.5;
public double OC2 => (Open + Close) * 0.5;
public double OHL3 => (Open + High + Low) / 3;
public double HLC3 => (High + Low + Close) / 3;
public double OHLC4 => (Open + High + Low + Close) * 0.25;
public double HLCC4 => (High + Low + Close + Close) * 0.25;
public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { }
public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { }
// when TBar casts to double, it returns its Close
public static implicit operator double(TBar bar) => bar.Close;
public static implicit operator DateTime(TBar tv) => tv.Time;
// castings for sloppy people - a single double injected into a TBar, and a single TValue injected into a TBar
public TBar(double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) { }
public TBar(TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) { }
public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]";
}
public delegate void BarSignal(object source, in TBarEventArgs args);
public class TBarEventArgs : EventArgs
{
public TBar Bar { get; }
public TBarEventArgs(TBar bar) { Bar = bar; }
}
public class TBarSeries : List<TBar>
{
private readonly TBar Default = new(DateTime.MinValue, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN);
public TSeries Open;
public TSeries High;
public TSeries Low;
public TSeries Close;
public TSeries Volume;
public TBar Last => Count > 0 ? this[^1] : Default;
public TBar First => Count > 0 ? this[0] : Default;
public int Length => Count;
public string Name { get; set; }
public event BarSignal Pub = delegate { };
public TBarSeries()
{
this.Name = "Bar";
Open = new();
High = new();
Low = new();
Close = new();
Volume = new();
}
public TBarSeries(object source) : this()
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
}
public new virtual void Add(TBar bar)
{
if (bar.IsNew) { base.Add(bar); } else { this[^1] = bar; }
Pub?.Invoke(this, new TBarEventArgs(bar));
Open.Add(bar.Time, bar.Open, IsNew: bar.IsNew, IsHot: true);
High.Add(bar.Time, bar.High, IsNew: bar.IsNew, IsHot: true);
Low.Add(bar.Time, bar.Low, IsNew: bar.IsNew, IsHot: true);
Close.Add(bar.Time, bar.Close, IsNew: bar.IsNew, IsHot: true);
Volume.Add(bar.Time, bar.Volume, IsNew: bar.IsNew, IsHot: true);
}
public void Add(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) =>
this.Add(new TBar(Time, Open, High, Low, Close, Volume, IsNew));
public void Add(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) =>
this.Add(new TBar(DateTime.Now, Open, High, Low, Close, Volume, IsNew));
public void Add(TBarSeries series)
{
if (series == this)
{
// If adding itself, create a copy to avoid modification during enumeration
var copy = new TBarSeries { Name = this.Name };
copy.AddRange(this);
AddRange(copy);
}
else
{
AddRange(series);
}
}
public new virtual void AddRange(IEnumerable<TBar> collection)
{
foreach (var item in collection)
{
Add(item);
}
}
public void Sub(object source, in TBarEventArgs args)
{
Add(args.Bar);
}
}
+110
View File
@@ -0,0 +1,110 @@
namespace QuanTAlib;
public interface iTValue
{
DateTime Time { get; }
double Value { get; }
bool IsNew { get; }
bool IsHot { get; }
}
public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) : iTValue
{
public DateTime Time { get; init; } = Time;
public double Value { get; init; } = Value;
public bool IsNew { get; init; } = IsNew;
public bool IsHot { get; init; } = IsHot;
public DateTime t => Time;
public double v => Value;
public TValue() : this(DateTime.UtcNow, 0) { }
public TValue(double value, bool isNew = true, bool isHot = true) : this(DateTime.UtcNow, value, IsNew: isNew, IsHot: isHot) { }
public static implicit operator double(TValue tv) => tv.Value;
public static implicit operator DateTime(TValue tv) => tv.Time;
public static implicit operator TValue(double value) => new TValue(DateTime.UtcNow, value);
public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}, {Value:F2}, IsNew: {IsNew}, IsHot: {IsHot}]";
}
public delegate void ValueSignal(object source, in ValueEventArgs args);
public class ValueEventArgs : EventArgs
{
public TValue Tick { get; }
public ValueEventArgs(TValue value) { Tick = value; }
}
public class TSeries : List<TValue>
{
private readonly TValue Default = new(DateTime.MinValue, double.NaN);
public IEnumerable<DateTime> t => this.Select(item => item.t);
public IEnumerable<double> v => this.Select(item => item.v);
public TValue Last => Count > 0 ? this[^1] : Default;
public TValue First => Count > 0 ? this[0] : Default;
public int Length => Count;
public string Name { get; set; }
public event ValueSignal Pub = delegate { };
public TSeries() { this.Name = "Data"; }
public TSeries(object source) : this()
{
var pubEvent = source.GetType().GetEvent("Pub");
if (pubEvent != null)
{
/*
var nameProperty = source.GetType().GetProperty("Name");
if (nameProperty != null) {
Name = nameProperty.GetValue(nameProperty)?.ToString()!;
}
*/
pubEvent.AddEventHandler(source, new ValueSignal(Sub));
}
}
public static explicit operator List<double>(TSeries series) => series.Select(item => item.Value).ToList();
public static explicit operator double[](TSeries series) => series.Select(item => item.Value).ToArray();
public new virtual void Add(TValue tick)
{
if (tick.IsNew) { base.Add(tick); }
else { this[^1] = tick; }
Pub?.Invoke(this, new ValueEventArgs(tick));
}
public virtual void Add(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) => this.Add(new TValue(Time, Value, IsNew, IsHot));
public virtual void Add(double Value, bool IsNew = true, bool IsHot = true) => this.Add(new TValue(DateTime.UtcNow, Value, IsNew, IsHot));
public void Add(IEnumerable<double> values)
{
var valueList = values.ToList();
int count = valueList.Count;
DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count);
for (int i = 0; i < count; i++)
{
this.Add(startTime, valueList[i]);
startTime = startTime.AddHours(1);
}
}
public void Add(TSeries series)
{
if (series == this)
{
// If adding itself, create a copy to avoid modification during enumeration
var copy = new TSeries { Name = this.Name };
copy.AddRange(this);
AddRange(copy);
}
else
{
AddRange(series);
}
}
public new virtual void AddRange(IEnumerable<TValue> collection)
{
foreach (var item in collection)
{
Add(item);
}
}
public void Sub(object source, in ValueEventArgs args) { Add(args.Tick); }
}
+70
View File
@@ -0,0 +1,70 @@
using System.CommandLine.Rendering.Views;
namespace QuanTAlib;
public class GbmFeed : TBarSeries
{
private readonly double _mu, _sigma;
private readonly Random _random;
private double _lastClose, _lastHigh, _lastLow;
public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2) : base()
{
_lastClose = _lastHigh = _lastLow = initialPrice;
_mu = mu;
_sigma = sigma;
_random = new Random((int)DateTime.Now.Ticks);
this.Name = $"GBM({_sigma:F2})";
}
public void Add(bool isNew = true) => Add(time: DateTime.Now, isNew: isNew);
public void Add(DateTime time, bool isNew = true) => base.Add(Generate(time, isNew));
public void Add(int count)
{
DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count);
TBar lastBar = new();
for (int i = 0; i < count; i++)
{
Add(startTime, true);
Add(startTime, false);
Add(startTime, false);
startTime = startTime.AddHours(1);
}
}
public TBar Generate(DateTime time, bool isNew = true)
{
double dt = 1.0 / 252;
double drift = (_mu - 0.5 * _sigma * _sigma) * dt;
double diffusion = _sigma * Math.Sqrt(dt) * GenerateNormalRandom();
double newClose = _lastClose * Math.Exp(drift + diffusion);
double open = _lastClose;
double high = Math.Max(_lastHigh, Math.Max(open, newClose) * (1 + _random.NextDouble() * 0.01));
double low = Math.Min(_lastLow, Math.Min(open, newClose) * (1 - _random.NextDouble() * 0.01));
double volume = 1000 + _random.NextDouble() * 1000;
if (isNew)
{
_lastClose = newClose;
}
else
{
high = Math.Max(_lastHigh, high);
low = Math.Min(_lastLow, low);
}
_lastHigh = high;
_lastLow = low;
TBar bar = new(time, open, high, low, newClose, volume, isNew);
return bar;
}
private double GenerateNormalRandom()
{
// Box-Muller transform to generate standard normal random variable
double u1 = 1.0 - _random.NextDouble(); // Uniform(0,1] random doubles
double u2 = 1.0 - _random.NextDouble();
return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2);
}
}
+50
View File
@@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Title>QuanTAlib</Title>
<Product>Library of TA Calculations, Charts and Strategies for Quantower</Product>
<Description>Quantitative Technical Analysis Library in C# for Quantower</Description>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/mihakralj/QuanTAlib</RepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<Authors>Miha Kralj</Authors>
<Copyright>Miha Kralj</Copyright>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageReadmeFile>readme.md</PackageReadmeFile>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<DisableImplicitNamespaceImports>false</DisableImplicitNamespaceImports>
<NeutralLanguage>en-US</NeutralLanguage>
<RootNamespace>QuanTAlib</RootNamespace>
<AssemblyName>QuanTAlib</AssemblyName>
<IsPublishable>True</IsPublishable>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageTags>
Indicators;Stock;Market;Technical;Analysis;Algorithmic;Trading;Trade;Trend;Momentum;Finance;Algorithm;Algo;
AlgoTrading;Financial;Strategy;Chart;Charting;Oscillator;Overlay;Equity;Bitcoin;Crypto;Cryptocurrency;Forex;
Quantitative;Historical;Quotes;
</PackageTags>
<NoWarn>$(NoWarn);NU5104</NoWarn>
</PropertyGroup>
<PropertyGroup>
<PackageIcon>QuanTAlib2.png</PackageIcon>
<PackageIconUrl>https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png</PackageIconUrl>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<None Include="readme.md" Pack="true" PackagePath=""/>
<None Include="..\.github\QuanTAlib2.png" Pack="true" Visible="false" PackagePath=""/>
<PackageReference Include="GitVersion.MsBuild" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.DotNet.Interactive.Formatting" Version="1.0.0-beta.21459.1" />
</ItemGroup>
</Project>
+1
View File
@@ -0,0 +1 @@
**Quan**titative **TA** **lib**rary (QuanTAlib) is a C# library of classess and methods for quantitative technical analysis.
+80
View File
@@ -0,0 +1,80 @@
namespace QuanTAlib;
using System;
using System.Linq;
// Shannon's Entropy calculation
public class Entropy : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
public Entropy(int period) : base()
{
if (period < 2)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for entropy calculation.");
}
Period = period;
WarmupPeriod = 2;
_buffer = new CircularBuffer(period);
Name = $"Entropy(period={period})";
Init();
}
public Entropy(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double entropy = 0;
if (_index > 1) // We need at least two data points for entropy calculation
{
var values = _buffer.GetSpan().ToArray();
int n = values.Length;
// Calculate probabilities
var groupedValues = values.GroupBy(x => x).Select(g => new { Value = g.Key, Count = g.Count() });
// Use the actual count of values for probability calculation
foreach (var group in groupedValues)
{
double probability = (double)group.Count / n;
entropy -= probability * Math.Log2(probability);
}
// Normalize the entropy based on the current number of unique values
int uniqueValueCount = groupedValues.Count();
double maxEntropy = Math.Log2(uniqueValueCount);
entropy = entropy == 0 ? 1 : entropy / maxEntropy;
}
else { entropy = 1; }
IsHot = _buffer.Count >= Period;
return entropy;
}
}
+76
View File
@@ -0,0 +1,76 @@
namespace QuanTAlib;
// Excess kurtosis calculated with Sheskin Algorithm
public class Kurtosis : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
public Kurtosis(int period) : base()
{
if (period < 4)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 4 for kurtosis calculation.");
}
Period = period;
WarmupPeriod = Period - 1;
_buffer = new CircularBuffer(period);
Name = $"Kurtosis(period={period})";
Init();
}
public Kurtosis(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double kurtosis = 0;
if (_buffer.Count > 3)
{
var values = _buffer.GetSpan().ToArray();
double mean = values.Average();
double n = values.Length;
double s2 = 0;
double s4 = 0;
for (int i = 0; i < values.Length; i++)
{
double diff = values[i] - mean;
s2 += diff * diff;
s4 += diff * diff * diff * diff;
}
double variance = s2 / (n - 1);
// Using the Sheskin Algorithm for kurtosis
kurtosis = (n * (n + 1) * s4) / (variance * variance * (n - 3) * (n - 1) * (n - 2))
- (3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)));
}
IsHot = _buffer.Count >= Period;
return kurtosis;
}
}
+80
View File
@@ -0,0 +1,80 @@
using System;
namespace QuanTAlib
{
public class Max : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
private readonly double _halfLife;
private double _currentMax, _p_currentMax;
private int _timeSinceNewMax, _p_timeSinceNewMax;
public Max(int period, double decay = 0) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
if (decay < 0)
{
throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
}
Period = period;
WarmupPeriod = 0;
_buffer = new CircularBuffer(period);
_halfLife = decay * 0.1;
Name = $"Max(period={period}, halfLife={decay:F2})";
Init();
}
public Max(object source, int period, double decay = 0) : this(period, decay)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_currentMax = double.MinValue;
_timeSinceNewMax = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_currentMax = _currentMax;
_lastValidValue = Input.Value;
_index++;
_timeSinceNewMax++;
_p_timeSinceNewMax = _timeSinceNewMax;
}
else
{
_currentMax = _p_currentMax;
_timeSinceNewMax = _p_timeSinceNewMax;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
if (Input.Value >= _currentMax)
{
_currentMax = Input.Value;
_timeSinceNewMax = 0;
}
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period);
_currentMax = _currentMax - decayRate * (_currentMax - _buffer.Average());
_currentMax = Math.Min(_currentMax, _buffer.Max());
IsHot = true;
return _currentMax;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
using System;
using System.Linq;
namespace QuanTAlib
{
public class Median : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
public Median(int period) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
Period = period;
WarmupPeriod = period;
_buffer = new CircularBuffer(period);
Name = $"Median(period={period})";
Init();
}
public Median(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double median;
if (_index >= Period)
{
var sortedValues = _buffer.GetSpan().ToArray();
Array.Sort(sortedValues);
int middleIndex = sortedValues.Length / 2;
if (sortedValues.Length % 2 == 0)
{
median = (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0;
}
else
{
median = sortedValues[middleIndex];
}
}
else
{
median = _buffer.Average(); // Use average until we have enough data points
}
IsHot = _index >= WarmupPeriod;
return median;
}
}
}
+80
View File
@@ -0,0 +1,80 @@
using System;
namespace QuanTAlib
{
public class Min : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
private readonly double _halfLife;
private double _currentMin, _p_currentMin;
private int _timeSinceNewMin, _p_timeSinceNewMin;
public Min(int period, double decay = 0) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
if (decay < 0)
{
throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
}
Period = period;
WarmupPeriod = 0;
_buffer = new CircularBuffer(period);
_halfLife = decay * 0.1;
Name = $"Min(period={period}, halfLife={decay:F2})";
Init();
}
public Min(object source, int period, double decay = 0) : this(period, decay)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_currentMin = double.MaxValue;
_timeSinceNewMin = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_currentMin = _currentMin;
_lastValidValue = Input.Value;
_index++;
_timeSinceNewMin++;
_p_timeSinceNewMin = _timeSinceNewMin;
}
else
{
_currentMin = _p_currentMin;
_timeSinceNewMin = _p_timeSinceNewMin;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
if (Input.Value <= _currentMin)
{
_currentMin = Input.Value;
_timeSinceNewMin = 0;
}
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
_currentMin = _currentMin + decayRate * (_buffer.Average() - _currentMin);
_currentMin = Math.Max(_currentMin, _buffer.Min());
IsHot = true;
return _currentMin;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
namespace QuanTAlib;
public class Mode : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
public Mode(int period) : base()
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
Period = period;
WarmupPeriod = period;
_buffer = new CircularBuffer(period);
Name = $"Mode(period={period})";
Init();
}
public Mode(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double mode;
if (_index >= Period)
{
var values = _buffer.GetSpan().ToArray();
var groupedValues = values.GroupBy(v => v)
.OrderByDescending(g => g.Count())
.ThenBy(g => g.Key)
.ToList();
int maxCount = groupedValues.First().Count();
var modes = groupedValues.TakeWhile(g => g.Count() == maxCount)
.Select(g => g.Key)
.ToList();
mode = modes.Average(); // If there are multiple modes, we return their average
}
else
{
mode = _buffer.Average(); // Use average until we have enough data points
}
IsHot = _index >= WarmupPeriod;
return mode;
}
}
+88
View File
@@ -0,0 +1,88 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Percentile : AbstractBase
{
public readonly int Period;
public readonly double Percent;
private CircularBuffer _buffer;
public Percentile(int period, double percent) : base()
{
if (period < 2)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for percentile calculation.");
}
if (percent < 0 || percent > 100)
{
throw new ArgumentOutOfRangeException(nameof(percent), "Percent must be between 0 and 100.");
}
Period = period;
Percent = percent;
WarmupPeriod = 2;
_buffer = new CircularBuffer(period);
Name = $"Percentile(period={period}, percent={percent})";
Init();
}
public Percentile(object source, int period, double percent) : this(period, percent)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double result;
if (_buffer.Count >= Period)
{
var values = _buffer.GetSpan().ToArray();
Array.Sort(values);
double position = (Percent / 100.0) * (values.Length - 1);
int lowerIndex = (int)Math.Floor(position);
int upperIndex = (int)Math.Ceiling(position);
if (lowerIndex == upperIndex)
{
result = values[lowerIndex];
}
else
{
// Interpolate between the two nearest values
double lowerValue = values[lowerIndex];
double upperValue = values[upperIndex];
double fraction = position - lowerIndex;
result = lowerValue + (upperValue - lowerValue) * fraction;
}
}
else
{
// Use average for insufficient data, like the Median class
result = _buffer.Average();
}
IsHot = _buffer.Count >= Period;
return result;
}
}
+82
View File
@@ -0,0 +1,82 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Skew : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
public Skew(int period) : base()
{
if (period < 3)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 3 for skewness calculation.");
}
Period = period;
WarmupPeriod = 3;
_buffer = new CircularBuffer(period);
Name = $"Skew(period={period})";
Init();
}
public Skew(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double skew = 0;
if (_buffer.Count >= 3) // We need at least 3 data points for skewness
{
var values = _buffer.GetSpan().ToArray();
double mean = values.Average();
double n = values.Length;
double sumCubedDeviations = 0;
double sumSquaredDeviations = 0;
foreach (var value in values)
{
double deviation = value - mean;
sumCubedDeviations += Math.Pow(deviation, 3);
sumSquaredDeviations += Math.Pow(deviation, 2);
}
// Calculate sample skewness using the adjusted Fisher-Pearson standardized moment coefficient
double m3 = sumCubedDeviations / n;
double m2 = sumSquaredDeviations / n;
double s3 = Math.Pow(m2, 1.5);
if (s3 != 0) // Avoid division by zero
{
skew = (Math.Sqrt(n * (n - 1)) / (n - 2)) * (m3 / s3);
}
}
IsHot = _buffer.Count >= Period;
return skew;
}
}
+69
View File
@@ -0,0 +1,69 @@
using System;
using System.Linq;
namespace QuanTAlib
{
public class Stddev : AbstractBase
{
public readonly int Period;
public readonly bool IsPopulation;
private CircularBuffer _buffer;
public Stddev(int period, bool isPopulation = false) : base()
{
if (period < 2)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
Period = period;
IsPopulation = isPopulation;
WarmupPeriod = 0;
_buffer = new CircularBuffer(period);
Name = $"Stddev(period={period}, population={isPopulation})";
Init();
}
public Stddev(object source, int period, bool isPopulation = false) : this(period, isPopulation)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double stddev = 0;
if (_buffer.Count > 1)
{
var values = _buffer.GetSpan().ToArray();
double mean = values.Average();
double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
double variance = sumOfSquaredDifferences / divisor;
stddev = Math.Sqrt(variance);
}
IsHot = true; // StdDev calc is valid from bar 1
return stddev;
}
}
}
+68
View File
@@ -0,0 +1,68 @@
using System;
using System.Linq;
namespace QuanTAlib
{
public class Variance : AbstractBase
{
public readonly int Period;
public readonly bool IsPopulation;
private CircularBuffer _buffer;
public Variance(int period, bool isPopulation = false) : base()
{
if (period < 2)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
Period = period;
IsPopulation = isPopulation;
WarmupPeriod = 0;
_buffer = new CircularBuffer(period);
Name = $"Variance(period={period}, population={isPopulation})";
Init();
}
public Variance(object source, int period, bool isPopulation = false) : this(period, isPopulation)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double variance = 0;
if (_buffer.Count > 1)
{
var values = _buffer.GetSpan().ToArray();
double mean = values.Average();
double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
variance = sumOfSquaredDifferences / divisor;
}
IsHot = true;
return variance;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Zscore : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
public Zscore(int period) : base()
{
if (period < 2)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for Z-score calculation.");
}
Period = period;
WarmupPeriod = 2;
_buffer = new CircularBuffer(period);
Name = $"ZScore(period={period})";
Init();
}
public Zscore(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double zScore = 0;
if (_buffer.Count >= 2) // We need at least 2 data points for Z-score
{
var values = _buffer.GetSpan().ToArray();
double mean = values.Average();
double n = values.Length;
double sumSquaredDeviations = values.Sum(x => Math.Pow(x - mean, 2));
double standardDeviation = Math.Sqrt(sumSquaredDeviations / (n - 1)); // Sample standard deviation
if (standardDeviation != 0) // Avoid division by zero
{
zScore = (Input.Value - mean) / standardDeviation;
}
}
IsHot = _buffer.Count >= Period;
return zScore;
}
}