mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-13 16:18:05 +00:00
Merge branch 'dev' into add-fisher-transform
This commit is contained in:
+3
-8
@@ -43,14 +43,9 @@ public sealed class Huber : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Huber(int period, double delta = 1.0)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
if (delta <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(delta), "Delta must be greater than 0.");
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(delta, 0);
|
||||
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
|
||||
+45
-68
@@ -24,10 +24,13 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Formula:
|
||||
/// TR = max(high-low, abs(high-prevClose), abs(low-prevClose))
|
||||
/// +DM = if(high-prevHigh > prevLow-low) then max(high-prevHigh, 0) else 0
|
||||
/// -DM = if(prevLow-low > high-prevHigh) then max(prevLow-low, 0) else 0
|
||||
/// +DI = 100 * smoothed(+DM) / smoothed(TR)
|
||||
/// -DI = 100 * smoothed(-DM) / smoothed(TR)
|
||||
/// +DM = if(high-prevHigh > prevLow-low && high-prevHigh > 0) then high-prevHigh else 0
|
||||
/// -DM = if(prevLow-low > high-prevHigh && prevLow-low > 0) then prevLow-low else 0
|
||||
/// Smoothed TR = Wilder's smoothing of TR (ATR)
|
||||
/// Smoothed +DM = Wilder's smoothing of +DM
|
||||
/// Smoothed -DM = Wilder's smoothing of -DM
|
||||
/// +DI = 100 * Smoothed(+DM) / Smoothed(TR)
|
||||
/// -DI = 100 * Smoothed(-DM) / Smoothed(TR)
|
||||
///
|
||||
/// Sources:
|
||||
/// J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978)
|
||||
@@ -36,49 +39,41 @@ namespace QuanTAlib;
|
||||
/// Note: Default period of 14 was recommended by Wilder
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Dmi : AbstractBarBase
|
||||
public sealed class Dmi : AbstractBase
|
||||
{
|
||||
private readonly Rma _smoothedTr;
|
||||
private readonly Atr _atr;
|
||||
private readonly Rma _smoothedPlusDm;
|
||||
private readonly Rma _smoothedMinusDm;
|
||||
private double _prevHigh, _prevLow, _prevClose;
|
||||
private double _p_prevHigh, _p_prevLow, _p_prevClose;
|
||||
private double _prevHigh, _prevLow;
|
||||
private double _p_prevHigh, _p_prevLow;
|
||||
private double _plusDi, _minusDi;
|
||||
private const double ScalingFactor = 100.0;
|
||||
private const int DefaultPeriod = 14;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the most recent +DI value
|
||||
/// </summary>
|
||||
public double PlusDI => _plusDi;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the most recent -DI value
|
||||
/// </summary>
|
||||
public double MinusDI => _minusDi;
|
||||
|
||||
/// <param name="period">The number of periods used in the DMI calculation (default 14).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Dmi(int period = DefaultPeriod)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
_smoothedTr = new(period, useSma: true);
|
||||
_smoothedPlusDm = new(period, useSma: true);
|
||||
_smoothedMinusDm = new(period, useSma: true);
|
||||
_index = 0;
|
||||
_atr = new(period);
|
||||
_smoothedPlusDm = new(period);
|
||||
_smoothedMinusDm = new(period);
|
||||
WarmupPeriod = period + 1;
|
||||
Name = $"DMI({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods used in the DMI calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Dmi(object source, int period) : this(period)
|
||||
public override void Init()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
base.Init();
|
||||
_atr.Init();
|
||||
_smoothedPlusDm.Init();
|
||||
_smoothedMinusDm.Init();
|
||||
_prevHigh = _prevLow = double.NaN;
|
||||
_p_prevHigh = _p_prevLow = double.NaN;
|
||||
_plusDi = _minusDi = 0;
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -89,25 +84,14 @@ public sealed class Dmi : AbstractBarBase
|
||||
_index++;
|
||||
_p_prevHigh = _prevHigh;
|
||||
_p_prevLow = _prevLow;
|
||||
_p_prevClose = _prevClose;
|
||||
}
|
||||
else
|
||||
{
|
||||
_prevHigh = _p_prevHigh;
|
||||
_prevLow = _p_prevLow;
|
||||
_prevClose = _p_prevClose;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateTrueRange(double high, double low, double prevClose)
|
||||
{
|
||||
double hl = high - low;
|
||||
double hpc = Math.Abs(high - prevClose);
|
||||
double lpc = Math.Abs(low - prevClose);
|
||||
return Math.Max(hl, Math.Max(hpc, lpc));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double plusDm, double minusDm) CalculateDirectionalMovement(
|
||||
double high, double low, double prevHigh, double prevLow)
|
||||
@@ -115,13 +99,8 @@ public sealed class Dmi : AbstractBarBase
|
||||
double upMove = high - prevHigh;
|
||||
double downMove = prevLow - low;
|
||||
|
||||
double plusDm = 0.0;
|
||||
double minusDm = 0.0;
|
||||
|
||||
if (upMove > downMove && upMove > 0)
|
||||
plusDm = upMove;
|
||||
else if (downMove > upMove && downMove > 0)
|
||||
minusDm = downMove;
|
||||
double plusDm = (upMove > downMove && upMove > 0) ? upMove : 0;
|
||||
double minusDm = (downMove > upMove && downMove > 0) ? downMove : 0;
|
||||
|
||||
return (plusDm, minusDm);
|
||||
}
|
||||
@@ -129,38 +108,36 @@ public sealed class Dmi : AbstractBarBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
if (_index == 1)
|
||||
if (double.IsNaN(_prevHigh))
|
||||
{
|
||||
_prevHigh = Input.High;
|
||||
_prevLow = Input.Low;
|
||||
_prevClose = Input.Close;
|
||||
_prevHigh = BarInput.High;
|
||||
_prevLow = BarInput.Low;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Calculate True Range and Directional Movement
|
||||
double tr = CalculateTrueRange(Input.High, Input.Low, _prevClose);
|
||||
// Calculate ATR
|
||||
double atr = _atr.Calc(BarInput).Value;
|
||||
|
||||
// Calculate Directional Movement
|
||||
var (plusDm, minusDm) = CalculateDirectionalMovement(
|
||||
Input.High, Input.Low, _prevHigh, _prevLow);
|
||||
BarInput.High, BarInput.Low, _prevHigh, _prevLow);
|
||||
|
||||
// Update previous values
|
||||
_prevHigh = Input.High;
|
||||
_prevLow = Input.Low;
|
||||
_prevClose = Input.Close;
|
||||
// Update previous values for next calculation
|
||||
_prevHigh = BarInput.High;
|
||||
_prevLow = BarInput.Low;
|
||||
|
||||
// Smooth the indicators using Wilder's method
|
||||
_smoothedTr.Calc(tr, Input.IsNew);
|
||||
_smoothedPlusDm.Calc(plusDm, Input.IsNew);
|
||||
_smoothedMinusDm.Calc(minusDm, Input.IsNew);
|
||||
// Smooth DM values using Wilder's method
|
||||
double smoothedPlusDm = _smoothedPlusDm.Calc(plusDm, BarInput.IsNew).Value;
|
||||
double smoothedMinusDm = _smoothedMinusDm.Calc(minusDm, BarInput.IsNew).Value;
|
||||
|
||||
// Calculate +DI and -DI
|
||||
double smoothedTr = _smoothedTr.Value;
|
||||
if (smoothedTr > 0)
|
||||
// Calculate DI values
|
||||
if (atr > 0)
|
||||
{
|
||||
_plusDi = ScalingFactor * _smoothedPlusDm.Value / smoothedTr;
|
||||
_minusDi = ScalingFactor * _smoothedMinusDm.Value / smoothedTr;
|
||||
return _plusDi - _minusDi; // Return the difference as main value
|
||||
_plusDi = ScalingFactor * smoothedPlusDm / atr;
|
||||
_minusDi = ScalingFactor * smoothedMinusDm / atr;
|
||||
return _plusDi - _minusDi;
|
||||
}
|
||||
|
||||
_plusDi = 0.0;
|
||||
|
||||
+32
-113
@@ -4,16 +4,13 @@ namespace QuanTAlib;
|
||||
/// <summary>
|
||||
/// DMX: Enhanced Directional Movement Index using JMA smoothing
|
||||
/// An improvement over the traditional DMI indicator that uses Jurik Moving Average (JMA)
|
||||
/// for smoothing instead of Wilder's moving average. This enhancement provides better
|
||||
/// noise reduction while maintaining responsiveness to significant price movements.
|
||||
/// for smoothing. This enhancement provides better noise reduction while maintaining
|
||||
/// responsiveness to significant price movements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The DMX calculation process:
|
||||
/// 1. Calculate True Range (TR)
|
||||
/// 2. Calculate +DM (Positive Directional Movement)
|
||||
/// 3. Calculate -DM (Negative Directional Movement)
|
||||
/// 4. Smooth TR, +DM, and -DM using JMA instead of Wilder's smoothing
|
||||
/// 5. Calculate +DI and -DI as percentages
|
||||
/// 1. Calculate DMI using the standard Dmi class
|
||||
/// 2. Apply JMA smoothing to the +DI and -DI values
|
||||
///
|
||||
/// Key improvements over DMI:
|
||||
/// - Uses JMA's adaptive volatility-based smoothing
|
||||
@@ -22,11 +19,9 @@ namespace QuanTAlib;
|
||||
/// - Reduced lag through JMA's phase-shifting
|
||||
///
|
||||
/// Formula:
|
||||
/// TR = max(high-low, abs(high-prevClose), abs(low-prevClose))
|
||||
/// +DM = if(high-prevHigh > prevLow-low) then max(high-prevHigh, 0) else 0
|
||||
/// -DM = if(prevLow-low > high-prevHigh) then max(prevLow-low, 0) else 0
|
||||
/// +DI = 100 * JMA(+DM) / JMA(TR)
|
||||
/// -DI = 100 * JMA(-DM) / JMA(TR)
|
||||
/// DMI calculation as per standard DMI
|
||||
/// DMX +DI = JMA(DMI +DI)
|
||||
/// DMX -DI = JMA(DMI -DI)
|
||||
///
|
||||
/// Sources:
|
||||
/// Original DMI by J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978)
|
||||
@@ -35,53 +30,40 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Dmx : AbstractBarBase
|
||||
{
|
||||
private readonly Jma _smoothedTr;
|
||||
private readonly Jma _smoothedPlusDm;
|
||||
private readonly Jma _smoothedMinusDm;
|
||||
private double _prevHigh, _prevLow, _prevClose;
|
||||
private double _p_prevHigh, _p_prevLow, _p_prevClose;
|
||||
private readonly Dmi _dmi;
|
||||
private readonly Jma _smoothedPlusDi;
|
||||
private readonly Jma _smoothedMinusDi;
|
||||
private double _plusDi, _minusDi;
|
||||
private const double ScalingFactor = 100.0;
|
||||
private const int DefaultPeriod = 10;
|
||||
private const int DefaultDmiPeriod = 14;
|
||||
private const int DefaultJmaPeriod = 7;
|
||||
private const int DefaultPhase = 100;
|
||||
private const double DefaultFactor = 0.25;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the most recent +DI value
|
||||
/// Gets the most recent smoothed +DI value
|
||||
/// </summary>
|
||||
public double PlusDI => _plusDi;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the most recent -DI value
|
||||
/// Gets the most recent smoothed -DI value
|
||||
/// </summary>
|
||||
public double MinusDI => _minusDi;
|
||||
|
||||
/// <param name="period">The number of periods used in the DMX calculation (default 14).</param>
|
||||
/// <param name="phase">The phase for the JMA smoothing (default 0).</param>
|
||||
/// <param name="factor">The factor for the JMA smoothing (default 0.45).</param>
|
||||
/// <param name="dmiPeriod">The number of periods used in the DMI calculation (default 14).</param>
|
||||
/// <param name="jmaPeriod">The number of periods used in the JMA smoothing (default 10).</param>
|
||||
/// <param name="phase">The phase for the JMA smoothing (default 100).</param>
|
||||
/// <param name="factor">The factor for the JMA smoothing (default 0.25).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Dmx(int period = DefaultPeriod, int phase = DefaultPhase, double factor = DefaultFactor)
|
||||
public Dmx(int period = DefaultDmiPeriod, int jmaPeriod = DefaultJmaPeriod, int phase = DefaultPhase, double factor = DefaultFactor)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
_smoothedTr = new(period, phase, factor);
|
||||
_smoothedPlusDm = new(period, phase, factor);
|
||||
_smoothedMinusDm = new(period, phase, factor);
|
||||
_index = 0;
|
||||
WarmupPeriod = period * 2; // JMA needs more warmup periods than RMA
|
||||
Name = $"DMX({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods used in the DMX calculation.</param>
|
||||
/// <param name="phase">The phase for the JMA smoothing.</param>
|
||||
/// <param name="factor">The factor for the JMA smoothing.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Dmx(object source, int period, int phase = DefaultPhase, double factor = DefaultFactor) : this(period, phase, factor)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
if (period < 1 || jmaPeriod < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Periods must be greater than or equal to 1.");
|
||||
_dmi = new(period);
|
||||
_smoothedPlusDi = new(jmaPeriod, phase, factor);
|
||||
_smoothedMinusDi = new(jmaPeriod, phase, factor);
|
||||
WarmupPeriod = period + jmaPeriod;
|
||||
Name = $"DMX({period},{jmaPeriod})";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -90,43 +72,7 @@ public sealed class Dmx : AbstractBarBase
|
||||
if (isNew)
|
||||
{
|
||||
_index++;
|
||||
_p_prevHigh = _prevHigh;
|
||||
_p_prevLow = _prevLow;
|
||||
_p_prevClose = _prevClose;
|
||||
}
|
||||
else
|
||||
{
|
||||
_prevHigh = _p_prevHigh;
|
||||
_prevLow = _p_prevLow;
|
||||
_prevClose = _p_prevClose;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateTrueRange(double high, double low, double prevClose)
|
||||
{
|
||||
double hl = high - low;
|
||||
double hpc = Math.Abs(high - prevClose);
|
||||
double lpc = Math.Abs(low - prevClose);
|
||||
return Math.Max(hl, Math.Max(hpc, lpc));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double plusDm, double minusDm) CalculateDirectionalMovement(
|
||||
double high, double low, double prevHigh, double prevLow)
|
||||
{
|
||||
double upMove = high - prevHigh;
|
||||
double downMove = prevLow - low;
|
||||
|
||||
double plusDm = 0.0;
|
||||
double minusDm = 0.0;
|
||||
|
||||
if (upMove > downMove && upMove > 0)
|
||||
plusDm = upMove;
|
||||
else if (downMove > upMove && downMove > 0)
|
||||
minusDm = downMove;
|
||||
|
||||
return (plusDm, minusDm);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -134,40 +80,13 @@ public sealed class Dmx : AbstractBarBase
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
if (_index == 1)
|
||||
{
|
||||
_prevHigh = Input.High;
|
||||
_prevLow = Input.Low;
|
||||
_prevClose = Input.Close;
|
||||
return 0.0;
|
||||
}
|
||||
// Calculate DMI
|
||||
_dmi.Calc(Input);
|
||||
|
||||
// Calculate True Range and Directional Movement
|
||||
double tr = CalculateTrueRange(Input.High, Input.Low, _prevClose);
|
||||
var (plusDm, minusDm) = CalculateDirectionalMovement(
|
||||
Input.High, Input.Low, _prevHigh, _prevLow);
|
||||
// Smooth the DMI values using JMA
|
||||
_plusDi = _smoothedPlusDi.Calc(_dmi.PlusDI, Input.IsNew).Value;
|
||||
_minusDi = _smoothedMinusDi.Calc(_dmi.MinusDI, Input.IsNew).Value;
|
||||
|
||||
// Update previous values
|
||||
_prevHigh = Input.High;
|
||||
_prevLow = Input.Low;
|
||||
_prevClose = Input.Close;
|
||||
|
||||
// Smooth the indicators using JMA
|
||||
_smoothedTr.Calc(tr, Input.IsNew);
|
||||
_smoothedPlusDm.Calc(plusDm, Input.IsNew);
|
||||
_smoothedMinusDm.Calc(minusDm, Input.IsNew);
|
||||
|
||||
// Calculate +DI and -DI
|
||||
double smoothedTr = _smoothedTr.Value;
|
||||
if (smoothedTr > 0)
|
||||
{
|
||||
_plusDi = ScalingFactor * _smoothedPlusDm.Value / smoothedTr;
|
||||
_minusDi = ScalingFactor * _smoothedMinusDm.Value / smoothedTr;
|
||||
return _plusDi - _minusDi; // Return the difference as main value
|
||||
}
|
||||
|
||||
_plusDi = 0.0;
|
||||
_minusDi = 0.0;
|
||||
return 0.0;
|
||||
return _plusDi - _minusDi; // Return the difference as main value
|
||||
}
|
||||
}
|
||||
|
||||
+2
-8
@@ -85,9 +85,9 @@ public sealed class Dpo : AbstractBase
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
// Add current price to buffer
|
||||
_prices.Add(BarInput.Close);
|
||||
|
||||
_prices.Add(BarInput.Close, BarInput.IsNew);
|
||||
// Need enough prices for the shifted SMA calculation
|
||||
|
||||
if (_index <= _shift)
|
||||
{
|
||||
return 0;
|
||||
@@ -96,12 +96,6 @@ public sealed class Dpo : AbstractBase
|
||||
// Add price from shift periods ago to SMA buffer
|
||||
_sma.Add(_prices[_shift]);
|
||||
|
||||
// Need enough prices for full calculation
|
||||
if (_index <= WarmupPeriod)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate DPO
|
||||
double dpo = BarInput.Close - _sma.Average();
|
||||
|
||||
|
||||
@@ -60,14 +60,14 @@ public sealed class Macd : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Macd(int fastPeriod = DefaultFastPeriod, int slowPeriod = DefaultSlowPeriod, int signalPeriod = DefaultSignalPeriod)
|
||||
{
|
||||
if (fastPeriod < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(fastPeriod));
|
||||
if (slowPeriod < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(slowPeriod));
|
||||
if (signalPeriod < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(signalPeriod));
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(fastPeriod, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(slowPeriod, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(signalPeriod, 1);
|
||||
|
||||
if (fastPeriod >= slowPeriod)
|
||||
throw new ArgumentException("Fast period must be less than slow period");
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(fastPeriod), "Fast period must be less than slow period");
|
||||
}
|
||||
|
||||
_fastEma = new(fastPeriod);
|
||||
_slowEma = new(slowPeriod);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# Momentum indicators
|
||||
Done: 15, Todo: 2
|
||||
|
||||
✔️ ADX - Average Directional Movement Index
|
||||
✔️ ADXR - Average Directional Movement Index Rating
|
||||
✔️ APO - Absolute Price Oscillator
|
||||
✔️ *DMI - Directional Movement Index (DI+, DI-)
|
||||
✔️ DMI - Directional Movement Index (DI+, DI-)
|
||||
✔️ DMX - Jurik Directional Movement Index
|
||||
✔️ DPO - Detrended Price Oscillator
|
||||
✔️ *MACD - Moving Average Convergence/Divergence (MACD, Signal, Histogram)
|
||||
|
||||
@@ -51,12 +51,9 @@ public sealed class Coppock : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Coppock(int roc1Period = DefaultRoc1Period, int roc2Period = DefaultRoc2Period, int wmaPeriod = DefaultWmaPeriod)
|
||||
{
|
||||
if (roc1Period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(roc1Period), "ROC1 period must be greater than 0");
|
||||
if (roc2Period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(roc2Period), "ROC2 period must be greater than 0");
|
||||
if (wmaPeriod < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(wmaPeriod), "WMA period must be greater than 0");
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(roc1Period, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(roc2Period, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(wmaPeriod, 1);
|
||||
|
||||
_roc1Period = roc1Period;
|
||||
_roc2Period = roc2Period;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// EFI: Elder Ray's Force Index
|
||||
/// A volume-based oscillator that measures the strength of price movements using volume.
|
||||
/// It helps identify potential trend reversals and confirm price movements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The EFI calculation process:
|
||||
/// 1. Calculate the difference between the current close and the previous close
|
||||
/// 2. Multiply the difference by the current volume
|
||||
/// 3. Apply an exponential moving average (EMA) to smooth the result
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Oscillates above and below zero
|
||||
/// - Positive values indicate buying pressure
|
||||
/// - Negative values indicate selling pressure
|
||||
/// - Crosses above zero suggest buying opportunities
|
||||
/// - Crosses below zero suggest selling opportunities
|
||||
///
|
||||
/// Formula:
|
||||
/// EFI = EMA((Close - Close[1]) * Volume, period)
|
||||
///
|
||||
/// Sources:
|
||||
/// Alexander Elder - "Trading for a Living" (1993)
|
||||
/// https://www.investopedia.com/terms/f/force-index.asp
|
||||
///
|
||||
/// Note: Default period is 13
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Efi : AbstractBase
|
||||
{
|
||||
private readonly Ema _ema;
|
||||
private double _prevClose;
|
||||
private double _p_prevClose;
|
||||
private const int DefaultPeriod = 13;
|
||||
|
||||
/// <param name="period">The smoothing period for EMA calculation (default 13).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Efi(int period = DefaultPeriod)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
|
||||
_ema = new(period);
|
||||
WarmupPeriod = period + 1;
|
||||
Name = $"EFI({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The smoothing period for EMA calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Efi(object source, int period = DefaultPeriod) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_ema.Init();
|
||||
_prevClose = double.NaN;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_index++;
|
||||
_p_prevClose = _prevClose;
|
||||
}
|
||||
else
|
||||
{
|
||||
_prevClose = _p_prevClose;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
if (_index == 1)
|
||||
{
|
||||
_prevClose = BarInput.Close;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate raw force index
|
||||
double priceChange = BarInput.Close - _prevClose;
|
||||
double forceIndex = priceChange * BarInput.Volume;
|
||||
|
||||
// Update previous close
|
||||
_prevClose = BarInput.Close;
|
||||
|
||||
// Apply EMA smoothing
|
||||
return _ema.Calc(forceIndex, BarInput.IsNew);
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,7 @@ public sealed class Rsi : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rsi(int period = DefaultPeriod)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
|
||||
_avgGain = new(period, useSma: true);
|
||||
_avgLoss = new(period, useSma: true);
|
||||
_index = 0;
|
||||
|
||||
@@ -57,12 +57,9 @@ public sealed class Smi : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Smi(int period = DefaultPeriod, int smooth1 = DefaultSmooth1, int smooth2 = DefaultSmooth2)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0");
|
||||
if (smooth1 < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(smooth1), "Smooth1 must be greater than 0");
|
||||
if (smooth2 < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(smooth2), "Smooth2 must be greater than 0");
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(smooth1, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(smooth2, 1);
|
||||
|
||||
_highs = new(period);
|
||||
_lows = new(period);
|
||||
|
||||
+4
-18
@@ -40,7 +40,6 @@ public sealed class Srsi : AbstractBase
|
||||
private readonly CircularBuffer _srsiValues;
|
||||
private readonly Sma _signal;
|
||||
private readonly int _rsiPeriod;
|
||||
private readonly int _stochPeriod;
|
||||
private const int DefaultRsiPeriod = 14;
|
||||
private const int DefaultStochPeriod = 14;
|
||||
private const int DefaultSmoothK = 3;
|
||||
@@ -56,25 +55,12 @@ public sealed class Srsi : AbstractBase
|
||||
public Srsi(int rsiPeriod = DefaultRsiPeriod, int stochPeriod = DefaultStochPeriod,
|
||||
int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD)
|
||||
{
|
||||
if (rsiPeriod < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(rsiPeriod), "Period must be greater than 0");
|
||||
}
|
||||
if (stochPeriod < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(stochPeriod), "Period must be greater than 0");
|
||||
}
|
||||
if (smoothK < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(smoothK), "Period must be greater than 0");
|
||||
}
|
||||
if (smoothD < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(smoothD), "Period must be greater than 0");
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(rsiPeriod, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(stochPeriod, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(smoothK, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(smoothD, 1);
|
||||
|
||||
_rsiPeriod = rsiPeriod;
|
||||
_stochPeriod = stochPeriod;
|
||||
_rsi = new(rsiPeriod);
|
||||
_rsiValues = new(stochPeriod);
|
||||
_srsiValues = new(smoothK);
|
||||
|
||||
+6
-21
@@ -62,32 +62,17 @@ public sealed class Stc : AbstractBase
|
||||
int slowPeriod = DefaultSlowPeriod, int d1Period = DefaultD1Period,
|
||||
int stcPeriod = DefaultStcPeriod)
|
||||
{
|
||||
string err = "All periods must be greater than 0";
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(cyclePeriod, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(fastPeriod, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(slowPeriod, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(d1Period, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(stcPeriod, 1);
|
||||
|
||||
if (cyclePeriod < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(cyclePeriod), err);
|
||||
}
|
||||
if (fastPeriod < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(fastPeriod), err);
|
||||
}
|
||||
if (slowPeriod < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(slowPeriod), err);
|
||||
}
|
||||
if (d1Period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(d1Period), err);
|
||||
}
|
||||
if (stcPeriod < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(stcPeriod), err);
|
||||
}
|
||||
if (fastPeriod >= slowPeriod)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(fastPeriod), "Fast period must be less than slow period");
|
||||
}
|
||||
|
||||
_fastEma = new(fastPeriod);
|
||||
_slowEma = new(slowPeriod);
|
||||
_macdValues = new(cyclePeriod);
|
||||
|
||||
@@ -52,12 +52,9 @@ public sealed class Stoch : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Stoch(int period = DefaultPeriod, int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0");
|
||||
if (smoothK < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(smoothK), "%K smoothing period must be greater than 0");
|
||||
if (smoothD < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(smoothD), "%D smoothing period must be greater than 0");
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(smoothK, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(smoothD, 1);
|
||||
|
||||
_highs = new(period);
|
||||
_lows = new(period);
|
||||
|
||||
+6
-24
@@ -67,30 +67,12 @@ public sealed class Uo : AbstractBase
|
||||
public Uo(int period1 = DefaultPeriod1, int period2 = DefaultPeriod2, int period3 = DefaultPeriod3,
|
||||
double weight1 = DefaultWeight1, double weight2 = DefaultWeight2, double weight3 = DefaultWeight3)
|
||||
{
|
||||
if (period1 < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period1), "Period1 must be greater than 0");
|
||||
}
|
||||
if (period2 < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period2), "Period2 must be greater than 0");
|
||||
}
|
||||
if (period3 < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period3), "Period3 must be greater than 0");
|
||||
}
|
||||
if (weight1 <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(weight1), "Weight1 must be greater than 0");
|
||||
}
|
||||
if (weight2 <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(weight2), "Weight2 must be greater than 0");
|
||||
}
|
||||
if (weight3 <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(weight3), "Weight3 must be greater than 0");
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period1, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period2, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period3, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight1, 0);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight2, 0);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight3, 0);
|
||||
|
||||
_weight1 = weight1;
|
||||
_weight2 = weight2;
|
||||
|
||||
@@ -14,8 +14,8 @@ Done: 22, Todo: 7
|
||||
✔️ CRSI - Connor RSI
|
||||
CTI - Ehler's Correlation Trend Indicator
|
||||
✔️ DOSC - Derivative Oscillator
|
||||
EFI - Elder Ray's Force Index
|
||||
✔️ FISHER - Fisher Transform
|
||||
✔️ EFI - Elder Ray's Force Index
|
||||
FOSC - Forecast Oscillator
|
||||
*GATOR - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)
|
||||
*KDJ - KDJ Indicator (K, D, J lines)
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// BETA: Beta Coefficient
|
||||
/// A statistical measure that quantifies the volatility of an asset or portfolio
|
||||
/// in relation to the overall market. Beta is used to assess the risk and return
|
||||
/// characteristics of an investment.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Beta calculation process:
|
||||
/// 1. Calculates covariance between asset and market returns
|
||||
/// 2. Computes variance of market returns
|
||||
/// 3. Divides covariance by market variance
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Measures relative volatility
|
||||
/// - Beta > 1: More volatile than market
|
||||
/// - Beta < 1: Less volatile than market
|
||||
/// - Beta = 1: Same volatility as market
|
||||
/// - Beta < 0: Inverse relationship with market
|
||||
///
|
||||
/// Formula:
|
||||
/// β = Cov(Ra, Rm) / Var(Rm)
|
||||
/// where:
|
||||
/// Ra = asset returns
|
||||
/// Rm = market returns
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Risk assessment
|
||||
/// - Portfolio management
|
||||
/// - Asset allocation
|
||||
/// - Performance analysis
|
||||
/// - Hedging strategies
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Beta_(finance)
|
||||
/// "Modern Portfolio Theory" - Harry Markowitz
|
||||
///
|
||||
/// Note: Assumes linear relationship between asset and market returns
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Beta : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _assetReturns;
|
||||
private readonly CircularBuffer _marketReturns;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <param name="period">The number of points to consider for beta calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Beta(int period)
|
||||
{
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for beta calculation.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = MinimumPoints;
|
||||
_assetReturns = new CircularBuffer(period);
|
||||
_marketReturns = new CircularBuffer(period);
|
||||
Name = $"Beta(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for beta calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Beta(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();
|
||||
_assetReturns.Clear();
|
||||
_marketReturns.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMean(ReadOnlySpan<double> values)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
return sum / values.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateCovariance(ReadOnlySpan<double> assetReturns, ReadOnlySpan<double> marketReturns, double assetMean, double marketMean)
|
||||
{
|
||||
double covariance = 0;
|
||||
for (int i = 0; i < assetReturns.Length; i++)
|
||||
{
|
||||
covariance += (assetReturns[i] - assetMean) * (marketReturns[i] - marketMean);
|
||||
}
|
||||
return covariance / assetReturns.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateVariance(ReadOnlySpan<double> values, double mean)
|
||||
{
|
||||
double variance = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double diff = values[i] - mean;
|
||||
variance += diff * diff;
|
||||
}
|
||||
return variance / values.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_assetReturns.Add(Input.Value, Input.IsNew);
|
||||
_marketReturns.Add(Input2.Value, Input.IsNew);
|
||||
|
||||
double beta = 0;
|
||||
if (_assetReturns.Count >= MinimumPoints && _marketReturns.Count >= MinimumPoints)
|
||||
{
|
||||
ReadOnlySpan<double> assetValues = _assetReturns.GetSpan();
|
||||
ReadOnlySpan<double> marketValues = _marketReturns.GetSpan();
|
||||
|
||||
double assetMean = CalculateMean(assetValues);
|
||||
double marketMean = CalculateMean(marketValues);
|
||||
|
||||
double covariance = CalculateCovariance(assetValues, marketValues, assetMean, marketMean);
|
||||
double marketVariance = CalculateVariance(marketValues, marketMean);
|
||||
|
||||
if (marketVariance > Epsilon)
|
||||
{
|
||||
beta = covariance / marketVariance;
|
||||
}
|
||||
}
|
||||
|
||||
IsHot = _assetReturns.Count >= Period && _marketReturns.Count >= Period;
|
||||
return beta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// CORR: Correlation Coefficient
|
||||
/// A statistical measure that quantifies the strength and direction of the relationship
|
||||
/// between two variables. The correlation coefficient ranges from -1 to 1, where 1 indicates
|
||||
/// a perfect positive correlation, -1 indicates a perfect negative correlation, and 0 indicates
|
||||
/// no correlation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Correlation calculation process:
|
||||
/// 1. Calculates mean of both variables
|
||||
/// 2. Computes covariance between variables
|
||||
/// 3. Calculates standard deviation of both variables
|
||||
/// 4. Divides covariance by product of standard deviations
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Measures linear relationship strength
|
||||
/// - Symmetric around zero
|
||||
/// - Scale-independent measure
|
||||
/// - Sensitive to outliers
|
||||
/// - Useful for portfolio diversification
|
||||
///
|
||||
/// Formula:
|
||||
/// ρ = Cov(X, Y) / (σX * σY)
|
||||
/// where:
|
||||
/// X, Y = variables
|
||||
/// Cov = covariance
|
||||
/// σ = standard deviation
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Portfolio diversification
|
||||
/// - Risk management
|
||||
/// - Pairs trading
|
||||
/// - Performance analysis
|
||||
/// - Market sentiment analysis
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Correlation_coefficient
|
||||
/// "Modern Portfolio Theory" - Harry Markowitz
|
||||
///
|
||||
/// Note: Assumes linear relationship between variables
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Corr : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _xValues;
|
||||
private readonly CircularBuffer _yValues;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <param name="period">The number of points to consider for correlation calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Corr(int period)
|
||||
{
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for correlation calculation.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = MinimumPoints;
|
||||
_xValues = new CircularBuffer(period);
|
||||
_yValues = new CircularBuffer(period);
|
||||
Name = $"Corr(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for correlation calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Corr(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();
|
||||
_xValues.Clear();
|
||||
_yValues.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMean(ReadOnlySpan<double> values)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
return sum / values.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateCovariance(ReadOnlySpan<double> xValues, ReadOnlySpan<double> yValues, double xMean, double yMean)
|
||||
{
|
||||
double covariance = 0;
|
||||
for (int i = 0; i < xValues.Length; i++)
|
||||
{
|
||||
covariance += (xValues[i] - xMean) * (yValues[i] - yMean);
|
||||
}
|
||||
return covariance / xValues.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateStandardDeviation(ReadOnlySpan<double> values, double mean)
|
||||
{
|
||||
double sumSquaredDeviations = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double deviation = values[i] - mean;
|
||||
sumSquaredDeviations += deviation * deviation;
|
||||
}
|
||||
return Math.Sqrt(sumSquaredDeviations / values.Length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_xValues.Add(Input.Value, Input.IsNew);
|
||||
_yValues.Add(Input2.Value, Input.IsNew);
|
||||
|
||||
double correlation = 0;
|
||||
if (_xValues.Count >= MinimumPoints && _yValues.Count >= MinimumPoints)
|
||||
{
|
||||
ReadOnlySpan<double> xValues = _xValues.GetSpan();
|
||||
ReadOnlySpan<double> yValues = _yValues.GetSpan();
|
||||
|
||||
double xMean = CalculateMean(xValues);
|
||||
double yMean = CalculateMean(yValues);
|
||||
|
||||
double covariance = CalculateCovariance(xValues, yValues, xMean, yMean);
|
||||
double xStdDev = CalculateStandardDeviation(xValues, xMean);
|
||||
double yStdDev = CalculateStandardDeviation(yValues, yMean);
|
||||
|
||||
if (xStdDev > Epsilon && yStdDev > Epsilon)
|
||||
{
|
||||
correlation = covariance / (xStdDev * yStdDev);
|
||||
}
|
||||
}
|
||||
|
||||
IsHot = _xValues.Count >= Period && _yValues.Count >= Period;
|
||||
return correlation;
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,6 @@ public sealed class Percentile : AbstractBase
|
||||
private readonly int Period;
|
||||
private readonly double Percent;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <param name="period">The number of points to consider for percentile calculation.</param>
|
||||
@@ -56,16 +55,10 @@ public sealed class Percentile : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Percentile(int period, double percent)
|
||||
{
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for percentile calculation.");
|
||||
}
|
||||
if (percent < 0 || percent > 100)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(percent),
|
||||
"Percent must be between 0 and 100.");
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, MinimumPoints);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(percent, 0);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(percent, 100);
|
||||
|
||||
Period = period;
|
||||
Percent = percent;
|
||||
WarmupPeriod = MinimumPoints; // Minimum number of points needed for percentile calculation
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// THEIL: Theil's U Statistics (U1, U2)
|
||||
/// A statistical measure that quantifies the accuracy of forecasts compared to actual values
|
||||
/// and naive forecasts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Theil's U calculation process:
|
||||
/// 1. Calculate U1 statistic (relative accuracy)
|
||||
/// 2. Calculate U2 statistic (comparison with naive forecast)
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - U1 ranges from 0 to 1, with 0 indicating perfect forecast
|
||||
/// - U2 < 1: forecast better than naive forecast
|
||||
/// - U2 = 1: forecast equal to naive forecast
|
||||
/// - U2 > 1: forecast worse than naive forecast
|
||||
///
|
||||
/// Formula:
|
||||
/// U1 = √[Σ(Ft - At)² / Σ(At)²]
|
||||
/// U2 = √[Σ(Ft - At)² / Σ(At - At-1)²]
|
||||
/// where:
|
||||
/// Ft = forecasted value
|
||||
/// At = actual value
|
||||
/// At-1 = previous actual value
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Evaluating forecast accuracy
|
||||
/// - Comparing forecasting models
|
||||
/// - Assessing forecasting methods
|
||||
/// - Model selection
|
||||
/// - Performance analysis
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Theil%27s_U
|
||||
/// "Forecasting: Principles and Practice" - Rob J Hyndman
|
||||
///
|
||||
/// Note: Should be used alongside other accuracy measures
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Theil : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _actual;
|
||||
private readonly CircularBuffer _forecast;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the U2 statistic comparing forecast with naive forecast
|
||||
/// </summary>
|
||||
public double U2 { get; private set; }
|
||||
|
||||
/// <param name="period">The number of points to consider for Theil's U calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Theil(int period)
|
||||
{
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for Theil's U calculation.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = MinimumPoints;
|
||||
_actual = new CircularBuffer(period);
|
||||
_forecast = new CircularBuffer(period);
|
||||
Name = $"Theil(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for Theil's U calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Theil(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();
|
||||
_actual.Clear();
|
||||
_forecast.Clear();
|
||||
U2 = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSquaredSum(ReadOnlySpan<double> values)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i] * values[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSquaredErrorSum(ReadOnlySpan<double> forecast, ReadOnlySpan<double> actual)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < forecast.Length; i++)
|
||||
{
|
||||
double error = forecast[i] - actual[i];
|
||||
sum += error * error;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateNaiveSquaredErrorSum(ReadOnlySpan<double> actual)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 1; i < actual.Length; i++)
|
||||
{
|
||||
double error = actual[i] - actual[i - 1];
|
||||
sum += error * error;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_actual.Add(Input.Value, Input.IsNew);
|
||||
_forecast.Add(Input2.Value, Input.IsNew);
|
||||
|
||||
double u1 = 0;
|
||||
if (_actual.Count >= MinimumPoints && _forecast.Count >= MinimumPoints)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actual.GetSpan();
|
||||
ReadOnlySpan<double> forecastValues = _forecast.GetSpan();
|
||||
|
||||
double squaredErrorSum = CalculateSquaredErrorSum(forecastValues, actualValues);
|
||||
double squaredActualSum = CalculateSquaredSum(actualValues);
|
||||
double naiveSquaredErrorSum = CalculateNaiveSquaredErrorSum(actualValues);
|
||||
|
||||
if (squaredActualSum > double.Epsilon)
|
||||
{
|
||||
u1 = Math.Sqrt(squaredErrorSum / squaredActualSum);
|
||||
}
|
||||
|
||||
if (naiveSquaredErrorSum > double.Epsilon)
|
||||
{
|
||||
U2 = Math.Sqrt(squaredErrorSum / naiveSquaredErrorSum);
|
||||
}
|
||||
}
|
||||
|
||||
IsHot = _actual.Count >= Period && _forecast.Count >= Period;
|
||||
return u1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// TSF: Time Series Forecast
|
||||
/// A statistical indicator that provides a linear regression forecast of future values
|
||||
/// based on historical data. It includes both the forecast value and a confidence interval.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Time Series Forecast calculation process:
|
||||
/// 1. Calculates linear regression on the input data
|
||||
/// 2. Extrapolates the regression line to forecast future values
|
||||
/// 3. Computes confidence intervals based on the standard error of the forecast
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Provides point forecast and confidence interval
|
||||
/// - Based on linear regression principles
|
||||
/// - Assumes trend continuity
|
||||
/// - Sensitive to recent data changes
|
||||
/// - Useful for short-term predictions
|
||||
///
|
||||
/// Formula:
|
||||
/// Forecast = a + b * (n + 1)
|
||||
/// where:
|
||||
/// a = y-intercept
|
||||
/// b = slope
|
||||
/// n = number of periods
|
||||
///
|
||||
/// Confidence Interval = Forecast ± (t * SE)
|
||||
/// where:
|
||||
/// t = t-value for desired confidence level
|
||||
/// SE = Standard Error of the forecast
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Price target estimation
|
||||
/// - Trend analysis
|
||||
/// - Risk assessment
|
||||
/// - Trading strategy development
|
||||
/// - Market behavior prediction
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Time_series
|
||||
/// "Forecasting: Principles and Practice" - Rob J Hyndman and George Athanasopoulos
|
||||
///
|
||||
/// Note: Assumes linear trend in the data and may not capture non-linear patterns
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Tsf : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _values;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <summary>
|
||||
/// The forecasted value for the next period.
|
||||
/// </summary>
|
||||
public double Forecast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The lower bound of the confidence interval.
|
||||
/// </summary>
|
||||
public double LowerBound { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The upper bound of the confidence interval.
|
||||
/// </summary>
|
||||
public double UpperBound { get; private set; }
|
||||
|
||||
/// <param name="period">The number of historical data points to consider for forecasting.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Tsf(int period)
|
||||
{
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for time series forecasting.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = MinimumPoints;
|
||||
_values = new CircularBuffer(period);
|
||||
Name = $"TSF(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of historical data points to consider for forecasting.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Tsf(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();
|
||||
_values.Clear();
|
||||
Forecast = 0;
|
||||
LowerBound = 0;
|
||||
UpperBound = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double slope, double intercept) CalculateLinearRegression(ReadOnlySpan<double> values)
|
||||
{
|
||||
int n = values.Length;
|
||||
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
double x = i + 1;
|
||||
double y = values[i];
|
||||
sumX += x;
|
||||
sumY += y;
|
||||
sumXY += x * y;
|
||||
sumX2 += x * x;
|
||||
}
|
||||
|
||||
double slope = ((n * sumXY) - (sumX * sumY)) / ((n * sumX2) - (sumX * sumX));
|
||||
double intercept = (sumY - (slope * sumX)) / n;
|
||||
|
||||
return (slope, intercept);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateStandardError(ReadOnlySpan<double> values, double slope, double intercept)
|
||||
{
|
||||
int n = values.Length;
|
||||
double sumSquaredResiduals = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
double x = i + 1;
|
||||
double y = values[i];
|
||||
double predicted = (slope * x) + intercept;
|
||||
double residual = y - predicted;
|
||||
sumSquaredResiduals += residual * residual;
|
||||
}
|
||||
|
||||
return Math.Sqrt(sumSquaredResiduals / (n - 2));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_values.Add(Input.Value, Input.IsNew);
|
||||
|
||||
if (_values.Count >= MinimumPoints)
|
||||
{
|
||||
ReadOnlySpan<double> values = _values.GetSpan();
|
||||
|
||||
var (slope, intercept) = CalculateLinearRegression(values);
|
||||
|
||||
// Calculate forecast for the next period
|
||||
Forecast = (slope * (Period + 1)) + intercept;
|
||||
|
||||
// Calculate standard error
|
||||
double standardError = CalculateStandardError(values, slope, intercept);
|
||||
|
||||
// Calculate confidence interval (using t-distribution with n-2 degrees of freedom)
|
||||
double tValue = 1.96; // Approximation for 95% confidence interval
|
||||
double marginOfError = tValue * standardError * Math.Sqrt(1 + (1.0 / Period));
|
||||
|
||||
LowerBound = Forecast - marginOfError;
|
||||
UpperBound = Forecast + marginOfError;
|
||||
}
|
||||
|
||||
IsHot = _values.Count >= Period;
|
||||
return Forecast;
|
||||
}
|
||||
}
|
||||
+31
-21
@@ -1,22 +1,32 @@
|
||||
# Statistics indicators
|
||||
Done: 13, Todo: 6
|
||||
# Statistics
|
||||
|
||||
*BETA - Beta coefficient (Beta, R-squared)
|
||||
*CORR - Correlation Coefficient (Correlation, P-value)
|
||||
✔️ CURVATURE - Rate of Change in Direction or Slope
|
||||
✔️ ENTROPY - Measure of Uncertainty or Disorder
|
||||
✔️ HURST - Hurst Exponent
|
||||
✔️ KURTOSIS - Measure of Tails/Peakedness
|
||||
✔️ MAX - Maximum with exponential decay
|
||||
✔️ MEDIAN - Middle value
|
||||
✔️ MIN - Minimum with exponential decay
|
||||
✔️ MODE - Most Frequent Value
|
||||
✔️ PERCENTILE - Rank Order
|
||||
*RSQUARED - Coefficient of Determination (R-squared, Adjusted R-squared)
|
||||
✔️ SKEW - Skewness, asymmetry of distribution
|
||||
✔️ SLOPE - Rate of Change, Linear Regression
|
||||
✔️ STDDEV - Standard Deviation, Measure of Spread
|
||||
*THEIL - Theil's U Statistics (U1, U2)
|
||||
*TSF - Time Series Forecast (Forecast, Confidence Interval)
|
||||
✔️ VARIANCE - Average of Squared Deviations
|
||||
✔️ ZSCORE - Standardized Score
|
||||
Statistical functions and indicators for financial analysis.
|
||||
|
||||
## Implemented
|
||||
|
||||
- [Beta](Beta.cs) - Beta coefficient measuring volatility relative to market
|
||||
- [Corr](Corr.cs) - Correlation coefficient between two series
|
||||
- [Curvature](Curvature.cs) - Curvature of a time series
|
||||
- [Entropy](Entropy.cs) - Information entropy of a series
|
||||
- [Hurst](Hurst.cs) - Hurst exponent for trend strength
|
||||
- [Kurtosis](Kurtosis.cs) - Kurtosis measuring tail extremity
|
||||
- [Max](Max.cs) - Maximum value over period
|
||||
- [Median](Median.cs) - Median value over period
|
||||
- [Min](Min.cs) - Minimum value over period
|
||||
- [Mode](Mode.cs) - Mode (most frequent value)
|
||||
- [Percentile](Percentile.cs) - Percentile rank calculation
|
||||
- [Skew](Skew.cs) - Skewness measuring distribution asymmetry
|
||||
- [Slope](Slope.cs) - Linear regression slope
|
||||
- [Stddev](Stddev.cs) - Standard deviation
|
||||
- [Theil](Theil.cs) - Theil's U statistics for forecast accuracy
|
||||
- [Tsf](Tsf.cs) - Time series forecast
|
||||
- [Variance](Variance.cs) - Statistical variance
|
||||
- [Zscore](Zscore.cs) - Z-score standardization
|
||||
|
||||
## Planned
|
||||
|
||||
- Cointegration - Test for cointegrated series
|
||||
- Granger - Granger causality test
|
||||
- Jarque-Bera - Normality test
|
||||
- Kendall - Kendall rank correlation
|
||||
- Spearman - Spearman rank correlation
|
||||
|
||||
Reference in New Issue
Block a user