How Does the Stochastic Indicator Work? A Beginner's Guide to Overbought & Oversold Signals in Pine Script v6
The Stochastic Oscillator is one of the most widely used momentum indicators in technical analysis, designed to measure where the current closing price sits relative to the high-low range over a specified lookback period. By normalizing price into a 0–100 scale, it provides a mathematically consistent framework for identifying when an asset may be statistically extended — either to the upside (overbought) or downside (oversold). This guide walks through the mathematics, the Pine Script v6 implementation, and the key parameters that govern its behavior.
1. The Mathematics Behind the Stochastic Oscillator
The Stochastic Oscillator was developed by George Lane and is built on a straightforward normalization formula. The core value, called %K, answers the question: "Where does today's close fall within the recent price range?"
The formula for %K is:
$$\%K = \frac{\text{Close} - \text{Lowest Low}(n)}{\text{Highest High}(n) - \text{Lowest Low}(n)} \times 100$$
Where n is the lookback period (commonly 14 bars). The result is then smoothed using a Simple Moving Average to produce %D:
$$\%D = \text{SMA}(\%K, m)$$
Where m is the smoothing period (commonly 3 bars). The %D line acts as a signal line, and crossovers between %K and %D are often used as entry/exit cues.
2. Key Parameters Explained
Understanding each parameter is essential before writing any code. The table below summarizes the three core inputs:
| Parameter | Common Default | Role |
|---|---|---|
| %K Length (n) | 14 | Lookback window for Highest High and Lowest Low |
| %K Smoothing (d) | 3 | SMA applied to raw %K to produce the smooth %K line |
| %D Smoothing (m) | 3 | SMA applied to smooth %K to produce the signal (%D) line |
Note that TradingView's built-in Stochastic applies a first smoothing pass to raw %K (producing "Smooth %K"), and then a second smoothing pass to produce %D. This is the "Slow Stochastic" variant. The "Fast Stochastic" skips the first smoothing pass.
3. Logic Flow Diagram
The diagram below illustrates the data flow from raw price to the final overbought/oversold signal:
4. Pine Script v6 Implementation
The following script implements the Stochastic Oscillator from scratch in Pine Script v6, using ta.highest(), ta.lowest(), and ta.sma() — all of which natively accept series int lengths, making them compatible with user-defined input lengths. Reference lines at 80 and 20 are drawn using hline(), which plots constant horizontal lines in the indicator's own pane.
🔽 [Click to expand] View Full Pine Script Code
//@version=6
indicator(
title = "Stochastic Oscillator — Beginner Guide",
shorttitle = "Stoch Guide",
overlay = false, // Renders in a separate pane below the chart
format = format.price,
precision = 2
)
// ─────────────────────────────────────────────
// INPUTS
// ─────────────────────────────────────────────
int kLength = input.int(14, title = "%K Length", minval = 1)
int kSmooth = input.int(3, title = "%K Smoothing", minval = 1)
int dSmooth = input.int(3, title = "%D Smoothing", minval = 1)
float obLevel = input.float(80.0, title = "Overbought Level")
float osLevel = input.float(20.0, title = "Oversold Level")
// ─────────────────────────────────────────────
// CORE CALCULATION
// ─────────────────────────────────────────────
// Step 1: Find the highest high and lowest low over kLength bars.
// ta.highest() and ta.lowest() accept series int lengths — fully legal.
float highestHigh = ta.highest(high, kLength)
float lowestLow = ta.lowest(low, kLength)
// Step 2: Calculate raw %K — normalize close within the range.
// Guard against division by zero when highestHigh == lowestLow (flat market).
float range_hl = highestHigh - lowestLow
float rawK = range_hl != 0.0 ? (close - lowestLow) / range_hl * 100.0 : na
// Step 3: Smooth raw %K with an SMA to produce the "Slow %K" line.
// ta.sma() accepts series int — kSmooth is input int (≤ series), fully legal.
float smoothK = ta.sma(rawK, kSmooth)
// Step 4: Smooth the Slow %K again to produce the %D signal line.
float signalD = ta.sma(smoothK, dSmooth)
// ─────────────────────────────────────────────
// OVERBOUGHT / OVERSOLD STATE
// ─────────────────────────────────────────────
bool isOverbought = smoothK >= obLevel
bool isOversold = smoothK <= osLevel
// ─────────────────────────────────────────────
// PLOTS
// ─────────────────────────────────────────────
// Plot the Slow %K line
plot(
smoothK,
title = "%K",
color = color.new(color.blue, 0),
linewidth = 2
)
// Plot the %D signal line
plot(
signalD,
title = "%D",
color = color.new(color.orange, 0),
linewidth = 1
)
// ─────────────────────────────────────────────
// REFERENCE LINES via hline()
// hline() draws constant horizontal lines in this indicator's own pane.
// Since overlay = false, these lines appear in the separate oscillator pane
// and do NOT affect the main chart's price scale.
// ─────────────────────────────────────────────
hline(obLevel, title = "Overbought", color = color.red, linestyle = hline.style_dashed, linewidth = 1)
hline(osLevel, title = "Oversold", color = color.green, linestyle = hline.style_dashed, linewidth = 1)
hline(50, title = "Midline", color = color.gray, linestyle = hline.style_dotted, linewidth = 1)
// ─────────────────────────────────────────────
// BACKGROUND SHADING for overbought/oversold zones
// ─────────────────────────────────────────────
bgcolor(
isOverbought ? color.new(color.red, 88) :
isOversold ? color.new(color.green, 88) :
na,
title = "Zone Background"
)
// ─────────────────────────────────────────────
// CROSSOVER SIGNALS
// ─────────────────────────────────────────────
// Bullish crossover: %K crosses above %D
bool bullCross = ta.crossover(smoothK, signalD)
// Bearish crossover: %K crosses under %D
bool bearCross = ta.crossunder(smoothK, signalD)
// Plot shapes only when crossover occurs inside the relevant zone
plotshape(
bullCross and isOversold ? smoothK : na,
title = "Bull Cross",
style = shape.triangleup,
location = location.absolute,
color = color.green,
size = size.small
)
plotshape(
bearCross and isOverbought ? smoothK : na,
title = "Bear Cross",
style = shape.triangledown,
location = location.absolute,
color = color.red,
size = size.small
)
5. Understanding the Division-by-Zero Guard
A critical edge case occurs when the market is perfectly flat over the lookback window — meaning highestHigh == lowestLow. In this scenario, the denominator of the %K formula equals zero. The script handles this explicitly:
float range_hl = highestHigh - lowestLow
float rawK = range_hl != 0.0 ? (close - lowestLow) / range_hl * 100.0 : na
When range_hl is zero, rawK is assigned na. This causes ta.sma(rawK, kSmooth) to also return na for that bar, which in turn causes plot() to break the line — visually indicating missing data rather than drawing a misleading value. This is the correct behavior per Pine Script's na propagation rules.
6. Why ta.sma() Works Here (Type Qualifier Analysis)
A common source of confusion in Pine Script v6 is the type qualifier system. The table below clarifies why ta.sma() is the correct choice for this script, while ta.ema() would require a different approach:
| Function | Length Qualifier Required | input.int() Compatible? | Dynamic (series int) Compatible? |
|---|---|---|---|
ta.sma() |
series int | ✅ Yes | ✅ Yes |
ta.highest() |
series int | ✅ Yes | ✅ Yes |
ta.lowest() |
series int | ✅ Yes | ✅ Yes |
ta.ema() |
simple int | ✅ Yes | ❌ No — compile error |
ta.rsi() |
simple int | ✅ Yes | ❌ No — compile error |
In this script, kLength, kSmooth, and dSmooth are declared via input.int(), which returns an input int qualifier. Since input int is narrower than (and therefore satisfies) series int, all three ta.sma(), ta.highest(), and ta.lowest() calls are fully legal with no compile errors.
7. Interpreting the Signals
The Stochastic Oscillator produces two primary categories of signals, both grounded in the mathematical normalization of price:
- Overbought Zone (above 80): The closing price is near the top of its recent range. The background turns red in the script. This does not guarantee a reversal — it indicates statistical extension.
- Oversold Zone (below 20): The closing price is near the bottom of its recent range. The background turns green. Again, this is a statistical observation, not a directional prediction.
- %K / %D Crossovers: When %K crosses above %D inside the oversold zone, a bullish triangle is plotted. When %K crosses below %D inside the overbought zone, a bearish triangle is plotted. These crossovers are the most commonly referenced signal events.
8. Conclusion
- The Stochastic Oscillator normalizes the closing price within the recent high-low range into a 0–100 scale using the formula $\%K = \frac{\text{Close} - \text{LL}(n)}{\text{HH}(n) - \text{LL}(n)} \times 100$, making it directly comparable across different assets and timeframes.
- In Pine Script v6,
ta.sma(),ta.highest(), andta.lowest()all acceptseries intlengths, making them fully compatible withinput.int()user inputs. A division-by-zero guard usingnais essential for flat-market edge cases. - The
hline()function draws constant horizontal reference lines in the indicator's own separate pane (sinceoverlay = false), and does not interfere with the main chart's price scale.
Ideas for Further Development
- Multi-Timeframe Stochastic: Use
request.security()withbarmerge.lookahead_onandclose[1]to fetch a higher-timeframe Stochastic value and overlay it as a confirmation filter on the current chart's oscillator pane. - Adaptive Length via ATR: Replace the fixed
kLengthwith a dynamically computed length based onta.atr()volatility, usingta.sma()(which acceptsseries int) to adapt the lookback window to current market conditions.
Comments
Post a Comment