From 5b3f302ce42de2730772c2790c27405bb545edc0 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Fri, 1 Nov 2024 17:38:50 -0700 Subject: [PATCH] Macd --- Tests/test_eventing.cs | 1 + Tests/test_updates_momentum.cs | 15 +++++ docs/indicators/indicators.md | 5 +- lib/momentum/Macd.cs | 114 +++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 lib/momentum/Macd.cs diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs index 3564894e..45f49a3d 100644 --- a/Tests/test_eventing.cs +++ b/Tests/test_eventing.cs @@ -58,6 +58,7 @@ public class EventingTests ("Trima", new Trima(p), new Trima(input, p)), ("Vidya", new Vidya(p), new Vidya(input, p)), ("Apo", new Apo(12, 26), new Apo(input, 12, 26)), + ("Macd", new Macd(12, 26, 9), new Macd(input, 12, 26, 9)), ("Rsi", new Rsi(p), new Rsi(input, p)), ("Rsx", new Rsx(p), new Rsx(input, p)), ("Cmo", new Cmo(p), new Cmo(input, p)), diff --git a/Tests/test_updates_momentum.cs b/Tests/test_updates_momentum.cs index 7338cbdd..4f8c3028 100644 --- a/Tests/test_updates_momentum.cs +++ b/Tests/test_updates_momentum.cs @@ -120,6 +120,21 @@ public class MomentumUpdateTests Assert.Equal(initialValue, finalValue, precision); } + [Fact] + public void Macd_Update() + { + var indicator = new Macd(fastPeriod: 12, slowPeriod: 26, signalPeriod: 9); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble() + 100, IsNew: false)); // Ensure positive prices + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + [Fact] public void Pmo_Update() { diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index 35bd8401..abdc6468 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -14,7 +14,7 @@ - Total: 116 of 175 indicators implemented (66%) -|BASIC TRANSFORMS|Class Name| +|**BASIC TRANSFORMS**|**Class Name**| |---|:--:| |OC2 - Midpoint price|`.OC2`| |HL2 - Median Price|`.HL2`| @@ -22,8 +22,9 @@ |OHL3 - Mean Price|`.OHL3`| |OHLC4 - Average Price|`.OHLC4`| |HLCC4 - Weighted Price|`.HLCC4`| -
+ |**AVERAGES & TRENDS**|**Class Name**| +|--|:--:| |AFIRMA - Adaptive FIR Moving Average|`Afirma`| |ALMA - Arnaud Legoux Moving Average|`Alma`| |DEMA - Double Exponential Moving Average|`Dema`| diff --git a/lib/momentum/Macd.cs b/lib/momentum/Macd.cs new file mode 100644 index 00000000..eb8ad743 --- /dev/null +++ b/lib/momentum/Macd.cs @@ -0,0 +1,114 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// MACD: Moving Average Convergence Divergence +/// A trend-following momentum indicator that shows the relationship between two moving +/// averages of an asset's price. MACD is calculated by subtracting the longer-period +/// EMA from the shorter-period EMA. The result is then used to calculate a signal line +/// (EMA of MACD) and histogram (MACD - Signal). +/// +/// +/// The MACD calculation process: +/// 1. Calculate the fast EMA (default 12 periods) +/// 2. Calculate the slow EMA (default 26 periods) +/// 3. MACD Line = Fast EMA - Slow EMA +/// 4. Signal Line = EMA of MACD Line (default 9 periods) +/// 5. MACD Histogram = MACD Line - Signal Line +/// +/// Key characteristics: +/// - Centerline crossovers signal trend changes +/// - Signal line crossovers indicate trading opportunities +/// - Histogram shows momentum of price movement +/// - Divergences can signal potential reversals +/// +/// Formula: +/// MACD Line = EMA(fast) - EMA(slow) +/// Signal Line = EMA(MACD Line, signal) +/// Histogram = MACD Line - Signal Line +/// +/// Sources: +/// https://www.investopedia.com/terms/m/macd.asp +/// https://school.stockcharts.com/doku.php?id=technical_indicators:macd +/// + +[SkipLocalsInit] +public sealed class Macd : AbstractBase +{ + private readonly Ema _fastEma; + private readonly Ema _slowEma; + private readonly Ema _signalEma; + private const int DefaultFastPeriod = 12; + private const int DefaultSlowPeriod = 26; + private const int DefaultSignalPeriod = 9; + private double _macdLine; + private double _signalLine; + + /// + /// Gets the MACD line value (Fast EMA - Slow EMA) + /// + public double MacdLine => _macdLine; + + /// + /// Gets the Signal line value (EMA of MACD line) + /// + public double SignalLine => _signalLine; + + /// The number of periods for the fast EMA (default 12). + /// The number of periods for the slow EMA (default 26). + /// The number of periods for the signal line EMA (default 9). + /// Thrown when any period is less than 1. + [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)); + if (fastPeriod >= slowPeriod) + throw new ArgumentException("Fast period must be less than slow period"); + + _fastEma = new(fastPeriod); + _slowEma = new(slowPeriod); + _signalEma = new(signalPeriod); + WarmupPeriod = slowPeriod + signalPeriod; + Name = $"MACD({fastPeriod},{slowPeriod},{signalPeriod})"; + } + + /// The data source object that publishes updates. + /// The number of periods for the fast EMA. + /// The number of periods for the slow EMA. + /// The number of periods for the signal line EMA. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Macd(object source, int fastPeriod, int slowPeriod, int signalPeriod) : this(fastPeriod, slowPeriod, signalPeriod) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + _index++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + // Calculate MACD line + double fastEma = _fastEma.Calc(Input.Value, Input.IsNew); + double slowEma = _slowEma.Calc(Input.Value, Input.IsNew); + _macdLine = fastEma - slowEma; + + // Calculate Signal line + _signalLine = _signalEma.Calc(_macdLine, Input.IsNew); + + // Return histogram + return _macdLine - _signalLine; + } +}