On this page
Stochastic POP Method 2 Classic+Pro - Technical Guide
Stochastic oscillator with classic SMA or zero-lag HMA/ALMA smoothing, trading above 55 and below 45, with crossover signals.
| Language | Indie Script v5 |
| Platform | TakeProfit |
| Category | Oscillators |
| Type | Indicator |
| Author | @insurgent on TakeProfit |
| Original | Original: ChrisMoody (2015), based on Jake Bernstein’s algo, |
| Original (TradingView) | CM Stochastic POP Method 2 - Jake Bernstein by ChrisMoody |
| License | MPL-2.0 (derivative work) |
| Live script | Open on TakeProfit |
| Source file | Stochastic POP Method 2 Classic+Pro.indie5 |
Overview
The indicator calculates a Stochastic oscillator from high, low, and close prices, then smooths it with one of three engines: Classic SMA, Pro HMA, or Pro ALMA. The chart shows the 100/0 bounds, the upper/lower breakout lines, a color-coded Stochastic line, and zone fills. Optionally, candles can be colored Lime/Red/Blue to distinguish long, short, and no-trade regimes. Lime/red circular markers appear at the exact bar where K crosses through the upper or lower level.
How it works
- Calculate raw Stochastic %K with Stoch.new using the configured length.
- Smooth raw Stochastic: SMA in Classic mode, HMA (three WMA passes) in Pro/HMA mode, or ALMA using precomputed Gaussian weights in Pro/ALMA mode.
- Read the previous bar’s smoothed K from _prev_k, stored via new_var/set/get to survive realtime rollback.
- Color the Stochastic line green when k_val >= ul, red when k_val <= ll, blue otherwise.
- If st is enabled, color bars lime (long), red (short), or blue (no trade) based on the same thresholds.
- On show_signals, emit a long marker when prev_k <= ul and k_val > ul, and a short marker when prev_k >= ll and k_val < ll; otherwise return math.nan.
- Store the current k_val into _prev_k for the next bar.
- Return plot lines, fills, markers, and bar color in the order declared by the decorators.
Mathematical model
For a raw Stochastic oscillator of period n, %K is calculated per the Stoch algorithm. The indicator then applies SMA or HMA smoothing:
\[K_{\text{raw}} = 100 \cdot \frac{C - L_n}{H_n - L_n}\]Hull MA is computed as:
\[\text{HMA}(x, n) = \text{WMA}\left( 2 \cdot \text{WMA}(x, \lfloor n/2 \rfloor) - \text{WMA}(x, n), \lfloor \sqrt{n} \rfloor \right)\]ALMA weights are precomputed once per window m and sigma s:
\[w_i = \exp\left( -\frac{(i - m)^2}{2 s^2} \right), \quad m = \text{offset} \cdot (\text{window} - 1), \quad s = \frac{\text{window}}{\text{sigma}}\] \[K_{\text{alma}} = \frac{\sum_{i=0}^{W-1} x_{[W-i-1]} \cdot w_i}{\sum w_i}\]Logic flow
flowchart TD
A["calc: compute raw Stochastic"] --> B{"Mode?"}
B -- "Classic" --> C["k_val = SMA(raw, smooth_k)"]
B -- "Pro + HMA" --> D["k_val = HMA(raw, max(2, smooth_k))"]
B -- "Pro + ALMA" --> E["k_val = ALMA via precomputed weights"]
C --> F["Read prev_k"]
D --> F
E --> F
F --> G{"Color K line by thresholds"}
G --> H{"st enabled?"}
H -- yes --> I{"Color bars lime/red/blue"}
H -- no --> J["bar_col = TRANSPARENT"]
I --> K{"show_signals?"}
J --> K
K -- yes --> L{"Cross over UL or under LL?"}
L -- yes --> M["Marker at ul or ll"]
L -- no --> N["marker = nan"]
K -- no --> N
M --> O["Store k_val in _prev_k"]
N --> O
O --> P["Return plot tuple"]
Parameters
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
length |
int | 14 | ≥ 1 | Stochastic Length |
smooth_k |
int | 5 | ≥ 1 | Smooth K |
mode |
str | Classic | Mode | |
ul |
float | 55.0 | ≥ 50.0 | Buy Entry/Exit Line |
ll |
float | 45.0 | ≤ 50.0 | Sell Entry/Exit Line |
st |
bool | false | Color Bars (Long / Short / NoTrade) | |
show_signals |
bool | true | Show Crossover Signals |
Code walkthrough
Precomputed ALMA weights
Lines 66-81 of Stochastic POP Method 2 Classic+Pro.indie5:
# Precompute ALMA weights once at indicator init.
# Weights depend only on window/offset/sigma — never on per-bar data.
# On a 50K-bar history with window=10 this saves ~500K exp/pow calls.
window = max(2, smooth_k)
self._alma_window = window
self._alma_weights: list[float] = []
self._alma_norm = 1.0
if mode == 'Pro' and pro_algo == 'ALMA':
m = alma_offset * (window - 1)
s = window / alma_sigma
norm_acc = 0.0
for i in range(window):
w = exp(-1 * pow(i - m, 2) / (2 * pow(s, 2)))
self._alma_weights.append(w)
norm_acc += w
self._alma_norm = norm_acc
The ALMA Gaussian weights depend only on the window, offset, and sigma. By computing them once in init and storing them with the indicator instance, the calc loop still gets O(window) multiplications but avoids repeated exp/pow calls. The window is clamped to at least 2 even though smooth_k has a minimum of 1.
Smoothing dispatch
Lines 87-100 of Stochastic POP Method 2 Classic+Pro.indie5:
# k_val is declared above the if-block — Indie's scoping ends with
# indentation, so vars defined inside if/elif don't survive outside.
k_val = 0.0
if self._mode == 'Classic':
k_val = Sma.new(raw_stoch, self._smooth_k)[0]
elif self._pro_algo == 'HMA':
k_val = Hma.new(raw_stoch, max(2, self._smooth_k))[0]
else: # Pro + ALMA — uses precomputed weights from __init__
window = self._alma_window
raw_stoch.request_size(window)
weighted_sum = 0.0
for i in range(window):
weighted_sum += raw_stoch[window - i - 1] * self._alma_weights[i]
k_val = divide(weighted_sum, self._alma_norm)
k_val is declared before the if/elif chain because Indie scoping is indentation-based; variables created inside branches do not survive outside the branch. ALMA indexing pulls the raw Stochastic from the current bar backward: raw_stoch[window-i-1] is multiplied by the precomputed weight for i.
Stateful previous K
Lines 102-133 of Stochastic POP Method 2 Classic+Pro.indie5:
prev_k = self._prev_k.get()
# Stochastic line color.
line_col = color.BLUE
if k_val >= ul:
line_col = color.GREEN
elif k_val <= ll:
line_col = color.RED
# Bar coloring (only when st toggle is on).
bar_col = color.TRANSPARENT
if st:
if k_val >= ul:
bar_col = color.LIME
elif k_val <= ll:
bar_col = color.RED
else:
bar_col = color.BLUE
# Crossover signals — fire on the bar where K crosses through a level.
# Long: K crosses up through UL (entry / short-exit).
# Short: K crosses down through LL (entry / long-exit).
long_signal = nan
short_signal = nan
if show_signals:
if prev_k <= ul and k_val > ul:
long_signal = ul
if prev_k >= ll and k_val < ll:
short_signal = ll
# Persist current K for next bar's crossover comparison.
self._prev_k.set(k_val)
The previous bar’s K value is read from a Var so realtime rollback works, then the current K is stored using set(). This is what prevents crossover signals from repainting or blinking when the last bar is updated during live trading.
Signal and output tuple
Lines 121-146 of Stochastic POP Method 2 Classic+Pro.indie5:
# Crossover signals — fire on the bar where K crosses through a level.
# Long: K crosses up through UL (entry / short-exit).
# Short: K crosses down through LL (entry / long-exit).
long_signal = nan
short_signal = nan
if show_signals:
if prev_k <= ul and k_val > ul:
long_signal = ul
if prev_k >= ll and k_val < ll:
short_signal = ll
# Persist current K for next bar's crossover comparison.
self._prev_k.set(k_val)
return (
plot.Line(ul),
plot.Line(100.0),
plot.Line(ll),
plot.Line(0.0),
plot.Line(k_val, color=line_col),
plot.Fill(),
plot.Fill(),
plot.Fill(),
plot.Marker(long_signal),
plot.Marker(short_signal),
plot.BarColor(color=bar_col),
Crossover signals are computed as the numeric level value, not a boolean. When no crossover occurs, plot.Marker receives math.nan, which tells the chart to draw nothing. The return tuple must contain one plot object per declared decorator in the same order: four lines, three fills, two markers, and one bar color.
Reading the chart
- The Stochastic line is GREEN when k_val >= upper line, RED when k_val <= lower line, BLUE otherwise.
- The upper and lower lines act as trade-state thresholds: above upper means long regime, below lower means short regime, between is no-trade/neutral.
- Zone fills: green above upper, blue between upper and lower, red below lower.
- Lime/Red circular markers at the threshold levels mark the exact bar of a bullish cross above the upper line or bearish cross below the lower line.
- If ‘st’ is enabled, candles are Lime (long), Red (short), or Blue (no trade); if disabled, candles keep their default color because bar_color returns transparent.
Implementation notes
- ALMA weights are computed in init only when mode is Pro and pro_algo is ALMA; switching modes via UI recreates the indicator instance.
- The raw Stochastic passes through a separate smoothing length (smooth_k), so the final line is not the raw %K.
- min/max constraints on ul and ll enforce ul >= 50 and ll <= 50; a user could still set ul very close to ll, leaving a thin neutral band.
- The markers are plotted at the threshold value itself (ul or ll), not at the Stochastic value, so they sit on the level line rather than on the K line.
FAQ
What is the difference between Classic, HMA, and ALMA modes?
Classic applies SMA smoothing to the raw Stochastic. HMA applies a Hull Moving Average which reduces lag. ALMA applies a Gaussian-weighted moving average with configurable offset and sigma to filter noise; ALMA weights are precomputed once for performance.
How can I make the indicator show fewer or more signals?
Change the breakout lines with the ul and ll parameters. Raising ul above 55 requires a stronger bullish momentum to trigger a long signal; lowering ll below 45 makes short signals harder. You can also smooth the Stochastic more heavily by increasing smooth_k.
Why do the markers sometimes not appear at the Stochastic line?
Markers are placed at the threshold value that was crossed (ul or ll), not at the K value. That is why a long marker is drawn at 55 while K could be 55.1 on the same bar.
License and attribution
This Indie script is a derivative work of CM Stochastic POP Method 2 by ChrisMoody on TradingView. This is a port of an open-source TradingView script whose header we could not retrieve; TradingView applies MPL-2.0 by default to open-source scripts, so that license is assumed. A modified version of MPL-2.0 code must stay under MPL-2.0, so this file is distributed under that license (the notice is appended to the end of the source file). The original author keeps the credit for the algorithm; this page is not affiliated with or endorsed by them.
Full source code
Indie Script v5, as published on TakeProfit. Copy it into the platform’s script editor or open the live script.
# indie:lang_version = 5
# CM_Stochastic POP Method 2 — V3 (2026 ultimate)
# Original: ChrisMoody (2015), based on Jake Bernstein's algo,
# V3 changes:
# - ALMA weights precomputed once in __init__ (massive speedup on long history)
# - Crossover signal markers (Long when K crosses up through UL, Short for LL)
# - Class-based Main for stateful initialization
# - Thinner default lines, English comments, ASCII-only marker titles
from math import exp, pow, sqrt, floor, nan
from indie import (
indicator, algorithm, format, param, color, plot,
SeriesF, MutSeriesF, MainContext,
)
from indie.algorithms import Sma, Stoch, Wma
from indie.math import divide
@algorithm
def Hma(self, src: SeriesF, length: int) -> SeriesF:
"""Hull MA: WMA(2*WMA(n/2) - WMA(n)) finished with sqrt(n) WMA pass."""
half = max(1, length // 2)
sqrt_len = max(1, floor(sqrt(length)))
wma_l2 = Wma.new(src, half)[0]
wma_l = Wma.new(src, length)[0]
return Wma.new(MutSeriesF.new(2 * wma_l2 - wma_l), sqrt_len)
@indicator('Stochastic POP Method 2 Classic+Pro', format=format.PRICE)
@param.int('length', default=14, min=1, title='Stochastic Length')
@param.int('smooth_k', default=5, min=1, title='Smooth K')
@param.str('mode', default='Classic', options=['Classic', 'Pro'], title='Mode')
@param.str('pro_algo', default='HMA', options=['HMA', 'ALMA'],
title='Pro: Smoothing Algorithm')
@param.float('alma_offset', default=0.85, min=0.0, max=1.0, step=0.05,
title='Pro/ALMA: Offset (only used when Pro+ALMA)')
@param.float('alma_sigma', default=6.0, min=0.5, step=0.5,
title='Pro/ALMA: Sigma (only used when Pro+ALMA)')
@param.float('ul', default=55.0, min=50.0, title='Buy Entry/Exit Line')
@param.float('ll', default=45.0, max=50.0, title='Sell Entry/Exit Line')
@param.bool('st', default=False, title='Color Bars (Long / Short / NoTrade)')
@param.bool('show_signals', default=True, title='Show Crossover Signals')
@plot.line('upper', line_width=2, color=color.GREEN, title='Upper Line')
@plot.line('top', color=color.GRAY(0.3), title='100 Line')
@plot.line('lower', line_width=2, color=color.RED, title='Lower Line')
@plot.line('bottom', color=color.GRAY(0.3), title='0 Line')
@plot.line('k', line_width=2, title='Stochastic')
@plot.fill('upper', 'top', color=color.GREEN(0.1), title='Long Trade Fill')
@plot.fill('upper', 'lower', color=color.BLUE(0.1), title='No Trade Fill')
@plot.fill('lower', 'bottom', color=color.RED(0.1), title='Short Trade Fill')
@plot.marker(color=color.LIME, style=plot.marker_style.CIRCLE,
position=plot.marker_position.CENTER, size=6, title='Long Signal')
@plot.marker(color=color.RED, style=plot.marker_style.CIRCLE,
position=plot.marker_position.CENTER, size=6, title='Short Signal')
@plot.bar_color(title='Bar Color')
class Main(MainContext):
def __init__(self, smooth_k, mode, pro_algo, alma_offset, alma_sigma):
# Cache mode-related params for fast access in calc().
self._smooth_k = smooth_k
self._mode = mode
self._pro_algo = pro_algo
# Var holds the previous bar's K value for crossover detection.
# Var supports rollback on realtime updates — correct in live mode.
self._prev_k = self.new_var(nan)
# Precompute ALMA weights once at indicator init.
# Weights depend only on window/offset/sigma — never on per-bar data.
# On a 50K-bar history with window=10 this saves ~500K exp/pow calls.
window = max(2, smooth_k)
self._alma_window = window
self._alma_weights: list[float] = []
self._alma_norm = 1.0
if mode == 'Pro' and pro_algo == 'ALMA':
m = alma_offset * (window - 1)
s = window / alma_sigma
norm_acc = 0.0
for i in range(window):
w = exp(-1 * pow(i - m, 2) / (2 * pow(s, 2)))
self._alma_weights.append(w)
norm_acc += w
self._alma_norm = norm_acc
def calc(self, length, ul, ll, st, show_signals):
raw_stoch = Stoch.new(self.close, self.low, self.high, length)
# Smoothing dispatch.
# k_val is declared above the if-block — Indie's scoping ends with
# indentation, so vars defined inside if/elif don't survive outside.
k_val = 0.0
if self._mode == 'Classic':
k_val = Sma.new(raw_stoch, self._smooth_k)[0]
elif self._pro_algo == 'HMA':
k_val = Hma.new(raw_stoch, max(2, self._smooth_k))[0]
else: # Pro + ALMA — uses precomputed weights from __init__
window = self._alma_window
raw_stoch.request_size(window)
weighted_sum = 0.0
for i in range(window):
weighted_sum += raw_stoch[window - i - 1] * self._alma_weights[i]
k_val = divide(weighted_sum, self._alma_norm)
prev_k = self._prev_k.get()
# Stochastic line color.
line_col = color.BLUE
if k_val >= ul:
line_col = color.GREEN
elif k_val <= ll:
line_col = color.RED
# Bar coloring (only when st toggle is on).
bar_col = color.TRANSPARENT
if st:
if k_val >= ul:
bar_col = color.LIME
elif k_val <= ll:
bar_col = color.RED
else:
bar_col = color.BLUE
# Crossover signals — fire on the bar where K crosses through a level.
# Long: K crosses up through UL (entry / short-exit).
# Short: K crosses down through LL (entry / long-exit).
long_signal = nan
short_signal = nan
if show_signals:
if prev_k <= ul and k_val > ul:
long_signal = ul
if prev_k >= ll and k_val < ll:
short_signal = ll
# Persist current K for next bar's crossover comparison.
self._prev_k.set(k_val)
return (
plot.Line(ul),
plot.Line(100.0),
plot.Line(ll),
plot.Line(0.0),
plot.Line(k_val, color=line_col),
plot.Fill(),
plot.Fill(),
plot.Fill(),
plot.Marker(long_signal),
plot.Marker(short_signal),
plot.BarColor(color=bar_col),
)
# ---------------------------------------------------------------------------
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/
# Derived from "CM Stochastic POP Method 2 by ChrisMoody" (TradingView).
# ---------------------------------------------------------------------------