The Stochastic Oscillator is just “where’s the close within the recent range,” as the explainer showed, so in pandas it’s a handful of rolling operations. The one thing to get right is the divide-by-zero when the range is flat.
What you’ll need
- Python 3.9+,
pandas(pip install pandas) high,low,closeseries.
Fast and slow stochastic
import numpy as np
import pandas as pd
def stochastic(high, low, close, k=14, d=3, smooth=3, slow=True):
lowest = low.rolling(k).min()
highest = high.rolling(k).max()
rng = (highest - lowest).replace(0, np.nan) # guard flat-range div-by-zero
fast_k = 100 * (close - lowest) / rng
if slow:
percent_k = fast_k.rolling(smooth).mean() # slow %K
else:
percent_k = fast_k # fast %K
percent_d = percent_k.rolling(d).mean() # %D signal
return pd.DataFrame({"k": percent_k, "d": percent_d})
Set slow=True (the default) for the popular 14, 3, 3 slow stochastic — the blue
and orange lines in the chart above; slow=False gives the raw fast version.
The %K/%D cross signal
s = stochastic(df["high"], df["low"], df["close"])
below20 = s["k"] < 20
bull = (s["k"] > s["d"]) & (s["k"].shift(1) <= s["d"].shift(1)) & below20.shift(1)
bull flags %K crossing up through %D while coming out of oversold — a classic
setup to backtest in AlgoGen rather than trust blind.
Gotchas
- Divide-by-zero. When the last N bars are perfectly flat,
highest == lowestand the formula divides by zero.replace(0, np.nan)turns those bars into NaN instead ofinf— the correct, honest result. - Fast vs slow. The difference is one extra smoothing of %K. Quoting “14,3,3” almost always means the slow version.
- Range uses highs and lows, not closes. A common bug is computing the range
from closes; use the actual
highandlow.
Fast, slow, and “full”
The three names you’ll see are the same code with different smoothing:
- Fast — raw %K,
%D = SMA(%K, 3). Callstochastic(..., slow=False). - Slow — smooth %K once, then
%D = SMA(slow %K, 3). The 14,3,3 default. - Full — the general case where the %K lookback, the %K smoothing, and the %D
length are all independent parameters. That’s exactly the
(k, smooth, d)signature above, so our one function covers all three by choice of arguments.
Knowing they’re one formula with different knobs saves you from installing three “different” indicators that are really the same arithmetic.
The lazy one-liner
import pandas_ta as ta
df.ta.stoch(k=14, d=3, smooth_k=3, append=True) # adds STOCHk/STOCHd columns
Same indicator elsewhere: MQL5, Pine Script, EasyLanguage, NinjaScript.
This post is educational, not financial advice. Indicators describe the past; they don’t predict the future. Backtest anything before you risk real money on it.
Sources and further reading
- The Origins of the Stochastic Oscillator (CMT Association)
- Windowing operations (pandas documentation)
