diff --git a/Tests/test_updates_oscillators.cs b/Tests/test_updates_oscillators.cs index c1630815..24964699 100644 --- a/Tests/test_updates_oscillators.cs +++ b/Tests/test_updates_oscillators.cs @@ -321,4 +321,20 @@ public class OscillatorsUpdateTests Assert.Equal(initialValue, finalValue, precision); } + + [Fact] + public void Efi_Update() + { + var indicator = new Efi(period: 13); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } } diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index 3916949a..a88d07f9 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -5,12 +5,12 @@ | Basic Transforms | 6 of 6 | 100% | | Averages & Trends | 33 of 33 | 100% | | Momentum | 16 of 16 | 100% | -| Oscillators | 21 of 29 | 72% | +| Oscillators | 22 of 29 | 76% | | Volatility | 24 of 35 | 69% | | Volume | 15 of 19 | 79% | | Numerical Analysis | 13 of 19 | 68% | | Errors | 16 of 16 | 100% | -| **Total** | **144 of 173** | **83%** | +| **Total** | **145 of 173** | **84%** | |Technical Indicator Name| Class Name| |-----------|:----------:| @@ -85,9 +85,9 @@ |COPPOCK - Coppock Curve|`Coppock`| |CRSI - Connor RSI|`Crsi`| |🚧 CTI - Ehler's Correlation Trend Indicator|`Cti`| -|🚧 EFI - Elder Ray's Force Index|`Efi`| |🚧 FISHER - Fisher Transform|`Fisher`| |🚧 FOSC - Forecast Oscillator|`Fosc`| +|EFI - Elder Ray's Force Index|`Efi`| |🚧 GATOR* - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)|`Gator`| |🚧 KDJ* - KDJ Indicator (K, D, J lines)|`Kdj`| |🚧 KRI - Kairi Relative Index|`Kri`| diff --git a/lib/oscillators/Efi.cs b/lib/oscillators/Efi.cs new file mode 100644 index 00000000..a22fdde1 --- /dev/null +++ b/lib/oscillators/Efi.cs @@ -0,0 +1,104 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// 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. +/// +/// +/// 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 +/// +[SkipLocalsInit] +public sealed class Efi : AbstractBase +{ + private readonly Ema _ema; + private double _prevClose; + private double _p_prevClose; + private const int DefaultPeriod = 13; + + /// The smoothing period for EMA calculation (default 13). + /// Thrown when period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Efi(int period = DefaultPeriod) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + + _ema = new(period); + WarmupPeriod = period + 1; + Name = $"EFI({period})"; + } + + /// The data source object that publishes updates. + /// The smoothing period for EMA calculation. + [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); + } +} diff --git a/lib/oscillators/_list.md b/lib/oscillators/_list.md index 72b74c86..e2ab8a34 100644 --- a/lib/oscillators/_list.md +++ b/lib/oscillators/_list.md @@ -1,5 +1,5 @@ # Oscillators indicators -Done: 21, Todo: 8 +Done: 22, Todo: 7 ✔️ AC - Acceleration Oscillator ✔️ AO - Awesome Oscillator @@ -14,7 +14,7 @@ Done: 21, Todo: 8 ✔️ CRSI - Connor RSI CTI - Ehler's Correlation Trend Indicator ✔️ DOSC - Derivative Oscillator -EFI - Elder Ray's Force Index +✔️ EFI - Elder Ray's Force Index FISHER - Fisher Transform FOSC - Forecast Oscillator *GATOR - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)