# MACD: Moving Average Convergence Divergence

> "The trend is your friend, until it bends." — Ed Seykota

The Moving Average Convergence Divergence measures momentum through the relationship between two exponential moving averages. Created by Gerald Appel in 1979, the indicator transforms price into a bounded oscillator that reveals trend strength, direction, and potential reversals. Standard parameters (12, 26, 9) detect monthly and biweekly cycles: the 26-period represents roughly one trading month, the 12-period half that duration.

## Historical Context

Gerald Appel developed MACD during the late 1970s, initially publishing it in his "Systems and Forecasts" newsletter. The indicator emerged from Appel's observation that the relationship between two moving averages contained more information than either average alone. The difference between fast and slow EMAs creates a momentum oscillator; smoothing that difference with a signal line generates actionable crossover signals.

Thomas Aspray added the histogram component in 1986, providing visual representation of the distance between MACD and signal lines. This enhancement allowed traders to anticipate crossovers rather than react to them: histogram contraction precedes the actual cross, offering earlier warning of momentum shifts.

The elegance lies in simplicity. Three EMAs produce three distinct signals: the MACD line crossing zero (trend direction), MACD crossing the signal line (momentum shifts), and histogram slope changes (acceleration). Each operates on different timescales, creating a multi-layered momentum analysis from minimal computation.

## Architecture & Physics

MACD architecture cascades three EMA filters in a specific topology. Price feeds two parallel EMAs; their difference feeds a third EMA. This creates a momentum-detection system with inherent lag characteristics.

### 1. Fast EMA (12-period default)

The fast EMA responds to recent price changes with decay constant:

$$
\alpha_{fast} = \frac{2}{12 + 1} \approx 0.1538
$$

Half-life of approximately 7.5 bars. Reacts quickly to price movements but carries more noise.

### 2. Slow EMA (26-period default)

The slow EMA provides the reference baseline:

$$
\alpha_{slow} = \frac{2}{26 + 1} \approx 0.0741
$$

Half-life of approximately 17 bars. Smoother, more stable, but delayed in its response.

### 3. MACD Line (Convergence/Divergence)

The difference between fast and slow EMAs:

$$
MACD_t = EMA_{fast,t} - EMA_{slow,t}
$$

This difference oscillates around zero. Positive values indicate the fast EMA above the slow (bullish momentum); negative values indicate bearish momentum. The name derives from the behavior: converging averages push MACD toward zero; diverging averages push it away.

### 4. Signal Line (9-period EMA of MACD)

Smooths the MACD line for crossover detection:

$$
Signal_t = EMA_9(MACD_t)
$$

The 9-period provides roughly two weeks of smoothing. Crossovers between MACD and Signal generate trading signals.

### 5. Histogram (Visual Momentum)

The difference between MACD and Signal:

$$
Histogram_t = MACD_t - Signal_t
$$

Histogram represents the "momentum of momentum." Positive histogram indicates MACD above signal (bullish acceleration); shrinking histogram warns of potential crossover.

### 6. System Lag Analysis

Total system lag accumulates from all three EMAs:

| Component | Period | Effective Lag (bars) |
| :-------- | -----: | -------------------: |
| Fast EMA | 12 | 5.5 |
| Slow EMA | 26 | 12.5 |
| Signal EMA | 9 | 4.0 |
| **MACD Line** | — | **~7** (weighted average) |
| **Full System** | — | **~11** (to histogram) |

The MACD line inherits lag from both source EMAs. Signal line adds additional smoothing delay. Histogram responds fastest to price changes since it measures the rate of MACD change.

## Mathematical Foundation

### Transfer Function Analysis

The MACD line can be expressed as the difference of two first-order IIR filters:

$$
H_{MACD}(z) = \frac{\alpha_{fast}}{1 - (1-\alpha_{fast})z^{-1}} - \frac{\alpha_{slow}}{1 - (1-\alpha_{slow})z^{-1}}
$$

This difference filter creates a bandpass characteristic: it attenuates both very high frequencies (noise) and very low frequencies (long-term trend), passing the intermediate frequencies that represent tradeable momentum.

### Frequency Response

The MACD acts as a crude bandpass filter:

| Frequency Band | MACD Response |
| :------------- | :------------ |
| Very High (noise) | Attenuated by both EMAs |
| High (5-10 bars) | Passed through fast EMA, attenuated by slow |
| Medium (15-30 bars) | Maximum response zone |
| Low (>50 bars) | Both EMAs track similarly, difference approaches zero |

### Zero-Crossing Dynamics

MACD crosses zero when fast and slow EMAs intersect:

$$
EMA_{fast,t} = EMA_{slow,t} \implies MACD_t = 0
$$

This occurs during trend transitions. The slope of MACD at zero-crossing indicates the strength of the new trend.

### Signal Line Crossover Mathematics

Crossover occurs when:

$$
MACD_t = Signal_t \implies Histogram_t = 0
$$

The histogram's zero-crossing precedes neither bullish nor bearish bias: it marks the inflection point. Histogram direction (positive or negative slope) provides the directional signal.

