Skip to content

MacdIndicator

Moving Average Convergence Divergence — the difference of two EMAs, with a third EMA on top as the signal line.

The Rust struct is MacdIndicator (since Macd would collide with the output struct on case-insensitive file systems and several existing trait imports). The Python and Node bindings expose the same engine under the shorter, conventional name MACD.

Quick reference

FieldValue
FamilyTrend & Directional
Input typef64 (close)
Output typeMacdOutput { macd, signal, histogram }
Output rangeunbounded (centred on 0)
Default parametersfast = 12, slow = 26, signal = 9 (MacdIndicator::classic())
Warmup periodslow + signal − 1 (34 for the classic configuration)
Interpretationcrossovers of macd and signal; zero-line crosses; histogram momentum

Formula

EMA_n(x) = exponential moving average of x over n periods
           (Wickra's EMA seeds from a simple average of the first n inputs)

macd_t   = EMA_fast(close)_t  −  EMA_slow(close)_t
signal_t = EMA_signal(macd)_t
hist_t   = macd_t − signal_t

The signal EMA does not start consuming inputs until macd_t becomes defined (i.e. until both the fast and slow EMAs have seeded), which is why the overall warmup is slow + signal − 1 rather than max(slow, signal).

Parameters

NameTypeDefault (Python)Valid rangeDescription
fastusize12>= 1 and < slowFast EMA period.
slowusize26>= 1 and > fastSlow EMA period.
signalusize9>= 1EMA period applied to the raw MACD line.

MacdIndicator::new returns Error::PeriodZero if any period is zero and Error::InvalidPeriod if fast >= slow.

Inputs / Outputs

From impl Indicator for MacdIndicator:

rust
use wickra::{Indicator, MacdIndicator, MacdOutput};
// MacdIndicator: Input = f64, Output = MacdOutput
const _: fn(&mut MacdIndicator, f64) -> Option<MacdOutput> = <MacdIndicator as Indicator>::update;

MacdOutput carries three fields:

FieldDescription
macdEMA(fast) − EMA(slow) of the input series.
signalEMA(signal) of macd.
histogrammacd − signal.

Python's MACD.batch(prices) returns a (n, 3) float64 array with columns [macd, signal, histogram]; warmup rows are entirely NaN.

Node's MACD.batch(prices) returns a flat number[] of length n * 3 in the same interleaved order: index i*3 + 0 is macd, i*3 + 1 is signal, i*3 + 2 is histogram. The streaming update(value) returns a { macd, signal, histogram } object (or null during warmup).

Warmup

warmup_period() returns slow + signal − 1. The slow EMA seeds at input slow; from that point onward the signal EMA starts receiving macd values, and needs signal − 1 further inputs to seed itself. For the classic (12, 26, 9) configuration this gives 26 + 9 − 1 = 34 inputs before the first complete MacdOutput is emitted, as pinned by the unit test first_emission_matches_warmup_period.

Edge cases

  • Constant input. Both EMAs converge to the constant value, so macd approaches 0; with no movement in macd, the signal EMA also approaches 0, and so does the histogram. The Rust test constant_series_yields_zero_macd_eventually pins this.
  • Non-finite input. update(NaN) or update(±∞) returns the previously emitted MacdOutput without advancing any internal EMA.
  • Reset. reset() resets all three EMAs and clears last. The next warmup_period() calls return None again.

Examples

Rust

rust
use wickra::{BatchExt, Indicator, MacdIndicator};

let prices: Vec<f64> = (0..40).map(|i| 100.0 + i as f64 * (20.0 / 39.0)).collect();
let mut macd = MacdIndicator::classic();
let out = macd.batch(&prices);
let v = out[33].unwrap();
println!("row 33  macd={} signal={} hist={}", v.macd, v.signal, v.histogram);
let v = out[39].unwrap();
println!("row 39  macd={} signal={} hist={}", v.macd, v.signal, v.histogram);

Verified output:

row 33  macd=3.589743589743577 signal=3.5897435897435788 hist=-0.0000000000000017763568394002505
row 39  macd=3.589743589743591 signal=3.589743589743585 hist=0.000000000000006217248937900877

Python

python
import numpy as np
import wickra as ta

prices = np.linspace(100.0, 120.0, 40)
macd = ta.MACD(12, 26, 9)
out = macd.batch(prices)
print('shape :', out.shape)
print('warmup:', macd.warmup_period())
print('row 33:', out[33])
print('row 39:', out[39])

Verified output:

shape : (40, 3)
warmup: 34
row 33: [ 3.58974359e+00  3.58974359e+00 -1.77635684e-15]
row 39: [3.58974359e+00 3.58974359e+00 6.21724894e-15]

Node

javascript
const wickra = require('wickra');

const macd = new wickra.MACD(12, 26, 9);
const prices = Array.from({ length: 40 }, (_, i) => 100 + i * 20 / 39);
const flat = macd.batch(prices);
console.log('flat length:', flat.length);
console.log('row 33 macd  :', flat[33 * 3]);
console.log('row 33 signal:', flat[33 * 3 + 1]);
console.log('row 33 hist  :', flat[33 * 3 + 2]);
console.log('row 39 macd  :', flat[39 * 3]);
console.log('row 39 signal:', flat[39 * 3 + 1]);
console.log('row 39 hist  :', flat[39 * 3 + 2]);

Verified output:

flat length: 120
row 33 macd  : 3.589743589743577
row 33 signal: 3.5897435897435788
row 33 hist  : -1.7763568394002505e-15
row 39 macd  : 3.589743589743591
row 39 signal: 3.589743589743585
row 39 hist  : 6.217248937900877e-15

Interpretation

  • Signal-line crossover. macd crossing above signal is the canonical bullish signal; the symmetric crossover below is bearish. The histogram makes this explicit — it crosses zero on the same bar.
  • Zero-line crossover. macd crossing above zero says the fast EMA has overtaken the slow EMA; a longer-term trend confirmation, weaker than the signal-line cross.
  • Histogram momentum. Rising histogram bars (even while negative) indicate that bearish momentum is fading, and vice versa. Traders use this to anticipate signal-line crosses.

Common pitfalls

  • The signal line lags the MACD line by signal_period bars. A crossover signal therefore arrives one full EMA-cycle after the underlying momentum turn, which is why MACD is a confirmation indicator, not a leading one.
  • fast >= slow is rejected. A common bug when reading parameters from a config file is swapping the two — the constructor returns Error::InvalidPeriod rather than silently producing an inverted MACD line.
  • Don't slice a single column out of a warmup row. During the first slow + signal − 1 inputs every field is NaN (Python) or absent (None in Rust / null in Node). Filter by checking macd for finiteness before reading signal or histogram.

References

  • Gerald Appel, Technical Analysis: Power Tools for Active Investors, Financial Times Prentice Hall, 2005 — the canonical modern treatment of the MACD line/signal-line/histogram trio Appel popularised in the late 1970s.

See also

  • Indicator: Rsi — bounded sibling oscillator, useful as a confirmation filter on top of MACD signals.
  • Indicator: Trix — another EMA-based momentum oscillator (triple-smoothed rate of change).
  • Warmup Periods — table including the slow + signal − 1 rule.
  • Quickstart: Python — MACD multi-column NaN pattern explained.