Trading Indicators in Python: Moving Averages, RSI, MACD, Bollinger Bands, and ATR

🌐 한국어

You don't need twenty indicators in a bot. What actually survives in the code is usually fewer than five. This article covers those five — moving averages, RSI, MACD, Bollinger Bands, and ATR — as one line of meaning plus the Python that computes it.

⚠️ This article is for educational and informational purposes only. No indicator guarantees a profit, and any losses are your own responsibility.

All the code below assumes a pandas DataFrame named df with high, low, and close columns.

1. Moving Average (MA) — Which Way the Trend Points

The average price over the last N periods. Its job is to erase the jitter so you can see whether the flow is currently upward, and nothing more.

df["sma20"] = df["close"].rolling(20).mean()       # simple moving average

df["ema20"] = df["close"].ewm(span=20).mean()      # exponential, weights recent bars

The simple version (SMA) treats every period equally; the exponential version (EMA) weights recent periods more heavily. Want responsiveness, use EMA. Want stability, use SMA.

Common misreading: a moving average is not a forecasting tool, it's a smoothing tool. It always moves later than price does. The lag isn't a defect — it's the mechanism.

2. RSI — The Ratio of Recent Gains to Recent Losses

RSI (Relative Strength Index) is a 0–100 value expressing the ratio between how much price has risen and how much it has fallen recently. Above 70 is conventionally called overbought, below 30 oversold.

def rsi(close, period=14):

    delta = close.diff()                       # change vs. previous bar

    gain  = delta.clip(lower=0)                # size of up moves

    loss  = -delta.clip(upper=0)               # size of down moves (positive)

    # Wilder smoothing — the standard RSI definition

    avg_gain = gain.ewm(alpha=1/period, adjust=False).mean()

    avg_loss = loss.ewm(alpha=1/period, adjust=False).mean()

    rs = avg_gain / avg_loss.replace(0, 1e-9)  # guard against divide-by-zero

    return 100 - (100 / (1 + rs))

df["rsi14"] = rsi(df["close"])

Plenty of examples online compute this with rolling().mean(), which produces values subtly different from the original definition. If you want your numbers to line up with your charting software, use Wilder smoothing as above.

Common misreading: "RSI below 30 = buy signal" is false. In a strong downtrend, RSI will sit in the twenties for weeks. Counter-trend entries need a trend filter alongside them.

3. MACD — The Gap Between Two Moving Averages

A plot of the difference between a fast EMA and a slow EMA. Widening gap reads as a strengthening trend; narrowing gap as one running out of steam.

def macd(close, fast=12, slow=26, signal=9):

    ema_fast = close.ewm(span=fast).mean()

    ema_slow = close.ewm(span=slow).mean()

    macd_line   = ema_fast - ema_slow           # MACD line

    signal_line = macd_line.ewm(span=signal).mean()

    hist        = macd_line - signal_line       # histogram

    return macd_line, signal_line, hist

df["macd"], df["signal"], df["hist"] = macd(df["close"])

# Golden cross: below yesterday, above today

buy = (df["macd"] > df["signal"]) & (df["macd"].shift(1) <= df["signal"].shift(1))

Because MACD is built on moving averages, signals explode in a sideways market. In a range with no direction, crossovers fire continuously and all you accumulate is fees. That's exactly why people pair it with a volatility filter.

4. Bollinger Bands — How Far From the Mean Are We

Lines drawn two standard deviations above and below a moving average. They tell you how statistically stretched the current price is.

def bollinger(close, period=20, k=2):

    mid = close.rolling(period).mean()

    std = close.rolling(period).std()

    return mid - k * std, mid, mid + k * std

df["bb_low"], df["bb_mid"], df["bb_up"] = bollinger(df["close"])

# Band width — narrow means volatility contraction (a squeeze)

df["bb_width"] = (df["bb_up"] - df["bb_low"]) / df["bb_mid"]

In practice the more useful reading isn't a touch of the upper or lower band but the band width. Large moves tend to follow extreme contraction, which makes width a solid entry-timing filter for a strategy.

5. ATR — How Much This Instrument Normally Swings

The most underrated indicator on the list. It says nothing about direction and only reports the size of the movement. You use it to size stops and positions.

def atr(df, period=14):

    prev_close = df["close"].shift(1)

    tr = pd.concat([

        df["high"] - df["low"],                # today's high-low range

        (df["high"] - prev_close).abs(),       # accounts for gap up

        (df["low"]  - prev_close).abs(),       # accounts for gap down

    ], axis=1).max(axis=1)

    return tr.ewm(alpha=1/period, adjust=False).mean()

df["atr14"] = atr(df)

# Set the stop in ATR terms (width adapts per instrument automatically)

entry = df["close"].iloc[-1]

stop  = entry - 2 * df["atr14"].iloc[-1]

Use a fixed percentage like "3% stop" and an instrument that moves 5% a day will shake you out the moment you enter. An ATR-based stop adjusts its width to the character of the instrument on its own.

Principles for Using Indicators

  • Don't stack indicators that do the same job — a MACD crossover and a moving-average crossover are effectively telling you the same thing twice.
  • Combine one trend indicator with one volatility indicator — one for direction (MA/MACD), one for magnitude (ATR/band width). That's the practical pairing.
  • Don't over-tune parameters — if 14 makes twice the profit that 13 does, that isn't a discovery, it's overfitting.
  • Apply signals one bar later — a backtest that generates a signal from today's close and fills at today's close does not exist in reality.

Summary

  • MA — direction; lagging is normal
  • RSI — ratio of up-force to down-force, computed with Wilder smoothing
  • MACD — the gap between two EMAs; fragile in ranges
  • Bollingerband width is more useful than band breaks
  • ATR — the basis for stop distance and position size

Knowing many indicators matters far less than being able to explain why a given indicator is in your system. If you can't explain it, you're usually better off removing it.

댓글

이 블로그의 인기 게시물

한국투자증권 KIS API로 실시간 시세 받기 (WebSocket 실전)

파이썬으로 업비트 API 연동하기 — 시세 조회부터 주문까지 기초

Go로 자동매매 신호봇 프레임워크 설계하기