### Divergence Detection

Divergence between price and MACD occurs when:

$$
\frac{d(Price)}{dt} \cdot \frac{d(MACD)}{dt} < 0
$$

Price making new highs while MACD makes lower highs (bearish divergence) suggests weakening momentum. The mathematical basis: MACD responds to rate of change, not absolute levels.

## Performance Profile

### Operation Count (Streaming Mode, Scalar)

Per-bar update requires three EMA updates plus arithmetic:

| Operation | Count | Cost (cycles) | Subtotal |
| :-------- | ----: | ------------: | -------: |
| EMA multiply | 6 | 3 | 18 |
| EMA add/sub | 6 | 1 | 6 |
| MACD subtract | 1 | 1 | 1 |
| Histogram subtract | 1 | 1 | 1 |
| State loads | 6 | 3 | 18 |
| State stores | 6 | 3 | 18 |
| **Total** | **26** | — | **~62 cycles** |

Dominated by state management (58%). No divisions, no transcendentals. Pure arithmetic operations.

### Batch Mode (SIMD Analysis)

The span-based Calculate method uses SIMD for the subtraction:

| Operation | Scalar Ops | SIMD Ops (AVX2) | Speedup |
| :-------- | ---------: | --------------: | ------: |
| Fast EMA batch | N | N (recursive) | 1× |
| Slow EMA batch | N | N (recursive) | 1× |
| MACD subtraction | N | N/4 (AVX2) | 4× |

EMA calculations remain sequential due to recursive dependency. Only the final subtraction benefits from SIMD. With AVX-512, subtraction achieves 8× speedup.

### Benchmark Results

Test environment: AMD Ryzen 9 7950X, 128 GB DDR5-6000, .NET 10.0 Preview 1, Windows 11 24H2

| Operation | Time (μs) | Throughput | Allocations |
| :-------- | --------: | ---------: | ----------: |
| Streaming 100K bars | 2,847 | 35.1M bars/s | 0 bytes |
| Batch 100K bars | 2,412 | 41.5M bars/s | 1.6 MB |
| Span Calculate 100K | 2,156 | 46.4M bars/s | 0 bytes* |

*Span Calculate uses ArrayPool, returning buffers after use.

### Comparative Performance

| Indicator | Cycles/bar | Relative |
| :-------- | ---------: | -------: |
| EMA | 21 | 0.34× |
| **MACD** | 62 | 1.0× |
| RSI | 73 | 1.18× |
| Bollinger | 156 | 2.52× |
| ATR | 26 | 0.42× |

MACD costs approximately 3× a single EMA: expected given three internal EMA instances.

### Quality Metrics

| Metric | Score | Notes |
| :----- | ----: | :---- |
| **Accuracy** | 10/10 | Exact match with TA-Lib, Skender, Tulip |
| **Timeliness** | 6/10 | ~11 bar lag to histogram; ~7 bar lag to MACD line |
| **Overshoot** | 4/10 | Can overshoot significantly during strong trends |
| **Smoothness** | 9/10 | Double smoothing produces very smooth signal line |
| **Responsiveness** | 7/10 | Histogram responds faster than MACD line |
| **False Signals** | 5/10 | Prone to whipsaws in ranging markets |

## Validation

Validated against four external libraries across all operating modes.

| Library | Batch | Streaming | Span | Notes |
| :------ | :---: | :-------: | :--: | :---- |
| **TA-Lib** | ✅ | ✅ | ✅ | Exact match with `TA_MACD` |
| **Skender** | ✅ | ✅ | ✅ | Exact match with `GetMacd` |
| **Tulip** | ✅ | ✅ | ✅ | Exact match with `macd` |
| **Ooples** | ✅ | — | — | Exact match (batch only) |

Tolerance: 1e-9 for all comparisons. Zero discrepancies found across 100K bar test series.

## Common Pitfalls

1. **Warmup Period Underestimation**: Full warmup requires `max(fast, slow) + signal = 35` bars with default parameters. Using MACD values before warmup produces unreliable readings. The IsHot property only returns true when all three EMAs have stabilized.

2. **Histogram Misinterpretation**: Histogram shows momentum acceleration, not momentum itself. Shrinking positive histogram indicates slowing bullish momentum, not bearish momentum. The histogram can shrink while price continues rising.

3. **Zero-Line Obsession**: Many traders focus exclusively on zero-line crossings. These lag significantly: price has already moved substantially before MACD crosses zero. Signal line crossovers provide earlier entries with more whipsaws; histogram direction changes provide earliest entries with most noise.

4. **Parameter Blindness**: Default (12, 26, 9) parameters target roughly monthly cycles. Shorter timeframes or different instruments may require adjustment. Crypto markets never sleep: 24/7 trading changes effective "month" lengths. Use (8, 17, 6) for faster response or (19, 39, 9) for longer-term signals.

5. **Divergence Time Lag**: Price-MACD divergence can persist for extended periods before reversal. Divergence identifies weakening momentum, not imminent reversal. Some divergences never resolve with reversal: momentum simply stabilizes.

