first iteration

This commit is contained in:
Miha Kralj
2025-11-25 20:40:46 -08:00
parent b5881b9bb4
commit 33ffd3a37a
594 changed files with 117007 additions and 80111 deletions
-187
View File
@@ -1,187 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// AFIRMA: Adaptive FIR Moving Average
/// A finite impulse response (FIR) filter that combines windowing functions with sinc-based filtering.
/// Provides superior noise reduction while maintaining signal fidelity through adaptive filtering.
/// </summary>
/// <remarks>
/// Implementation:
/// Original implementation based on FIR filter design principles
/// </remarks>
public class Afirma : AbstractBase
{
public enum WindowType
{
Rectangular,
Hanning1,
Hanning2,
Blackman,
BlackmanHarris
}
private readonly int Periods;
private readonly int Taps;
private readonly WindowType Window;
private readonly CircularBuffer _buffer;
private readonly double[] _weights;
private readonly double _wsum;
private readonly double[] _armaBuffer;
private readonly int _n;
private readonly double _sx2, _sx3, _sx4, _sx5, _sx6, _den;
private readonly double _twoPi = 2.0 * Math.PI;
private readonly double _fourPi = 4.0 * Math.PI;
private readonly double _sixPi = 6.0 * Math.PI;
/// <param name="periods">The number of periods for the sinc filter calculation.</param>
/// <param name="taps">The number of filter taps (filter length). Must be odd number.</param>
/// <param name="window">The type of window function to apply (Rectangular, Hanning1, Hanning2, Blackman, or BlackmanHarris).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when periods or taps is less than 1.</exception>
public Afirma(int periods, int taps, WindowType window)
{
if (periods < 1)
{
throw new ArgumentOutOfRangeException(nameof(periods), "Periods must be greater than or equal to 1.");
}
if (taps < 1)
{
throw new ArgumentOutOfRangeException(nameof(taps), "Taps must be greater than or equal to 1.");
}
Periods = periods;
Taps = taps;
Window = window;
WarmupPeriod = taps;
_buffer = new CircularBuffer(taps);
_weights = new double[taps];
_wsum = CalculateWeights();
_armaBuffer = new double[taps];
_n = (Taps - 1) / 2;
// Precalculate least squares coefficients
_sx2 = ((2 * _n) + 1) / 3.0;
_sx3 = _n * (_n + 1) / 2.0;
_sx4 = _sx2 * ((3 * _n * _n) + (3 * _n) - 1) / 5.0;
_sx5 = _sx3 * ((2 * _n * _n) + (2 * _n) - 1) / 3.0;
_sx6 = _sx2 * ((3 * Math.Pow(_n, 3) * (_n + 2)) - (3 * _n) + 1) / 7.0;
_den = (_sx6 * _sx4 / _sx5) - _sx5;
Name = "Afirma";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="periods">The number of periods for the sinc filter calculation.</param>
/// <param name="taps">The number of filter taps (filter length). Must be odd number.</param>
/// <param name="window">The type of window function to apply (Rectangular, Hanning1, Hanning2, Blackman, or BlackmanHarris).</param>
public Afirma(object source, int periods, int taps, WindowType window) : this(periods, taps, window)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateSincWeight(double x)
{
return Math.Abs(x) < 1e-10 ? 1.0 : Math.Sin(x) / x;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double GetWindowWeight(int k, int tapsMinusOne)
{
switch (Window)
{
case WindowType.Rectangular:
return 1.0;
case WindowType.Hanning1:
return 0.50 - (0.50 * Math.Cos(_twoPi * k / tapsMinusOne));
case WindowType.Hanning2:
return 0.54 - (0.46 * Math.Cos(_twoPi * k / tapsMinusOne));
case WindowType.Blackman:
return 0.42 - (0.50 * Math.Cos(_twoPi * k / tapsMinusOne)) + (0.08 * Math.Cos(_fourPi * k / tapsMinusOne));
case WindowType.BlackmanHarris:
return 0.35875 - (0.48829 * Math.Cos(_twoPi * k / tapsMinusOne)) +
(0.14128 * Math.Cos(_fourPi * k / tapsMinusOne)) -
(0.01168 * Math.Cos(_sixPi * k / tapsMinusOne));
default:
return 1.0;
}
}
protected override double Calculation()
{
ManageState(IsNew);
_buffer.Add(Input.Value, Input.IsNew);
if (_index >= Taps)
{
CalculateAdaptiveCoefficients();
}
double result = 0.0;
for (int k = 0; k < Taps; k++)
{
result += _buffer[k] * _weights[k];
}
IsHot = _index >= WarmupPeriod;
return result / _wsum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CalculateAdaptiveCoefficients()
{
double a0 = _buffer[_n];
double a1 = _buffer[_n] - _buffer[_n + 1];
double sx2y = 0.0;
double sx3y = 0.0;
for (int i = 0; i <= _n; i++)
{
double i2 = i * i;
sx2y += i2 * _buffer[_n - i];
sx3y += i2 * i * _buffer[_n - i];
}
sx2y = 2.0 * sx2y / _n / (_n + 1);
sx3y = 2.0 * sx3y / _n / (_n + 1);
double p = sx2y - (a0 * _sx2) - (a1 * _sx3);
double q = sx3y - (a0 * _sx3) - (a1 * _sx4);
double a2 = ((p * _sx6 / _sx5) - q) / _den;
double a3 = ((q * _sx4 / _sx5) - p) / _den;
for (int k = 0; k <= _n; k++)
{
double k2 = k * k;
_armaBuffer[_n - k] = a0 + (k * a1) + (k2 * a2) + (k2 * k * a3);
}
}
private double CalculateWeights()
{
double wsum = 0.0;
double centerTap = (Taps - 1) / 2.0;
int tapsMinusOne = Taps - 1;
for (int k = 0; k < Taps; k++)
{
double windowWeight = GetWindowWeight(k, tapsMinusOne);
double x = Math.PI * (k - centerTap) / Periods;
double sincWeight = CalculateSincWeight(x);
_weights[k] = windowWeight * sincWeight;
wsum += _weights[k];
}
return wsum;
}
}
-115
View File
@@ -1,115 +0,0 @@
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>
/// 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)
{
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;
}
}
}
-141
View File
@@ -1,141 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Convolution: A fundamental signal processing operation that combines two signals to form a third signal
/// Applies a custom kernel (weight array) to the input data through convolution, allowing for flexible
/// filtering operations. The kernel is automatically normalized to ensure consistent output scaling.
/// </summary>
/// <remarks>
/// Implementation:
/// Based on standard discrete convolution principles from signal processing
/// </remarks>
public class Convolution : AbstractBase
{
private readonly double[] _kernel;
private readonly int _kernelSize;
private readonly CircularBuffer _buffer;
private readonly double[] _normalizedKernel;
private int _activeLength;
/// <param name="kernel">Array of weights defining the convolution operation. The length of this array determines the filter's window size.</param>
/// <exception cref="ArgumentException">Thrown when kernel is null or empty.</exception>
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();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="kernel">Array of weights defining the convolution operation.</param>
public Convolution(object source, double[] kernel) : this(kernel)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_buffer.Clear();
System.Array.Copy(_kernel, _normalizedKernel, _kernelSize);
_activeLength = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_activeLength = System.Math.Min(_index, _kernelSize);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void NormalizeKernel()
{
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 >= double.Epsilon) ? sum : _activeLength;
double invNormFactor = 1.0 / normalizationFactor;
for (int i = 0; i < _activeLength; i++)
{
_normalizedKernel[i] = _kernel[i] * invNormFactor;
}
// Set the rest of the normalized kernel to zero
if (_activeLength < _kernelSize)
{
System.Array.Clear(_normalizedKernel, _activeLength, _kernelSize - _activeLength);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double ConvolveBuffer()
{
double sum = 0;
var bufferSpan = _buffer.GetSpan();
int offset = _activeLength - 1;
// Unroll the loop for better performance when possible
int i = 0;
while (i <= offset - 3)
{
sum += (bufferSpan[offset - i] * _normalizedKernel[i]) +
(bufferSpan[offset - (i + 1)] * _normalizedKernel[i + 1]) +
(bufferSpan[offset - (i + 2)] * _normalizedKernel[i + 2]) +
(bufferSpan[offset - (i + 3)] * _normalizedKernel[i + 3]);
i += 4;
}
// Handle remaining elements
while (i < _activeLength)
{
sum += bufferSpan[offset - i] * _normalizedKernel[i];
i++;
}
return sum;
}
}
-102
View File
@@ -1,102 +0,0 @@
using System.Runtime.CompilerServices;
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>
/// Sources:
/// https://en.wikipedia.org/wiki/Double_exponential_moving_average
/// 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
{
private readonly double _k;
private readonly double _epsilon = 1e-10;
private double _lastEma1, _p_lastEma1;
private double _lastEma2, _p_lastEma2;
private double _e, _p_e;
public Dema(int period)
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_k = 2.0 / (period + 1);
Name = "Dema";
double percentile = 0.85; //targeting 85th percentile of correctness of converging EMA
WarmupPeriod = (int)System.Math.Ceiling(-period * System.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));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_e = 1.0;
_lastEma1 = 0;
_lastEma2 = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateEma(double input, double lastEma)
{
return (_k * (input - lastEma)) + lastEma;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Compensator for early EMA values
_e = (_e > _epsilon) ? (1 - _k) * _e : 0;
double invE = (_e > _epsilon) ? 1 / (1 - _e) : 1;
// Calculate EMAs
double ema1 = CalculateEma(Input.Value, _lastEma1);
double compensatedEma1 = ema1 * invE;
double ema2 = CalculateEma(compensatedEma1, _lastEma2);
// Store values for next iteration
_lastEma1 = ema1;
_lastEma2 = ema2;
// Calculate final DEMA
double result = (2 * compensatedEma1) - (ema2 * invE);
IsHot = _index >= WarmupPeriod;
return result;
}
}
-166
View File
@@ -1,166 +0,0 @@
using System.Runtime.CompilerServices;
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>
/// 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 CircularBuffer _buffer;
private readonly double _c2, _c3;
private readonly double _scaleFactor;
private readonly double _periodRecip; // 1/_period
private readonly double _scaleByPeriod; // 5/_period
private readonly double _c1Half; // _c1/2
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, double scaleFactor = 0.9)
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
if (scaleFactor <= 0 || scaleFactor > 1)
{
throw new ArgumentOutOfRangeException(nameof(scaleFactor), "Scale factor must be between 0 and 1 (exclusive).");
}
_periodRecip = 1.0 / period;
_scaleFactor = scaleFactor;
_buffer = new CircularBuffer(period);
// SuperSmoother filter coefficients
double halfPeriod = 0.5 * period;
double a1 = System.Math.Exp(-1.414 * System.Math.PI / halfPeriod);
double b1 = 2.0 * a1 * System.Math.Cos(1.414 * System.Math.PI / halfPeriod);
_c2 = b1;
_c3 = -a1 * a1;
double _c1 = 1.0 - _c2 - _c3;
_c1Half = _c1 * 0.5;
_scaleByPeriod = 5.0 / period;
Name = "Dsma";
WarmupPeriod = (int)(period * 1.5); // A conservative estimate
Init();
}
public Dsma(object source, int period, double scaleFactor = 0.9) : this(period, scaleFactor)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_lastDsma = 0;
_filt = _filt1 = _filt2 = 0;
_zeros = _zeros1 = 0;
_isInit = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateSuperSmootherFilter()
{
return (_c1Half * (_zeros + _zeros1)) + (_c2 * _filt1) + (_c3 * _filt2);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateAdaptiveAlpha(double scaledFilt)
{
double alpha = _scaleFactor * System.Math.Abs(scaledFilt) * _scaleByPeriod;
return System.Math.Clamp(alpha, 0.1, 1.0);
}
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 = CalculateSuperSmootherFilter();
// Update buffer for RMS calculation
double filtSquared = _filt * _filt;
_buffer.Add(filtSquared, Input.IsNew);
// Compute RMS (Root Mean Square)
double rms = System.Math.Sqrt(_buffer.Sum() * _periodRecip);
// Rescale Filt in terms of Standard Deviations and calculate adaptive alpha
double scaledFilt = rms > 0 ? _filt / rms : 0;
double alpha = CalculateAdaptiveAlpha(scaledFilt);
// 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;
}
}
-85
View File
@@ -1,85 +0,0 @@
using System.Runtime.CompilerServices;
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>
/// 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 Wma _innerWma;
private readonly Wma _outerWma;
public Dwma(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_innerWma = new Wma(period);
_outerWma = new Wma(period);
Name = "Dwma";
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));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_innerWma.Init();
_outerWma.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override double GetLastValid()
{
return _lastValidValue;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Calculate inner WMA
var innerResult = _innerWma.Calc(Input);
// Calculate outer WMA using the result of inner WMA
var outerResult = _outerWma.Calc(innerResult);
IsHot = _index >= WarmupPeriod;
return outerResult.Value;
}
}
-150
View File
@@ -1,150 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// EMA: Exponential Moving Average
/// </summary>
/// <remarks>
/// 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)
///
/// 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
{
private readonly int _period;
private readonly double _k;
private readonly bool _useSma;
private readonly double _epsilon = 1e-10;
private CircularBuffer _sma;
private double _lastEma, _p_lastEma;
private double _e, _p_e;
private bool _isInit, _p_isInit;
/// <summary>
/// Initializes a new instance of the Ema class with a specified period.
/// </summary>
/// <param name="period">The period for EMA calculation.</param>
/// <param name="useSma">Whether to use SMA for initial values. Default is true.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
public Ema(int period, bool useSma = true)
{
if (period < 1)
{
throw new System.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)System.Math.Ceiling(System.Math.Log(0.05) / System.Math.Log(1 - _k)); //95th percentile
Init();
}
/// <summary>
/// Initializes a new instance of the Ema class with a specified alpha value.
/// </summary>
/// <param name="alpha">The smoothing factor for EMA calculation.</param>
public Ema(double alpha)
{
_k = alpha;
_useSma = false;
_sma = new(1);
Name = "Ema";
_period = 1;
WarmupPeriod = (int)System.Math.Ceiling(System.Math.Log(0.05) / System.Math.Log(1 - _k)); //95th percentile
Init();
}
/// <summary>
/// Initializes a new instance of the Ema class with a specified source and period.
/// </summary>
/// <param name="source">The source object for event subscription.</param>
/// <param name="period">The period for EMA calculation.</param>
/// <param name="useSma">Whether to use SMA for initial values. Default is true.</param>
public Ema(object source, int period, bool useSma = true) : this(period, useSma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_e = 1.0;
_lastEma = 0;
_isInit = false;
_p_isInit = false;
_sma = new(_period);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateEma(double input, double lastEma)
{
return (_k * (input - lastEma)) + lastEma;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CompensateEma(double ema)
{
return (_useSma || _e <= _epsilon) ? ema : ema / (1 - _e);
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double ema;
if (!_isInit && _useSma)
{
_sma.Add(Input.Value, Input.IsNew);
ema = _sma.Average();
if (_index >= _period)
{
_isInit = true;
}
}
else
{
// Compensator for early EMA values
_e = (_e > _epsilon) ? (1 - _k) * _e : 0;
ema = CalculateEma(Input.Value, _lastEma);
ema = CompensateEma(ema);
}
_lastEma = ema;
IsHot = _index >= WarmupPeriod;
return ema;
}
}
-119
View File
@@ -1,119 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// EPMA: Endpoint Moving Average
/// A moving average that uses a specialized convolution kernel to emphasize recent price movements
/// while maintaining a connection to historical data. The weights decrease linearly with a focus
/// on endpoints.
/// </summary>
/// <remarks>
/// The EPMA uses a unique weighting scheme where:
/// - The most recent price gets the highest weight: (2 * period - 1)
/// - Each previous price gets a weight reduced by 3: (2 * period - 1) - 3i
/// - Weights are normalized to sum to 1
///
/// Key characteristics:
/// - Emphasizes recent price movements more than traditional moving averages
/// - Maintains some influence from historical data
/// - Uses convolution for efficient calculation
/// - Provides better endpoint preservation than simple moving averages
///
/// Implementation:
/// Original implementation based on convolution principles
/// </remarks>
public class Epma : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
/// <param name="period">The number of data points used in the EPMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Epma(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
double[] _baseKernel = GenerateKernel(_period);
_convolution = new Convolution(_baseKernel);
Name = "Epma";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the EPMA calculation.</param>
public Epma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_convolution.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateKernelSum(int period)
{
// Using arithmetic sequence sum formula: n(a1 + an)/2
// where a1 = (2p-1) and an = (2p-1) - 3(n-1)
double firstTerm = (2 * period) - 1;
double lastTerm = firstTerm - (3 * (period - 1));
return period * (firstTerm + lastTerm) * 0.5;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
var convolutionResult = _convolution.Calc(Input);
double result = convolutionResult.Value;
// Adjust for partial periods during warmup
if (_index < _period)
{
result *= CalculateKernelSum(_period) / CalculateKernelSum(_index);
}
IsHot = _index >= WarmupPeriod;
return result;
}
/// <summary>
/// Generates the convolution kernel for the EPMA calculation.
/// </summary>
/// <param name="period">The period for which to generate the kernel.</param>
/// <returns>An array of normalized weights for the convolution operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
double weightSum = CalculateKernelSum(period);
double invWeightSum = 1.0 / weightSum;
double baseWeight = (2 * period) - 1;
for (int i = 0; i < period; i++)
{
kernel[i] = (baseWeight - (3 * i)) * invWeightSum;
}
return kernel;
}
}
-146
View File
@@ -1,146 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// FRAMA: Fractal Adaptive Moving Average
/// An adaptive moving average that adjusts its smoothing factor based on the fractal dimension
/// of the price series. FRAMA automatically adapts to market conditions, becoming more responsive
/// during trends and more stable during sideways markets.
/// </summary>
/// <remarks>
/// The FRAMA algorithm works by:
/// 1. Calculating the fractal dimension of the price series
/// 2. Using this dimension to determine the optimal alpha (smoothing factor)
/// 3. Applying an EMA with the adaptive alpha
///
/// Key characteristics:
/// - Self-adaptive to market conditions
/// - Reduces lag during trending periods
/// - Increases smoothing during sideways markets
/// - Uses fractal geometry principles for market analysis
///
/// Sources:
/// John Ehlers - "FRAMA: A Trend-Following Indicator"
/// https://www.mesasoftware.com/papers/FRAMA.pdf
/// </remarks>
public class Frama : AbstractBase
{
private readonly int _period;
private readonly int _halfPeriod;
private readonly double _periodRecip;
private readonly double _halfPeriodRecip;
private readonly double _log2 = System.Math.Log(2);
private readonly double _epsilon = double.Epsilon;
private readonly CircularBuffer _buffer;
private double _lastFrama;
private double _prevLastFrama;
/// <param name="period">The number of periods used for fractal dimension calculation. Must be at least 2.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 2.</exception>
public Frama(int period)
{
if (period < 2)
throw new System.ArgumentException("Period must be at least 2", nameof(period));
_period = period;
_halfPeriod = period / 2;
_periodRecip = 1.0 / period;
_halfPeriodRecip = 1.0 / _halfPeriod;
_buffer = new CircularBuffer(period);
WarmupPeriod = period;
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used for fractal dimension calculation.</param>
public Frama(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_buffer.Clear();
_lastFrama = 0;
_prevLastFrama = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_prevLastFrama = _lastFrama;
_index++;
}
else
{
_lastFrama = _prevLastFrama;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void UpdateMinMax(double price, ref double high, ref double low)
{
high = System.Math.Max(high, price);
low = System.Math.Min(low, price);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateAlpha(double dimension)
{
double alpha = System.Math.Exp(-4.6 * (dimension - 1));
return System.Math.Clamp(alpha, 0.01, 1.0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override double GetLastValid()
{
return _lastFrama;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
if (_buffer.Count < _period)
{
_lastFrama = _buffer.Average();
return _lastFrama;
}
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];
UpdateMinMax(price, ref hh, ref ll);
if (i < _halfPeriod)
{
UpdateMinMax(price, ref hh1, ref ll1);
}
else
{
UpdateMinMax(price, ref hh2, ref ll2);
}
}
double n1 = (hh - ll) * _periodRecip;
double n2 = (hh1 - ll1 + hh2 - ll2) * _halfPeriodRecip;
double dimension = (System.Math.Log(n2 + _epsilon) - System.Math.Log(n1 + _epsilon)) / _log2;
double alpha = CalculateAlpha(dimension);
_lastFrama = (alpha * (Input.Value - _lastFrama)) + _lastFrama;
IsHot = _index >= WarmupPeriod;
return _lastFrama;
}
}
-113
View File
@@ -1,113 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// FWMA: Fibonacci Weighted Moving Average
/// A moving average that uses Fibonacci numbers as weights in its calculation. The weights
/// are arranged in reverse order so that recent prices receive higher weights corresponding
/// to larger Fibonacci numbers.
/// </summary>
/// <remarks>
/// The FWMA calculation process:
/// 1. Generates a Fibonacci sequence up to the specified period
/// 2. Reverses the sequence to give higher weights to recent prices
/// 3. Normalizes the weights to sum to 1
/// 4. Applies the weights through convolution
///
/// Key characteristics:
/// - Uses Fibonacci sequence for weight distribution
/// - Recent prices receive higher weights
/// - Natural progression of weights based on the golden ratio
/// - Implemented using efficient convolution operations
///
/// Implementation:
/// Original implementation based on Fibonacci sequence principles
/// </remarks>
public class Fwma : AbstractBase
{
private readonly Convolution _convolution;
/// <param name="period">The number of data points used in the FWMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Fwma(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
double[] _kernel = GenerateKernel(period);
_convolution = new Convolution(_kernel);
Name = "Fwma";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the FWMA calculation.</param>
public Fwma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Generates the Fibonacci-based convolution kernel for the FWMA calculation.
/// </summary>
/// <param name="period">The period for which to generate the kernel.</param>
/// <returns>An array of normalized Fibonacci-based weights for the convolution operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
double[] fibSeries = new double[period];
// Generate Fibonacci series with running sum
fibSeries[0] = fibSeries[1] = 1;
double weightSum = 2.0; // Initial sum for first two Fibonacci numbers
for (int i = 2; i < period; i++)
{
fibSeries[i] = fibSeries[i - 1] + fibSeries[i - 2];
weightSum += fibSeries[i];
}
// Calculate inverse of weight sum for normalization
double invWeightSum = 1.0 / weightSum;
// Reverse and normalize the series in one pass
for (int i = 0; i < period; i++)
{
kernel[i] = fibSeries[period - 1 - i] * invWeightSum;
}
return kernel;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_convolution.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
var convolutionResult = _convolution.Calc(Input);
IsHot = _index >= WarmupPeriod;
return convolutionResult.Value;
}
}
-113
View File
@@ -1,113 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// GMA: Gaussian Moving Average
/// A moving average that uses weights based on the Gaussian (normal) distribution curve.
/// This creates a smooth, bell-shaped weighting scheme that gives maximum weight to the
/// center of the period and gradually decreasing weights towards the edges.
/// </summary>
/// <remarks>
/// The GMA calculation process:
/// 1. Creates a Gaussian distribution of weights centered on the period
/// 2. Normalizes the weights to sum to 1
/// 3. Applies the weights through convolution
///
/// Key characteristics:
/// - Smooth, symmetric weight distribution
/// - Natural bell curve weighting
/// - Reduces noise while preserving signal characteristics
/// - Less sensitive to outliers than simple moving averages
/// - Implemented using efficient convolution operations
///
/// Implementation:
/// Based on Gaussian distribution principles from statistics
/// </remarks>
public class Gma : AbstractBase
{
private readonly Convolution _convolution;
/// <param name="period">The number of data points used in the GMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Gma(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
double[] _kernel = GenerateKernel(period);
_convolution = new Convolution(_kernel);
Name = "Gma";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the GMA calculation.</param>
public Gma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Generates the Gaussian-based convolution kernel for the GMA calculation.
/// </summary>
/// <param name="period">The period for which to generate the kernel.</param>
/// <param name="sigma">The standard deviation parameter controlling the spread of the Gaussian curve. Default is 1.0.</param>
/// <returns>An array of normalized Gaussian-based weights for the convolution operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double[] GenerateKernel(int period, double sigma = 1.0)
{
double[] kernel = new double[period];
double weightSum = 0;
int center = period / 2;
double centerRecip = 1.0 / center;
double sigmaSquared2 = 2.0 * sigma * sigma;
// Calculate weights and sum in one pass
for (int i = 0; i < period; i++)
{
double x = (i - center) * centerRecip;
kernel[i] = System.Math.Exp(-(x * x) / sigmaSquared2);
weightSum += kernel[i];
}
// Normalize using multiplication instead of division
double invWeightSum = 1.0 / weightSum;
for (int i = 0; i < period; i++)
{
kernel[i] *= invWeightSum;
}
return kernel;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_convolution.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
var convolutionResult = _convolution.Calc(Input);
IsHot = _index >= WarmupPeriod;
return convolutionResult.Value;
}
}
-122
View File
@@ -1,122 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// HMA: Hull Moving Average
/// A moving average designed by Alan Hull to reduce lag while maintaining smoothness.
/// It combines weighted moving averages of different periods to achieve better
/// responsiveness to price changes while minimizing noise.
/// </summary>
/// <remarks>
/// The HMA calculation process:
/// 1. Calculate WMA with period n/2
/// 2. Calculate WMA with period n
/// 3. Calculate difference: 2*WMA(n/2) - WMA(n)
/// 4. Apply final WMA with period sqrt(n) to the difference
///
/// Key characteristics:
/// - Significantly reduced lag compared to traditional moving averages
/// - Maintains smoothness despite the reduced lag
/// - Responds more quickly to price changes
/// - Better at identifying trend changes
/// - Uses weighted moving averages for all calculations
///
/// Sources:
/// Alan Hull - "Better Trading with Hull Moving Average"
/// https://alanhull.com/hull-moving-average
/// </remarks>
public class Hma : AbstractBase
{
private readonly Convolution _wmaHalf, _wmaFull, _wmaFinal;
/// <param name="period">The number of data points used in the HMA calculation. Must be at least 2.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 2.</exception>
public Hma(int period)
{
if (period < 2)
{
throw new System.ArgumentException("Period must be greater than or equal to 2.", nameof(period));
}
int _sqrtPeriod = (int)System.Math.Sqrt(period);
// Generate all kernels once
double[] _kernelHalf = GenerateWmaKernel(period / 2);
double[] _kernelFull = GenerateWmaKernel(period);
double[] _kernelFinal = GenerateWmaKernel(_sqrtPeriod);
// Initialize convolutions with pre-generated kernels
_wmaHalf = new Convolution(_kernelHalf);
_wmaFull = new Convolution(_kernelFull);
_wmaFinal = new Convolution(_kernelFinal);
Name = "Hma";
WarmupPeriod = period + _sqrtPeriod - 1;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the HMA calculation.</param>
public Hma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Generates the weighted moving average kernel for the HMA calculation.
/// </summary>
/// <param name="period">The period for which to generate the kernel.</param>
/// <returns>An array of linearly weighted values for the convolution operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double[] GenerateWmaKernel(int period)
{
double[] kernel = new double[period];
double weightSum = period * (period + 1) * 0.5; // Multiply by 0.5 instead of dividing by 2
double invWeightSum = 1.0 / weightSum;
for (int i = 0; i < period; i++)
{
kernel[i] = (period - i) * invWeightSum;
}
return kernel;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_wmaHalf.Init();
_wmaFull.Init();
_wmaFinal.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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.0 * wmaHalfResult) - wmaFullResult;
// Calculate final WMA
var finalInput = new TValue(Input.Time, intermediateResult, Input.IsNew);
double result = _wmaFinal.Calc(finalInput).Value;
IsHot = _index >= WarmupPeriod;
return result;
}
}
-184
View File
@@ -1,184 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// HTIT: Hilbert Transform Instantaneous Trendline
/// A sophisticated moving average that uses the Hilbert Transform to identify the dominant cycle
/// period in price data and create a smooth trend line. It adapts to the market's natural cycles
/// and provides a dynamic moving average.
/// </summary>
/// <remarks>
/// The HTIT calculation process:
/// 1. Uses a Hilbert Transform to decompose price into in-phase and quadrature components
/// 2. Employs a homodyne discriminator to determine the dominant cycle period
/// 3. Applies smoothing based on the detected cycle period
/// 4. Creates a trend line that automatically adapts to market cycles
///
/// Key characteristics:
/// - Automatically adapts to market cycles
/// - Reduces lag by using cycle analysis
/// - Complex signal processing for better trend identification
/// - Combines multiple digital signal processing techniques
///
/// Sources:
/// John Ehlers - "Cycle Analytics for Traders"
///
/// Note: This implementation is currently under development and may not pass
/// all consistency tests.
/// </remarks>
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 const double ALPHA = 0.2;
private const double BETA = 0.8;
private const double TWO_PI = 2.0 * System.Math.PI;
private const double MIN_PERIOD = 6.0;
private const double MAX_PERIOD = 50.0;
private const double PERIOD_UPPER_LIMIT = 1.5;
private const double PERIOD_LOWER_LIMIT = 0.67;
private double _lastPd = 0;
private double _p_lastPd = 0;
public Htit()
{
Name = "Htit";
WarmupPeriod = 12;
}
public Htit(object source) : this()
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastPd = _lastPd;
_index++;
}
else
{
_lastPd = _p_lastPd;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateSmoothedPrice(double p0, double p1, double p2, double p3)
{
return ((4.0 * p0) + (3.0 * p1) + (2.0 * p2) + p3) * 0.1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateHilbertTransform(double b0, double b2, double b4, double b6, double adj)
{
return ((0.0962 * (b0 - b6)) + (0.5769 * (b2 - b4))) * adj;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double ClampPeriod(double pd, double lastPd)
{
pd = pd > PERIOD_UPPER_LIMIT * lastPd ? PERIOD_UPPER_LIMIT * lastPd : pd;
pd = pd < PERIOD_LOWER_LIMIT * lastPd ? PERIOD_LOWER_LIMIT * lastPd : pd;
return System.Math.Clamp(pd, MIN_PERIOD, MAX_PERIOD);
}
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 = CalculateSmoothedPrice(_priceBuffer[0], _priceBuffer[1], _priceBuffer[2], _priceBuffer[3]);
_spBuffer.Add(sp, Input.IsNew);
double dt = CalculateHilbertTransform(_spBuffer[0], _spBuffer[2], _spBuffer[4], _spBuffer[6], adj);
_dtBuffer.Add(dt, Input.IsNew);
// In-phase and quadrature
double q1 = CalculateHilbertTransform(_dtBuffer[0], _dtBuffer[2], _dtBuffer[4], _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 = CalculateHilbertTransform(_i1Buffer[0], _i1Buffer[2], _i1Buffer[4], _i1Buffer[6], adj);
double jQ = CalculateHilbertTransform(_q1Buffer[0], _q1Buffer[2], _q1Buffer[4], _q1Buffer[6], adj);
// Phasor addition for 3-bar averaging
double i2 = (ALPHA * (i1 - jQ)) + (BETA * _i2Buffer[0]);
double q2 = (ALPHA * (q1 + jI)) + (BETA * _q2Buffer[0]);
_i2Buffer.Add(i2, Input.IsNew);
_q2Buffer.Add(q2, Input.IsNew);
// Homodyne discriminator
double re = (ALPHA * ((i2 * _i2Buffer[1]) + (q2 * _q2Buffer[1]))) + (BETA * _reBuffer[0]);
double im = (ALPHA * ((i2 * _q2Buffer[1]) - (q2 * _i2Buffer[1]))) + (BETA * _imBuffer[0]);
_reBuffer.Add(re, Input.IsNew);
_imBuffer.Add(im, Input.IsNew);
// Calculate period
double pd = (im >= double.Epsilon && re >= double.Epsilon) ? TWO_PI / System.Math.Atan(im / re) : 0;
pd = ClampPeriod(pd, _lastPd);
pd = (ALPHA * pd) + (BETA * _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, System.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)
{
return CalculateSmoothedPrice(_itBuffer[0], _itBuffer[1], _itBuffer[2], _itBuffer[3]);
}
return pr;
}
}
-156
View File
@@ -1,156 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// HWMA: Holt-Winters Moving Average
/// A triple exponential smoothing method that incorporates level (F), velocity (V), and
/// acceleration (A) components to create a responsive yet smooth moving average. This
/// implementation uses optimized smoothing factors for each component.
/// </summary>
/// <remarks>
/// The HWMA calculation process:
/// 1. Updates the level (F) component using alpha smoothing
/// 2. Updates the velocity (V) component using beta smoothing
/// 3. Updates the acceleration (A) component using gamma smoothing
/// 4. Combines all components for final value: F + V + 0.5A
///
/// Key characteristics:
/// - Adapts to both trends and acceleration in price movement
/// - Three separate smoothing factors for fine-tuned control
/// - More responsive to changes than simple moving averages
/// - Handles both linear and non-linear trends
///
/// Implementation:
/// Based on Holt-Winters triple exponential smoothing principles
/// with optimized default parameters:
/// - Alpha (nA) = 2/(period + 1)
/// - Beta (nB) = 1/period
/// - Gamma (nC) = 1/period
/// </remarks>
public class Hwma : AbstractBase
{
private readonly int _period;
private readonly double _nA, _nB, _nC;
private readonly double _oneMinusNa, _oneMinusNb, _oneMinusNc;
private readonly double _halfA = 0.5;
private double _pF, _pV, _pA;
private double _ppF, _ppV, _ppA;
/// <param name="period">The number of data points used in the HWMA calculation.</param>
public Hwma(int period) : this(period, 2.0 / (1 + period), 1.0 / period, 1.0 / period)
{
}
/// <param name="nA">Alpha smoothing factor for the level component.</param>
/// <param name="nB">Beta smoothing factor for the velocity component.</param>
/// <param name="nC">Gamma smoothing factor for the acceleration component.</param>
public Hwma(double nA, double nB, double nC) : this((int)((2 - nA) / nA), nA, nB, nC)
{
}
/// <param name="period">The number of data points used in the HWMA calculation.</param>
/// <param name="nA">Alpha smoothing factor for the level component.</param>
/// <param name="nB">Beta smoothing factor for the velocity component.</param>
/// <param name="nC">Gamma smoothing factor for the acceleration component.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Hwma(int period, double nA, double nB, double nC)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_nA = nA;
_nB = nB;
_nC = nC;
_oneMinusNa = 1.0 - nA;
_oneMinusNb = 1.0 - nB;
_oneMinusNc = 1.0 - nC;
WarmupPeriod = period;
Name = $"Hwma({_period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the HWMA calculation.</param>
public Hwma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_pF = _pV = _pA = 0;
_ppF = _ppV = _ppA = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateLevel(double input)
{
return (_oneMinusNa * (_pF + _pV + (_halfA * _pA))) + (_nA * input);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateVelocity(double F)
{
return (_oneMinusNb * (_pV + _pA)) + (_nB * (F - _pF));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateAcceleration(double V)
{
return (_oneMinusNc * _pA) + (_nC * (V - _pV));
}
protected override double Calculation()
{
ManageState(Input.IsNew);
if (_index == 1)
{
_pF = Input.Value;
_pA = _pV = 0;
return Input.Value;
}
if (_period == 1)
{
_pF = Input.Value;
_pV = _pA = 0;
return Input.Value;
}
double F = CalculateLevel(Input.Value);
double V = CalculateVelocity(F);
double A = CalculateAcceleration(V);
_pF = F;
_pV = V;
_pA = A;
IsHot = _index >= WarmupPeriod;
return F + V + (_halfA * A);
}
}
-178
View File
@@ -1,178 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// JMA: Jurik Moving Average
/// A sophisticated moving average that combines adaptive volatility measurement with
/// phase-shifted smoothing. JMA provides excellent noise reduction while maintaining
/// responsiveness to significant price movements.
/// </summary>
/// <remarks>
/// The JMA calculation process:
/// 1. Calculates adaptive volatility bands
/// 2. Uses volatility to adjust smoothing parameters
/// 3. Applies phase-shifted smoothing for lag reduction
/// 4. Combines multiple smoothing stages for final output
///
/// Key characteristics:
/// - Adaptive smoothing based on price volatility
/// - Phase-shifting to reduce lag
/// - Excellent noise reduction
/// - Maintains responsiveness to significant moves
/// - Provides volatility bands as additional outputs
///
/// Implementation:
/// Based on known and reverse-engineered insights from Jurik Research
/// Original work by Mark Jurik
/// </remarks>
public class Jma : AbstractBase
{
private readonly double _phase;
private readonly CircularBuffer _vsumBuff;
private readonly CircularBuffer _avoltyBuff;
private readonly double _beta;
private readonly double _len1;
private readonly double _pow1;
private readonly double _oneMinusAlphaSquared;
private readonly double _alphaSquared;
private double _upperBand, _lowerBand, _p_upperBand, _p_lowerBand;
private double _prevMa1, _prevDet0, _prevDet1, _prevJma;
private double _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma;
private double _vSum, _p_vSum;
public double UpperBand { get; set; }
public double LowerBand { get; set; }
public double Volty { get; set; }
public double Factor { get; set; }
public Jma(int period, int phase = 0, double factor = 0.45, int buffer = 10)
{
if (period < 1)
{
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
Factor = factor;
_phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5);
_vsumBuff = new CircularBuffer(buffer);
_avoltyBuff = new CircularBuffer(65);
_beta = factor * (period - 1) / ((factor * (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);
// Precalculate constants for alpha-based calculations
double alpha = Math.Pow(_beta, _pow1);
double _oneMinusAlpha = 1.0 - alpha;
_oneMinusAlphaSquared = _oneMinusAlpha * _oneMinusAlpha;
_alphaSquared = alpha * alpha;
WarmupPeriod = period * 2;
Name = $"JMA({period})";
}
public Jma(object source, int period, int phase = 0, double factor = 0.45, int buffer = 10) : this(period, phase, factor, buffer)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_upperBand = _lowerBand = 0.0;
_p_upperBand = _p_lowerBand = 0.0;
_avoltyBuff.Clear();
_vsumBuff.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_index++;
_p_upperBand = _upperBand;
_p_lowerBand = _lowerBand;
_p_vSum = _vSum;
_p_prevMa1 = _prevMa1;
_p_prevDet0 = _prevDet0;
_p_prevDet1 = _prevDet1;
_p_prevJma = _prevJma;
}
else
{
_upperBand = _p_upperBand;
_lowerBand = _p_lowerBand;
_vSum = _p_vSum;
_prevMa1 = _p_prevMa1;
_prevDet0 = _p_prevDet0;
_prevDet1 = _p_prevDet1;
_prevJma = _p_prevJma;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateVolatility(double price, double del1, double del2)
{
double volty = Math.Max(Math.Abs(del1), Math.Abs(del2));
_vsumBuff.Add(volty, Input.IsNew);
_vSum += (_vsumBuff[^1] - _vsumBuff[0]) / _vsumBuff.Count;
_avoltyBuff.Add(_vSum, Input.IsNew);
return volty;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateRelativeVolatility(double volty, double avgVolty)
{
double rvolty = (avgVolty > 0) ? volty / avgVolty : 1;
return Math.Min(Math.Max(rvolty, 1.0), Math.Pow(_len1, 1.0 / _pow1));
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double price = Input.Value;
if (_index <= 1)
{
_upperBand = _lowerBand = price;
_prevMa1 = _prevJma = price;
return price;
}
double del1 = price - _upperBand;
double del2 = price - _lowerBand;
double volty = CalculateVolatility(price, del1, del2);
double avgVolty = _avoltyBuff.Average();
double rvolty = CalculateRelativeVolatility(volty, avgVolty);
double pow2 = Math.Pow(rvolty, _pow1);
double Kv = Math.Pow(_beta, Math.Sqrt(pow2));
_upperBand = (del1 >= 0) ? price : price - (Kv * del1);
_lowerBand = (del2 <= 0) ? price : price - (Kv * del2);
double alpha = Math.Pow(_beta, pow2);
double ma1 = price + (alpha * (_prevMa1 - price));
_prevMa1 = ma1;
double det0 = price + (_beta * (_prevDet0 - price + ma1)) - ma1;
_prevDet0 = det0;
double ma2 = ma1 + (_phase * det0);
double det1 = ((ma2 - _prevJma) * _oneMinusAlphaSquared) + (_alphaSquared * _prevDet1);
_prevDet1 = det1;
double jma = _prevJma + det1;
_prevJma = jma;
UpperBand = _upperBand;
LowerBand = _lowerBand;
Volty = volty;
IsHot = _index >= WarmupPeriod;
return jma;
}
}
-134
View File
@@ -1,134 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// KAMA: Kaufman's Adaptive Moving Average
/// An adaptive moving average that adjusts its smoothing based on market efficiency.
/// KAMA responds quickly during trending periods and becomes more stable during
/// sideways or choppy markets.
/// </summary>
/// <remarks>
/// The KAMA calculation process:
/// 1. Calculates the Efficiency Ratio (ER) to measure market noise
/// 2. Uses ER to determine the optimal smoothing between fast and slow constants
/// 3. Applies the adaptive smoothing to create the moving average
///
/// Key characteristics:
/// - Self-adaptive to market conditions
/// - Fast response during trends
/// - Stable during sideways markets
/// - Uses market efficiency for smoothing adjustment
/// - Reduces whipsaws in choppy markets
///
/// Sources:
/// Perry Kaufman - "Smarter Trading"
/// https://www.investopedia.com/terms/k/kaufmansadaptivemovingaverage.asp
/// </remarks>
public class Kama : AbstractBase
{
private readonly int _period;
private readonly double _scSlow;
private readonly double _scDiff; // Precalculated (_scFast - _scSlow)
private readonly CircularBuffer _buffer;
private double _lastKama, _p_lastKama;
/// <param name="period">The number of periods used to calculate the Efficiency Ratio.</param>
/// <param name="fast">The number of periods for the fastest EMA response (default 2).</param>
/// <param name="slow">The number of periods for the slowest EMA response (default 30).</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Kama(int period, int fast = 2, int slow = 30)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
double _scFast = 2.0 / (((period < fast) ? period : fast) + 1);
_scSlow = 2.0 / (slow + 1);
_scDiff = _scFast - _scSlow;
_buffer = new CircularBuffer(_period + 1);
WarmupPeriod = period;
Name = $"Kama({_period}, {fast}, {slow})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used to calculate the Efficiency Ratio.</param>
/// <param name="fast">The number of periods for the fastest EMA response (default 2).</param>
/// <param name="slow">The number of periods for the slowest EMA response (default 30).</param>
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));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_buffer.Clear();
_lastKama = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_p_lastKama = _lastKama;
}
else
{
_lastKama = _p_lastKama;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateVolatility()
{
double volatility = 0;
for (int i = 1; i < _buffer.Count; i++)
{
volatility += System.Math.Abs(_buffer[i] - _buffer[i - 1]);
}
return volatility;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateEfficiencyRatio(double change, double volatility)
{
return volatility >= double.Epsilon ? change / volatility : 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateSmoothingConstant(double er)
{
double sc = (er * _scDiff) + _scSlow;
return sc * sc; // Square the smoothing constant
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
if (_index <= _period)
{
_lastKama = Input.Value;
return Input.Value;
}
double change = System.Math.Abs(_buffer[^1] - _buffer[0]);
double volatility = CalculateVolatility();
double er = CalculateEfficiencyRatio(change, volatility);
double sc = CalculateSmoothingConstant(er);
_lastKama += sc * (Input.Value - _lastKama);
IsHot = _index >= WarmupPeriod;
return _lastKama;
}
}
-122
View File
@@ -1,122 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// LTMA: Laguerre Time Moving Average
/// A sophisticated moving average that uses Laguerre polynomials to create a time-based
/// filter. This approach provides excellent noise reduction while maintaining
/// responsiveness to price changes.
/// </summary>
/// <remarks>
/// The LTMA calculation process:
/// 1. Applies a cascade of four Laguerre filters
/// 2. Each filter stage provides additional smoothing
/// 3. Combines the filtered outputs with optimal weights
/// 4. Produces a smooth output with minimal lag
///
/// Key characteristics:
/// - Time-based filtering using Laguerre polynomials
/// - Excellent noise reduction
/// - Maintains good responsiveness
/// - Single parameter (gamma) controls smoothing
/// - Computationally efficient
///
/// Sources:
/// John Ehlers - "Time Warp - Without Space Travel"
/// https://www.mesasoftware.com/papers/TimeWarp.pdf
/// </remarks>
public class Ltma : AbstractBase
{
private readonly double _gamma;
private readonly double _oneMinusGamma;
private readonly double _invSix = 1.0 / 6.0; // Precalculated constant for final averaging
private double _prevL0, _prevL1, _prevL2, _prevL3;
private double _p_prevL0, _p_prevL1, _p_prevL2, _p_prevL3;
/// <summary>
/// Gets the gamma parameter value used in the Laguerre filter.
/// </summary>
public double Gamma => _gamma;
/// <param name="gamma">The damping factor (0 to 1) controlling the smoothing. Lower values provide more smoothing.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when gamma is not between 0 and 1.</exception>
public Ltma(double gamma = 0.1)
{
if (gamma < 0 || gamma > 1)
throw new System.ArgumentOutOfRangeException(nameof(gamma), "Gamma must be between 0 and 1.");
_gamma = gamma;
_oneMinusGamma = 1.0 - gamma;
Name = $"Laguerre({gamma:F2})";
WarmupPeriod = 4; // Minimum number of samples needed
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="gamma">The damping factor (0 to 1) controlling the smoothing.</param>
public Ltma(object source, double gamma = 0.1) : this(gamma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_prevL0 = _prevL1 = _prevL2 = _prevL3 = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateLaguerreStage(double input, double prev, double prevPrev)
{
return (-_gamma * input) + prev + (_gamma * prevPrev);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CombineOutputs(double l0, double l1, double l2, double l3)
{
return (l0 + (2.0 * (l1 + l2)) + l3) * _invSix;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// First stage
double l0 = (_oneMinusGamma * Input.Value) + (_gamma * _prevL0);
// Subsequent stages using helper method
double l1 = CalculateLaguerreStage(l0, _prevL0, _prevL1);
double l2 = CalculateLaguerreStage(l1, _prevL1, _prevL2);
double l3 = CalculateLaguerreStage(l2, _prevL2, _prevL3);
// Store values for next iteration
_prevL0 = l0;
_prevL1 = l1;
_prevL2 = l2;
_prevL3 = l3;
IsHot = _index >= WarmupPeriod;
return CombineOutputs(l0, l1, l2, l3);
}
}
-163
View File
@@ -1,163 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// MAAF: Median Adaptive Average Filter
/// A sophisticated moving average that combines median filtering with adaptive smoothing
/// to provide robust noise reduction while maintaining signal fidelity. The filter
/// automatically adjusts its length based on market conditions.
/// </summary>
/// <remarks>
/// The MAAF calculation process:
/// 1. Applies initial smoothing using weighted moving average
/// 2. Uses median filtering to remove outliers
/// 3. Adaptively adjusts filter length based on price deviation
/// 4. Applies final EMA smoothing with adaptive period
///
/// Key characteristics:
/// - Combines median and exponential filtering
/// - Adaptive period adjustment
/// - Robust noise reduction
/// - Preserves significant price movements
/// - Reduces impact of outliers
///
/// Sources:
/// John F. Ehlers - "The Secret Behind The Filter"
/// https://efs.kb.esignal.com/hc/en-us/articles/6362791434395-2005-Mar-The-Secret-Behind-The-Filter-MedianAdaptiveFilter-efs
///
/// Note: Initial values handling is currently under development.
/// </remarks>
public class Maaf : AbstractBase
{
private readonly CircularBuffer _priceBuffer;
private readonly CircularBuffer _smoothBuffer;
private readonly double _threshold;
private readonly int _period;
private readonly double _invSix = 1.0 / 6.0;
private readonly double[] _sortBuffer; // Pre-allocated buffer for sorting
private double _prevFilter, _prevValue2;
private double _p_prevFilter, _p_prevValue2;
/// <param name="period">The initial period for the filter (default 39).</param>
/// <param name="threshold">The threshold for adaptive adjustment (default 0.002).</param>
public Maaf(int period = 39, double threshold = 0.002)
{
_period = period;
_threshold = threshold;
_priceBuffer = new CircularBuffer(4);
_smoothBuffer = new CircularBuffer(period);
_sortBuffer = new double[period]; // Pre-allocate sorting buffer
Name = "MAAF";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The initial period for the filter (default 39).</param>
/// <param name="threshold">The threshold for adaptive adjustment (default 0.002).</param>
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));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_priceBuffer.Clear();
_smoothBuffer.Clear();
_prevFilter = 0;
_prevValue2 = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateSmooth()
{
return (_priceBuffer[^1] + (2.0 * (_priceBuffer[^2] + _priceBuffer[^3])) + _priceBuffer[^4]) * _invSix;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double GetMedian(int length)
{
// Copy values to pre-allocated buffer
var span = _smoothBuffer.GetSpan().Slice(_smoothBuffer.Count - length, length);
span.CopyTo(_sortBuffer.AsSpan(0, length));
// Sort the required portion
System.Array.Sort(_sortBuffer, 0, length);
return _sortBuffer[length / 2];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateAlpha(int length)
{
return 2.0 / (length + 1);
}
protected override double Calculation()
{
ManageState(IsNew);
_priceBuffer.Add(Input.Value, Input.IsNew);
if (_priceBuffer.Count < 4)
{
return Input.Value;
}
double smooth = CalculateSmooth();
_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 = CalculateAlpha(length);
double value1 = GetMedian(length);
value2 = (alpha * (smooth - _prevValue2)) + _prevValue2;
if (value1 >= double.Epsilon)
{
value3 = Math.Abs(value1 - value2) / value1;
}
length -= 2;
}
length = Math.Max(length, 3);
double finalAlpha = CalculateAlpha(length);
double filter = (finalAlpha * (smooth - _prevFilter)) + _prevFilter;
_prevFilter = filter;
_prevValue2 = value2;
IsHot = _index >= WarmupPeriod;
return filter;
}
}
-211
View File
@@ -1,211 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// MAMA: MESA Adaptive Moving Average
/// A highly sophisticated adaptive moving average that uses the MESA (Maximum Entropy
/// Spectral Analysis) algorithm to detect market cycles and adjust its smoothing
/// accordingly. MAMA provides both a faster (MAMA) and slower (FAMA) moving average.
/// </summary>
/// <remarks>
/// The MAMA calculation process:
/// 1. Uses Hilbert Transform to decompose price into phase and amplitude
/// 2. Calculates the dominant cycle period using phase analysis
/// 3. Determines phase position and rate of change
/// 4. Adapts smoothing based on phase changes
/// 5. Generates both MAMA and FAMA (Following Adaptive Moving Average)
///
/// Key characteristics:
/// - Highly adaptive to market conditions
/// - Provides two synchronized moving averages
/// - Uses cycle analysis for adaptation
/// - Excellent at identifying trend changes
/// - Combines multiple signal processing techniques
///
/// Sources:
/// John Ehlers - "MESA Adaptive Moving Averages"
/// https://www.mesasoftware.com/papers/MAMA.pdf
/// </remarks>
public class Mama : AbstractBase
{
private readonly double _fastLimit, _slowLimit;
private readonly CircularBuffer _pr, _sm, _dt, _i1, _q1, _i2, _q2, _re, _im, _pd, _ph;
private readonly double _twoPi = 2.0 * System.Math.PI;
private readonly double _radToDeg = 180.0 / System.Math.PI;
private readonly double _alpha02 = 0.2;
private readonly double _alpha08 = 0.8;
private readonly double _famaAlpha = 0.5;
private double _mama, _fama;
private double _prevMama, _prevFama, _sumPr;
private double _p_prevMama, _p_prevFama, _p_sumPr;
/// <summary>
/// Gets the Following Adaptive Moving Average (FAMA) value.
/// </summary>
public TValue Fama { get; private set; }
public Mama(double fastLimit = 0.5, double slowLimit = 0.05)
{
Fama = new TValue();
_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);
Name = $"Mama({_fastLimit:F2}, {_slowLimit:F2})";
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));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
Fama = new TValue();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateSmooth()
{
return ((4.0 * _pr[^1]) + (3.0 * _pr[^2]) + (2.0 * _pr[^3]) + _pr[^4]) * 0.1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateHilbertTransform(CircularBuffer buffer, double adj)
{
return ((0.0962 * (buffer[^1] - buffer[^7])) + (0.5769 * (buffer[^3] - buffer[^5]))) * adj;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculatePeriod(double im, double re)
{
if (System.Math.Abs(im) <= double.Epsilon || System.Math.Abs(re) <= double.Epsilon) return _pd[^2];
return _twoPi / System.Math.Atan(im / re);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double AdjustPeriod(double period)
{
period = System.Math.Clamp(period, 0.67 * _pd[^2], 1.5 * _pd[^2]);
period = System.Math.Clamp(period, 6.0, 50.0);
return (_alpha02 * period) + (_alpha08 * _pd[^2]);
}
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 and Detrender
_sm.Add(CalculateSmooth(), Input.IsNew);
_dt.Add(CalculateHilbertTransform(_sm, adj), Input.IsNew);
// In-phase and quadrature
_q1.Add(CalculateHilbertTransform(_dt, adj), Input.IsNew);
_i1.Add(_dt[^4], Input.IsNew);
// Advance phases
double jI = CalculateHilbertTransform(_i1, adj);
double jQ = CalculateHilbertTransform(_q1, adj);
// Phasor addition
double i2 = _i1[^1] - jQ;
double q2 = _q1[^1] + jI;
_i2.Add(i2, Input.IsNew);
_q2.Add(q2, Input.IsNew);
_i2[^1] = (_alpha02 * _i2[^1]) + (_alpha08 * _i2[^2]);
_q2[^1] = (_alpha02 * _q2[^1]) + (_alpha08 * _q2[^2]);
// Homodyne discriminator
double re = (_i2[^1] * _i2[^2]) + (_q2[^1] * _q2[^2]);
double im = (_i2[^1] * _q2[^2]) - (_q2[^1] * _i2[^2]);
_re.Add(re, Input.IsNew);
_im.Add(im, Input.IsNew);
_re[^1] = (_alpha02 * _re[^1]) + (_alpha08 * _re[^2]);
_im[^1] = (_alpha02 * _im[^1]) + (_alpha08 * _im[^2]);
// Calculate and adjust period
double period = CalculatePeriod(_im[^1], _re[^1]);
_pd.Add(period, Input.IsNew);
_pd[^1] = AdjustPeriod(_pd[^1]);
// Phase calculation
double phase = Math.Abs(_i1[^1]) >= double.Epsilon ? System.Math.Atan(_q1[^1] / _i1[^1]) * _radToDeg : _ph[^2];
_ph.Add(phase, Input.IsNew);
// Adaptive alpha
double delta = System.Math.Max(_ph[^2] - _ph[^1], 1.0);
double alpha = System.Math.Clamp(_fastLimit / delta, _slowLimit, _fastLimit);
// Final indicators
_mama = (alpha * (_pr[^1] - _prevMama)) + _prevMama;
_fama = (_famaAlpha * alpha * (_mama - _prevFama)) + _prevFama;
_prevMama = _mama;
_prevFama = _fama;
}
else
{
InitializeBuffers();
_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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void InitializeBuffers()
{
_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);
}
}
-113
View File
@@ -1,113 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// MGDI: Modified Geometric Decay Index
/// A moving average that uses geometric decay with a ratio-based adjustment factor.
/// The decay rate is modified based on the ratio between current and previous values,
/// allowing for adaptive smoothing based on price movement magnitude.
/// </summary>
/// <remarks>
/// The MGDI calculation process:
/// 1. Calculates ratio between current and previous values
/// 2. Uses ratio to modify the geometric decay rate
/// 3. Applies modified decay to smooth the data
/// 4. Adjusts smoothing based on K-factor parameter
///
/// Key characteristics:
/// - Geometric decay-based smoothing
/// - Adaptive to price movement magnitude
/// - Adjustable smoothing via K-factor
/// - More responsive to large price changes
/// - Maintains smoothness during small fluctuations
///
/// Implementation:
/// Based on geometric decay principles with ratio-based modification
/// </remarks>
public class Mgdi : AbstractBase
{
private readonly int _period;
private readonly double _kFactorPeriod; // Precalculated k * period
private double _prevMd, _p_prevMd;
/// <param name="period">The number of periods used in the MGDI calculation.</param>
/// <param name="kFactor">The K-factor controlling the decay rate adjustment (default 0.6).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period or kFactor is less than or equal to 0.</exception>
public Mgdi(int period, double kFactor = 0.6)
{
if (period <= 0)
{
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0.");
}
if (kFactor <= 0)
{
throw new System.ArgumentOutOfRangeException(nameof(kFactor), "K-Factor must be greater than 0.");
}
_period = period;
_kFactorPeriod = kFactor * period;
Name = "Mgdi";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in the MGDI calculation.</param>
/// <param name="kFactor">The K-factor controlling the decay rate adjustment (default 0.6).</param>
public Mgdi(object source, int period, double kFactor = 0.6) : this(period, kFactor)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_prevMd = _p_prevMd = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_prevMd = _prevMd;
_index++;
}
else
{
_prevMd = _p_prevMd;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateRatio(double value)
{
return _prevMd >= double.Epsilon ? value / _prevMd : 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateMd(double value, double ratio)
{
return _prevMd + ((value - _prevMd) / (_kFactorPeriod * System.Math.Pow(ratio, 4)));
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double value = Input.Value;
if (_index < 2)
{
_prevMd = value;
}
else
{
double ratio = CalculateRatio(value);
_prevMd = CalculateMd(value, ratio);
}
IsHot = _index >= _period;
return _prevMd;
}
}
-117
View File
@@ -1,117 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// MMA: Modified Moving Average
/// A moving average that combines a simple moving average with a weighted component
/// to provide a balanced smoothing effect. The weighting scheme emphasizes central
/// values while maintaining overall data representation.
/// </summary>
/// <remarks>
/// The MMA calculation process:
/// 1. Calculates the simple moving average component (T/period)
/// 2. Calculates a weighted sum with symmetric weights around the center
/// 3. Combines both components using the formula: SMA + 6*WeightedSum/((period+1)*period)
///
/// Key characteristics:
/// - Combines simple and weighted moving averages
/// - Symmetric weighting around the center
/// - Better balance between smoothing and responsiveness
/// - Reduces lag compared to simple moving average
/// - Maintains stability through dual-component approach
///
/// Implementation:
/// Based on modified moving average principles combining
/// simple and weighted components for optimal smoothing
/// </remarks>
public class Mma : AbstractBase
{
private readonly int _period;
private readonly CircularBuffer _buffer;
private readonly double _periodRecip; // 1/period
private readonly double _combinedRecip; // 6/((period+1)*period)
private readonly double[] _weights; // Precalculated weights
private double _lastMma;
/// <param name="period">The number of periods used in the MMA calculation. Must be at least 2.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
public Mma(int period)
{
if (period < 2)
{
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
_period = period;
_buffer = new CircularBuffer(period);
_periodRecip = 1.0 / period;
_combinedRecip = 6.0 / ((period + 1) * period);
// Precalculate weights
_weights = new double[period];
for (int i = 0; i < period; i++)
{
_weights[i] = (period - ((2 * i) + 1)) * 0.5;
}
Name = "Mma";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in the MMA calculation.</param>
public Mma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_lastMma = 0;
_buffer.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateWeightedSum()
{
double sum = 0;
for (int i = 0; i < _period; i++)
{
sum += _weights[i] * _buffer[^(i + 1)];
}
return sum;
}
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 * _periodRecip) + (S * _combinedRecip);
}
else
{
// Use simple average until we have enough data points
_lastMma = _buffer.Average();
}
IsHot = _index >= _period;
return _lastMma;
}
}
-136
View File
@@ -1,136 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// PWMA: Pascal Weighted Moving Average
/// A moving average that uses Pascal's triangle coefficients as weights, providing
/// a natural distribution of weights that increases towards the center of the period.
/// This creates a smooth average with balanced emphasis on central values.
/// </summary>
/// <remarks>
/// The PWMA calculation process:
/// 1. Generates weights using Pascal's triangle coefficients
/// 2. Normalizes the weights to sum to 1
/// 3. Applies the weights through convolution
/// 4. Adjusts for partial periods during warmup
///
/// Key characteristics:
/// - Natural weight distribution from Pascal's triangle
/// - Symmetric weighting around the center
/// - Smooth response to price changes
/// - Balanced between recent and historical data
/// - Implemented using efficient convolution operations
///
/// Implementation:
/// Based on Pascal's triangle principles for weight generation
/// Uses convolution for efficient calculation
/// </remarks>
public class Pwma : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
private readonly double[] _kernel;
/// <param name="period">The number of data points used in the PWMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Pwma(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_kernel = GenerateKernel(_period);
_convolution = new Convolution(_kernel);
Name = "Pwma";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the PWMA calculation.</param>
public Pwma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_convolution.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateKernelSum(double[] kernel, int length)
{
double sum = 0;
for (int i = 0; i < length; i++)
{
sum += kernel[i];
}
return sum;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
var convolutionResult = _convolution.Calc(Input);
double result = convolutionResult.Value;
// Adjust for partial periods during warmup
if (_index < _period)
{
double[] partialKernel = GenerateKernel(_index);
result *= CalculateKernelSum(_kernel, _period) / CalculateKernelSum(partialKernel, _index);
}
IsHot = _index >= WarmupPeriod;
return result;
}
/// <summary>
/// Generates the Pascal's triangle-based convolution kernel for the PWMA calculation.
/// </summary>
/// <param name="period">The period for which to generate the kernel.</param>
/// <returns>An array of normalized Pascal's triangle-based weights for the convolution operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
kernel[0] = 1;
// Generate Pascal's triangle coefficients
for (int i = 1; i < period; i++)
{
for (int j = i; j > 0; j--)
{
kernel[j] += kernel[j - 1];
}
}
// Calculate sum and normalize in one pass
double weightSum = CalculateKernelSum(kernel, period);
double invWeightSum = 1.0 / weightSum;
for (int i = 0; i < period; i++)
{
kernel[i] *= invWeightSum;
}
return kernel;
}
}
-114
View File
@@ -1,114 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// QEMA: Quadruple Exponential Moving Average
/// A sophisticated moving average that applies four exponential moving averages in sequence
/// and combines them using a specific formula to reduce lag while maintaining smoothness.
/// The final combination is: 4*EMA1 - 6*EMA2 + 4*EMA3 - EMA4
/// </summary>
/// <remarks>
/// The QEMA calculation process:
/// 1. Applies first EMA to price data
/// 2. Applies second EMA to result of first EMA
/// 3. Applies third EMA to result of second EMA
/// 4. Applies fourth EMA to result of third EMA
/// 5. Combines results using the formula: 4*EMA1 - 6*EMA2 + 4*EMA3 - EMA4
///
/// Key characteristics:
/// - Multiple EMA smoothing stages
/// - Reduced lag through combination formula
/// - Customizable smoothing factors for each EMA
/// - Better noise reduction than single EMA
/// - Maintains responsiveness to significant moves
///
/// Implementation:
/// Based on quadruple exponential smoothing principles
/// with optimized combination formula
/// </remarks>
public class Qema : AbstractBase
{
private readonly Ema _ema1, _ema2, _ema3, _ema4;
private double _lastQema, _p_lastQema;
/// <param name="k1">Smoothing factor for first EMA (default 0.2).</param>
/// <param name="k2">Smoothing factor for second EMA (default 0.2).</param>
/// <param name="k3">Smoothing factor for third EMA (default 0.2).</param>
/// <param name="k4">Smoothing factor for fourth EMA (default 0.2).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when any k value is less than or equal to 0.</exception>
public Qema(double k1 = 0.2, double k2 = 0.2, double k3 = 0.2, double k4 = 0.2)
{
if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0)
{
throw new System.ArgumentOutOfRangeException(nameof(k1), "All k values must be in the range (0, 1].");
}
_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 = System.Math.Min(System.Math.Min(k1, k2), System.Math.Min(k3, k4));
WarmupPeriod = (int)((2 - smK) / smK);
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="k1">Smoothing factor for first EMA.</param>
/// <param name="k2">Smoothing factor for second EMA.</param>
/// <param name="k3">Smoothing factor for third EMA.</param>
/// <param name="k4">Smoothing factor for fourth EMA.</param>
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));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_lastQema = 0;
_p_lastQema = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastQema = _lastQema;
_index++;
}
else
{
_lastQema = _p_lastQema;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateEma(Ema ema, double value)
{
var tempValue = new TValue(Input.Time, value, Input.IsNew);
return ema.Calc(tempValue).Value;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Calculate EMAs in sequence
double ema1 = CalculateEma(_ema1, Input.Value);
double ema2 = CalculateEma(_ema2, ema1);
double ema3 = CalculateEma(_ema3, ema2);
double ema4 = CalculateEma(_ema4, ema3);
// Combine EMAs using optimized formula
_lastQema = (4.0 * (ema1 + ema3)) - ((6.0 * ema2) + ema4);
IsHot = _index >= WarmupPeriod;
return _lastQema;
}
}
-136
View File
@@ -1,136 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// REMA: Regularized Exponential Moving Average
/// A modified exponential moving average that includes a regularization term to reduce
/// noise and improve trend following. The regularization helps to smooth the output
/// while maintaining responsiveness to significant price movements.
/// </summary>
/// <remarks>
/// The REMA calculation process:
/// 1. Uses standard EMA smoothing with adaptive alpha
/// 2. Adds regularization term based on previous values
/// 3. Balances new and regularized terms using lambda parameter
/// 4. Provides smoother output than standard EMA
///
/// Key characteristics:
/// - Improved noise reduction through regularization
/// - Better trend following than standard EMA
/// - Adjustable regularization via lambda parameter
/// - Adaptive alpha based on period
/// - Reduced whipsaws in choppy markets
///
/// Sources:
/// https://user42.tuxfamily.org/chart/manual/Regularized-Exponential-Moving-Average.html
/// </remarks>
public class Rema : AbstractBase
{
private readonly int _period;
private readonly double _lambda;
private readonly double _lambdaPlus1Recip; // 1/(1 + lambda)
private double _lastRema, _prevRema;
private double _savedLastRema, _savedPrevRema;
/// <summary>
/// Gets the period used in the REMA calculation.
/// </summary>
public int Period => _period;
/// <summary>
/// Gets the lambda (regularization) parameter value.
/// </summary>
public double Lambda => _lambda;
/// <param name="period">The number of periods used in the REMA calculation.</param>
/// <param name="lambda">The regularization parameter (default 0.5). Higher values increase smoothing.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1 or lambda is negative.</exception>
public Rema(int period, double lambda = 0.5)
{
if (period < 1)
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
if (lambda < 0)
throw new System.ArgumentOutOfRangeException(nameof(lambda), "Lambda must be non-negative.");
_period = period;
_lambda = lambda;
_lambdaPlus1Recip = 1.0 / (1.0 + lambda);
Name = $"REMA({period},{lambda:F2})";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in the REMA calculation.</param>
/// <param name="lambda">The regularization parameter (default 0.5).</param>
public Rema(object source, int period, double lambda = 0.5) : this(period, lambda)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_lastRema = 0;
_prevRema = 0;
_savedLastRema = 0;
_savedPrevRema = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_savedLastRema = _lastRema;
_savedPrevRema = _prevRema;
_index++;
}
else
{
_lastRema = _savedLastRema;
_prevRema = _savedPrevRema;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateAlpha()
{
return 2.0 / (System.Math.Min(_period, _index) + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateRema(double alpha, double input)
{
double standardTerm = _lastRema + (alpha * (input - _lastRema));
double regularizationTerm = _lastRema + (_lastRema - _prevRema);
return (standardTerm + (_lambda * regularizationTerm)) * _lambdaPlus1Recip;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
if (_index > 2)
{
double alpha = CalculateAlpha();
double rema = CalculateRema(alpha, Input.Value);
_prevRema = _lastRema;
_lastRema = rema;
}
else if (_index == 2)
{
_prevRema = _lastRema;
_lastRema = Input.Value;
}
else
{
_lastRema = Input.Value;
}
IsHot = _index >= WarmupPeriod;
return _lastRema;
}
}
-136
View File
@@ -1,136 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// RMA: Relative Moving Average (also known as Wilder's Moving Average)
/// </summary>
/// <remarks>
/// RMA is similar to EMA but uses a different smoothing factor.
///
/// Key characteristics:
/// - Uses no buffer, relying only on the previous RMA value.
/// - The weight of new data points (alpha) is calculated as 1 / period.
/// - Provides a smoother curve compared to SMA and EMA, reacting more slowly to price changes.
///
/// Calculation method:
/// This implementation can use SMA for the first Period bars as a seeding value for RMA when useSma is true.
///
/// Sources:
/// - https://www.tradingview.com/pine-script-reference/v5/#fun_ta{dot}rma
/// - https://www.investopedia.com/terms/w/wilders-smoothing.asp
/// </remarks>
public class Rma : AbstractBase
{
private readonly int _period;
private readonly double _k; // Wilder's smoothing factor
private readonly double _oneMinusK; // 1 - k
private readonly double _epsilon = 1e-10;
private readonly bool _useSma;
private CircularBuffer _sma;
private double _lastRma, _p_lastRma;
private double _e, _p_e;
private bool _isInit, _p_isInit;
/// <summary>
/// Initializes a new instance of the Rma class with a specified period.
/// </summary>
/// <param name="period">The period for RMA calculation.</param>
/// <param name="useSma">Whether to use SMA for initial values. Default is true.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
public Rma(int period, bool useSma = true)
{
if (period < 1)
{
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_period = period;
_k = 1.0 / period;
_oneMinusK = 1.0 - _k;
_useSma = useSma;
_sma = new(period);
Name = "Rma";
WarmupPeriod = period * 2; // RMA typically needs more warmup periods
Init();
}
/// <summary>
/// Initializes a new instance of the Rma class with a specified source and period.
/// </summary>
/// <param name="source">The source object for event subscription.</param>
/// <param name="period">The period for RMA calculation.</param>
/// <param name="useSma">Whether to use SMA for initial values. Default is true.</param>
public Rma(object source, int period, bool useSma = true) : this(period, useSma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_e = 1.0;
_lastRma = 0;
_isInit = false;
_p_isInit = false;
_sma = new(_period);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_lastRma = _lastRma;
_p_isInit = _isInit;
_p_e = _e;
_index++;
}
else
{
_lastRma = _p_lastRma;
_isInit = _p_isInit;
_e = _p_e;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateRma(double input)
{
return (_k * input) + (_oneMinusK * _lastRma);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CompensateRma(double rma)
{
_e = (_e > _epsilon) ? _oneMinusK * _e : 0;
return (_useSma || _e <= double.Epsilon) ? rma : rma / (1.0 - _e);
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double result;
if (!_isInit && _useSma)
{
_sma.Add(Input.Value, Input.IsNew);
_lastRma = _sma.Average();
result = _lastRma;
if (_index >= _period)
{
_isInit = true;
}
}
else
{
_lastRma = CalculateRma(Input.Value);
result = CompensateRma(_lastRma);
}
IsHot = _index >= WarmupPeriod;
return result;
}
}
-111
View File
@@ -1,111 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// SINEMA: Sine-weighted Exponential Moving Average
/// A moving average that uses sine function-based weights to create a natural
/// distribution of importance across the period. The weights follow a sine curve,
/// providing smooth transitions and natural emphasis on different parts of the data.
/// </summary>
/// <remarks>
/// The SINEMA calculation process:
/// 1. Generates weights using sine function over the period
/// 2. Normalizes weights to sum to 1
/// 3. Applies weights through convolution
/// 4. Produces smooth output with natural weight distribution
///
/// Key characteristics:
/// - Sine-based weight distribution
/// - Natural smoothing through trigonometric weights
/// - No sharp transitions in weight values
/// - Balanced emphasis across the period
/// - Implemented using efficient convolution operations
///
/// Implementation:
/// Based on sine function principles for weight generation
/// Uses convolution for efficient calculation
/// </remarks>
public class Sinema : AbstractBase
{
private readonly Convolution _convolution;
/// <param name="period">The number of data points used in the SINEMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Sinema(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
double[] _kernel = GenerateKernel(period);
_convolution = new Convolution(_kernel);
Name = "Sinema";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the SINEMA calculation.</param>
public Sinema(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_convolution.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
/// <summary>
/// Generates the sine-based convolution kernel for the SINEMA calculation.
/// </summary>
/// <param name="period">The period for which to generate the kernel.</param>
/// <returns>An array of normalized sine-based weights for the convolution operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
double weightSum = 0;
double piDivPeriodPlus1 = System.Math.PI / (period + 1);
// Calculate weights and sum in one pass
for (int i = 0; i < period; i++)
{
kernel[i] = System.Math.Sin((i + 1) * piDivPeriodPlus1);
weightSum += kernel[i];
}
// Normalize using multiplication instead of division
double invWeightSum = 1.0 / weightSum;
for (int i = 0; i < period; i++)
{
kernel[i] *= invWeightSum;
}
return kernel;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
var convolutionResult = _convolution.Calc(Input);
IsHot = _index >= WarmupPeriod;
return convolutionResult.Value;
}
}
-99
View File
@@ -1,99 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// SMA: Simple Moving Average
/// The most basic form of moving average, calculating the arithmetic mean over a
/// specified period. Each data point in the period has equal weight in the
/// calculation.
/// </summary>
/// <remarks>
/// The SMA calculation process:
/// 1. Maintains a circular buffer of the last 'period' values
/// 2. Maintains a running sum for O(1) calculation
/// 3. Updates: sum = sum - oldest + newest
/// 4. Returns sum / count for the average
///
/// Key characteristics:
/// - Equal weight for all values in the period
/// - O(1) time complexity using running sum
/// - Simple and straightforward calculation
/// - Significant lag due to equal weighting
/// - Smooth output with good noise reduction
/// - Most basic form of trend following
///
/// Sources:
/// https://www.investopedia.com/terms/s/sma.asp
/// https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages
/// </remarks>
[SkipLocalsInit]
public sealed class Sma : AbstractBase
{
private readonly CircularBuffer _buffer;
private double _sum, _p_sum;
private double _lastValue, _p_lastValue;
/// <param name="period">The number of data points used in the SMA calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Sma(int period)
{
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
_buffer = new CircularBuffer(period);
Name = $"Sma({period})";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the SMA calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Sma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_index++;
_p_sum = _sum;
_p_lastValue = _lastValue;
}
else
{
_sum = _p_sum;
_lastValue = _p_lastValue;
}
}
/// <summary>
/// Performs the core SMA calculation using O(1) running sum algorithm.
/// </summary>
/// <returns>The calculated SMA value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
double oldValue;
if (Input.IsNew)
{
oldValue = _buffer.Count == _buffer.Capacity ? _buffer.Oldest() : 0.0;
_lastValue = Input.Value;
}
else
{
oldValue = _lastValue;
}
_sum = _sum - oldValue + Input.Value;
_buffer.Add(Input.Value, Input.IsNew);
IsHot = _index >= WarmupPeriod;
return _sum / _buffer.Count;
}
}
-113
View File
@@ -1,113 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// SMMA: Smoothed Moving Average
/// A modified moving average that gives more weight to recent prices while maintaining
/// a smooth output. It uses the previous SMMA value in its calculation, creating
/// a smoother line than traditional moving averages.
/// </summary>
/// <remarks>
/// The SMMA calculation process:
/// 1. Uses SMA for initial value (first period points)
/// 2. For subsequent points, calculates: (prevSMMA * (period-1) + price) / period
/// 3. This creates a smoothed effect with reduced volatility
///
/// Key characteristics:
/// - Smoother than traditional moving averages
/// - Reduced volatility in output
/// - Takes into account all previous prices
/// - Good for identifying overall trends
/// - Less lag than SMA but more than EMA
///
/// Implementation:
/// Based on smoothed moving average principles with
/// initial SMA seeding for stability
/// </remarks>
public class Smma : AbstractBase
{
private readonly int _period;
private readonly double _periodRecip; // 1/period
private readonly double _periodMinusOne; // period-1
private readonly CircularBuffer _buffer;
private double _lastSmma, _p_lastSmma;
/// <param name="period">The number of data points used in the SMMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Smma(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_periodRecip = 1.0 / period;
_periodMinusOne = period - 1;
_buffer = new CircularBuffer(period);
WarmupPeriod = period;
Name = $"Smma({_period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the SMMA calculation.</param>
public Smma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_buffer.Clear();
_lastSmma = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_p_lastSmma = _lastSmma;
_index++;
}
else
{
_lastSmma = _p_lastSmma;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateSmma(double input)
{
return ((_lastSmma * _periodMinusOne) + input) * _periodRecip;
}
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 = CalculateSmma(Input.Value);
}
_lastSmma = smma;
IsHot = _index >= WarmupPeriod;
return smma;
}
}
-178
View File
@@ -1,178 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// T3: Tillson T3 Moving Average
/// A sophisticated moving average developed by Tim Tillson that applies six EMAs
/// in sequence with optimized coefficients. The T3 provides excellent smoothing
/// while maintaining responsiveness and minimal lag.
/// </summary>
/// <remarks>
/// The T3 calculation process:
/// 1. Applies six EMAs in sequence
/// 2. Uses volume factor to determine optimal coefficients
/// 3. Combines EMAs using specific formula: c1*EMA6 + c2*EMA5 + c3*EMA4 + c4*EMA3
/// 4. Coefficients are based on the volume factor parameter
///
/// Key characteristics:
/// - Excellent smoothing with minimal lag
/// - Adjustable via volume factor parameter
/// - No overshooting like triple EMA
/// - Better noise reduction than traditional EMAs
/// - Maintains responsiveness to significant moves
///
/// Sources:
/// Tim Tillson - "Better Moving Averages"
/// TASC Magazine, 1998
/// </remarks>
public class T3 : AbstractBase
{
private readonly int _period;
private readonly bool _useSma;
private readonly double _k;
private readonly double _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;
/// <param name="period">The number of periods used in each EMA calculation.</param>
/// <param name="vfactor">Volume factor controlling smoothing (default 0.7).</param>
/// <param name="useSma">Whether to use SMA for initial values (default true).</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public T3(int period, double vfactor = 0.7, bool useSma = true)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
_useSma = useSma;
WarmupPeriod = period;
_k = 2.0 / (_period + 1);
// Precalculate coefficients
double v2 = vfactor * vfactor;
double v3 = v2 * vfactor;
_c1 = -v3;
_c2 = 3.0 * (v2 + v3);
_c3 = -3.0 * ((2.0 * v2) + vfactor + v3);
_c4 = 1.0 + (3.0 * vfactor) + v3 + (3.0 * v2);
_buffer1 = new(period);
_buffer2 = new(period);
_buffer3 = new(period);
_buffer4 = new(period);
_buffer5 = new(period);
_buffer6 = new(period);
Name = $"T3({_period}, {vfactor})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in each EMA calculation.</param>
/// <param name="vfactor">Volume factor controlling smoothing (default 0.7).</param>
/// <param name="useSma">Whether to use SMA for initial values (default true).</param>
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));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
_lastEma1 = _lastEma2 = _lastEma3 = _lastEma4 = _lastEma5 = _lastEma6 = 0;
_buffer1.Clear();
_buffer2.Clear();
_buffer3.Clear();
_buffer4.Clear();
_buffer5.Clear();
_buffer6.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateEma(double input, double lastEma)
{
return (_k * (input - lastEma)) + lastEma;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateT3(double ema3, double ema4, double ema5, double ema6)
{
return (_c1 * ema6) + (_c2 * ema5) + (_c3 * ema4) + (_c4 * ema3);
}
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 = CalculateEma(Input.Value, _lastEma1);
ema2 = CalculateEma(ema1, _lastEma2);
ema3 = CalculateEma(ema2, _lastEma3);
ema4 = CalculateEma(ema3, _lastEma4);
ema5 = CalculateEma(ema4, _lastEma5);
ema6 = CalculateEma(ema5, _lastEma6);
}
_lastEma1 = ema1;
_lastEma2 = ema2;
_lastEma3 = ema3;
_lastEma4 = ema4;
_lastEma5 = ema5;
_lastEma6 = ema6;
IsHot = _index >= WarmupPeriod;
return CalculateT3(ema3, ema4, ema5, ema6);
}
}
-125
View File
@@ -1,125 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// TEMA: Triple Exponential Moving Average
/// A sophisticated moving average that applies three EMAs in sequence with a specific
/// combination formula to reduce lag while maintaining smoothness. The formula
/// 3*EMA1 - 3*EMA2 + EMA3 helps eliminate lag in trending markets.
/// </summary>
/// <remarks>
/// The TEMA calculation process:
/// 1. Calculates first EMA of the price
/// 2. Calculates second EMA of the first EMA
/// 3. Calculates third EMA of the second EMA
/// 4. Combines using formula: 3*EMA1 - 3*EMA2 + EMA3
///
/// Key characteristics:
/// - Significantly reduced lag compared to single EMA
/// - Better response to trends than standard EMAs
/// - Maintains smoothness despite reduced lag
/// - More responsive than double EMA (DEMA)
/// - Uses compensator for early values
///
/// Sources:
/// Patrick Mulloy - "Smoothing Data with Faster Moving Averages"
/// Technical Analysis of Stocks and Commodities, 1994
/// </remarks>
public class Tema : AbstractBase
{
private readonly double _k;
private readonly double _oneMinusK;
private readonly double _epsilon = 1e-10;
private double _lastEma1, _p_lastEma1;
private double _lastEma2, _p_lastEma2;
private double _lastEma3, _p_lastEma3;
private double _e, _p_e;
/// <param name="period">The number of periods used in each EMA calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
public Tema(int period)
{
if (period < 1)
{
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_k = 2.0 / (period + 1);
_oneMinusK = 1.0 - _k;
Name = "Tema";
double percentile = 0.85; //targeting 85th percentile of correctness of converging EMA
WarmupPeriod = (int)System.Math.Ceiling(-period * System.Math.Log(1 - percentile));
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in each EMA calculation.</param>
public Tema(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_e = 1.0;
_lastEma1 = _lastEma2 = _lastEma3 = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateEma(double input, double lastEma, double invE)
{
return (_k * ((input * invE) - lastEma)) + lastEma;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double UpdateCompensator()
{
_e = (_e > _epsilon) ? _oneMinusK * _e : 0;
return (_e > _epsilon) ? 1.0 / (1.0 - _e) : 1.0;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
double invE = UpdateCompensator();
// Calculate EMAs with compensation
double ema1 = CalculateEma(Input.Value, _lastEma1, 1.0); // First EMA doesn't need compensation
double ema2 = CalculateEma(ema1, _lastEma2, invE);
double ema3 = CalculateEma(ema2, _lastEma3, invE);
// Store values for next iteration
_lastEma1 = ema1;
_lastEma2 = ema2;
_lastEma3 = ema3;
// Calculate final TEMA with compensation
double result = ((3.0 * ema1) - (3.0 * ema2) + ema3) * invE;
IsHot = _index >= WarmupPeriod;
return result;
}
}
-111
View File
@@ -1,111 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// TRIMA: Triangular Moving Average
/// A moving average that uses triangular-shaped weights that increase linearly to
/// the middle of the period and then decrease linearly. This creates a smoother
/// output than simple moving averages.
/// </summary>
/// <remarks>
/// The TRIMA calculation process:
/// 1. Generates triangular weights that peak at the center
/// 2. Weights increase linearly to middle point
/// 3. Weights decrease linearly from middle point
/// 4. Applies normalized weights through convolution
///
/// Key characteristics:
/// - Smoother than simple moving average
/// - Natural emphasis on central values
/// - Reduced noise sensitivity
/// - Double smoothing effect
/// - Implemented using efficient convolution operations
///
/// Sources:
/// https://www.investopedia.com/terms/t/triangularaverage.asp
/// Technical Analysis of Stocks & Commodities magazine
/// </remarks>
public class Trima : AbstractBase
{
private readonly Convolution _convolution;
/// <param name="period">The number of data points used in the TRIMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Trima(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
double[] _kernel = GenerateKernel(period);
_convolution = new Convolution(_kernel);
Name = "Trima";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the TRIMA calculation.</param>
public Trima(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Generates the triangular-shaped convolution kernel for the TRIMA calculation.
/// </summary>
/// <param name="period">The period for which to generate the kernel.</param>
/// <returns>An array of normalized triangular weights for the convolution operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double[] GenerateKernel(int period)
{
double[] kernel = new double[period];
int halfPeriod = (period + 1) / 2;
double weightSum = 0;
// Calculate weights and sum in one pass
for (int i = 0; i < period; i++)
{
kernel[i] = i < halfPeriod ? i + 1 : period - i;
weightSum += kernel[i];
}
// Normalize using multiplication instead of division
double invWeightSum = 1.0 / weightSum;
for (int i = 0; i < period; i++)
{
kernel[i] *= invWeightSum;
}
return kernel;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_convolution.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
var convolutionResult = _convolution.Calc(Input);
IsHot = _index >= WarmupPeriod;
return convolutionResult.Value;
}
}
-135
View File
@@ -1,135 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// VIDYA: Variable Index Dynamic Average
/// An adaptive moving average that adjusts its smoothing based on the ratio of
/// short-term to long-term volatility. This allows the average to become more
/// responsive during volatile periods and more stable during quiet periods.
/// </summary>
/// <remarks>
/// The VIDYA calculation process:
/// 1. Calculates standard deviation for short and long periods
/// 2. Uses ratio of short/long volatility to determine smoothing
/// 3. Applies variable smoothing factor to price data
/// 4. Adapts automatically to changing market conditions
///
/// Key characteristics:
/// - Adaptive smoothing based on volatility
/// - More responsive during volatile periods
/// - More stable during quiet periods
/// - Uses standard deviation for volatility measurement
/// - Combines short and long-term market analysis
///
/// Sources:
/// Tushar Chande - "Beyond Technical Analysis"
/// https://www.investopedia.com/terms/v/vidya.asp
/// </remarks>
public class Vidya : AbstractBase
{
private readonly int _longPeriod;
private readonly double _alpha;
private readonly CircularBuffer _shortBuffer;
private readonly CircularBuffer _longBuffer;
private double _lastVIDYA, _p_lastVIDYA;
/// <param name="shortPeriod">The number of periods for short-term volatility calculation.</param>
/// <param name="longPeriod">The number of periods for long-term volatility calculation (default is 4x shortPeriod).</param>
/// <param name="alpha">The alpha parameter controlling the base smoothing factor (default 0.2).</param>
/// <exception cref="ArgumentException">Thrown when shortPeriod is less than 1.</exception>
public Vidya(int shortPeriod, int longPeriod = 0, double alpha = 0.2)
{
if (shortPeriod < 1)
{
throw new System.ArgumentException("Short period must be greater than or equal to 1.", nameof(shortPeriod));
}
_longPeriod = (longPeriod == 0) ? shortPeriod * 4 : longPeriod;
_alpha = alpha;
_shortBuffer = new CircularBuffer(shortPeriod);
_longBuffer = new CircularBuffer(_longPeriod);
WarmupPeriod = _longPeriod;
Name = $"Vidya({shortPeriod},{_longPeriod})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="shortPeriod">The number of periods for short-term volatility calculation.</param>
/// <param name="longPeriod">The number of periods for long-term volatility calculation (default is 4x shortPeriod).</param>
/// <param name="alpha">The alpha parameter controlling the base smoothing factor (default 0.2).</param>
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));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_lastVIDYA = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_p_lastVIDYA = _lastVIDYA;
}
else
{
_lastVIDYA = _p_lastVIDYA;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateStdDev(CircularBuffer buffer)
{
double mean = buffer.Average();
double sumSquaredDiff = 0;
var span = buffer.GetSpan();
for (int i = 0; i < buffer.Count; i++)
{
double diff = span[i] - mean;
sumSquaredDiff += diff * diff;
}
return System.Math.Sqrt(sumSquaredDiff / buffer.Count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateVidya(double shortStdDev, double longStdDev)
{
double s = _alpha * (shortStdDev / longStdDev);
return (s * Input.Value) + ((1.0 - s) * _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);
vidya = CalculateVidya(shortStdDev, longStdDev);
}
_lastVIDYA = vidya;
IsHot = _index >= WarmupPeriod;
return vidya;
}
}
-103
View File
@@ -1,103 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// WMA: Weighted Moving Average
/// A moving average that assigns linearly decreasing weights to older data points.
/// The most recent price has the highest weight, and each older price receives
/// linearly less weight, creating a more responsive average than SMA.
/// </summary>
/// <remarks>
/// The WMA calculation process:
/// 1. Assigns weights linearly decreasing with age
/// 2. Most recent price gets weight of period
/// 3. Each older price gets decremented weight
/// 4. Normalizes weights by sum of weights
/// 5. Applies weights through convolution
///
/// Key characteristics:
/// - Linear weight distribution
/// - More responsive than SMA
/// - Less lag than SMA
/// - Emphasizes recent prices
/// - Implemented using efficient convolution operations
///
/// Sources:
/// https://www.investopedia.com/articles/technical/060401.asp
/// https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:weighted_moving_average
/// </remarks>
public class Wma : AbstractBase
{
private readonly Convolution _convolution;
/// <param name="period">The number of data points used in the WMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Wma(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
double[] _kernel = GenerateWmaKernel(period);
_convolution = new Convolution(_kernel);
Name = "Wma";
WarmupPeriod = period;
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of data points used in the WMA calculation.</param>
public Wma(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Generates the linearly weighted convolution kernel for the WMA calculation.
/// </summary>
/// <param name="period">The period for which to generate the kernel.</param>
/// <returns>An array of normalized linearly decreasing weights for the convolution operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double[] GenerateWmaKernel(int period)
{
double[] kernel = new double[period];
double weightSum = period * (period + 1) * 0.5; // Multiply by 0.5 instead of dividing by 2
double invWeightSum = 1.0 / weightSum;
for (int i = 0; i < period; i++)
{
kernel[i] = (period - i) * invWeightSum;
}
return kernel;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private new void Init()
{
base.Init();
_convolution.Init();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
// Use Convolution for calculation
var convolutionResult = _convolution.Calc(Input);
IsHot = _index >= WarmupPeriod;
return convolutionResult.Value;
}
}
-112
View File
@@ -1,112 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// ZLEMA: Zero Lag Exponential Moving Average
/// A modified exponential moving average designed to reduce lag by incorporating
/// error correction based on predicted values. It estimates and removes lag by
/// extrapolating the trend using the difference between current and lagged prices.
/// </summary>
/// <remarks>
/// The ZLEMA calculation process:
/// 1. Calculates lag period as (period - 1) / 2
/// 2. Gets error correction term: 2 * price - lag_price
/// 3. Applies EMA to error-corrected price
/// 4. Results in reduced lag compared to standard EMA
///
/// Key characteristics:
/// - Significantly reduced lag compared to EMA
/// - More responsive to price changes
/// - Uses error correction mechanism
/// - Maintains smoothness despite reduced lag
/// - Better trend following capabilities
///
/// Sources:
/// John Ehlers and Ric Way - "Zero Lag (Well, Almost)"
/// Technical Analysis of Stocks and Commodities, 2010
/// </remarks>
public class Zlema : AbstractBase
{
private readonly CircularBuffer _buffer;
private readonly int _lag;
private readonly Ema _ema;
private double _lastZLEMA, _p_lastZLEMA;
/// <param name="period">The number of periods used in the ZLEMA calculation.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
public Zlema(int period)
{
if (period < 1)
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
WarmupPeriod = period;
_lag = (int)(0.5 * (period - 1));
_buffer = new CircularBuffer(_lag + 1);
_ema = new Ema(period, useSma: false);
Name = $"Zlema({period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in the ZLEMA calculation.</param>
public Zlema(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_buffer.Clear();
_ema.Init();
_lastZLEMA = 0;
_p_lastZLEMA = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
_p_lastZLEMA = _lastZLEMA;
}
else
{
_lastZLEMA = _p_lastZLEMA;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateErrorCorrection()
{
double lagValue = _buffer[System.Math.Max(0, _buffer.Count - 1 - _lag)];
return (2.0 * Input.Value) - lagValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateZlema(double errorCorrection)
{
var tempValue = new TValue(Input.Time, errorCorrection, Input.IsNew);
return _ema.Calc(tempValue).Value;
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
// Calculate error correction and apply EMA
double errorCorrection = CalculateErrorCorrection();
double zlema = CalculateZlema(errorCorrection);
_lastZLEMA = zlema;
IsHot = _index >= WarmupPeriod;
return zlema;
}
}
-36
View File
@@ -1,36 +0,0 @@
# Averages indicators
✔️ AFIRMA - Adaptive FIR Moving Average
✔️ ALMA - Arnaud Legoux Moving Average
✔️ CONVOLUTION - 1D Convolution with sliding kernel
✔️ DEMA - Double Exponential Moving Average
✔️ DSMA - Dynamic Simple Moving Average
✔️ DWMA - Dynamic Weighted Moving Average
✔️ EMA - Exponential Moving Average
✔️ EPMA - Endpoint Moving Average
✔️ FRAMA - Fractal Adaptive Moving Average
✔️ FWMA - Forward Weighted Moving Average
✔️ GMA - Gaussian Moving Average
✔️ HMA - Hull Moving Average
✔️ HTIT - Hilbert Transform Instantaneous Trendline
✔️ HWMA - Hann Weighted Moving Average
✔️ JMA - Jurik Moving Average
✔️ KAMA - Kaufman Adaptive Moving Average
✔️ LTMA - Linear Time Moving Average
✔️ MAAF - Moving Average Adaptive Filter
✔️ MAMA - MESA Adaptive Moving Average (MAMA, FAMA)
✔️ MGDI - McGinley Dynamic Indicator
✔️ MMA - Modified Moving Average
✔️ PWMA - Parabolic Weighted Moving Average
✔️ QEMA - Quick Exponential Moving Average
✔️ REMA - Regularized Exponential Moving Average
✔️ RMA - Running Moving Average
✔️ SINEMA - Sine-weighted Moving Average
✔️ SMA - Simple Moving Average
✔️ SMMA - Smoothed Moving Average
✔️ T3 - Triple Exponential Moving Average (T3)
✔️ TEMA - Triple Exponential Moving Average
✔️ TRIMA - Triangular Moving Average
✔️ VIDYA - Variable Index Dynamic Average
✔️ WMA - Weighted Moving Average
✔️ ZLEMA - Zero-Lag Exponential Moving Average