Five Python Trading Indicators: Specify Seeds, Warm-Up and Edge Cases

한국어로 읽기

TECH INSIGHT / EXPLAINED

Indicator names do not fully specify their calculation. Seed values, warm-up length, standard-deviation convention and missing bars can change the output even when the visible period is identical.

Three colleagues compare the roles and initialization of five technical indicators.
AI-generated educational illustration. Three colleagues compare the roles and initialization of five technical indicators.

Local execution of the actual project

These captures show the existing project UI or functions executed locally with non-private sample inputs. Each caption states the scope of the execution.

Actual execution of the original LLMStockTrading/lib/indicators.py functions on 48 synthetic prices, displayed in a purpose-built browser report rather than a trading screen. The source uses rolling-mean RSI, ddof=1 bands and first-value EWM seeds, so it differs from the article’s Wilder/ddof=0 example. This verifies calculations, not data cleaning or order execution.
Actual execution of the original LLMStockTrading/lib/indicators.py functions on 48 synthetic prices, displayed in a purpose-built browser report rather than a trading screen. The source uses rolling-mean RSI, ddof=1 bands and first-value EWM seeds, so it differs from the article’s Wilder/ddof=0 example. This verifies calculations, not data cleaning or order execution.
Both original Python and Go functions were actually executed on identical synthetic prices. For period-3 RSI, rising and flat inputs returned NaN in Python and 100 in Go. This report records a boundary-convention difference, not a language-speed benchmark; neither implementation nor output was altered to force agreement.
Both original Python and Go functions were actually executed on identical synthetic prices. For period-3 RSI, rising and flat inputs returned NaN in Python and 100 in Go. This report records a boundary-convention difference, not a language-speed benchmark; neither implementation nor output was altered to force agreement.

Five measurements, five contracts

SMA averages a fixed window; EMA recursively weights recent observations. RSI compares smoothed gains and losses. MACD subtracts two EMAs and compares the result with a signal average. Bollinger Bands add a multiple of standard deviation around an average. ATR smooths true range, including gaps from the previous close. None of these definitions is a trading-profit claim.

For reproducibility, the implementation below uses SMA-seeded Wilder smoothing for RSI and ATR, population deviation (ddof=0) for bands, and first-value-seeded recursive EMA (adjust=False). This EMA convention may differ from libraries using an SMA seed.

import numpy as np
import pandas as pd
def wilder(values, period):
    if not isinstance(period, int) or isinstance(period, bool) or period < 1:
        raise ValueError('positive integer period required')
    values = pd.Series(values, dtype=float)
    if not np.isfinite(values).all():
        raise ValueError('finite contiguous input required')
    out = pd.Series(np.nan, index=values.index, dtype=float)
    if len(values) < period: return out
    out.iloc[period-1] = values.iloc[:period].mean()
    for i in range(period, len(values)):
        out.iloc[i] = (out.iloc[i-1]*(period-1)+values.iloc[i])/period
    return out

def indicators(frame, period=14):
    x = frame[['high','low','close']].astype(float)
    if x.empty or not np.isfinite(x.to_numpy()).all():
        raise ValueError('finite OHLC data required')
    if not ((x.low <= x.close) & (x.close <= x.high) & (x.low > 0)).all():
        raise ValueError('invalid price bounds')
    c = x.close
    out = pd.DataFrame(index=x.index)
    out['sma20'] = c.rolling(20, min_periods=20).mean()
    out['ema20'] = c.ewm(span=20, adjust=False, min_periods=20).mean()
    d = c.diff().iloc[1:]
    g, l = wilder(d.clip(lower=0), period), wilder(-d.clip(upper=0), period)
    rsi = 100*g/(g+l)
    rsi = rsi.mask((g == 0) & (l == 0), 50)  # explicit flat-market convention
    out['rsi'] = rsi.reindex(x.index)
    fast = c.ewm(span=12, adjust=False, min_periods=12).mean()
    slow = c.ewm(span=26, adjust=False, min_periods=26).mean()
    out['macd'] = fast-slow
    out['signal'] = out.macd.ewm(span=9, adjust=False, min_periods=9).mean()
    out['hist'] = out.macd-out.signal
    sd = c.rolling(20, min_periods=20).std(ddof=0)
    out['bb_low'], out['bb_high'] = out.sma20-2*sd, out.sma20+2*sd
    prev = c.shift(1)
    tr = pd.concat([x.high-x.low,(x.high-prev).abs(),(x.low-prev).abs()],axis=1).max(axis=1)
    out['atr'] = wilder(tr.iloc[1:],period).reindex(x.index)
    return out

State the edge cases, not an epsilon

Fourteen RSI changes require fifteen closing observations. With gains but no losses, RSI is 100; with losses but no gains, it is 0. With neither, this example explicitly chooses 50. Another library may choose 0 or unavailable for that flat case, so do not promise a chart match without checking its convention. Replacing every zero loss with 1e-9 hides that choice and changes the scale.

ATR here starts with the first bar that has a previous close; its initial range window therefore also needs one extra bar. The input must already be chronological, unique and complete under your chosen missing-bar policy.

Test before interpreting

For close values 10,11,10,12 and period 3, gains average 1, losses average 1/3 and RSI is 75. A constant close has zero population band width; constant high–low range two gives ATR two after warm-up. Use these hand-checkable cases before comparing a long chart.

A narrow band does not predict the direction or timing of a breakout, and an RSI threshold is not an instruction to buy. Apply completed-bar signals to a later executable price, compare parameter neighborhoods and test costs on held-out data. Several correlated indicators do not provide several independent confirmations.

Sources and further reading

TA-Lib RSI formula · TA-Lib Wilder smoothing · pandas ewm conventions · Bollinger original explanation

Content and examples reviewed: 2026-09-29

댓글