Schaff Trend Cycle on TradingView
The Schaff Trend Cycle (STC) applies two stochastic normalizations and two smoothing steps to the MACD line. It expresses that transformed momentum on a 0 to 100 scale. A reading near an extreme does not prove that price will reverse.
To reproduce an STC indicator, match more than its lengths: smoothing, zero-range behavior, initial values and bar-close confirmation affect the result. The example uses cycle 10, EMA lengths 23 and 50, and smoothing factor 0.5.
The STC formula: normalize, smooth, repeat
Start with the MACD line M, the fast EMA minus the slow EMA. This is the EMA difference itself, not the MACD histogram or a signal-line crossover. Let L be the cycle length, and let lowest and highest include the current bar.
M = EMA(source, fast) − EMA(source, slow)
K1 = 100 × (M − lowest(M, L)) / (highest(M, L) − lowest(M, L))
D1 = previous D1 + 0.5 × (K1 − previous D1)
K2 = 100 × (D1 − lowest(D1, L)) / (highest(D1, L) − lowest(D1, L))
STC = previous STC + 0.5 × (K2 − previous STC)
The fractions apply only when their rolling range is positive. In this implementation a zero range carries the previous K value forward, or uses zero when none exists. The first D1 is seeded from K1; the first STC is seeded from K2. Both smoothers then move halfway toward their current input.
With finite inputs, valid normalization and seeds inside the range, the result stays between 0 and 100. The second normalization acts on D1, not directly on price. A value of 80 therefore does not mean an 80% chance of a rising market.
A nine-bar calculation you can reproduce
This is a synthetic arithmetic example, not market data or a backtest. Supply MACD values 0, 2, 1, 3, 2, 0, 1, 0, 2; use L = 3 and smoothing = 0.5. Start with zero K values and use the available window on the first two bars. The short cycle exposes each stage; it is not a recommended setting.
| Bar | MACD M | K1 | D1 | K2 | STC |
|---|---|---|---|---|---|
| 1 | 0 | 0 | 0 | 0 | 0 |
| 2 | 2 | 100 | 50 | 100 | 50 |
| 3 | 1 | 50 | 50 | 100 | 75 |
| 4 | 3 | 100 | 75 | 100 | 87.5 |
| 5 | 2 | 50 | 62.5 | 50 | 68.75 |
| 6 | 0 | 0 | 31.25 | 0 | 34.375 |
| 7 | 1 | 50 | 40.625 | 30 | 32.1875 |
| 8 | 0 | 0 | 20.3125 | 0 | 16.0938 |
| 9 | 2 | 100 | 60.1563 | 100 | 58.0469 |
At bar 7, M is 1 within the three-bar range [0, 2], so K1 = 50. D1 = 31.25 + 0.5 × (50 − 31.25) = 40.625. The D1 window is [62.5, 31.25, 40.625], giving K2 = 100 × 9.375 / 31.25 = 30. Finally, STC = 34.375 + 0.5 × (30 − 34.375) = 32.1875.
The return of M to 2 at bar 9 does not reproduce its bar-2 STC value: the rolling windows and recursive state differ. This is why a single current MACD value cannot determine STC.
Settings, initialization and implementation variants
| Input | Example value | Effect or constraint |
|---|---|---|
| Source | Close | Changing the price input changes both EMAs. Keep it fixed when comparing scripts. |
| Cycle length | 10 bars | Used for both rolling ranges; the example requires at least 2 bars. |
| Fast / slow EMA | 23 / 50 bars | Fast must be shorter than slow. These are bar counts, not calendar durations. |
| Smoothing factor | 0.5, fixed | Both recursive smoothers move halfway toward each new input. |
| Lower / upper level | 25 / 75 | Editable reference levels; lower must be below upper. |
| Initial bars without signals | 100 | Suppresses markers and alerts for bar_index 0 through 99, while calculation continues. |
The 100-bar gate is an example policy, not proof of convergence. EMAs and recursive smoothers depend on their starting state, and early rolling windows may be incomplete. Compare recent closed values after loading more history; if they still move materially, extend the comparison history before evaluating signals.
For timeframe comparisons, 50 hourly bars represent 50 chart hours, whereas 50 daily bars represent 50 daily observations. Shorter lengths respond to a shorter history but may produce more alternating threshold events. Choose a test interval, hold the other rules fixed and compare nearby settings. There is no supported universal schedule of best settings for scalping, swing or position trading.
The LEAN STC implementation uses two smoothing stages and defaults to cycle 10, fast 23 and slow 50. Its zero-range stochastic result is zero, while this page preserves the previous K value. Its initialization also follows its own indicator classes. A 0.5 recurrence has the coefficient of an EMA of length 3, but matching that coefficient alone does not match seeding or zero-range behavior.
Threshold states are different from confirmed crossings
| Condition | What it records | What it does not establish |
|---|---|---|
| STC below 25 | A low oscillator state that can persist over many bars. | A price bottom or an immediate entry. |
| Previous STC ≤ 25; current STC > 25 | A crossing above the lower level. | That price will keep rising. |
| STC above 75 | A high oscillator state. | An automatic short signal. |
| Previous STC ≥ 75; current STC < 75 | A crossing below the upper level. | That the broader trend has reversed. |
Range normalization discards the absolute size of MACD. A small fluctuation inside a narrow range can travel from one oscillator extreme to the other. Sideways price can therefore cause repeated crossings; a persistent trend can hold STC near an extreme. Neither failure is fixed by calling the levels overbought or oversold.
On an open chart bar, the changing source price can move STC across a level and back again. The code evaluates crossings on every bar, then requires barstate.isconfirmed for markers and alerts. TradingView documents this as the closing update of a realtime bar. The plotted line still moves before close. This rule is for the chart timeframe; it is not a solution for confirmation inside request.security().
Before converting a crossing into a strategy, specify order timing, position sizing, exits, loss limits, fees and slippage. If adding a price trend filter, test its extra rule separately. These indicator events provide no evidence of expected profitability.
Pine Script v6: STC with confirmed threshold events
Paste this complete script into the TradingView Pine Editor and add it to a chart. It retains Pineify's STC calculation and puts the oscillator in a separate pane. Blue lower-level markers and orange upper-level markers appear only after the warmup gate and bar close.
// This source code is subject to the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//@version=6
indicator("Schaff Trend Cycle: confirmed thresholds", overlay=false)
source = input.source(close, "Source")
length = input.int(10, "Cycle length", minval=2)
fastLength = input.int(23, "Fast EMA length", minval=1)
slowLength = input.int(50, "Slow EMA length", minval=2)
lowerLevel = input.float(25.0, "Lower level", minval=0, maxval=100)
upperLevel = input.float(75.0, "Upper level", minval=0, maxval=100)
warmupBars = input.int(100, "Initial bars without signals", minval=0)
if fastLength >= slowLength
runtime.error("Fast EMA length must be smaller than slow EMA length.")
if lowerLevel >= upperLevel
runtime.error("Lower level must be smaller than upper level.")
p_ta_stc(series float source, simple int length, simple int fast_length, simple int slow_length) =>
factor = 0.5
m = ta.ema(source, fast_length) - ta.ema(source, slow_length)
v1 = ta.lowest(m, length)
v2 = ta.highest(m, length) - v1
float f1 = 0
f1 := v2 > 0 ? ((m - v1) / v2) * 100 : nz(f1[1])
float pf = 0
pf := na(pf[1]) ? f1 : pf[1] + (factor * (f1 - pf[1]))
v3 = ta.lowest(pf, length)
v4 = ta.highest(pf, length) - v3
float f2 = 0
f2 := v4 > 0 ? ((pf - v3) / v4) * 100 : nz(f2[1])
float pff = 0
pff := na(pff[1]) ? f2 : pff[1] + (factor * (f2 - pff[1]))
pff
stc = p_ta_stc(source, length, fastLength, slowLength)
plot(stc, "STC", color=color.blue, linewidth=2)
hline(lowerLevel, "Lower level", color=color.gray)
hline(upperLevel, "Upper level", color=color.gray)
// Compute crosses every bar. The gate suppresses signals, not calculations.
crossUp = ta.crossover(stc, lowerLevel)
crossDown = ta.crossunder(stc, upperLevel)
ready = bar_index >= warmupBars
confirmedUp = ready and crossUp and barstate.isconfirmed
confirmedDown = ready and crossDown and barstate.isconfirmed
plotshape(confirmedUp, "Confirmed lower-level cross", shape.triangleup, location.bottom, color.blue, size=size.tiny)
plotshape(confirmedDown, "Confirmed upper-level cross", shape.triangledown, location.top, color.orange, size=size.tiny)
alertcondition(confirmedUp, "STC crossed above lower level", "STC crossed above its lower level on a closed bar.")
alertcondition(confirmedDown, "STC crossed below upper level", "STC crossed below its upper level on a closed bar.")To receive notifications, create an alert from the relevant script condition and select Once Per Bar Close. The script is an indicator, so its markers do not place or backtest orders. Historical markers represent calculation events, not simulated fills.
The formula runs on every bar, including the first 100 bars. Gating the function call itself would interrupt its history. The function name p_ta_stc is defined in this example; it is not a Pine built-in that works without its definition.
Check a custom STC script with Pineify
Give the Pine Script AI Coding Agent the complete calculation contract: source, EMA lengths, both cycle windows, smoothing factor, zero-range fallback, seed behavior and confirmation timing. Request a review of the generated code before adding trading rules.
- Start with the 10/23/50 example and state that both smoothers use 0.5.
- Preserve the previous K value on a zero range and seed the smoothers from their first K values.
- Specify that the warmup gate suppresses signals while calculations continue.
- Compare the same completed bars on the same symbol, feed, timeframe and session. Inspect intermediate M, K1, D1 and K2 values if the outputs disagree.
- Define exits and costs separately before requesting a strategy. Syntax validation does not validate a trading hypothesis.
Schaff Trend Cycle questions
Sources and related indicators
References checked October 17, 2026. The numerical example is synthetic and calculated from the formula shown here. This is educational material, not investment advice.
- QuantConnect LEAN: Schaff Trend Cycle implementation
- QuantConnect: STC initialization and indicator history
- TradingView: bar states and close confirmation
- TradingView: historical and realtime execution
Double smoothing of signed and absolute price changes.
Commodity Channel IndexPrice deviation from its mean on an unbounded scale.