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;
+ }
+}