D07-F02-A01 / Complete engineering topic

MACD: Read the Spreads Before You Read the Signals

A production-minded guide to MACD: Read the Spreads Before You Read the Signals.

D07 · TECHNICAL INDICATORS
D07-F02-A01Canonical / Tested / Open
D07 / D07-F02

MACD is usually introduced as a bundle of crossovers: buy here, sell there, watch the bars change color. That shortcut hides the most useful idea.

MACD is a measurement system built from two spreads:

  1. the distance between a fast and a slow exponential moving average;
  2. the distance between that first spread and its own smoothed history.

Once those two distances are clear, the zero line, signal line, and histogram stop being separate pieces of chart folklore. They become simple algebra that you can calculate, test, and reason about.

This tutorial builds that understanding from the inside out. We will calculate a complete example by hand, connect every crossover to an equality, expose the two-stage warm-up, test scale behavior, and finish with production-safe implementations.

Using the tables on a narrow screen: wide tables scroll horizontally inside their own table frame. Swipe within the table to inspect later columns; the article page itself should remain fixed.

The full MACD calculation machine

The three outputs answer three different questions

Let FtF_t be the fast EMA and StS_t the slow EMA.

Mt=FtStM_t = F_t-S_t

The MACD line MtM_t asks:

Is the shorter-horizon average above or below the longer-horizon average, and by how much?

Now smooth the MACD line with another EMA:

Gt=EMA(Mt)G_t = \operatorname{EMA}(M_t)

The signal line GtG_t asks:

What is the recent smoothed level of the fast-minus-slow spread?

Finally:

Ht=MtGtH_t = M_t-G_t

The histogram HtH_t asks:

Is the current spread above or below its own smoothed history, and by how much?

ComponentExact calculationUnitPositive meansZero means
Fast EMAEMA of source with lower spansource unitnot a signed outputnot special
Slow EMAEMA of source with higher spansource unitnot a signed outputnot special
MACDfast EMA − slow EMAsource unitfast EMA above slow EMAthe two EMAs are equal
SignalEMA of defined MACD valuessource unitsmoothed spread is above zerosmoothed spread is zero
HistogramMACD − signalsource unitMACD above signalMACD and signal are equal

The word “momentum” is often attached to these outputs. That can be a useful interpretation, but the equations give a narrower and safer statement: MACD measures relationships among recursively smoothed levels. It does not directly measure return, velocity, acceleration, probability, or expected profit.

First visual key: the sign is just the EMA gap

Positive, zero, and negative EMA gaps

This identity is exact:

Mt>0    Ft>StM_t > 0 \iff F_t > S_t Mt=0    Ft=StM_t = 0 \iff F_t = S_t Mt<0    Ft<StM_t < 0 \iff F_t < S_t

So “MACD crossed zero” and “the fast EMA crossed the slow EMA” are not two confirmations. They are two drawings of the same event.

That distinction matters. Counting the same equality twice can create false confidence. A dashboard that shows both a moving-average crossover badge and a MACD-zero-cross badge may be presenting duplicate information, not independent evidence.

The EMA convention must be named

“Use a 12-period EMA” is not yet a complete computational specification. An EMA needs both a recurrence and an initial value.

For span nn, this package uses:

αn=2n+1\alpha_n=\frac{2}{n+1}

The first defined EMA is the simple mean of the first nn observations:

En=x1+x2++xnnE_n=\frac{x_1+x_2+\cdots+x_n}{n}

Every later value is:

Et=Et1+αn(xtEt1)E_t=E_{t-1}+\alpha_n(x_t-E_{t-1})

This is the classic SMA-seeded recursive convention. The fast and slow EMAs are seeded independently from the source. The signal EMA is then seeded from the first signal_span defined MACD values.

ChoiceThis packageA material alternativeConsequence
EMA coefficient2/(span+1)fixed default coefficientsslightly different recursive path
Price-EMA seedSMA of first span source valuesfirst source valueearly output differs
Signal seedSMA of first signal-span MACD valuesfirst MACD valueearly signal and bars differ
Output alignmentpreserve every source rowreturn only complete rowsindex mapping differs
Reversed spansrejectsilently swapconfiguration mistakes can be hidden
Missing valuerejectskip, fill, or resetstate and elapsed-time meaning differ

When two charting packages disagree near the beginning of a series, compare these choices before assuming that either one has an arithmetic bug.

