Python 3 and a terminal. No trading platform, no broker, no account and no
money. macOS and Linux already include Python; on Windows install it from
python.org with “Add python.exe to PATH” ticked, then check with
python3 --version. You do not need MetaTrader for this page.
It helps to have met a moving average first, because MACD is built entirely from them — but everything needed is written out in the code below, so you can start here if you would rather. Run the five files in order; the first writes a file the others read.
Nothing here is trading advice. Every measurement is taken on one generated series with no spread, commission or slippage. They show how the indicator behaves, never what would have happened to an account.
What is MACD?
MACD (Moving Average Convergence Divergence) is a trend-following momentum indicator created by Gerald Appel in the 1970s. It shows the relationship between two exponential moving averages and is one of the most popular indicators in trading — used by beginners and professionals alike.
MACD works by measuring how two moving averages are converging (coming together) or diverging (moving apart), which reveals changes in trend strength, direction, and momentum.
MACD Components
MACD consists of three visual elements:
- MACD Line — The difference between the 12-period EMA and the 26-period EMA. When this line is positive, the short-term trend is above the long-term trend (bullish). When negative, it is bearish.
- Signal Line — A 9-period EMA of the MACD line. It acts as a smoothed trigger for buy/sell signals.
- Histogram — The difference between the MACD line and the signal line, displayed as bars. Positive bars mean MACD is above the signal (bullish momentum); negative bars mean it is below (bearish momentum).
MACD Line = EMA(12) - EMA(26)
Signal Line = EMA(9) of MACD Line
Histogram = MACD Line - Signal Line
MACD Trading Signals
Signal Line Crossover (most common):
- Bullish: MACD line crosses above the signal line — momentum is shifting upward
- Bearish: MACD line crosses below the signal line — momentum is shifting downward
Centerline Crossover:
- Bullish: MACD line crosses above zero — the 12 EMA is now above the 26 EMA, confirming an uptrend
- Bearish: MACD line crosses below zero — the 12 EMA is below the 26 EMA, confirming a downtrend
Histogram Analysis:
Watch the histogram bars for changes in momentum before the lines cross. When histogram bars start shrinking (getting shorter), momentum is fading — a crossover may be coming. This gives you an early warning before the actual signal.
MACD Divergence
Like RSI, MACD divergence is a powerful reversal signal:
Bullish Divergence: Price makes lower lows, but MACD makes higher lows. Selling pressure is decreasing — potential upward reversal.
Bearish Divergence: Price makes higher highs, but MACD makes lower highs. Buying pressure is fading — potential downward reversal.
A bullish MACD crossover confirmed by RSI moving above 50 is a stronger signal than either alone. Combining a trend indicator (MACD) with a momentum oscillator (RSI) reduces false signals significantly.
MACD Settings
The default settings (12, 26, 9) work well for most instruments and timeframes. Adjustments:
- Faster settings (8, 17, 9): More sensitive to price changes, generates more signals. Better for short-term trading.
- Slower settings (19, 39, 9): Smoother, fewer signals but more reliable. Better for position trading.
Common Mistakes
- Treating every crossover as a trade signal — In ranging markets, MACD generates many false crossovers. Always confirm the market is trending first.
- Ignoring the histogram — The histogram gives early warnings that many traders miss. Shrinking bars are the first sign of weakening momentum.
- Using MACD in isolation — MACD is a lagging indicator. Combine it with price action and support/resistance levels for better results.
Build MACD From Its Three Parts and Test Two Claims About It, in Five Steps
MACD is drawn as three things — two lines and a bar chart — which makes it look like three sources of information. It is not, and proving that is the most useful thing on this page: the histogram is a subtraction of the other two, so a strategy demanding both a line crossover and a histogram turn is asking for one condition twice. In the next twenty-five minutes you will build all three from scratch, prove the identity by counting events rather than by argument, find out why a MACD threshold copied from someone else's chart cannot work, and measure how early MACD fires and what that costs. Every line of output below came from running these files.
Go: open a terminal in a folder you can write to — cd ~/Desktop on macOS or Linux, cd %USERPROFILE%\Desktop on Windows.
Do: save this as prices.py and run python3 prices.py.
"""Build a price series once, so every later step works on the same numbers."""
import random
random.seed(11) # fixed, so your figures match this page exactly
# Eight regimes of 50 bars: up, down, flat, repeating. The turns are at
# bar 50, 100, 150, 200, 250, 300, 350 -- known, because we put them there.
REGIMES = [0.35, -0.30, 0.0, 0.35, -0.30, 0.0, 0.35, -0.30]
price, series = 100.0, []
for bar in range(400):
drift = REGIMES[bar // 50]
price += drift + random.uniform(-1.2, 1.2)
series.append(round(price, 2))
with open("closes.txt", "w") as f:
for p in series:
f.write("%.2f\n" % p)
print("bars written :", len(series))
print("first 5 :", series[:5])
print("last 5 :", series[-5:])
print("high / low : %.2f / %.2f" % (max(series), min(series)))
print("regime turns : bars 50, 100, 150, 200, 250, 300, 350")
print()
print("Three kinds of market on purpose -- rising, falling and flat, each")
print("appearing more than once. Most indicator advice is written as if only")
print("the first two exist.")
You should see: four hundred bars with known turning points:
bars written : 400
first 5 : [100.24, 100.73, 102.1, 102.36, 102.73]
last 5 : [116.86, 116.63, 115.51, 114.5, 114.51]
high / low : 134.48 / 92.25
regime turns : bars 50, 100, 150, 200, 250, 300, 350
Three kinds of market on purpose -- rising, falling and flat, each
appearing more than once. Most indicator advice is written as if only
the first two exist.
The turns are known because they were placed deliberately. That is what makes step 5 possible — with real market data you can see what an indicator did, but never how late it was.
If not: PermissionError means the folder is not writable — cd
somewhere you own. Different first values mean random.seed(11) is missing.
Go: the same folder.
Do: save this as macd.py and run python3 macd.py.
"""MACD is three numbers, and only two of them are calculated."""
closes = [float(line) for line in open("closes.txt")]
def ema(values, period):
k = 2.0 / (period + 1)
out = [None] * (period - 1)
out.append(sum(values[:period]) / period)
for v in values[period:]:
out.append(v * k + out[-1] * (1 - k))
return out
fast, slow = ema(closes, 12), ema(closes, 26)
macd = [None if None in (f, s) else f - s for f, s in zip(fast, slow)]
defined = [v for v in macd if v is not None]
sig_raw = ema(defined, 9)
offset = len(macd) - len(defined)
signal = [None] * offset + sig_raw
hist = [None if None in (m, s) else m - s for m, s in zip(macd, signal)]
print("bar close EMA12 EMA26 MACD signal hist")
for i in (25, 33, 34, 100, 250):
row = [closes[i], fast[i], slow[i], macd[i], signal[i], hist[i]]
print("%3d %s" % (i, "".join("%10s" % ("%.4f" % v if v is not None else "--") for v in row)))
print()
print("MACD line = EMA(12) - EMA(26) -- a difference, not an average")
print("signal line = EMA(9) of the MACD line")
print("histogram = MACD - signal -- not calculated, just subtracted")
You should see: the MACD line defined from bar 25 and the signal only from bar 33:
bar close EMA12 EMA26 MACD signal hist
25 106.8400 106.9168 105.0485 1.8683 -- --
33 109.8800 107.9414 106.4272 1.5142 1.4963 0.0179
34 110.1300 108.2781 106.7015 1.5766 1.5124 0.0642
100 95.5700 98.3459 101.2788 -2.9329 -2.6548 -0.2780
250 106.6600 108.9754 111.0338 -2.0584 -1.8185 -0.2398
MACD line = EMA(12) - EMA(26) -- a difference, not an average
signal line = EMA(9) of the MACD line
histogram = MACD - signal -- not calculated, just subtracted
Notice the two different starting points. The MACD line needs 26 bars, because EMA(26) does; the signal line then needs a further nine of those, so it does not exist until bar 33. Anything at the left edge of a MACD panel is warm-up, not signal.
The last three lines are the definition worth memorising. MACD is a difference, not an average — which is the fact step 4 turns into a practical consequence.
If not: if the signal column stays --, the offset arithmetic is
wrong: the signal is an EMA of the defined MACD values only, then padded back out to the
full length so the indexes line up with the price bars.
Go: the same folder.
Do: save this as identity.py and run python3 identity.py.
"""Prove the histogram carries no information the other two lines lack."""
closes = [float(line) for line in open("closes.txt")]
def ema(values, period):
k = 2.0 / (period + 1)
out = [None] * (period - 1)
out.append(sum(values[:period]) / period)
for v in values[period:]:
out.append(v * k + out[-1] * (1 - k))
return out
fast, slow = ema(closes, 12), ema(closes, 26)
macd = [None if None in (f, s) else f - s for f, s in zip(fast, slow)]
defined = [v for v in macd if v is not None]
offset = len(macd) - len(defined)
signal = [None] * offset + ema(defined, 9)
hist = [None if None in (m, s) else m - s for m, s in zip(macd, signal)]
hist_zero, macd_cross, both = 0, 0, 0
for i in range(1, len(closes)):
if None in (hist[i], hist[i-1], macd[i], macd[i-1], signal[i], signal[i-1]):
continue
h = (hist[i-1] <= 0) != (hist[i] <= 0)
c = (macd[i-1] <= signal[i-1]) != (macd[i] <= signal[i])
hist_zero += h
macd_cross += c
both += (h and c)
print("histogram crossed zero :", hist_zero, "times")
print("MACD crossed its signal line :", macd_cross, "times")
print("both happened on the same bar :", both, "times")
print()
print("identical:", hist_zero == macd_cross == both)
print()
print("They are the same event, always, by construction: hist = MACD - signal,")
print("so hist = 0 is exactly MACD = signal. The histogram is a redrawing of")
print("a crossing you can already see, not a third opinion about it.")
print()
print("This matters when a strategy claims confirmation from 'MACD crossover")
print("AND histogram turning positive'. That is one condition counted twice,")
print("and counting a condition twice does not make it more likely to be right.")
You should see: three counts that are the same number:
histogram crossed zero : 13 times
MACD crossed its signal line : 13 times
both happened on the same bar : 13 times
identical: True
They are the same event, always, by construction: hist = MACD - signal,
so hist = 0 is exactly MACD = signal. The histogram is a redrawing of
a crossing you can already see, not a third opinion about it.
This matters when a strategy claims confirmation from 'MACD crossover
AND histogram turning positive'. That is one condition counted twice,
and counting a condition twice does not make it more likely to be right.
Thirteen, thirteen, thirteen. Not approximately — identically, on every bar, and it could not be otherwise: the histogram is MACD minus signal, so it crosses zero exactly when MACD crosses signal. There is no arrangement of prices for which one happens and the other does not.
This is a general habit worth more than the specific fact. Before combining two indicators for “confirmation”, check whether they are computed from the same inputs. Two views of one calculation agree with each other for reasons that have nothing to do with the market, and stacking them raises your confidence without raising your accuracy — which is the worst possible combination.
If not: if the three counts differ, the comparison operators are inconsistent — both
tests must treat the boundary the same way, which is why each uses <= and compares
the resulting booleans rather than testing signs directly.
Go: the same folder.
Do: save this as unbounded.py and run python3 unbounded.py.
"""RSI has a ceiling. MACD does not -- and that changes how you may use it."""
closes = [float(line) for line in open("closes.txt")]
def ema(values, period):
k = 2.0 / (period + 1)
out = [None] * (period - 1); out.append(sum(values[:period]) / period)
for v in values[period:]:
out.append(v * k + out[-1] * (1 - k))
return out
def macd_of(values):
f, s = ema(values, 12), ema(values, 26)
return [None if None in (a, b) else a - b for a, b in zip(f, s)]
# The same shape of market, priced in three different units.
INSTRUMENTS = [("a share at ~100", 1.0), ("an index at ~10,000", 100.0),
("a currency pair at ~1.10", 0.011)]
print("%-26s %10s %10s %10s" % ("INSTRUMENT", "MIN MACD", "MAX MACD", "RANGE"))
print("-" * 60)
for label, scale in INSTRUMENTS:
scaled = [c * scale for c in closes]
m = [v for v in macd_of(scaled) if v is not None]
print("%-26s %10.4f %10.4f %10.4f" % (label, min(m), max(m), max(m) - min(m)))
print()
print("Identical price movement. The MACD values differ by a factor of ~10,000,")
print("because MACD is a price difference and inherits the price's units.")
print()
print("So 'MACD above 2 is strong' is meaningless without saying which")
print("instrument, and a threshold copied from someone else's chart is")
print("copied along with their instrument's price scale. RSI does not have")
print("this problem: it is a ratio, so it is always between 0 and 100.")
You should see: the same market producing MACD values four orders of magnitude apart:
INSTRUMENT MIN MACD MAX MACD RANGE
------------------------------------------------------------
a share at ~100 -3.2272 4.3345 7.5618
an index at ~10,000 -322.7227 433.4538 756.1765
a currency pair at ~1.10 -0.0355 0.0477 0.0832
Identical price movement. The MACD values differ by a factor of ~10,000,
because MACD is a price difference and inherits the price's units.
So 'MACD above 2 is strong' is meaningless without saying which
instrument, and a threshold copied from someone else's chart is
copied along with their instrument's price scale. RSI does not have
this problem: it is a ratio, so it is always between 0 and 100.
The three rows are the identical price series multiplied by a constant — the same percentage moves, the same shape, the same turning points. The MACD values differ by a factor of roughly ten thousand, because MACD is a price difference and carries the price's units with it.
So any rule of the form “enter when MACD exceeds N” is instrument-specific by construction, and a number taken from a video about one market is meaningless on another. RSI does not have this problem because it is a ratio: gains divided by losses cancels the units, which is why 70 means the same thing on every chart. That difference — bounded ratio versus unbounded difference — decides which questions each indicator can answer.
If not: if the three ranges are identical, the scale is not being applied —
the list comprehension must multiply each close before the MACD is computed, not after.
Go: the same folder.
Do: save this as lag.py and run python3 lag.py.
"""Compare like with like: how many bars after the turn does each CROSSOVER fire?"""
closes = [float(line) for line in open("closes.txt")]
def ema(values, period):
k = 2.0 / (period + 1)
out = [None] * (period - 1); out.append(sum(values[:period]) / period)
for v in values[period:]:
out.append(v * k + out[-1] * (1 - k))
return out
def sma(values, period):
return [None if i < period-1 else sum(values[i-period+1:i+1])/period
for i in range(len(values))]
fast, slow = ema(closes, 12), ema(closes, 26)
macd = [None if None in (f, s) else f - s for f, s in zip(fast, slow)]
defined = [v for v in macd if v is not None]
offset = len(macd) - len(defined)
signal = [None] * offset + ema(defined, 9)
TRUE_TURN = 50
def first_cross_down(a, b, start):
"""First bar at or after `start` where series a drops below series b."""
for i in range(max(start, 1), len(a)):
if None in (a[i], b[i], a[i-1], b[i-1]):
continue
if a[i-1] >= b[i-1] and a[i] < b[i]:
return i
return None
CANDIDATES = [
("MA(5) crosses below MA(20)", sma(closes, 5), sma(closes, 20)),
("EMA(12) crosses below EMA(26)", fast, slow),
("MACD crosses below signal", macd, signal),
]
print("the trend actually reverses at bar :", TRUE_TURN)
print()
print("%-32s %8s %10s" % ("CROSSOVER", "AT BAR", "BARS LATE"))
print("-" * 52)
for label, a, b in CANDIDATES:
bar = first_cross_down(a, b, TRUE_TURN)
print("%-32s %8s %10s" % (label, bar, bar - TRUE_TURN if bar else "never"))
print()
print("MACD fires FIRST here, which is the opposite of what 'an average of")
print("averages must be slower' would predict. The reason is worth having:")
print("MACD does not wait for either average to turn down. It measures the")
print("GAP between them, and the gap starts narrowing while both are still")
print("rising -- a change of acceleration, not of direction.")
def count_crosses(a, b):
n = 0
for i in range(1, len(a)):
if None in (a[i], b[i], a[i-1], b[i-1]): continue
if (a[i-1] > b[i-1]) != (a[i] > b[i]): n += 1
return n
print()
print("And the price of firing early, over all 400 bars:")
for label, a, b in CANDIDATES:
print(" %-32s %2d crossings" % (label, count_crosses(a, b)))
print()
print("That is what 'momentum indicator' actually means: acceleration changes")
print("constantly inside a trend that never reverses, so the earliest signal")
print("is also the noisiest. Early and reliable are the same trade-off the")
print("moving-average page measured, seen from another angle.")
You should see: MACD firing first, and producing the most signals:
the trend actually reverses at bar : 50
CROSSOVER AT BAR BARS LATE
----------------------------------------------------
MA(5) crosses below MA(20) 59 9
EMA(12) crosses below EMA(26) 66 16
MACD crosses below signal 52 2
MACD fires FIRST here, which is the opposite of what 'an average of
averages must be slower' would predict. The reason is worth having:
MACD does not wait for either average to turn down. It measures the
GAP between them, and the gap starts narrowing while both are still
rising -- a change of acceleration, not of direction.
And the price of firing early, over all 400 bars:
MA(5) crosses below MA(20) 9 crossings
EMA(12) crosses below EMA(26) 5 crossings
MACD crosses below signal 13 crossings
That is what 'momentum indicator' actually means: acceleration changes
constantly inside a trend that never reverses, so the earliest signal
is also the noisiest. Early and reliable are the same trade-off the
moving-average page measured, seen from another angle.
This result surprised me while writing the page, and the first version of it was wrong. I had assumed — and written — that MACD must be the slowest, since it is built from averages of averages. The measurement said two bars against nine and sixteen, so the prose was wrong, not the code.
The explanation is the useful part. MACD does not wait for an average to turn down. It tracks the gap between two averages, and that gap begins narrowing while both are still rising — a change in acceleration, which necessarily precedes a change in direction. That is what “momentum indicator” means, stated precisely.
And the bottom table is the price. Thirteen crossings against five for the slower pair: the earliest signal is the noisiest one, because acceleration changes constantly inside a trend that never reverses. Early and reliable are two ends of one dial, and every indicator on every chart is a position on it.
If not: if MACD reports never, the signal array is misaligned with the price
bars — the padding in step 2 is what keeps index i meaning bar i in every
series. The exact bar numbers depend on the seeded series and will match this page.
Without scrolling up: a strategy says to buy when the MACD line crosses above its signal line, the histogram turns positive, and MACD is above zero. Its author calls these three independent confirmations. How many conditions is it really, and what would genuine confirmation look like? Answer: the first two are one condition. Step 3 counted them and found they fire on exactly the same bars — thirteen and thirteen — because the histogram is defined as MACD minus signal, so it crosses zero precisely when the lines cross. The third is genuinely different: MACD above zero means EMA(12) is above EMA(26), which is a statement about direction rather than about the crossing, so the strategy has two conditions, not three. Genuine confirmation would come from something computed from a different input entirely — volume, volatility, or the behaviour of a related instrument — because two functions of the same closing prices will agree with each other for reasons that have nothing to do with whether the trade is good. And the danger is specific: stacking correlated conditions increases confidence without increasing accuracy, which is exactly the combination that makes people raise their position size.
Now do it without the page: change the periods in macd.py from 12/26/9 to 5/13/4 and re-run
lag.py. Watch the crossing count rise and the lag fall, then try 24/52/18 and watch
the opposite. Neither is better; you are moving along the dial. Then answer the question the code
raises: for the way you would actually trade — how often you can watch, how much a wrong
signal costs you — which end of that dial do you want, and can you say why in one
sentence?
MACD in MQL5
In MQL5, access MACD through the iMACD() function:
int macdHandle = iMACD(_Symbol, PERIOD_CURRENT, 12, 26, 9, PRICE_CLOSE);
double macdLine[], signalLine[], histogram[];
CopyBuffer(macdHandle, 0, 0, 3, macdLine); // MACD line
CopyBuffer(macdHandle, 1, 0, 3, signalLine); // Signal line
Note that MACD buffer index 0 is the MACD line and index 1 is the signal line. The histogram must be calculated manually (macdLine - signalLine) in MQL5.