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 case | Typical choice |
|---|---|
| Long-term trend filter | SMA 200 or EMA 200 |
| Medium-term trend | EMA 50 |
| Fast reaction, scalping/intraday | EMA 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