Fix the timestamp and price basis before calculating

The numeric kernel receives only an ordered array, so it cannot discover whether two adjacent values are daily closes, five-minute bar closes, mixed sessions, or differently adjusted prices. The caller must attach a unique timestamp to every value and document:

  • time zone and whether a bar timestamp denotes its opening or closing instant;
  • frequency, trading calendar, and the treatment of missing sessions or bars;
  • instrument identifier, currency, and source field;
  • adjusted or unadjusted price basis and the upstream corporate-action policy;
  • finalized versus provisional status.

Use one convention for the entire series. Splicing adjusted history into an unadjusted suffix can create a discontinuity that both EMAs faithfully process even though the discontinuity came from data construction rather than the market path. Likewise, a provisional latest bar can change before finalization and recursively change its MACD row.

Two warm-ups, not one

The slow EMA controls when MACD first exists. Then the signal needs its own history of MACD values.

Two-stage warm-up for default and educational spans

Counting observations from one:

MilestoneFormulaDefault 12/26/9Worked 3/5/3
First fast EMAfast_span123
First slow EMAslow_span265
First MACDslow_span265
First signalslow_span + signal_span − 1347
First histogramslow_span + signal_span − 1347

Before observation 26 in the default case, there is no canonical slow EMA under this seed convention, so there is no MACD. At observations 26 through 33, MACD exists but the nine-period signal is still accumulating its seed. Observation 34 is the first row with all three plotted outputs.

Returning those stages explicitly is safer than dropping rows. An aligned result can attach each component to the exact source timestamp and label its state:

StateMACDSignalHistogram
warming_price_emasunavailableunavailableunavailable
warming_signalavailableunavailableunavailable
readyavailableavailableavailable

Unavailable is represented by None or null. It is never represented by zero, because zero is a meaningful equality.

A complete 3/5/3 worked example

Short spans make every step visible. Use:

  • fast span 33, so αf=2/(3+1)=1/2\alpha_f=2/(3+1)=1/2;
  • slow span 55, so αs=2/(5+1)=1/3\alpha_s=2/(5+1)=1/3;
  • signal span 33, so αg=1/2\alpha_g=1/2.

The synthetic source is:

Plain text
10, 11, 12, 13, 14, 15, 16, 15, 14, 13, 12, 13, 15, 18

The path rises, reverses, and recovers. It is deliberately educational rather than representative of a particular instrument.

The worked path across price, MACD lines, and histogram

Step 1: seed the price EMAs

The first fast EMA occurs at observation 3:

F3=10+11+123=11F_3=\frac{10+11+12}{3}=11

The first slow EMA occurs at observation 5:

S5=10+11+12+13+145=12S_5=\frac{10+11+12+13+14}{5}=12

The fast EMA has already updated twice:

F4=11+12(1311)=12F_4=11+\frac12(13-11)=12 F5=12+12(1412)=13F_5=12+\frac12(14-12)=13

Therefore the first MACD is:

M5=1312=1M_5=13-12=1

The signal is still unavailable. It needs three MACD values.

Step 2: seed the signal

At observations 5, 6, and 7, the MACD values are 1,1,11,1,1. Therefore:

G7=1+1+13=1G_7=\frac{1+1+1}{3}=1 H7=M7G7=11=0H_7=M_7-G_7=1-1=0

Observation 7 is the first fully ready row.

Step 3: see why MACD and histogram can disagree in sign

At observation 8:

F8=15+12(1515)=15F_8=15+\frac12(15-15)=15 S8=14+13(1514)=433S_8=14+\frac13(15-14)=\frac{43}{3} M8=15433=23M_8=15-\frac{43}{3}=\frac23

MACD remains positive because the fast EMA remains above the slow EMA.

But the signal updates from 11:

G8=1+12(231)=56G_8=1+\frac12\left(\frac23-1\right)=\frac56

So:

H8=2356=16H_8=\frac23-\frac56=-\frac16

The two signs now say different, perfectly compatible things:

Observation 8 factValueExact reading
MACD2/32/3fast EMA remains above slow EMA
Signal5/65/6recent smoothed MACD is higher than current MACD
Histogram1/6-1/6current MACD is below its signal

A negative histogram does not require a negative MACD. The first spread can be positive while the residual spread is negative.

Step 4: cross the zero line

At observation 10:

