mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 17:18:05 +00:00
Class optimization
This commit is contained in:
+71
-51
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -31,6 +31,9 @@ public class Afirma : AbstractBase
|
||||
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>
|
||||
@@ -56,7 +59,7 @@ public class Afirma : AbstractBase
|
||||
_armaBuffer = new double[taps];
|
||||
_n = (Taps - 1) / 2;
|
||||
|
||||
// Calculate least squares coefficients in the constructor
|
||||
// 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;
|
||||
@@ -78,6 +81,7 @@ public class Afirma : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -87,6 +91,34 @@ public class Afirma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
@@ -94,71 +126,59 @@ public class Afirma : AbstractBase
|
||||
|
||||
if (_index >= Taps)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
sx2y += i * i * _buffer[_n - i];
|
||||
sx3y += i * i * 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++)
|
||||
{
|
||||
_armaBuffer[_n - k] = a0 + k * a1 + k * k * a2 + k * k * k * a3;
|
||||
}
|
||||
CalculateAdaptiveCoefficients();
|
||||
}
|
||||
|
||||
double result = 0.0;
|
||||
for (int k = 0; k < Taps; k++)
|
||||
{
|
||||
result += _buffer[k] * _weights[k] / _wsum;
|
||||
result += _buffer[k] * _weights[k];
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return result;
|
||||
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;
|
||||
switch (Window)
|
||||
{
|
||||
case WindowType.Rectangular:
|
||||
windowWeight = 1.0;
|
||||
break;
|
||||
case WindowType.Hanning1:
|
||||
windowWeight = 0.50 - 0.50 * Math.Cos(2.0 * Math.PI * k / (Taps - 1));
|
||||
break;
|
||||
case WindowType.Hanning2:
|
||||
windowWeight = 0.54 - 0.46 * Math.Cos(2.0 * Math.PI * k / (Taps - 1));
|
||||
break;
|
||||
case WindowType.Blackman:
|
||||
windowWeight = 0.42 - 0.50 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)) + 0.08 * Math.Cos(4.0 * Math.PI * k / (Taps - 1));
|
||||
break;
|
||||
case WindowType.BlackmanHarris:
|
||||
windowWeight = 0.35875 - 0.48829 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)) + 0.14128 * Math.Cos(4.0 * Math.PI * k / (Taps - 1)) - 0.01168 * Math.Cos(6.0 * Math.PI * k / (Taps - 1));
|
||||
break;
|
||||
default:
|
||||
windowWeight = 1.0;
|
||||
break;
|
||||
}
|
||||
|
||||
double sincWeight;
|
||||
sincWeight = Math.Abs(k - centerTap) < 1e-10 ? 1.0 : Math.Sin(Math.PI * (k - centerTap) / Periods) / (Math.PI * (k - centerTap) / Periods);
|
||||
double windowWeight = GetWindowWeight(k, tapsMinusOne);
|
||||
double x = Math.PI * (k - centerTap) / Periods;
|
||||
double sincWeight = CalculateSincWeight(x);
|
||||
|
||||
_weights[k] = windowWeight * sincWeight;
|
||||
wsum += _weights[k];
|
||||
|
||||
+36
-12
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -17,6 +17,7 @@ public class Convolution : AbstractBase
|
||||
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>
|
||||
@@ -41,22 +42,27 @@ public class Convolution : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private new void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
Array.Copy(_kernel, _normalizedKernel, _kernelSize);
|
||||
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;
|
||||
@@ -65,7 +71,6 @@ public class Convolution : AbstractBase
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
// Normalize kernel on each calculation until buffer is full
|
||||
@@ -80,37 +85,56 @@ public class Convolution : AbstractBase
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void NormalizeKernel()
|
||||
{
|
||||
int activeLength = Math.Min(_index, _kernelSize);
|
||||
double sum = 0;
|
||||
|
||||
// Calculate the sum of the active kernel elements
|
||||
for (int i = 0; i < activeLength; i++)
|
||||
for (int i = 0; i < _activeLength; i++)
|
||||
{
|
||||
sum += _kernel[i];
|
||||
}
|
||||
|
||||
// Normalize the kernel or set equal weights if the sum is zero
|
||||
double normalizationFactor = (sum != 0) ? sum : activeLength;
|
||||
for (int i = 0; i < activeLength; i++)
|
||||
double normalizationFactor = (sum != 0) ? sum : _activeLength;
|
||||
double invNormFactor = 1.0 / normalizationFactor;
|
||||
|
||||
for (int i = 0; i < _activeLength; i++)
|
||||
{
|
||||
_normalizedKernel[i] = _kernel[i] / normalizationFactor;
|
||||
_normalizedKernel[i] = _kernel[i] * invNormFactor;
|
||||
}
|
||||
|
||||
// Set the rest of the normalized kernel to zero
|
||||
Array.Clear(_normalizedKernel, activeLength, _kernelSize - activeLength);
|
||||
if (_activeLength < _kernelSize)
|
||||
{
|
||||
System.Array.Clear(_normalizedKernel, _activeLength, _kernelSize - _activeLength);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double ConvolveBuffer()
|
||||
{
|
||||
double sum = 0;
|
||||
var bufferSpan = _buffer.GetSpan();
|
||||
int activeLength = Math.Min(_index, _kernelSize);
|
||||
int offset = _activeLength - 1;
|
||||
|
||||
for (int i = 0; i < activeLength; i++)
|
||||
// Unroll the loop for better performance when possible
|
||||
int i = 0;
|
||||
while (i <= offset - 3)
|
||||
{
|
||||
sum += bufferSpan[activeLength - 1 - i] * _normalizedKernel[i];
|
||||
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;
|
||||
|
||||
+27
-28
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,12 +8,8 @@ namespace QuanTAlib;
|
||||
/// smoothness, at the cost of overshooting the signal line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Smoothness: ★★★☆☆ (3/5)
|
||||
/// Sensitivity: ★★★★☆ (4/5)
|
||||
/// Overshooting: ★★★☆☆ (3/5)
|
||||
/// Lag: ★★★★☆ (4/5)
|
||||
///
|
||||
/// Sources:
|
||||
/// https://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/
|
||||
///
|
||||
@@ -21,12 +18,12 @@ namespace QuanTAlib;
|
||||
/// </remarks>
|
||||
public class Dema : AbstractBase
|
||||
{
|
||||
// inherited _index
|
||||
// inherited _value
|
||||
private readonly int _period;
|
||||
private readonly double _k;
|
||||
private readonly double _epsilon = 1e-10;
|
||||
private double _lastEma1, _p_lastEma1;
|
||||
private double _lastEma2, _p_lastEma2;
|
||||
private double _k, _e, _p_e;
|
||||
private double _e, _p_e;
|
||||
|
||||
public Dema(int period)
|
||||
{
|
||||
@@ -35,9 +32,10 @@ public class Dema : AbstractBase
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
_period = period;
|
||||
_k = 2.0 / (_period + 1);
|
||||
Name = "Dema";
|
||||
double percentile = 0.85; //targeting 85th percentile of correctness of converging EMA
|
||||
WarmupPeriod = (int)Math.Ceiling(-period * Math.Log(1 - percentile));
|
||||
WarmupPeriod = (int)System.Math.Ceiling(-period * System.Math.Log(1 - percentile));
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -46,17 +44,17 @@ public class Dema : AbstractBase
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
//inhereted public void Sub(object source, in ValueEventArgs args)
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_k = 2.0 / (_period + 1);
|
||||
_e = 1.0;
|
||||
_lastEma1 = 0;
|
||||
_lastEma2 = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -74,30 +72,31 @@ public class Dema : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core DEMA calculation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double CalculateEma(double input, double lastEma)
|
||||
{
|
||||
return _k * (input - lastEma) + lastEma;
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double result, _ema1, _ema2;
|
||||
// compensator for early ema values
|
||||
_e = (_e > 1e-10) ? (1 - _k) * _e : 0;
|
||||
double _invE = (_e > 1e-10) ? 1 / (1 - _e) : 1;
|
||||
// Compensator for early EMA values
|
||||
_e = (_e > _epsilon) ? (1 - _k) * _e : 0;
|
||||
double invE = (_e > _epsilon) ? 1 / (1 - _e) : 1;
|
||||
|
||||
// Calculate EMA1
|
||||
_ema1 = _k * (Input.Value - _lastEma1) + _lastEma1;
|
||||
// Calculate EMAs
|
||||
double ema1 = CalculateEma(Input.Value, _lastEma1);
|
||||
double compensatedEma1 = ema1 * invE;
|
||||
double ema2 = CalculateEma(compensatedEma1, _lastEma2);
|
||||
|
||||
// Calculate EMA2 using compensatedEma1
|
||||
_ema2 = _k * (_ema1 * _invE - _lastEma2) + _lastEma2;
|
||||
// Store values for next iteration
|
||||
_lastEma1 = ema1;
|
||||
_lastEma2 = ema2;
|
||||
|
||||
// Calculate DEMA
|
||||
double _dema = 2 * _ema1 * _invE - (_ema2 * _invE);
|
||||
|
||||
result = _dema;
|
||||
_lastEma1 = _ema1;
|
||||
_lastEma2 = _ema2;
|
||||
// Calculate final DEMA
|
||||
double result = 2 * compensatedEma1 - (ema2 * invE);
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return result;
|
||||
|
||||
+36
-14
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -25,6 +26,10 @@ public class Dsma : AbstractBase
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly double _c1, _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;
|
||||
@@ -46,16 +51,20 @@ public class Dsma : AbstractBase
|
||||
throw new ArgumentOutOfRangeException(nameof(scaleFactor), "Scale factor must be between 0 and 1 (exclusive).");
|
||||
}
|
||||
_period = period;
|
||||
_periodRecip = 1.0 / period;
|
||||
_scaleFactor = scaleFactor;
|
||||
_buffer = new CircularBuffer(period);
|
||||
|
||||
// SuperSmoother filter coefficients
|
||||
double _a1 = Math.Exp(-1.414 * Math.PI / (0.5 * period));
|
||||
double _b1 = 2 * _a1 * Math.Cos(1.414 * Math.PI / (0.5 * period));
|
||||
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;
|
||||
_c1 = 1 - _c2 - _c3;
|
||||
_c2 = b1;
|
||||
_c3 = -a1 * a1;
|
||||
_c1 = 1.0 - _c2 - _c3;
|
||||
_c1Half = _c1 * 0.5;
|
||||
_scaleByPeriod = 5.0 / period;
|
||||
|
||||
Name = "Dsma";
|
||||
WarmupPeriod = (int)(period * 1.5); // A conservative estimate
|
||||
@@ -68,6 +77,7 @@ public class Dsma : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -77,6 +87,7 @@ public class Dsma : AbstractBase
|
||||
_isInit = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -102,6 +113,19 @@ public class Dsma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
@@ -117,20 +141,18 @@ public class Dsma : AbstractBase
|
||||
_zeros = Input.Value - _lastDsma;
|
||||
|
||||
// SuperSmoother Filter
|
||||
_filt = _c1 * (_zeros + _zeros1) / 2 + _c2 * _filt1 + _c3 * _filt2;
|
||||
_filt = CalculateSuperSmootherFilter();
|
||||
|
||||
// Update buffer for RMS calculation
|
||||
_buffer.Add(_filt * _filt, Input.IsNew);
|
||||
double filtSquared = _filt * _filt;
|
||||
_buffer.Add(filtSquared, Input.IsNew);
|
||||
|
||||
// Compute RMS (Root Mean Square)
|
||||
double rms = Math.Sqrt(_buffer.Sum() / _period);
|
||||
double rms = System.Math.Sqrt(_buffer.Sum() * _periodRecip);
|
||||
|
||||
// Rescale Filt in terms of Standard Deviations
|
||||
double scaledFilt = rms != 0 ? _filt / rms : 0;
|
||||
|
||||
// Calculate adaptive alpha
|
||||
double alpha = _scaleFactor * Math.Abs(scaledFilt) * 5 / _period;
|
||||
alpha = Math.Max(0.1, Math.Min(1.0, alpha));
|
||||
// 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;
|
||||
|
||||
+16
-7
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -25,16 +26,18 @@ public class Dwma : AbstractBase
|
||||
{
|
||||
private readonly Wma _innerWma;
|
||||
private readonly Wma _outerWma;
|
||||
private readonly int _period;
|
||||
|
||||
public Dwma(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_period = period;
|
||||
_innerWma = new Wma(period);
|
||||
_outerWma = new Wma(period);
|
||||
Name = "Wma";
|
||||
Name = "Dwma";
|
||||
WarmupPeriod = 2 * period - 1;
|
||||
Init();
|
||||
}
|
||||
@@ -45,6 +48,7 @@ public class Dwma : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -52,6 +56,7 @@ public class Dwma : AbstractBase
|
||||
_outerWma.Init();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -61,19 +66,23 @@ public class Dwma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override double GetLastValid()
|
||||
{
|
||||
return _lastValidValue;
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// Calculate inner WMA
|
||||
TValue innerResult = _innerWma.Calc(Input);
|
||||
var innerResult = _innerWma.Calc(Input);
|
||||
|
||||
// Calculate outer WMA using the result of inner WMA
|
||||
TValue outerResult = _outerWma.Calc(innerResult);
|
||||
var outerResult = _outerWma.Calc(innerResult);
|
||||
|
||||
double result = outerResult.Value;
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return result;
|
||||
return outerResult.Value;
|
||||
}
|
||||
}
|
||||
|
||||
+33
-62
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -22,43 +23,14 @@ namespace QuanTAlib;
|
||||
/// </remarks>
|
||||
public class Ema : AbstractBase
|
||||
{
|
||||
// inherited _index
|
||||
// inherited _value
|
||||
|
||||
/// <summary>
|
||||
/// The period for the EMA calculation.
|
||||
/// </summary>
|
||||
private readonly int _period;
|
||||
|
||||
/// <summary>
|
||||
/// Circular buffer for SMA calculation.
|
||||
/// </summary>
|
||||
private CircularBuffer _sma;
|
||||
|
||||
/// <summary>
|
||||
/// The last calculated EMA value.
|
||||
/// </summary>
|
||||
private double _lastEma, _p_lastEma;
|
||||
|
||||
/// <summary>
|
||||
/// Compensator for early EMA values.
|
||||
/// </summary>
|
||||
private double _e, _p_e;
|
||||
|
||||
/// <summary>
|
||||
/// The smoothing factor for EMA calculation.
|
||||
/// </summary>
|
||||
private readonly double _k;
|
||||
|
||||
/// <summary>
|
||||
/// Flags to track initialization status.
|
||||
/// </summary>
|
||||
private bool _isInit, _p_isInit;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to determine whether to use SMA for initial values.
|
||||
/// </summary>
|
||||
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.
|
||||
@@ -70,14 +42,14 @@ public class Ema : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 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);
|
||||
_sma = new(_period);
|
||||
Name = "Ema";
|
||||
WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - _k)); //95th percentile
|
||||
WarmupPeriod = (int)System.Math.Ceiling(System.Math.Log(0.05) / System.Math.Log(1 - _k)); //95th percentile
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -92,7 +64,7 @@ public class Ema : AbstractBase
|
||||
_sma = new(1);
|
||||
Name = "Ema";
|
||||
_period = 1;
|
||||
WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - _k)); //95th percentile
|
||||
WarmupPeriod = (int)System.Math.Ceiling(System.Math.Log(0.05) / System.Math.Log(1 - _k)); //95th percentile
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -108,9 +80,7 @@ public class Ema : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Ema instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -121,10 +91,7 @@ public class Ema : AbstractBase
|
||||
_sma = new(_period);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the Ema instance.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the input is new.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -142,21 +109,27 @@ public class Ema : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the EMA calculation.
|
||||
/// </summary>
|
||||
/// <returns>The calculated EMA value.</returns>
|
||||
[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()
|
||||
{
|
||||
double result, _ema;
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// when _UseSma == true, use SMA calculation until we have enough data points
|
||||
double ema;
|
||||
if (!_isInit && _useSma)
|
||||
{
|
||||
_sma.Add(Input.Value, Input.IsNew);
|
||||
_ema = _sma.Average();
|
||||
result = _ema;
|
||||
ema = _sma.Average();
|
||||
if (_index >= _period)
|
||||
{
|
||||
_isInit = true;
|
||||
@@ -164,16 +137,14 @@ public class Ema : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
// compensator for early ema values
|
||||
_e = (_e > 1e-10) ? (1 - _k) * _e : 0;
|
||||
|
||||
_ema = _k * (Input.Value - _lastEma) + _lastEma;
|
||||
|
||||
// _useSma decides if we use compensator or not
|
||||
result = (_useSma || _e <= double.Epsilon) ? _ema : _ema / (1 - _e);
|
||||
// Compensator for early EMA values
|
||||
_e = (_e > _epsilon) ? (1 - _k) * _e : 0;
|
||||
ema = CalculateEma(Input.Value, _lastEma);
|
||||
ema = CompensateEma(ema);
|
||||
}
|
||||
_lastEma = _ema;
|
||||
|
||||
_lastEma = ema;
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return result;
|
||||
return ema;
|
||||
}
|
||||
}
|
||||
|
||||
+24
-17
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,6 +27,7 @@ public class Epma : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly Convolution _convolution;
|
||||
private readonly double[] _baseKernel;
|
||||
|
||||
/// <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>
|
||||
@@ -34,10 +35,11 @@ public class Epma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_period = period;
|
||||
_convolution = new Convolution(GenerateKernel(_period));
|
||||
_baseKernel = GenerateKernel(_period);
|
||||
_convolution = new Convolution(_baseKernel);
|
||||
Name = "Epma";
|
||||
WarmupPeriod = period;
|
||||
Init();
|
||||
@@ -51,12 +53,14 @@ public class Epma : AbstractBase
|
||||
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)
|
||||
@@ -66,24 +70,31 @@ public class Epma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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
|
||||
TValue convolutionResult = _convolution.Calc(Input);
|
||||
|
||||
var convolutionResult = _convolution.Calc(Input);
|
||||
double result = convolutionResult.Value;
|
||||
|
||||
// Adjust for partial periods during warmup
|
||||
if (_index < _period)
|
||||
{
|
||||
double[] partialKernel = GenerateKernel(_index);
|
||||
result /= partialKernel.Sum();
|
||||
result *= CalculateKernelSum(_period) / CalculateKernelSum(_index);
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -92,21 +103,17 @@ public class Epma : AbstractBase
|
||||
/// </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 = 0;
|
||||
double weightSum = CalculateKernelSum(period);
|
||||
double invWeightSum = 1.0 / weightSum;
|
||||
double baseWeight = 2 * period - 1;
|
||||
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
kernel[i] = (2 * period - 1) - 3 * i;
|
||||
weightSum += kernel[i];
|
||||
}
|
||||
|
||||
// Normalize the kernel
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
kernel[i] /= weightSum;
|
||||
kernel[i] = (baseWeight - 3 * i) * invWeightSum;
|
||||
}
|
||||
|
||||
return kernel;
|
||||
|
||||
+40
-22
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -28,6 +27,11 @@ namespace QuanTAlib;
|
||||
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;
|
||||
@@ -37,9 +41,12 @@ public class Frama : AbstractBase
|
||||
public Frama(int period)
|
||||
{
|
||||
if (period < 2)
|
||||
throw new ArgumentException("Period must be at least 2", nameof(period));
|
||||
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;
|
||||
}
|
||||
@@ -52,6 +59,7 @@ public class Frama : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -60,6 +68,7 @@ public class Frama : AbstractBase
|
||||
_prevLastFrama = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -73,6 +82,26 @@ public class Frama : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private 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 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);
|
||||
@@ -85,7 +114,6 @@ public class Frama : AbstractBase
|
||||
return _lastFrama;
|
||||
}
|
||||
|
||||
int half = _period / 2;
|
||||
double hh = double.MinValue, ll = double.MaxValue;
|
||||
double hh1 = double.MinValue, ll1 = double.MaxValue;
|
||||
double hh2 = double.MinValue, ll2 = double.MaxValue;
|
||||
@@ -93,37 +121,27 @@ public class Frama : AbstractBase
|
||||
for (int i = 0; i < _period; i++)
|
||||
{
|
||||
double price = _buffer[i];
|
||||
hh = Math.Max(hh, price);
|
||||
ll = Math.Min(ll, price);
|
||||
UpdateMinMax(price, ref hh, ref ll);
|
||||
|
||||
if (i < half)
|
||||
if (i < _halfPeriod)
|
||||
{
|
||||
hh1 = Math.Max(hh1, price);
|
||||
ll1 = Math.Min(ll1, price);
|
||||
UpdateMinMax(price, ref hh1, ref ll1);
|
||||
}
|
||||
else
|
||||
{
|
||||
hh2 = Math.Max(hh2, price);
|
||||
ll2 = Math.Min(ll2, price);
|
||||
UpdateMinMax(price, ref hh2, ref ll2);
|
||||
}
|
||||
}
|
||||
|
||||
double n1 = (hh - ll) / _period;
|
||||
double n2 = (hh1 - ll1 + hh2 - ll2) / (_period / 2);
|
||||
double n1 = (hh - ll) * _periodRecip;
|
||||
double n2 = (hh1 - ll1 + hh2 - ll2) * _halfPeriodRecip;
|
||||
|
||||
double d = (Math.Log(n2 + double.Epsilon) - Math.Log(n1 + double.Epsilon)) / Math.Log(2);
|
||||
|
||||
double alpha = Math.Exp(-4.6 * (d - 1));
|
||||
alpha = Math.Max(Math.Min(alpha, 1), 0.01); // Ensure alpha is between 0.01 and 1
|
||||
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;
|
||||
}
|
||||
|
||||
protected override double GetLastValid()
|
||||
{
|
||||
return _lastFrama;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-17
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,6 +27,7 @@ namespace QuanTAlib;
|
||||
public class Fwma : AbstractBase
|
||||
{
|
||||
private readonly Convolution _convolution;
|
||||
private readonly double[] _kernel;
|
||||
|
||||
/// <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>
|
||||
@@ -34,9 +35,10 @@ public class Fwma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_convolution = new Convolution(GenerateKernel(period));
|
||||
_kernel = GenerateKernel(period);
|
||||
_convolution = new Convolution(_kernel);
|
||||
Name = "Fwma";
|
||||
WarmupPeriod = period;
|
||||
Init();
|
||||
@@ -55,41 +57,42 @@ public class Fwma : AbstractBase
|
||||
/// </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];
|
||||
double weightSum = 0;
|
||||
|
||||
// Generate Fibonacci series
|
||||
// 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];
|
||||
}
|
||||
|
||||
// Reverse the series to give more weight to recent prices
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
kernel[i] = fibSeries[period - 1 - i];
|
||||
weightSum += kernel[i];
|
||||
}
|
||||
// Calculate inverse of weight sum for normalization
|
||||
double invWeightSum = 1.0 / weightSum;
|
||||
|
||||
// Normalize the kernel
|
||||
// Reverse and normalize the series in one pass
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
kernel[i] /= weightSum;
|
||||
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)
|
||||
@@ -104,11 +107,9 @@ public class Fwma : AbstractBase
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// Use Convolution for calculation
|
||||
TValue convolutionResult = _convolution.Calc(Input);
|
||||
|
||||
double result = convolutionResult.Value;
|
||||
var convolutionResult = _convolution.Calc(Input);
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return result;
|
||||
return convolutionResult.Value;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-11
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,6 +27,7 @@ namespace QuanTAlib;
|
||||
public class Gma : AbstractBase
|
||||
{
|
||||
private readonly Convolution _convolution;
|
||||
private readonly double[] _kernel;
|
||||
|
||||
/// <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>
|
||||
@@ -34,9 +35,10 @@ public class Gma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_convolution = new Convolution(GenerateKernel(period));
|
||||
_kernel = GenerateKernel(period);
|
||||
_convolution = new Convolution(_kernel);
|
||||
Name = "Gma";
|
||||
WarmupPeriod = period;
|
||||
Init();
|
||||
@@ -56,34 +58,41 @@ public class Gma : AbstractBase
|
||||
/// <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) / (double)center;
|
||||
kernel[i] = Math.Exp(-(x * x) / (2 * sigma * sigma));
|
||||
double x = (i - center) * centerRecip;
|
||||
kernel[i] = System.Math.Exp(-(x * x) / sigmaSquared2);
|
||||
weightSum += kernel[i];
|
||||
}
|
||||
|
||||
// Normalize the kernel
|
||||
// Normalize using multiplication instead of division
|
||||
double invWeightSum = 1.0 / weightSum;
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
kernel[i] /= weightSum;
|
||||
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)
|
||||
@@ -98,11 +107,9 @@ public class Gma : AbstractBase
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// Use Convolution for calculation
|
||||
TValue convolutionResult = _convolution.Calc(Input);
|
||||
|
||||
double result = convolutionResult.Value;
|
||||
var convolutionResult = _convolution.Calc(Input);
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return result;
|
||||
return convolutionResult.Value;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-10
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,11 @@ namespace QuanTAlib;
|
||||
public class Hma : AbstractBase
|
||||
{
|
||||
private readonly Convolution _wmaHalf, _wmaFull, _wmaFinal;
|
||||
private readonly int _period;
|
||||
private readonly int _sqrtPeriod;
|
||||
private readonly double[] _kernelHalf;
|
||||
private readonly double[] _kernelFull;
|
||||
private readonly double[] _kernelFinal;
|
||||
|
||||
/// <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>
|
||||
@@ -36,12 +41,21 @@ public class Hma : AbstractBase
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 2.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 2.", nameof(period));
|
||||
}
|
||||
int _sqrtPeriod = (int)Math.Sqrt(period);
|
||||
_wmaHalf = new Convolution(GenerateWmaKernel(period / 2));
|
||||
_wmaFull = new Convolution(GenerateWmaKernel(period));
|
||||
_wmaFinal = new Convolution(GenerateWmaKernel(_sqrtPeriod));
|
||||
_period = period;
|
||||
_sqrtPeriod = (int)System.Math.Sqrt(period);
|
||||
|
||||
// Generate all kernels once
|
||||
_kernelHalf = GenerateWmaKernel(period / 2);
|
||||
_kernelFull = GenerateWmaKernel(period);
|
||||
_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();
|
||||
@@ -60,19 +74,22 @@ public class Hma : AbstractBase
|
||||
/// </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) / 2.0;
|
||||
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) / weightSum;
|
||||
kernel[i] = (period - i) * invWeightSum;
|
||||
}
|
||||
|
||||
return kernel;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private new void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -81,6 +98,7 @@ public class Hma : AbstractBase
|
||||
_wmaFinal.Init();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -99,10 +117,11 @@ public class Hma : AbstractBase
|
||||
double wmaFullResult = _wmaFull.Calc(Input).Value;
|
||||
|
||||
// Calculate 2*WMA(n/2) - WMA(n)
|
||||
double intermediateResult = 2 * wmaHalfResult - wmaFullResult;
|
||||
double intermediateResult = 2.0 * wmaHalfResult - wmaFullResult;
|
||||
|
||||
// Calculate final WMA
|
||||
double result = _wmaFinal.Calc(new TValue(Input.Time, intermediateResult, Input.IsNew)).Value;
|
||||
var finalInput = new TValue(Input.Time, intermediateResult, Input.IsNew);
|
||||
double result = _wmaFinal.Calc(finalInput).Value;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return result;
|
||||
|
||||
+47
-38
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -42,28 +42,30 @@ public class Htit : AbstractBase
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Htit class.
|
||||
/// </summary>
|
||||
public Htit()
|
||||
{
|
||||
Name = "Htit";
|
||||
WarmupPeriod = 12;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Htit class with a specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
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)
|
||||
@@ -77,6 +79,26 @@ public class Htit : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
@@ -100,65 +122,52 @@ public class Htit : AbstractBase
|
||||
return pr;
|
||||
}
|
||||
|
||||
double adj = (0.075 * _lastPd) + 0.54;
|
||||
double adj = 0.075 * _lastPd + 0.54;
|
||||
|
||||
// Smooth and detrender
|
||||
double sp = ((4 * _priceBuffer[0]) + (3 * _priceBuffer[1]) + (2 * _priceBuffer[2]) + _priceBuffer[3]) / 10;
|
||||
double sp = CalculateSmoothedPrice(_priceBuffer[0], _priceBuffer[1], _priceBuffer[2], _priceBuffer[3]);
|
||||
_spBuffer.Add(sp, Input.IsNew);
|
||||
|
||||
double dt = ((0.0962 * _spBuffer[0]) + (0.5769 * _spBuffer[2]) - (0.5769 * _spBuffer[4]) - (0.0962 * _spBuffer[6])) * adj;
|
||||
double dt = CalculateHilbertTransform(_spBuffer[0], _spBuffer[2], _spBuffer[4], _spBuffer[6], adj);
|
||||
_dtBuffer.Add(dt, Input.IsNew);
|
||||
|
||||
// In-phase and quadrature
|
||||
double q1 = ((0.0962 * _dtBuffer[0]) + (0.5769 * _dtBuffer[2]) - (0.5769 * _dtBuffer[4]) - (0.0962 * _dtBuffer[6])) * adj;
|
||||
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 = ((0.0962 * _i1Buffer[0]) + (0.5769 * _i1Buffer[2]) - (0.5769 * _i1Buffer[4]) - (0.0962 * _i1Buffer[6])) * adj;
|
||||
double jQ = ((0.0962 * _q1Buffer[0]) + (0.5769 * _q1Buffer[2]) - (0.5769 * _q1Buffer[4]) - (0.0962 * _q1Buffer[6])) * adj;
|
||||
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 = i1 - jQ;
|
||||
double q2 = q1 + jI;
|
||||
|
||||
i2 = (0.2 * i2) + (0.8 * _i2Buffer[0]);
|
||||
q2 = (0.2 * q2) + (0.8 * _q2Buffer[0]);
|
||||
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 = (i2 * _i2Buffer[1]) + (q2 * _q2Buffer[1]);
|
||||
double im = (i2 * _q2Buffer[1]) - (q2 * _i2Buffer[1]);
|
||||
|
||||
re = (0.2 * re) + (0.8 * _reBuffer[0]);
|
||||
im = (0.2 * im) + (0.8 * _imBuffer[0]);
|
||||
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 != 0 && re != 0) ? 2 * Math.PI / Math.Atan(im / re) : 0;
|
||||
|
||||
// Adjust period to thresholds
|
||||
pd = (pd > 1.5 * _lastPd) ? 1.5 * _lastPd : pd;
|
||||
pd = (pd < 0.67 * _lastPd) ? 0.67 * _lastPd : pd;
|
||||
pd = (pd < 6) ? 6 : pd;
|
||||
pd = (pd > 50) ? 50 : pd;
|
||||
|
||||
// Smooth the period
|
||||
pd = (0.2 * pd) + (0.8 * _lastPd);
|
||||
double pd = (im != 0 && re != 0) ? 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]);
|
||||
double sd = 0.33 * pd + 0.67 * _sdBuffer[0];
|
||||
_sdBuffer.Add(sd, Input.IsNew);
|
||||
|
||||
// Smooth dominant cycle period
|
||||
int dcPeriods = (int)(sd + 0.5);
|
||||
double sumPr = _priceBuffer.GetSpan().Slice(0, Math.Min(dcPeriods, _priceBuffer.Count)).ToArray().Sum();
|
||||
double 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);
|
||||
|
||||
@@ -166,9 +175,9 @@ public class Htit : AbstractBase
|
||||
_lastPd = pd;
|
||||
|
||||
// Final indicator
|
||||
if (_index >= 11) // 12th bar
|
||||
if (_index >= 11)
|
||||
{
|
||||
return ((4 * _itBuffer[0]) + (3 * _itBuffer[1]) + (2 * _itBuffer[2]) + _itBuffer[3]) / 10;
|
||||
return CalculateSmoothedPrice(_itBuffer[0], _itBuffer[1], _itBuffer[2], _itBuffer[3]);
|
||||
}
|
||||
|
||||
return pr;
|
||||
|
||||
+35
-12
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -32,6 +32,8 @@ 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;
|
||||
|
||||
@@ -56,12 +58,15 @@ public class Hwma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
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();
|
||||
@@ -75,6 +80,7 @@ public class Hwma : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -82,6 +88,7 @@ public class Hwma : AbstractBase
|
||||
_ppF = _ppV = _ppA = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -100,6 +107,24 @@ public class Hwma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
@@ -108,27 +133,25 @@ public class Hwma : AbstractBase
|
||||
{
|
||||
_pF = Input.Value;
|
||||
_pA = _pV = 0;
|
||||
return Input.Value;
|
||||
}
|
||||
|
||||
double nA = _nA, nB = _nB, nC = _nC;
|
||||
if (_period == 1)
|
||||
{
|
||||
nA = 1;
|
||||
nB = 0;
|
||||
nC = 0;
|
||||
_pF = Input.Value;
|
||||
_pV = _pA = 0;
|
||||
return Input.Value;
|
||||
}
|
||||
|
||||
double F = (1 - nA) * (_pF + _pV + 0.5 * _pA) + nA * Input.Value;
|
||||
double V = (1 - nB) * (_pV + _pA) + nB * (F - _pF);
|
||||
double A = (1 - nC) * _pA + nC * (V - _pV);
|
||||
|
||||
double hwma = F + V + 0.5 * A;
|
||||
double F = CalculateLevel(Input.Value);
|
||||
double V = CalculateVelocity(F);
|
||||
double A = CalculateAcceleration(V);
|
||||
|
||||
_pF = F;
|
||||
_pV = V;
|
||||
_pA = A;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return hwma;
|
||||
return F + V + _halfA * A;
|
||||
}
|
||||
}
|
||||
|
||||
+49
-40
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -32,12 +32,16 @@ public class Jma : AbstractBase
|
||||
private readonly double _phase;
|
||||
private readonly CircularBuffer _vsumBuff;
|
||||
private readonly CircularBuffer _avoltyBuff;
|
||||
|
||||
private double _len1;
|
||||
private double _pow1;
|
||||
private readonly double _beta;
|
||||
private readonly double _len1;
|
||||
private readonly double _pow1;
|
||||
private readonly double _oneMinusAlpha;
|
||||
private readonly double _oneMinusAlphaSquared;
|
||||
private readonly double _alphaSquared;
|
||||
|
||||
private double _upperBand, _lowerBand, _p_upperBand, _p_lowerBand;
|
||||
private double _prevMa1, _prevDet0, _prevDet1, _prevJma, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma;
|
||||
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; }
|
||||
@@ -45,57 +49,50 @@ public class Jma : AbstractBase
|
||||
public double Volty { get; set; }
|
||||
public double Factor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Jma class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the JMA.</param>
|
||||
/// <param name="phase">The phase parameter (-100 to +100) controlling lag compensation.</param>
|
||||
/// <param name="factor">The factor controlling volatility adaptation (default 0.45).</param>
|
||||
/// <param name="buffer">The size of the volatility buffer (default 10).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
public Jma(int period, int phase = 0, double factor = 0.45, int buffer = 10)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
Factor = factor;
|
||||
_period = period;
|
||||
_phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5);
|
||||
_phase = System.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);
|
||||
_beta = factor * (period - 1) / (factor * (period - 1) + 2);
|
||||
|
||||
_len1 = System.Math.Max((System.Math.Log(System.Math.Sqrt(period - 1)) / System.Math.Log(2.0)) + 2.0, 0);
|
||||
_pow1 = System.Math.Max(_len1 - 2.0, 0.5);
|
||||
|
||||
// Precalculate constants for alpha-based calculations
|
||||
double alpha = System.Math.Pow(_beta, _pow1);
|
||||
_oneMinusAlpha = 1.0 - alpha;
|
||||
_oneMinusAlphaSquared = _oneMinusAlpha * _oneMinusAlpha;
|
||||
_alphaSquared = alpha * alpha;
|
||||
|
||||
WarmupPeriod = period * 2;
|
||||
Name = $"JMA({period})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Jma class with a specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The period over which to calculate the JMA.</param>
|
||||
/// <param name="phase">The phase parameter (-100 to +100) controlling lag compensation.</param>
|
||||
/// <param name="factor">The factor controlling volatility adaptation (default 0.45).</param>
|
||||
/// <param name="buffer">The size of the volatility buffer (default 10).</param>
|
||||
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;
|
||||
_len1 = Math.Max((Math.Log(Math.Sqrt(_period - 1)) / Math.Log(2.0)) + 2.0, 0);
|
||||
_pow1 = Math.Max(_len1 - 2.0, 0.5);
|
||||
_avoltyBuff.Clear();
|
||||
_vsumBuff.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -121,6 +118,23 @@ public class Jma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double CalculateVolatility(double price, double del1, double del2)
|
||||
{
|
||||
double volty = System.Math.Max(System.Math.Abs(del1), System.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 System.Math.Min(System.Math.Max(rvolty, 1.0), System.Math.Pow(_len1, 1.0 / _pow1));
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -130,35 +144,30 @@ public class Jma : AbstractBase
|
||||
{
|
||||
_upperBand = _lowerBand = price;
|
||||
_prevMa1 = _prevJma = price;
|
||||
return price;
|
||||
}
|
||||
|
||||
double del1 = price - _upperBand;
|
||||
double del2 = price - _lowerBand;
|
||||
double volty = Math.Max(Math.Abs(del1), Math.Abs(del2));
|
||||
double volty = CalculateVolatility(price, del1, del2);
|
||||
double avgVolty = _avoltyBuff.Average();
|
||||
|
||||
_vsumBuff.Add(volty, Input.IsNew);
|
||||
_vSum += (_vsumBuff[^1] - _vsumBuff[0]) / _vsumBuff.Count;
|
||||
_avoltyBuff.Add(_vSum, Input.IsNew);
|
||||
double avgvolty = _avoltyBuff.Average();
|
||||
|
||||
double rvolty = (avgvolty > 0) ? volty / avgvolty : 1;
|
||||
rvolty = Math.Min(Math.Max(rvolty, 1.0), Math.Pow(_len1, 1.0 / _pow1));
|
||||
|
||||
double pow2 = Math.Pow(rvolty, _pow1);
|
||||
double Kv = Math.Pow(_beta, Math.Sqrt(pow2));
|
||||
double rvolty = CalculateRelativeVolatility(volty, avgVolty);
|
||||
double pow2 = System.Math.Pow(rvolty, _pow1);
|
||||
double Kv = System.Math.Pow(_beta, System.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 = Input.Value + _alpha * (_prevMa1 - Input.Value);
|
||||
double alpha = System.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) * (1 - _alpha) * (1 - _alpha)) + (_alpha * _alpha * _prevDet1);
|
||||
double det1 = ((ma2 - _prevJma) * _oneMinusAlphaSquared) + (_alphaSquared * _prevDet1);
|
||||
_prevDet1 = det1;
|
||||
double jma = _prevJma + det1;
|
||||
_prevJma = jma;
|
||||
|
||||
+43
-24
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,7 +29,8 @@ public class Kama : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly double _scFast, _scSlow;
|
||||
private CircularBuffer? _buffer;
|
||||
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>
|
||||
@@ -40,11 +41,13 @@ public class Kama : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_period = period;
|
||||
_scFast = 2.0 / (((period < fast) ? period : fast) + 1);
|
||||
_scSlow = 2.0 / (slow + 1);
|
||||
_scDiff = _scFast - _scSlow;
|
||||
_buffer = new CircularBuffer(_period + 1);
|
||||
WarmupPeriod = period;
|
||||
Name = $"Kama({_period}, {fast}, {slow})";
|
||||
Init();
|
||||
@@ -60,13 +63,15 @@ public class Kama : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer = new CircularBuffer(_period + 1);
|
||||
_buffer.Clear();
|
||||
_lastKama = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -81,36 +86,50 @@ public class Kama : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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 double CalculateEfficiencyRatio(double change, double volatility)
|
||||
{
|
||||
return volatility != 0 ? 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);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double kama;
|
||||
if (_index <= _period)
|
||||
{
|
||||
kama = Input.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
double change = Math.Abs(_buffer[^1] - _buffer[0]);
|
||||
double volatility = 0;
|
||||
for (int i = 1; i < _buffer.Count; i++)
|
||||
{
|
||||
volatility += Math.Abs(_buffer[i] - _buffer[i - 1]);
|
||||
}
|
||||
|
||||
double er = volatility != 0 ? change / volatility : 0;
|
||||
double sc = (er * (_scFast - _scSlow)) + _scSlow;
|
||||
sc *= sc; // Square the smoothing constant
|
||||
|
||||
kama = _lastKama + (sc * (Input.Value - _lastKama));
|
||||
_lastKama = Input.Value;
|
||||
return Input.Value;
|
||||
}
|
||||
|
||||
_lastKama = kama;
|
||||
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 kama;
|
||||
return _lastKama;
|
||||
}
|
||||
}
|
||||
|
||||
+32
-14
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,8 @@ namespace QuanTAlib;
|
||||
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;
|
||||
|
||||
@@ -42,8 +44,9 @@ public class Ltma : AbstractBase
|
||||
public Ltma(double gamma = 0.1)
|
||||
{
|
||||
if (gamma < 0 || gamma > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(gamma), "Gamma must be between 0 and 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();
|
||||
@@ -57,12 +60,14 @@ public class Ltma : AbstractBase
|
||||
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)
|
||||
@@ -82,24 +87,37 @@ public class Ltma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
// Laguerre filter calculation
|
||||
double _l0 = (1 - _gamma) * Input.Value + _gamma * _prevL0;
|
||||
double _l1 = -_gamma * _l0 + _prevL0 + _gamma * _prevL1;
|
||||
double _l2 = -_gamma * _l1 + _prevL1 + _gamma * _prevL2;
|
||||
double _l3 = -_gamma * _l2 + _prevL2 + _gamma * _prevL3;
|
||||
_prevL0 = _l0;
|
||||
_prevL1 = _l1;
|
||||
_prevL2 = _l2;
|
||||
_prevL3 = _l3;
|
||||
// First stage
|
||||
double l0 = _oneMinusGamma * Input.Value + _gamma * _prevL0;
|
||||
|
||||
double filteredValue = (_l0 + 2 * _l1 + 2 * _l2 + _l3) / 6;
|
||||
// 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 filteredValue;
|
||||
return CombineOutputs(l0, l1, l2, l3);
|
||||
}
|
||||
}
|
||||
|
||||
+40
-17
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -33,11 +32,13 @@ public class Maaf : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _priceBuffer;
|
||||
private readonly CircularBuffer _smoothBuffer;
|
||||
private double _prevFilter, _prevValue2;
|
||||
private readonly double _threshold;
|
||||
private double _p_prevFilter, _p_prevValue2;
|
||||
|
||||
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>
|
||||
@@ -47,6 +48,7 @@ public class Maaf : AbstractBase
|
||||
_threshold = threshold;
|
||||
_priceBuffer = new CircularBuffer(4);
|
||||
_smoothBuffer = new CircularBuffer(period);
|
||||
_sortBuffer = new double[period]; // Pre-allocate sorting buffer
|
||||
Name = "MAAF";
|
||||
WarmupPeriod = period;
|
||||
Init();
|
||||
@@ -61,15 +63,17 @@ public class Maaf : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_priceBuffer.Clear();
|
||||
_smoothBuffer.Clear();
|
||||
_prevFilter = 0;
|
||||
_prevValue2 = 0;
|
||||
base.Init();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -86,6 +90,30 @@ public class Maaf : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
@@ -97,7 +125,7 @@ public class Maaf : AbstractBase
|
||||
return Input.Value;
|
||||
}
|
||||
|
||||
double smooth = (_priceBuffer[^1] + (2 * _priceBuffer[^2]) + (2 * _priceBuffer[^3]) + _priceBuffer[^4]) / 6;
|
||||
double smooth = CalculateSmooth();
|
||||
_smoothBuffer.Add(smooth, Input.IsNew);
|
||||
|
||||
if (_smoothBuffer.Count < _period)
|
||||
@@ -111,28 +139,23 @@ public class Maaf : AbstractBase
|
||||
|
||||
while (value3 > _threshold && length > 0)
|
||||
{
|
||||
double alpha = 2.0 / (length + 1);
|
||||
|
||||
var sortedValues = _smoothBuffer.TakeLast(length).OrderBy(x => x).ToList();
|
||||
double value1 = sortedValues[length / 2];
|
||||
double alpha = CalculateAlpha(length);
|
||||
double value1 = GetMedian(length);
|
||||
value2 = alpha * (smooth - _prevValue2) + _prevValue2;
|
||||
|
||||
if (value1 != 0)
|
||||
{
|
||||
value3 = Math.Abs(value1 - value2) / value1;
|
||||
value3 = System.Math.Abs(value1 - value2) / value1;
|
||||
}
|
||||
|
||||
length -= 2;
|
||||
}
|
||||
|
||||
if (length < 3) length = 3;
|
||||
|
||||
double finalAlpha = 2.0 / (length + 1);
|
||||
length = System.Math.Max(length, 3);
|
||||
double finalAlpha = CalculateAlpha(length);
|
||||
double filter = finalAlpha * (smooth - _prevFilter) + _prevFilter;
|
||||
|
||||
_p_prevFilter = _prevFilter;
|
||||
_prevFilter = filter;
|
||||
_p_prevValue2 = _prevValue2;
|
||||
_prevValue2 = value2;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
+85
-66
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -31,6 +31,12 @@ 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;
|
||||
@@ -40,12 +46,9 @@ public class Mama : AbstractBase
|
||||
/// </summary>
|
||||
public TValue Fama { get; private set; }
|
||||
|
||||
/// <param name="fastLimit">The maximum adaptation speed (default 0.5).</param>
|
||||
/// <param name="slowLimit">The minimum adaptation speed (default 0.05).</param>
|
||||
public Mama(double fastLimit = 0.5, double slowLimit = 0.05)
|
||||
{
|
||||
Fama = new TValue();
|
||||
Name = $"Mama({_fastLimit:F2}, {_slowLimit:F2})";
|
||||
_fastLimit = fastLimit;
|
||||
_slowLimit = slowLimit;
|
||||
_pr = new(7);
|
||||
@@ -59,23 +62,23 @@ public class Mama : AbstractBase
|
||||
_im = new(2);
|
||||
_pd = new(2);
|
||||
_ph = new(2);
|
||||
Name = $"Mama({_fastLimit:F2}, {_slowLimit:F2})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="fastLimit">The maximum adaptation speed (default 0.5).</param>
|
||||
/// <param name="slowLimit">The minimum adaptation speed (default 0.05).</param>
|
||||
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)
|
||||
@@ -94,6 +97,33 @@ public class Mama : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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 (im == 0 || re == 0) 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);
|
||||
@@ -102,85 +132,59 @@ public class Mama : AbstractBase
|
||||
|
||||
if (_index > 6)
|
||||
{
|
||||
double adj = (0.075 * _pd[^1]) + 0.54;
|
||||
double adj = 0.075 * _pd[^1] + 0.54;
|
||||
|
||||
// Smooth
|
||||
_sm.Add(((4 * _pr[^1]) + (3 * _pr[^2]) + (2 * _pr[^3]) + _pr[^4]) / 10, Input.IsNew);
|
||||
|
||||
// Detrender
|
||||
_dt.Add(((0.0962 * _sm[^1]) + (0.5769 * _sm[^3]) - (0.5769 * _sm[^5]) - (0.0962 * _sm[^7])) * adj, Input.IsNew);
|
||||
// Smooth and Detrender
|
||||
_sm.Add(CalculateSmooth(), Input.IsNew);
|
||||
_dt.Add(CalculateHilbertTransform(_sm, adj), Input.IsNew);
|
||||
|
||||
// In-phase and quadrature
|
||||
_q1.Add(((0.0962 * _dt[^1]) + (0.5769 * _dt[^3]) - (0.5769 * _dt[^5]) - (0.0962 * _dt[^7])) * adj, Input.IsNew);
|
||||
_q1.Add(CalculateHilbertTransform(_dt, adj), Input.IsNew);
|
||||
_i1.Add(_dt[^4], Input.IsNew);
|
||||
|
||||
// Advance the phases by 90 degrees
|
||||
double jI = ((0.0962 * _i1[^1]) + (0.5769 * _i1[^3]) - (0.5769 * _i1[^5]) - (0.0962 * _i1[^7])) * adj;
|
||||
double jQ = ((0.0962 * _q1[^1]) + (0.5769 * _q1[^3]) - (0.5769 * _q1[^5]) - (0.0962 * _q1[^7])) * adj;
|
||||
// Advance phases
|
||||
double jI = CalculateHilbertTransform(_i1, adj);
|
||||
double jQ = CalculateHilbertTransform(_q1, adj);
|
||||
|
||||
// Phasor addition for 3-bar averaging
|
||||
_i2.Add(_i1[^1] - jQ, Input.IsNew);
|
||||
_q2.Add(_q1[^1] + jI, Input.IsNew);
|
||||
_i2[^1] = 0.2 * _i2[^1] + 0.8 * _i2[^2];
|
||||
_q2[^1] = 0.2 * _q2[^1] + 0.8 * _q2[^2];
|
||||
// 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
|
||||
_re.Add((_i2[^1] * _i2[^2]) + (_q2[^1] * _q2[^2]), Input.IsNew);
|
||||
_im.Add((_i2[^1] * _q2[^2]) - (_q2[^1] * _i2[^2]), Input.IsNew);
|
||||
_re[^1] = (0.2 * _re[^1]) + (0.8 * _re[^2]);
|
||||
_im[^1] = (0.2 * _im[^1]) + (0.8 * _im[^2]);
|
||||
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 period
|
||||
if (_im[^1] != 0 && _re[^1] != 0)
|
||||
{
|
||||
_pd.Add(2 * Math.PI / Math.Atan(_im[^1] / _re[^1]), Input.IsNew);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pd.Add(_pd[^2], Input.IsNew);
|
||||
}
|
||||
// Calculate and adjust period
|
||||
double period = CalculatePeriod(_im[^1], _re[^1]);
|
||||
_pd.Add(period, Input.IsNew);
|
||||
_pd[^1] = AdjustPeriod(_pd[^1]);
|
||||
|
||||
// Adjust period to thresholds
|
||||
_pd[^1] = Math.Max(Math.Min(_pd[^1], 1.5 * _pd[^2]), 0.67 * _pd[^2]);
|
||||
_pd[^1] = Math.Max(Math.Min(_pd[^1], 50), 6);
|
||||
_pd[^1] = (0.2 * _pd[^1]) + (0.8 * _pd[^2]);
|
||||
// Phase calculation
|
||||
double phase = _i1[^1] != 0 ? System.Math.Atan(_q1[^1] / _i1[^1]) * _radToDeg : _ph[^2];
|
||||
_ph.Add(phase, Input.IsNew);
|
||||
|
||||
// Determine phase position
|
||||
if (_i1[^1] != 0)
|
||||
{
|
||||
_ph.Add(Math.Atan(_q1[^1] / _i1[^1]) * 180 / Math.PI, Input.IsNew);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ph.Add(_ph[^2], Input.IsNew);
|
||||
}
|
||||
|
||||
// Change in phase
|
||||
double delta = Math.Max(_ph[^2] - _ph[^1], 1);
|
||||
|
||||
// Adaptive alpha value
|
||||
double alpha = Math.Max(_fastLimit / delta, _slowLimit);
|
||||
// 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 = 0.5 * alpha * (_mama - _prevFama) + _prevFama;
|
||||
_fama = _famaAlpha * alpha * (_mama - _prevFama) + _prevFama;
|
||||
|
||||
_prevMama = _mama;
|
||||
_prevFama = _fama;
|
||||
}
|
||||
else
|
||||
{
|
||||
_pd.Add(0, Input.IsNew);
|
||||
_sm.Add(0, Input.IsNew);
|
||||
_dt.Add(0, Input.IsNew);
|
||||
_i1.Add(0, Input.IsNew);
|
||||
_q1.Add(0, Input.IsNew);
|
||||
_i2.Add(0, Input.IsNew);
|
||||
_q2.Add(0, Input.IsNew);
|
||||
_re.Add(0, Input.IsNew);
|
||||
_im.Add(0, Input.IsNew);
|
||||
_ph.Add(0, Input.IsNew);
|
||||
|
||||
InitializeBuffers();
|
||||
_sumPr += Input.Value;
|
||||
_mama = _fama = _prevMama = _prevFama = _sumPr / _index;
|
||||
}
|
||||
@@ -190,4 +194,19 @@ public class Mama : AbstractBase
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,7 @@ public class Mgdi : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly double _kFactor;
|
||||
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>
|
||||
@@ -38,14 +39,15 @@ public class Mgdi : AbstractBase
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0.");
|
||||
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0.");
|
||||
}
|
||||
if (kFactor <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(kFactor), "K-Factor must be greater than 0.");
|
||||
throw new System.ArgumentOutOfRangeException(nameof(kFactor), "K-Factor must be greater than 0.");
|
||||
}
|
||||
_period = period;
|
||||
_kFactor = kFactor;
|
||||
_kFactorPeriod = kFactor * period;
|
||||
Name = "Mgdi";
|
||||
WarmupPeriod = period;
|
||||
Init();
|
||||
@@ -60,12 +62,14 @@ public class Mgdi : AbstractBase
|
||||
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)
|
||||
@@ -79,6 +83,18 @@ public class Mgdi : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double CalculateRatio(double value)
|
||||
{
|
||||
return _prevMd != 0 ? 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);
|
||||
@@ -90,10 +106,8 @@ public class Mgdi : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
double ratio = _prevMd != 0 ? value / _prevMd : 1;
|
||||
double md = _prevMd + ((value - _prevMd) /
|
||||
(_kFactor * _period * Math.Pow(ratio, 4)));
|
||||
_prevMd = md;
|
||||
double ratio = CalculateRatio(value);
|
||||
_prevMd = CalculateMd(value, ratio);
|
||||
}
|
||||
|
||||
IsHot = _index >= _period;
|
||||
|
||||
+29
-19
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,9 @@ 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>
|
||||
@@ -37,10 +40,20 @@ public class Mma : AbstractBase
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 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();
|
||||
@@ -54,6 +67,7 @@ public class Mma : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -61,6 +75,7 @@ public class Mma : AbstractBase
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -69,6 +84,17 @@ public class Mma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
@@ -78,7 +104,7 @@ public class Mma : AbstractBase
|
||||
{
|
||||
double T = _buffer.Sum();
|
||||
double S = CalculateWeightedSum();
|
||||
_lastMma = (T / _period) + (6 * S) / ((_period + 1) * _period);
|
||||
_lastMma = (T * _periodRecip) + (S * _combinedRecip);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -89,20 +115,4 @@ public class Mma : AbstractBase
|
||||
IsHot = _index >= _period;
|
||||
return _lastMma;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the weighted sum component of the MMA.
|
||||
/// The weights are symmetric around the center, decreasing linearly from the center outward.
|
||||
/// </summary>
|
||||
/// <returns>The weighted sum of the data points.</returns>
|
||||
private double CalculateWeightedSum()
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < _period; i++)
|
||||
{
|
||||
double weight = (_period - (2 * i + 1)) / 2.0;
|
||||
sum += weight * _buffer[^(i + 1)];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
+27
-11
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -31,6 +30,7 @@ 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>
|
||||
@@ -38,10 +38,11 @@ public class Pwma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_period = period;
|
||||
_convolution = new Convolution(GenerateKernel(_period));
|
||||
_kernel = GenerateKernel(_period);
|
||||
_convolution = new Convolution(_kernel);
|
||||
Name = "Pwma";
|
||||
WarmupPeriod = period;
|
||||
Init();
|
||||
@@ -55,12 +56,14 @@ public class Pwma : AbstractBase
|
||||
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)
|
||||
@@ -70,24 +73,33 @@ public class Pwma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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
|
||||
TValue convolutionResult = _convolution.Calc(Input);
|
||||
|
||||
var convolutionResult = _convolution.Calc(Input);
|
||||
double result = convolutionResult.Value;
|
||||
|
||||
// Adjust for partial periods during warmup
|
||||
if (_index < _period)
|
||||
{
|
||||
double[] partialKernel = GenerateKernel(_index);
|
||||
result /= partialKernel.Sum();
|
||||
result *= CalculateKernelSum(_kernel, _period) / CalculateKernelSum(partialKernel, _index);
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -96,11 +108,13 @@ public class Pwma : AbstractBase
|
||||
/// </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--)
|
||||
@@ -109,11 +123,13 @@ public class Pwma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the kernel
|
||||
double weightSum = kernel.Sum();
|
||||
// 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] /= weightSum;
|
||||
kernel[i] *= invWeightSum;
|
||||
}
|
||||
|
||||
return kernel;
|
||||
|
||||
+19
-9
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,7 +41,7 @@ public class Qema : AbstractBase
|
||||
{
|
||||
if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(k1), "All k values must be in the range (0, 1].");
|
||||
throw new System.ArgumentOutOfRangeException(nameof(k1), "All k values must be in the range (0, 1].");
|
||||
}
|
||||
|
||||
_ema1 = new Ema(k1);
|
||||
@@ -50,8 +50,7 @@ public class Qema : AbstractBase
|
||||
_ema4 = new Ema(k4);
|
||||
|
||||
Name = $"QEMA ({k1:F2},{k2:F2},{k3:F2},{k4:F2})";
|
||||
double smK = Math.Min(Math.Min(k1, k2), Math.Min(k3, k4));
|
||||
|
||||
double smK = System.Math.Min(System.Math.Min(k1, k2), System.Math.Min(k3, k4));
|
||||
WarmupPeriod = (int)((2 - smK) / smK);
|
||||
Init();
|
||||
}
|
||||
@@ -68,6 +67,7 @@ public class Qema : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -75,6 +75,7 @@ public class Qema : AbstractBase
|
||||
_p_lastQema = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -88,16 +89,25 @@ public class Qema : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
double ema1 = _ema1.Calc(new TValue(Input.Time, Input.Value, Input.IsNew));
|
||||
double ema2 = _ema2.Calc(new TValue(Input.Time, ema1, Input.IsNew));
|
||||
double ema3 = _ema3.Calc(new TValue(Input.Time, ema2, Input.IsNew));
|
||||
double ema4 = _ema4.Calc(new TValue(Input.Time, ema3, Input.IsNew));
|
||||
// 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);
|
||||
|
||||
_lastQema = 4 * ema1 - 6 * ema2 + 4 * ema3 - ema4;
|
||||
// Combine EMAs using optimized formula
|
||||
_lastQema = 4.0 * (ema1 + ema3) - (6.0 * ema2 + ema4);
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return _lastQema;
|
||||
|
||||
+24
-7
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,7 @@ 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;
|
||||
|
||||
@@ -48,12 +49,13 @@ public class Rema : AbstractBase
|
||||
public Rema(int period, double lambda = 0.5)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
if (lambda < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(lambda), "Lambda must be non-negative.");
|
||||
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();
|
||||
@@ -68,6 +70,7 @@ public class Rema : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -77,6 +80,7 @@ public class Rema : AbstractBase
|
||||
_savedPrevRema = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -92,15 +96,28 @@ public class Rema : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
double alpha = 2.0 / (Math.Min(_period, _index) + 1);
|
||||
|
||||
if (_index > 2)
|
||||
{
|
||||
double rema = (_lastRema + alpha * (Input.Value - _lastRema) + _lambda * (_lastRema + (_lastRema - _prevRema))) / (1 + _lambda);
|
||||
double alpha = CalculateAlpha();
|
||||
double rema = CalculateRema(alpha, Input.Value);
|
||||
_prevRema = _lastRema;
|
||||
_lastRema = rema;
|
||||
}
|
||||
@@ -110,7 +127,7 @@ public class Rema : AbstractBase
|
||||
_lastRema = Input.Value;
|
||||
}
|
||||
else
|
||||
{ // _index == 1
|
||||
{
|
||||
_lastRema = Input.Value;
|
||||
}
|
||||
|
||||
|
||||
+31
-57
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -20,44 +21,17 @@ namespace QuanTAlib;
|
||||
/// </remarks>
|
||||
public class Rma : AbstractBase
|
||||
{
|
||||
// inherited _index
|
||||
// inherited _value
|
||||
|
||||
/// <summary>
|
||||
/// The period for the RMA calculation.
|
||||
/// </summary>
|
||||
private readonly int _period;
|
||||
|
||||
/// <summary>
|
||||
/// Circular buffer for SMA calculation.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// The last calculated RMA value.
|
||||
/// </summary>
|
||||
private double _lastRma, _p_lastRma;
|
||||
|
||||
/// <summary>
|
||||
/// Compensator for early RMA values.
|
||||
/// </summary>
|
||||
private double _e, _p_e;
|
||||
|
||||
/// <summary>
|
||||
/// The smoothing factor for RMA calculation.
|
||||
/// </summary>
|
||||
private readonly double _k;
|
||||
|
||||
/// <summary>
|
||||
/// Flags to track initialization status.
|
||||
/// </summary>
|
||||
private bool _isInit, _p_isInit;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to determine whether to use SMA for initial values.
|
||||
/// </summary>
|
||||
private readonly bool _useSma;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Rma class with a specified period.
|
||||
/// </summary>
|
||||
@@ -68,14 +42,15 @@ public class Rma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
_period = period;
|
||||
_k = 1.0 / _period; // Wilder's smoothing factor
|
||||
_k = 1.0 / period;
|
||||
_oneMinusK = 1.0 - _k;
|
||||
_useSma = useSma;
|
||||
_sma = new(period);
|
||||
Name = "Rma";
|
||||
WarmupPeriod = _period * 2; // RMA typically needs more warmup periods
|
||||
WarmupPeriod = period * 2; // RMA typically needs more warmup periods
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -91,9 +66,7 @@ public class Rma : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Rma instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -104,10 +77,7 @@ public class Rma : AbstractBase
|
||||
_sma = new(_period);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the Rma instance.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the input is new.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -125,21 +95,30 @@ public class Rma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the RMA calculation.
|
||||
/// </summary>
|
||||
/// <returns>The calculated RMA value.</returns>
|
||||
[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()
|
||||
{
|
||||
double result, _rma;
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// when _UseSma == true, use SMA calculation until we have enough data points
|
||||
double result;
|
||||
if (!_isInit && _useSma)
|
||||
{
|
||||
_sma.Add(Input.Value, Input.IsNew);
|
||||
_rma = _sma.Average();
|
||||
result = _rma;
|
||||
_lastRma = _sma.Average();
|
||||
result = _lastRma;
|
||||
|
||||
if (_index >= _period)
|
||||
{
|
||||
_isInit = true;
|
||||
@@ -147,15 +126,10 @@ public class Rma : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
// compensator for early rma values
|
||||
_e = (_e > 1e-10) ? (1 - _k) * _e : 0;
|
||||
|
||||
_rma = _k * Input.Value + (1 - _k) * _lastRma;
|
||||
|
||||
// _useSma decides if we use compensator or not
|
||||
result = (_useSma || _e <= double.Epsilon) ? _rma : _rma / (1 - _e);
|
||||
_lastRma = CalculateRma(Input.Value);
|
||||
result = CompensateRma(_lastRma);
|
||||
}
|
||||
_lastRma = _rma;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return result;
|
||||
}
|
||||
|
||||
+25
-20
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,7 @@ namespace QuanTAlib;
|
||||
public class Sinema : AbstractBase
|
||||
{
|
||||
private readonly Convolution _convolution;
|
||||
private readonly double[] _kernel;
|
||||
|
||||
/// <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>
|
||||
@@ -36,9 +37,10 @@ public class Sinema : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_convolution = new Convolution(GenerateKernel(period));
|
||||
_kernel = GenerateKernel(period);
|
||||
_convolution = new Convolution(_kernel);
|
||||
Name = "Sinema";
|
||||
WarmupPeriod = period;
|
||||
Init();
|
||||
@@ -52,12 +54,14 @@ public class Sinema : AbstractBase
|
||||
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)
|
||||
@@ -67,42 +71,43 @@ public class Sinema : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// Use Convolution for calculation
|
||||
TValue convolutionResult = _convolution.Calc(Input);
|
||||
|
||||
double result = convolutionResult.Value;
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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++)
|
||||
{
|
||||
// Use sine function to generate weights
|
||||
kernel[i] = Math.Sin((i + 1) * Math.PI / (period + 1));
|
||||
kernel[i] = System.Math.Sin((i + 1) * piDivPeriodPlus1);
|
||||
weightSum += kernel[i];
|
||||
}
|
||||
|
||||
// Normalize the kernel
|
||||
// Normalize using multiplication instead of division
|
||||
double invWeightSum = 1.0 / weightSum;
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
kernel[i] /= weightSum;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-9
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,9 +27,8 @@ namespace QuanTAlib;
|
||||
|
||||
public class Sma : AbstractBase
|
||||
{
|
||||
// inherited _index
|
||||
// inherited _value
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly int _period;
|
||||
|
||||
/// <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>
|
||||
@@ -37,9 +36,9 @@ public class Sma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_period = period;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = "Sma";
|
||||
WarmupPeriod = period;
|
||||
@@ -48,12 +47,13 @@ public class Sma : AbstractBase
|
||||
|
||||
/// <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>
|
||||
public Sma(object source, int period) : this(period: period)
|
||||
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)
|
||||
@@ -69,12 +69,10 @@ public class Sma : AbstractBase
|
||||
/// <returns>The calculated SMA value.</returns>
|
||||
protected override double Calculation()
|
||||
{
|
||||
double result;
|
||||
ManageState(IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
result = _buffer.Average();
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return result;
|
||||
return _buffer.Average();
|
||||
}
|
||||
}
|
||||
|
||||
+19
-9
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -28,7 +28,9 @@ namespace QuanTAlib;
|
||||
public class Smma : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private CircularBuffer? _buffer;
|
||||
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>
|
||||
@@ -37,9 +39,12 @@ public class Smma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
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();
|
||||
@@ -53,13 +58,15 @@ public class Smma : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer = new CircularBuffer(_period);
|
||||
_buffer.Clear();
|
||||
_lastSmma = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -74,18 +81,21 @@ public class Smma : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double smma;
|
||||
|
||||
if (_index <= _period)
|
||||
{
|
||||
smma = _buffer.Average();
|
||||
|
||||
if (_index == _period)
|
||||
{
|
||||
_lastSmma = smma; // Initialize _lastSmma for the transition
|
||||
@@ -93,7 +103,7 @@ public class Smma : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
smma = ((_lastSmma * (_period - 1)) + Input.Value) / _period;
|
||||
smma = CalculateSmma(Input.Value);
|
||||
}
|
||||
|
||||
_lastSmma = smma;
|
||||
|
||||
+34
-17
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -31,8 +31,10 @@ public class T3 : AbstractBase
|
||||
private readonly int _period;
|
||||
private readonly double _vfactor;
|
||||
private readonly bool _useSma;
|
||||
private readonly double _k, _k1m, _c1, _c2, _c3, _c4;
|
||||
private readonly 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;
|
||||
|
||||
@@ -44,7 +46,7 @@ public class T3 : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_period = period;
|
||||
_vfactor = vfactor;
|
||||
@@ -52,11 +54,14 @@ public class T3 : AbstractBase
|
||||
WarmupPeriod = period;
|
||||
|
||||
_k = 2.0 / (_period + 1);
|
||||
_k1m = 1.0 - _k;
|
||||
_c1 = -_vfactor * _vfactor * _vfactor;
|
||||
_c2 = 3 * _vfactor * _vfactor + 3 * _vfactor * _vfactor * _vfactor;
|
||||
_c3 = -6 * _vfactor * _vfactor - 3 * _vfactor - 3 * _vfactor * _vfactor * _vfactor;
|
||||
_c4 = 1 + 3 * _vfactor + _vfactor * _vfactor * _vfactor + 3 * _vfactor * _vfactor;
|
||||
|
||||
// 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);
|
||||
@@ -79,6 +84,7 @@ public class T3 : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
_lastEma1 = _lastEma2 = _lastEma3 = _lastEma4 = _lastEma5 = _lastEma6 = 0;
|
||||
@@ -90,6 +96,7 @@ public class T3 : AbstractBase
|
||||
_buffer6.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -114,6 +121,18 @@ public class T3 : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
@@ -141,12 +160,12 @@ public class T3 : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
ema1 = _k * (Input.Value - _lastEma1) + _lastEma1;
|
||||
ema2 = _k * (ema1 - _lastEma2) + _lastEma2;
|
||||
ema3 = _k * (ema2 - _lastEma3) + _lastEma3;
|
||||
ema4 = _k * (ema3 - _lastEma4) + _lastEma4;
|
||||
ema5 = _k * (ema4 - _lastEma5) + _lastEma5;
|
||||
ema6 = _k * (ema5 - _lastEma6) + _lastEma6;
|
||||
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;
|
||||
@@ -156,9 +175,7 @@ public class T3 : AbstractBase
|
||||
_lastEma5 = ema5;
|
||||
_lastEma6 = ema6;
|
||||
|
||||
double t3 = _c1 * ema6 + _c2 * ema5 + _c3 * ema4 + _c4 * ema3;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return t3;
|
||||
return CalculateT3(ema3, ema4, ema5, ema6);
|
||||
}
|
||||
}
|
||||
|
||||
+35
-16
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,10 +29,13 @@ namespace QuanTAlib;
|
||||
public class Tema : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
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 _k, _e, _p_e;
|
||||
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>
|
||||
@@ -40,12 +43,14 @@ public class Tema : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
_period = period;
|
||||
_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)Math.Ceiling(-period * Math.Log(1 - percentile));
|
||||
WarmupPeriod = (int)System.Math.Ceiling(-period * System.Math.Log(1 - percentile));
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -57,14 +62,15 @@ public class Tema : AbstractBase
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_k = 2.0 / (_period + 1);
|
||||
_e = 1.0;
|
||||
_lastEma1 = _lastEma2 = _lastEma3 = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -84,24 +90,37 @@ public class Tema : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
double result, _ema1, _ema2, _ema3;
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_e = (_e > 1e-10) ? (1 - _k) * _e : 0;
|
||||
double _invE = (_e > 1e-10) ? 1 / (1 - _e) : 1;
|
||||
double invE = UpdateCompensator();
|
||||
|
||||
_ema1 = _k * (Input.Value - _lastEma1) + _lastEma1;
|
||||
_ema2 = _k * (_ema1 * _invE - _lastEma2) + _lastEma2;
|
||||
_ema3 = _k * (_ema2 * _invE - _lastEma3) + _lastEma3;
|
||||
// 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);
|
||||
|
||||
double _tema = 3 * _ema1 * _invE - 3 * _ema2 * _invE + _ema3 * _invE;
|
||||
// Store values for next iteration
|
||||
_lastEma1 = ema1;
|
||||
_lastEma2 = ema2;
|
||||
_lastEma3 = ema3;
|
||||
|
||||
result = _tema;
|
||||
_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;
|
||||
|
||||
+14
-9
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,7 @@ namespace QuanTAlib;
|
||||
public class Trima : AbstractBase
|
||||
{
|
||||
private readonly Convolution _convolution;
|
||||
private readonly double[] _kernel;
|
||||
|
||||
/// <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>
|
||||
@@ -36,9 +37,10 @@ public class Trima : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_convolution = new Convolution(GenerateKernel(period));
|
||||
_kernel = GenerateKernel(period);
|
||||
_convolution = new Convolution(_kernel);
|
||||
Name = "Trima";
|
||||
WarmupPeriod = period;
|
||||
Init();
|
||||
@@ -57,33 +59,38 @@ public class Trima : AbstractBase
|
||||
/// </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 the kernel
|
||||
// Normalize using multiplication instead of division
|
||||
double invWeightSum = 1.0 / weightSum;
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
kernel[i] /= weightSum;
|
||||
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)
|
||||
@@ -98,11 +105,9 @@ public class Trima : AbstractBase
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// Use Convolution for calculation
|
||||
TValue convolutionResult = _convolution.Calc(Input);
|
||||
|
||||
double result = convolutionResult.Value;
|
||||
var convolutionResult = _convolution.Calc(Input);
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return result;
|
||||
return convolutionResult.Value;
|
||||
}
|
||||
}
|
||||
|
||||
+33
-25
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -33,9 +30,9 @@ 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;
|
||||
private readonly CircularBuffer? _shortBuffer;
|
||||
private readonly CircularBuffer? _longBuffer;
|
||||
|
||||
/// <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>
|
||||
@@ -45,14 +42,14 @@ public class Vidya : AbstractBase
|
||||
{
|
||||
if (shortPeriod < 1)
|
||||
{
|
||||
throw new ArgumentException("Short period must be greater than or equal to 1.", nameof(shortPeriod));
|
||||
throw new System.ArgumentException("Short period must be greater than or equal to 1.", nameof(shortPeriod));
|
||||
}
|
||||
_longPeriod = (longPeriod == 0) ? shortPeriod * 4 : longPeriod;
|
||||
_alpha = alpha;
|
||||
WarmupPeriod = _longPeriod;
|
||||
Name = $"Vidya({shortPeriod},{_longPeriod})";
|
||||
_shortBuffer = new CircularBuffer(shortPeriod);
|
||||
_longBuffer = new CircularBuffer(_longPeriod);
|
||||
WarmupPeriod = _longPeriod;
|
||||
Name = $"Vidya({shortPeriod},{_longPeriod})";
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -67,12 +64,14 @@ public class Vidya : AbstractBase
|
||||
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)
|
||||
@@ -87,12 +86,35 @@ public class Vidya : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
_shortBuffer.Add(Input.Value, Input.IsNew);
|
||||
_longBuffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double vidya;
|
||||
if (_index <= _longPeriod)
|
||||
@@ -103,8 +125,7 @@ public class Vidya : AbstractBase
|
||||
{
|
||||
double shortStdDev = CalculateStdDev(_shortBuffer);
|
||||
double longStdDev = CalculateStdDev(_longBuffer);
|
||||
double s = _alpha * (shortStdDev / longStdDev);
|
||||
vidya = (s * Input.Value) + ((1 - s) * _lastVIDYA);
|
||||
vidya = CalculateVidya(shortStdDev, longStdDev);
|
||||
}
|
||||
|
||||
_lastVIDYA = vidya;
|
||||
@@ -112,17 +133,4 @@ public class Vidya : AbstractBase
|
||||
|
||||
return vidya;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the standard deviation of values in a circular buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The circular buffer containing the values.</param>
|
||||
/// <returns>The standard deviation of the values in the buffer.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double CalculateStdDev(CircularBuffer buffer)
|
||||
{
|
||||
double mean = buffer.Average();
|
||||
double sumSquaredDiff = buffer.Sum(x => Math.Pow(x - mean, 2));
|
||||
return Math.Sqrt(sumSquaredDiff / buffer.Count);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-9
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -31,6 +31,7 @@ public class Wma : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly Convolution _convolution;
|
||||
private readonly double[] _kernel;
|
||||
|
||||
/// <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>
|
||||
@@ -38,10 +39,11 @@ public class Wma : AbstractBase
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
||||
}
|
||||
_period = period;
|
||||
_convolution = new Convolution(GenerateWmaKernel(_period));
|
||||
_kernel = GenerateWmaKernel(_period);
|
||||
_convolution = new Convolution(_kernel);
|
||||
Name = "Wma";
|
||||
WarmupPeriod = _period;
|
||||
Init();
|
||||
@@ -60,25 +62,29 @@ public class Wma : AbstractBase
|
||||
/// </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) / 2.0;
|
||||
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) / weightSum;
|
||||
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)
|
||||
@@ -93,11 +99,9 @@ public class Wma : AbstractBase
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// Use Convolution for calculation
|
||||
TValue convolutionResult = _convolution.Calc(Input);
|
||||
|
||||
double result = convolutionResult.Value;
|
||||
var convolutionResult = _convolution.Calc(Input);
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return result;
|
||||
return convolutionResult.Value;
|
||||
}
|
||||
}
|
||||
|
||||
+99
-90
@@ -1,104 +1,113 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
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
|
||||
{
|
||||
/// <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>
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly int _lag;
|
||||
private readonly Ema _ema;
|
||||
private double _lastZLEMA, _p_lastZLEMA;
|
||||
|
||||
public class Zlema : AbstractBase
|
||||
/// <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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new 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();
|
||||
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)
|
||||
/// <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)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
_p_lastZLEMA = _lastZLEMA;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
else
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
_ema.Init();
|
||||
_lastZLEMA = 0;
|
||||
_p_lastZLEMA = 0;
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
_p_lastZLEMA = _lastZLEMA;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastZLEMA = _p_lastZLEMA;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
// Get lagged value and calculate error correction
|
||||
double lagValue = _buffer[Math.Max(0, _buffer.Count - 1 - _lag)];
|
||||
double errorCorrection = 2 * Input.Value - lagValue;
|
||||
|
||||
// Apply EMA to error-corrected value
|
||||
double zlema = _ema.Calc(new TValue(errorCorrection, Input.IsNew)).Value;
|
||||
|
||||
_lastZLEMA = zlema;
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
return zlema;
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user