What it does
The Schaff Trend Cycle (STC), developed by Doug Schaff, is an oscillator that runs a MACD line through a stochastic calculation twice. The result moves between 0 and 100 like a stochastic, but follows the trend-sensitive MACD rather than raw price, and tends to turn earlier than MACD while being smoother than a plain stochastic.
This version colors the line by direction and marks signals when STC crosses up through 25 or down through 75.
How it works
- MACD line = EMA(source, fast) − EMA(source, slow). The defaults of 23 and 50 are commonly quoted for STC.
- First stochastic: where MACD sits within its range over the cycle length, scaled 0–100. If the range is zero, the previous value is kept.
- First smoothing: smoothed = previous + factor × (new − previous), with factor 0.5 by default.
- Second stochastic: the same calculation applied to the smoothed series over the cycle length.
- Second smoothing with the same factor gives the final STC.
Signals, confirmed on bar close:
- Bullish: STC crosses above the lower level (25).
- Bearish: STC crosses below the upper level (75).
The line is green while rising and red while falling. The band between the levels is lightly shaded.
How to use it
- STC spends much of its time pinned near 0 or 100 and then moves quickly between them. The cross out of an extreme is the event of interest, not the extreme itself.
- Many traders only take STC signals in the direction of a slower trend tool, for example a 200-period moving average or the higher-timeframe structure.
- A failed signal, where STC crosses 25 and drops straight back, often appears in ranges and is a reason for caution.
- Works on most liquid markets from M15 to D1. The MACD lesson explains the base calculation.
Settings
| Input | Default | What it changes |
|---|---|---|
| Source | close | Price used for the MACD. |
| MACD fast / slow length | 23 / 50 | Speed of the underlying MACD. |
| Cycle length | 10 | Window for both stochastic passes. Shorter = faster, noisier. |
| Smoothing factor | 0.5 | Lower = smoother and slower. 1 = no smoothing. |
| Lower / upper signal level | 25 / 75 | Signal thresholds. |
| Rising / falling colors | teal / red | Line colors and signal markers. |
Limitations
- Because of the double stochastic, STC can jump from one extreme to the other in a few bars on small price moves, producing whipsaw in ranges.
- In strong trends it can sit at 100 or 0 for long periods and give early counter-trend signals when it dips.
- Small changes in cycle length or factor noticeably change signal timing; settings that look good on one chart rarely transfer unchanged.
- Like every oscillator it is derived from past prices and confirms rather than predicts.
Pine Script source
In TradingView open Pine Editor, paste the code, click Save and then Add to chart.
FXP_SchaffTrendCycle.pine
66 lines
// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/schaff-trend-cycle/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
// Schaff Trend Cycle: a MACD line passed through two smoothed stochastic stages, producing a
// 0–100 oscillator. Signals fire when STC crosses up through 25 or down through 75 (bar close).
indicator("FXP Schaff Trend Cycle", shorttitle = "FXP STC", overlay = false)
// ── Inputs ─────────────────────────────────────────────────────────────
grpS = "Schaff Trend Cycle"
src = input.source(close, "Source", group = grpS)
fastLen = input.int(23, "MACD fast length", minval = 1, group = grpS)
slowLen = input.int(50, "MACD slow length", minval = 2, group = grpS)
cycleLen = input.int(10, "Cycle length", minval = 2, group = grpS)
factor = input.float(0.5, "Smoothing factor", minval = 0.05, maxval = 1.0, step = 0.05, group = grpS, tooltip = "Weight of each new stochastic value in the two smoothing stages (1 = no smoothing).")
grpL = "Levels"
lowLevel = input.float(25.0, "Lower signal level", minval = 0.0, maxval = 50.0, group = grpL)
upLevel = input.float(75.0, "Upper signal level", minval = 50.0, maxval = 100.0, group = grpL)
grpV = "Display"
colUp = input.color(#26a69a, "Rising / bullish", group = grpV)
colDown = input.color(#ef5350, "Falling / bearish", group = grpV)
// ── Stage 1: MACD line ─────────────────────────────────────────────────
macdVal = ta.ema(src, fastLen) - ta.ema(src, slowLen)
// ── Stage 2: stochastic of MACD, smoothed ──────────────────────────────
lo1 = ta.lowest(macdVal, cycleLen)
hi1 = ta.highest(macdVal, cycleLen)
rng1 = hi1 - lo1
var float stoch1 = 0.0
stoch1 := rng1 > 0 ? (macdVal - lo1) / rng1 * 100.0 : stoch1
var float smooth1 = na
smooth1 := na(smooth1) ? stoch1 : smooth1 + factor * (stoch1 - smooth1)
// ── Stage 3: stochastic of the smoothed series, smoothed again ─────────
lo2 = ta.lowest(smooth1, cycleLen)
hi2 = ta.highest(smooth1, cycleLen)
rng2 = hi2 - lo2
var float stoch2 = 0.0
stoch2 := rng2 > 0 ? (smooth1 - lo2) / rng2 * 100.0 : stoch2
var float stc = na
stc := na(stc) ? stoch2 : stc + factor * (stoch2 - stc)
// ── Signals ────────────────────────────────────────────────────────────
stcRising = stc > nz(stc[1])
bullSignal = ta.crossover(stc, lowLevel) and barstate.isconfirmed
bearSignal = ta.crossunder(stc, upLevel) and barstate.isconfirmed
// ── Plots ──────────────────────────────────────────────────────────────
plot(stc, "STC", color = stcRising ? colUp : colDown, linewidth = 2)
hUp = hline(upLevel, "Upper level", color = color.new(colDown, 40), linestyle = hline.style_dashed)
hLow = hline(lowLevel, "Lower level", color = color.new(colUp, 40), linestyle = hline.style_dashed)
fill(hUp, hLow, color = color.new(color.gray, 94), title = "Neutral band")
plotshape(bullSignal, "STC crosses up through lower level", style = shape.triangleup, location = location.bottom, color = colUp, size = size.small)
plotshape(bearSignal, "STC crosses down through upper level", style = shape.triangledown, location = location.top, color = colDown, size = size.small)
// ── Alerts ─────────────────────────────────────────────────────────────
alertcondition(bullSignal, "STC bullish signal", "Schaff Trend Cycle crossed up through the lower level on {{ticker}} ({{interval}})")
alertcondition(bearSignal, "STC bearish signal", "Schaff Trend Cycle crossed down through the upper level on {{ticker}} ({{interval}})")