M10=7/108M_{10}=-7/108 G10=53/216G_{10}=53/216 H10=67/216H_{10}=-67/216

The negative MACD means the fast EMA is now below the slow EMA. This is not a nearby confirmation of that crossover—it is the same subtraction reaching a negative result.

Step 5: recover

At observation 13:

M13=4871/23328M_{13}=4871/23328 G13=497/11664G_{13}=497/11664 H13=3877/23328H_{13}=3877/23328

Both spreads are positive again in this path. That simultaneity is not a general rule. A MACD zero cross and a signal cross answer different equalities and can occur at different observations.

Full audit table

Rounded values are shown for readability; the shared fixture stores the verified path.

Mobile table cue: this eight-column audit table is intentionally preserved. Swipe left or right inside its table frame to follow the same observation across all components.

tsourcefast EMAslow EMAMACDsignalhistogramstate
110price EMAs warming
211price EMAs warming
31211.0000price EMAs warming
41312.0000price EMAs warming
51413.000012.00001.0000signal warming
61514.000013.00001.0000signal warming
71615.000014.00001.00001.00000.0000ready
81515.000014.33330.66670.8333-0.1667ready
91414.500014.22220.27780.5556-0.2778ready
101313.750013.8148-0.06480.2454-0.3102ready
111212.875013.2099-0.3349-0.0448-0.2901ready
121312.937513.1399-0.2024-0.1236-0.0788ready
131513.968813.75990.20880.04260.1662ready
141815.984415.17330.81110.42680.3842ready

Two crossover identities worth memorizing

MACD zero cross

Fast and slow EMA crossover aligned with MACD zero

Mt=0    FtSt=0    Ft=StM_t=0 \iff F_t-S_t=0 \iff F_t=S_t

The direction of the cross is the direction of the ordering change. A cross above zero means the fast EMA moved from at-or-below the slow EMA to above it, subject to the application’s equality and numeric-tolerance policy.

Histogram zero cross

MACD and signal crossover aligned with histogram zero

Ht=0    MtGt=0    Mt=GtH_t=0 \iff M_t-G_t=0 \iff M_t=G_t

A signal-line crossover and a histogram zero cross are likewise the same event drawn as lines and bars.

For event detectors, equality needs a policy. Exact IEEE-754 equality is often too brittle for independently computed series. A separate crossover component should define:

  • whether touching zero counts;
  • absolute or relative tolerance;
  • how repeated equal values are handled;
  • whether an event is emitted intrabar or only after finalization;
  • whether the first defined value can create an event.

Those are event semantics, not part of continuous MACD calculation.

Four tiny experiments that reveal the structure

1. Constant input

If every source value is 4242, each seeded and updated price EMA is 4242. Therefore:

Mt=4242=0,Gt=0,Ht=0M_t=42-42=0,\quad G_t=0,\quad H_t=0

This is a useful implementation test because any nonzero ready output reveals an alignment or seed defect.

2. Add a constant

Add 100100 to every source observation. Both price EMAs also increase by 100100, so their difference is unchanged:

(Ft+100)(St+100)=FtSt(F_t+100)-(S_t+100)=F_t-S_t

The signal and histogram are unchanged too. MACD is translation invariant.

3. Multiply the series

Multiply every source value by 1010. Every linear EMA component and difference is multiplied by 1010:

Mt=10Mt,Gt=10Gt,Ht=10HtM_t^{\prime}=10M_t,\qquad G_t^{\prime}=10G_t,\qquad H_t^{\prime}=10H_t

MACD is scale equivariant, not scale invariant.

Translation invariance and the cross-price scale trap

This is why “MACD is 5” has no portable meaning without the instrument’s price scale and history. The next catalog topic, Percentage Price Oscillator, addresses that normalization problem.

4. Make the signal span one

With a signal span of one, the signal EMA equals the current MACD immediately:

Gt=MtG_t=M_t

Therefore:

Ht=MtGt=0H_t=M_t-G_t=0

This edge case is valid and highly diagnostic. It confirms that the histogram is derived, not independent.

Why range-bound markets produce whipsaw

Repeated crossovers inside a range

A fast EMA reacts more quickly than a slow EMA. If the source oscillates back and forth without developing a durable directional path, the fast EMA can repeatedly move above and below the slow EMA. MACD crosses zero each time. The MACD line can also repeatedly cross its signal.

