Schaff Trend Cycle Momentum Following Strategy

Author: ChaoZhang, Date: 2023-11-01 16:08:35



This strategy is based on the Schaff Trend Cycle indicator, combined with the overbought and oversold principles of the Stoch RSI, to determine and follow trends using momentum metrics. It goes long when the price breaks out of the oversold region into the overbought region, and goes short when the price breaks down from the overbought region into the oversold region. The strategy dynamically adjusts positions by capturing changes in price trends.

Strategy Logic

    1. Calculate the MACD, where the default Fast Length is 23 and Slow Length is 50. MACD reflects the difference between short and long term moving averages to judge price momentum.
    1. Apply Stoch RSI to the MACD to form the K value, where the default Cycle Length is 10, reflecting overbought/oversold levels of the MACD momentum metric.
    1. Take the weighted moving average of K to form D, where the default 1st %D Length is 3, to remove noise from K.
    1. Apply Stoch RSI again to D to form the initial STC value, where the default 2nd %D Length is 3, to create precise overbought/oversold signals.
    1. Take the weighted moving average of the initial STC to get the final STC value, ranging from 0-100. STC above 75 is overbought, below 25 oversold.
    1. Go long when STC crosses above 25 upwards, and short when STC crosses downwards past 75.


    1. STC’s design combining Stoch RSI clearly identifies overbought/oversold regions, forming strong trend signals.
    1. The double Stoch RSI filtering effectively removes false breakouts.
    1. STC’s standardized 0-100 range allows straightforward mechanized trade signals.
    1. The backtest implements visual breakout markings and text popup alerts for clear and intuitive signal capturing.
    1. Optimized default parameters avoid oversensitive signals and needless trades.


    1. STC is parameter sensitive. Different coins and timeframes require parameter tuning to suit market characteristics.
    1. Breakout strategies are prone to traps, requiring stops to control risk.
    1. Low liquidity false breakouts may generate bad signals, needing volume filter.
    1. STC alone risks reversals. Confirmation using other factors is needed.
    1. Key support/resistance levels should be watched to avoid bad signals.

Enhancement Opportunities

    1. Optimize MACD parameters for different periods and coins.
    1. Refine Stoch RSI K and D values to smooth STC curve.
    1. Add volume filter to avoid low liquidity false breakouts.
    1. Incorporate additional indicators to confirm signals, e.g. Bollinger Bands.
    1. Add stop mechanisms like moving/ATR stops.
    1. Adjust entry, e.g. enter on pullback after breakout for trend confirmation.


The Schaff Trend Cycle strategy identifies overbought/oversold via momentum metrics to determine short-term price trend changes. Though simple and adjustable, it risks traps. Confirmation and stops aid optimization for strong trends.

start: 2023-10-01 00:00:00
end: 2023-10-31 00:00:00
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]

// Copyright (c) 2018-present, Alex Orekhov (everget)
// Schaff Trend Cycle script may be freely distributed under the MIT license.
strategy("Schaff Trend Cycle", shorttitle="STC Backtest", overlay=true)

fastLength = input(title="MACD Fast Length",  defval=23)
slowLength = input(title="MACD Slow Length",  defval=50)
cycleLength = input(title="Cycle Length",  defval=10)
d1Length = input(title="1st %D Length",  defval=3)
d2Length = input(title="2nd %D Length",  defval=3)
src = input(title="Source",  defval=close)
highlightBreakouts = input(title="Highlight Breakouts ?", type=bool, defval=true)

macd = ema(src, fastLength) - ema(src, slowLength)

k = nz(fixnan(stoch(macd, macd, macd, cycleLength)))

d = ema(k, d1Length)

kd = nz(fixnan(stoch(d, d, d, cycleLength)))

stc = ema(kd, d2Length)
stc := 	stc > 100 ? 100 : stc < 0 ? 0 : stc

//stcColor = not highlightBreakouts ? (stc > stc[1] ? green : red) : #ff3013
//stcPlot = plot(stc, title="STC", color=stcColor, transp=0)

upper = input(75, defval=75)
lower = input(25, defval=25)

transparent = color(white, 100)

upperLevel = plot(upper, title="Upper", color=gray)
// hline(50, title="Middle", linestyle=dotted)
lowerLevel = plot(lower, title="Lower", color=gray)

fill(upperLevel, lowerLevel, color=#f9cb9c, transp=90)

upperFillColor = stc > upper and highlightBreakouts ? green : transparent
lowerFillColor = stc < lower and highlightBreakouts ? red : transparent

//fill(upperLevel, stcPlot, color=upperFillColor, transp=80)
//fill(lowerLevel, stcPlot, color=lowerFillColor, transp=80)

long =  crossover(stc, lower) ? lower : na
short = crossunder(stc, upper) ? upper : na

long_filt = long and not short
short_filt = short and not long

prev = 0
prev := long_filt ? 1 : short_filt ? -1 : prev[1]

long_final = long_filt and prev[1] == -1
short_final = short_filt and prev[1] == 1

strategy.entry("long", strategy.long, when = long )
strategy.entry("short", strategy.short, when = short)

plotshape(crossover(stc, lower) ? lower : na, title="Crossover", location=location.absolute,, size=size.tiny, color=green, transp=0)
plotshape(crossunder(stc, upper) ? upper : na, title="Crossunder", location=location.absolute,, size=size.tiny, color=red, transp=0)

alertcondition(long_final, "Long", message="Long")
alertcondition(short_final,"Short", message="Short")

plotshape(long_final, style=shape.arrowup, text="Long", color=green, location=location.belowbar)
plotshape(short_final, style=shape.arrowdown, text="Short", color=red, location=location.abovebar)