6. **Ranging Market Whipsaws**: MACD oscillates around zero during sideways markets, generating frequent false crossover signals. Combining with volatility filters (like ATR) helps identify ranging conditions where MACD signals should be ignored.

7. **Bar Correction Handling**: When using `isNew=false` for bar corrections, all three internal EMAs roll back their state. Failing to use bar correction results in triple-counting the current bar's contribution.

## Usage Examples

### Streaming Mode

```csharp
var macd = new Macd(fastPeriod: 12, slowPeriod: 26, signalPeriod: 9);

foreach (var bar in priceData)
{
    var result = macd.Update(new TValue(bar.Time, bar.Close));
    
    // Access all three components
    double macdLine = macd.Last.Value;
    double signalLine = macd.Signal.Value;
    double histogram = macd.Histogram.Value;
    
    if (macd.IsHot)
    {
        // Crossover detection
        bool bullishCross = histogram > 0 && prevHistogram <= 0;
        bool bearishCross = histogram < 0 && prevHistogram >= 0;
    }
}
```

### Batch Mode

```csharp
var macd = new Macd(12, 26, 9);
TSeries macdSeries = macd.Update(closePrices);

// Access components through indicator state after batch
double lastMacd = macd.Last.Value;
double lastSignal = macd.Signal.Value;
double lastHistogram = macd.Histogram.Value;
```

### Span-Based Calculate

```csharp
Span<double> macdLine = stackalloc double[prices.Length];

// Calculates only MACD line (fast - slow), not signal or histogram
Macd.Calculate(prices, macdLine, fastPeriod: 12, slowPeriod: 26);

// For full MACD with signal, use streaming mode
```

### Event-Driven Chaining

```csharp
var source = new TSeries();
var macd = new Macd(source, 12, 26, 9);

// Subscribe to MACD updates
macd.Pub += (sender, args) =>
{
    if (args.IsNew)
    {
        var m = (Macd)sender!;
        Console.WriteLine($"MACD: {m.Last.Value:F4}, Signal: {m.Signal.Value:F4}");
    }
};

// Feed data
source.Add(new TValue(DateTime.UtcNow, 100.0));
```

## C# Implementation Considerations

### Triple EMA Architecture

```csharp
private readonly Ema _fastEma;
private readonly Ema _slowEma;
private readonly Ema _signalEma;
```

MACD delegates all state management to internal EMA instances. No separate state record needed: the three EMAs encapsulate all required history. Reset propagates to all three.

### Composite State Management

The `isNew` parameter cascades through all three EMAs:

```csharp
var fast = _fastEma.Update(input, isNew);
var slow = _slowEma.Update(input, isNew);
// ...
var signal = _signalEma.Update(macdTValue, isNew);
```

When `isNew=false`, all three EMAs roll back to previous state before reprocessing. This ensures bar corrections work correctly across the entire calculation chain.

### Multi-Output Pattern

MACD produces three outputs from single input:

```csharp
public TValue Last { get; private set; }      // MACD line
public TValue Signal { get; private set; }     // Signal line
public TValue Histogram { get; private set; }  // Histogram
```

The `Pub` event publishes only the MACD line value. Consumers requiring signal or histogram must access properties directly or subscribe to a wrapper.

### ArrayPool for Span Calculate

```csharp
double[] fastBuffer = ArrayPool<double>.Shared.Rent(len);
double[] slowBuffer = ArrayPool<double>.Shared.Rent(len);
try
{
    // Use buffers
    SimdExtensions.Subtract(fastSpan, slowSpan, destination);
}
finally
{
    ArrayPool<double>.Shared.Return(fastBuffer);
    ArrayPool<double>.Shared.Return(slowBuffer);
}
```

Span-based Calculate rents temporary buffers for intermediate EMA results. Total temporary allocation: 2× input length. ArrayPool eliminates GC pressure during repeated calls.

### Memory Layout Summary

| Component | Size | Lifetime |
| :-------- | ---: | :------- |
| Macd instance | ~200 bytes | Indicator lifetime |
| Fast EMA state | ~40 bytes | Per EMA |
| Slow EMA state | ~40 bytes | Per EMA |
| Signal EMA state | ~40 bytes | Per EMA |
| ArrayPool buffers | 2×N×8 bytes | Per batch call |
| **Streaming overhead** | **~320 bytes** | **Fixed** |

Zero allocations in streaming hot path. Batch mode uses ArrayPool, returning memory after each call.

## References

- Appel, G. (1979). "The Moving Average Convergence Divergence Trading Method." Signalert Corporation.
- Aspray, T. (1986). "MACD Histogram." *Technical Analysis of Stocks & Commodities*.
- Murphy, J. (1999). *Technical Analysis of the Financial Markets*. New York Institute of Finance.
- Pring, M. (2002). *Technical Analysis Explained*. McGraw-Hill.
- Elder, A. (1993). *Trading for a Living*. Wiley. (Discussion of MACD histogram interpretation)