Every event can be arithmetically correct while a naïve crossover strategy performs poorly after costs.

What the event provesWhat it does not prove
two averages changed orderingthe new ordering will persist
current MACD changed side of its signalthe next bar has the same direction
a signed spread reached zerothe move exceeds fees and slippage
recursive state responded to recent dataa structural regime changed
the chosen spans produced an eventthose spans are suitable for the instrument

The practical response is not to claim that MACD “fails.” It is to keep the claim boundary honest: MACD describes selected averages. Strategy quality depends on regime selection, event rules, execution timing, costs, and risk controls outside this indicator.

Use the causal lab

Open the interactive MACD causal lab.

Try this sequence:

  1. Keep spans at 3/5/3 and step through the reversal path.
  2. Stop at observation 5. Notice that MACD exists while signal and histogram do not.
  3. Stop at observation 8. Compare positive MACD with negative histogram.
  4. Switch to Range and whipsaw and play the complete path.
  5. Switch to Same shape × 10 and compare magnitudes.
  6. Set signal span to 1; every ready histogram bar should be zero.
  7. Attempt fast 5, slow 5; the lab rejects the ambiguous configuration.

The lab calculates only the visible prefix. Nothing at observation tt uses observation t+1t+1, so it also demonstrates causality.

Every lab scenario is synthetic. It demonstrates calculation, warm-up, scale, and whipsaw mechanics; it is not evidence that a crossover predicts a later observation or earns a return.

Implementation blueprint

The core algorithm is small:

Plain text
fast = SMA-seeded EMA(source, fast_span)
slow = SMA-seeded EMA(source, slow_span)
macd = fast - slow wherever both exist
signal = SMA-seeded EMA(defined macd values, signal_span)
histogram = macd - signal wherever signal exists
return all rows in source alignment

What makes an implementation production-safe is the surrounding contract:

ConcernPackage decision
Input mutationnever mutate caller input
Orderingcaller supplies chronological values
Missing valuesreject explicitly
Non-finite valuesreject explicitly
Fast/slow orderingreject fast >= slow
Warm-uppreserve aligned unavailable fields
Seedclassic SMA seed
Numeric typedouble precision
Revisionsrecompute affected suffix
Finalitycaller labels provisional versus finalized
Complexitylinear batch; constant-time warmed append

The Python implementation returns immutable dataclass rows. The TypeScript implementation returns typed component objects. Both read the same fixture, test algebraic identities, and test translation and scaling behavior.

Streaming state

After warm-up, a streaming system only needs:

  • previous fast EMA;
  • previous slow EMA;
  • previous signal EMA;
  • three coefficients;
  • current lifecycle status.

One appended finalized observation then takes constant time:

Plain text
fast   ← fast   + α_fast   × (value − fast)
slow   ← slow   + α_slow   × (value − slow)
macd   ← fast − slow
signal ← signal + α_signal × (macd − signal)
hist   ← macd − signal

During seeding, keep running sums and counts for the price EMAs and later the signal EMA. A revised historical observation is different: because the recurrence carries state forward, every later dependent value must be recomputed.

Common mistakes

MistakeWhy it failsSafer rule
Filling warm-up with zerozero falsely claims equalityuse unavailable values
Seeding signal from pricessignal is defined on MACDseed from defined MACD values
Treating MACD and zero cross as separate evidencethey encode the same EMA orderingcount the identity once
Treating bars as independent evidencehistogram is MACD minus signalinterpret the residual directly
Comparing raw magnitudes across assetsMACD has source unitsnormalize or use PPO
Silent span swappinghides configuration defectsreject and log
Skipping missing rows without a policychanges elapsed-time meaningreject or document reset/skip semantics
Using provisional bars as finalrecursive suffix can changecarry finality metadata
Claiming predictionmoving averages react to datastate measurement, then test strategies separately

What MACD can and cannot tell you

MACD can tell you, under a named source, span, seed, and finality convention:

  • the ordering and distance of two EMAs;
  • the position of that spread relative to its EMA;
  • when either exact equality changes sign;
  • how those measurements evolved over the observed history.

MACD alone cannot tell you:

  • the probability of the next return;
  • whether a crossover will persist;
  • whether expected profit exceeds transaction costs;
  • the correct position size;
  • whether a divergence rule has valid pivots;
  • whether the current observation is final;
  • whether raw magnitude is comparable to another instrument.

