From c5b583cd0df0a363a5b910c18366f2ae27aa2f76 Mon Sep 17 00:00:00 2001
From: Miha Kralj <31756078+mihakralj@users.noreply.github.com>
Date: Tue, 5 Nov 2024 06:42:31 -0800
Subject: [PATCH] Create EFI - Elder Ray's Force Index - add tests and xml
comments
---
For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/mihakralj/QuanTAlib?shareId=XXXX-XXXX-XXXX-XXXX).
---
Tests/test_updates_oscillators.cs | 16 +++++
docs/indicators/indicators.md | 6 +-
lib/oscillators/Efi.cs | 104 ++++++++++++++++++++++++++++++
lib/oscillators/_list.md | 4 +-
4 files changed, 125 insertions(+), 5 deletions(-)
create mode 100644 lib/oscillators/Efi.cs
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)