Trend indicators: SMA, EMA, WMA

Every moving average answers the same question — "what's the recent average price?" — with a different weighting scheme for how much recent bars matter versus older ones.

SMA — Simple Moving Average, equal weight to every bar

def sma(series: pd.Series, period: int) -> pd.Series:
    return series.rolling(period).mean()

Smooth, lags the most (every bar in the window counts equally, so a sharp recent move takes period bars to fully show up).

EMA — Exponential Moving Average, more weight to recent bars

def ema(series: pd.Series, period: int) -> pd.Series:
    return series.ewm(span=period, adjust=False).mean()

adjust=False matters: it makes the recursive formula EMA_t = price_t * k + EMA_{t-1} * (1-k) (with k = 2/(period+1)) match what every charting platform and indicator library actually computes — adjust=True (pandas' default) uses a different weighting in the early part of the series and will silently disagree with TradingView/broker charts for the first ~period bars.

WMA — Weighted Moving Average, linear weight ramp

def wma(series: pd.Series, period: int) -> pd.Series:
    weights = pd.Series(range(1, period + 1))
    return series.rolling(period).apply(lambda x: (x * weights.values).sum() / weights.sum(), raw=True)

Weights recent bars more than SMA but less aggressively than EMA's exponential decay — a middle ground, less commonly used alone but common as a building block in other indicators (e.g. Hull Moving Average).

Choosing period and type — no universal right answer

Use caseTypical choice
Long-term trend filterSMA 200 or EMA 200
Medium-term trendEMA 50
Fast reaction, scalping/intradayEMA 9-21
Crossover systems (chapter 113)fast EMA (9-20) vs slow EMA (50-100)

EMA is generally preferred over SMA when you want faster reaction to recent price action (most crossover strategies); SMA is preferred when you specifically want the smoothing effect of equal-weighting (less whipsaw in choppy markets, at the cost of more lag).

Verify against the broker's own chart before trusting your calculation

def sanity_check_ema(df: pd.DataFrame, period: int, broker_chart_value: float, tolerance: float = 0.5):
    computed = ema(df["close"], period).iloc[-1]
    assert abs(computed - broker_chart_value) < tolerance, f"EMA mismatch: {computed} vs {broker_chart_value}"

A subtly wrong adjust flag or an off-by-one in your rolling window can produce values close enough to look "roughly right" while being systematically off — always cross-check a few known values against Kite's own chart or another trusted source before building a strategy on top of a hand-rolled indicator.

Next: 102 — MACD