That boundary does not make the indicator less valuable. It makes it testable.

Final mental model

When you see a MACD panel, read it in this order:

  1. MACD sign: Which price EMA is on top?
  2. MACD magnitude: How large is their gap in source units?
  3. Histogram sign: Is that gap above or below its own signal EMA?
  4. Histogram magnitude: How large is the residual?
  5. Lifecycle: Are all components actually warmed and finalized?
  6. Context: Is the source trending, ranging, scaled differently, or being revised?

Then—and only then—attach a strategy interpretation.

MACD is important not because three plotted shapes predict the future. It is important because a compact chain of recursive averages exposes trend relationships in a form that is visual, causal, and mechanically auditable.

Continue the track

Next is D07-F02-A02 — Percentage Price Oscillator. It keeps the fast-versus-slow idea but divides by the slow average, turning an absolute price-unit spread into a relative percentage spread.

For formulas, implementation-source analysis, and claim provenance, see the reference ledger.

Calculation flow

Rendering system map…

The slow span controls when the MACD line first exists. The signal span adds a second, independent warm-up.

State lifecycle

Rendering system map…

Appending is causal and constant-state after warm-up. Revising history requires recomputing every dependent later row.

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

Reviewed 2026-07-23.

  • Accessed: 2026-07-23
  • Limitations: Each source supports only the claims named below; none establishes predictive power, profitability, or universal cross-platform parity.

Primary and authoritative sources

  1. TradingView Help Center — “Moving Average Convergence Divergence (MACD) indicator.”
    https://www.tradingview.com/support/solutions/43000502344-moving-average-convergence-divergence-macd-indicator/
    Source class: authoritative platform documentation.
    Supports: the three defining equations; fast/slow terminology; zero line; plotted components; configurable source, lengths, and moving-average types; Gerald Appel provenance and Thomas Aspray’s later histogram addition.
    Used in: formula definition, output anatomy, provenance, and variant notes.

  2. Fidelity Learning Center — “Moving Average Convergence/Divergence (MACD).”
    https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/macd
    Source class: authoritative broker education.
    Supports: conventional 12/26/9 periods; MACD oscillating around zero; unbounded scale; parameter adjustability; susceptibility to whipsaw in trading ranges.
    Used in: defaults, limitation discussion, and the range-bound example.

  3. TA-Lib function documentation — MACDEXT.
    https://ta-lib.org/functions/macdext/
    Source class: primary library documentation.
    Supports: fast MA minus slow MA; signal MA of MACD; histogram residual; selectable moving-average types; signal period of one yielding a zero histogram.
    Used in: formula cross-check and implementation-variant matrix.

  4. TA-Lib source — ta_MACD.c.
    https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_MACD.c
    Source class: primary implementation source.
    Supports: lookback as slow EMA lookback plus signal EMA lookback; classic SMA seeding path; EMA recursive coefficients; swapping reversed fast and slow inputs; line/signal/histogram computation.
    Used in: warm-up boundary, seed analysis, library comparison, and the deliberate reject-versus-swap design decision.

  5. TA-Lib source — ta_EMA.c.
    https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_EMA.c
    Source class: primary implementation source.
    Supports: the classic EMA seed as the simple average of the first period and documents first-price seeding as a material compatibility alternative.
    Used in: initialization contract and variant matrix.

  6. TradingView Help Center — “Moving averages.”
    https://www.tradingview.com/support/solutions/43000502589-moving-averages/
    Source class: authoritative platform documentation.
    Supports: the standard EMA multiplier 2/(length+1), recursive update, SMA initialization, and the fact that moving averages react to existing data rather than predict future values.
    Used in: EMA formula and claim-boundary language.

Claims derived algebraically in this package

These claims do not depend on empirical market behavior:

  • MACD = 0 if and only if fast EMA = slow EMA.
  • histogram = 0 if and only if MACD = signal.
  • Adding the same constant to every input leaves MACD, signal, and histogram unchanged under the stated initialization.
  • Multiplying every input by kk multiplies all three outputs by kk.
  • A constant input produces zero MACD, signal, and histogram after warm-up.
  • With the aligned SMA-seeded convention, first MACD is at slow_span; first signal and histogram are at slow_span + signal_span − 1.

Each is tested in both implementation languages or by the exact-arithmetic verifier.

Historical and interpretive caution

The commonly repeated attribution to Gerald Appel and histogram addition by Thomas Aspray is retained because an authoritative platform source reports it. This package does not reproduce an original 1970s publication, and it does not assign an exact invention year beyond that sourced account.

Directional labels such as “bullish” and “bearish” are industry interpretations, not guarantees. The article therefore leads with exact ordering statements and explicitly separates them from possible market narratives.

Reproducibility

All numeric examples and visual paths are synthetic. No vendor market history is embedded. The fixture convention is:

  • alpha 2/(span+1);
  • classic SMA seeds;
  • fast span strictly less than slow span;
  • aligned unavailable values represented by null;
  • comparisons at 1e-9 for decimal JSON and 1e-12 for directly derived values where appropriate.

Historical example decision

No historical market episode is published in this package. The worked path and all playground scenarios are labeled synthetic, so they support implementation verification without implying that a selected real-world interval is representative.

Before adding a historical example, archive or cite all of the following:

  • authoritative or appropriately licensed provider observations with a stable instrument identifier;
  • exact timestamps, time zone, session calendar, frequency, and bar-finality rule;
  • source field, currency, and an explicit adjusted-or-unadjusted price convention;
  • corporate-action notes covering the selected interval;
  • retrieval date and a reproducible observation table;
  • author-derived fast EMA, slow EMA, MACD, signal, and histogram values calculated with the package's named seed convention;
  • a limitation statement that the episode illustrates measurement behavior only and does not establish causation, prediction, persistence, profitability, or investment suitability.

Provider values and author-derived indicator values must appear in separate labeled columns or sections. Credentials, private provider payloads, and redistribution-restricted data must never be included.

macd.ts
export type MACDStatus = "warming_price_emas" | "warming_signal" | "ready";

export interface MACDPoint {
  index: number;
  value: number;
  fastEma: number | null;
  slowEma: number | null;
  macd: number | null;
  signal: number | null;
  histogram: number | null;
  status: MACDStatus;
}

function validateSpan(name: string, value: number): void {
  if (!Number.isInteger(value) || value < 1) {
    throw new RangeError(`${name} must be a positive integer`);
  }
}

function ema(values: readonly number[], span: number): Array<number | null> {
  const result: Array<number | null> = Array(values.length).fill(null);
  if (values.length < span) return result;

  let previous = values.slice(0, span).reduce((sum, value) => sum + value, 0) / span;
  result[span - 1] = previous;
  const alpha = 2 / (span + 1);
  for (let index = span; index < values.length; index += 1) {
    previous += alpha * (values[index] - previous);
    result[index] = previous;
  }
  return result;
}

export function macd(
  values: readonly number[],
  fastSpan = 12,
  slowSpan = 26,
  signalSpan = 9,
): MACDPoint[] {
  validateSpan("fastSpan", fastSpan);
  validateSpan("slowSpan", slowSpan);
  validateSpan("signalSpan", signalSpan);
  if (fastSpan >= slowSpan) {
    throw new RangeError("fastSpan must be smaller than slowSpan");
  }
  values.forEach((value, index) => {
    if (!Number.isFinite(value)) {
      throw new TypeError(`values[${index}] must be a finite number`);
    }
  });

  const fast = ema(values, fastSpan);
  const slow = ema(values, slowSpan);
  const macdValues = values.map((_, index) => {
    const fastValue = fast[index];
    const slowValue = slow[index];
    return fastValue === null || slowValue === null ? null : fastValue - slowValue;
  });
  const compactMacd = macdValues.filter((value): value is number => value !== null);
  const compactSignal = ema(compactMacd, signalSpan);
  const signal: Array<number | null> = Array(values.length).fill(null);
  const firstMacdIndex = slowSpan - 1;
  compactSignal.forEach((value, index) => {
    signal[firstMacdIndex + index] = value;
  });

  return values.map((value, index) => {
    const macdValue = macdValues[index];
    const signalValue = signal[index];
    const histogram =
      macdValue === null || signalValue === null ? null : macdValue - signalValue;
    const status: MACDStatus =
      macdValue === null
        ? "warming_price_emas"
        : signalValue === null
          ? "warming_signal"
          : "ready";
    return {
      index,
      value,
      fastEma: fast[index],
      slowEma: slow[index],
      macd: macdValue,
      signal: signalValue,
      histogram,
      status,
    };
  });
}
Full-height labmacd labOpen full screen