Skip to content

Strategy Examples

EMA Cross

Enter long when the fast EMA crosses above the slow EMA, exit on cross under.

ts
strategy('EMA Cross', { overlay: true })

const fast = ta.ema(close, input.int('fast', 9, 'Fast Period'))
const slow = ta.ema(close, input.int('slow', 21, 'Slow Period'))

plot(fast, 'Fast', '#26A69A')
plot(slow, 'Slow', '#EF5350')

if (ta.crossover(fast, slow))  strategy.entry('Long', 'long')
if (ta.crossunder(fast, slow)) strategy.close('Long')

How it works: ta.crossover returns true on the bar where the fast EMA crosses above the slow EMA. strategy.entry opens a long position. strategy.close exits when the fast crosses back below.


RSI Overbought/Oversold with SL/TP

Mean-reversion strategy: enter long when RSI is oversold, set stop-loss and take-profit based on ATR.

ts
strategy('RSI Mean Reversion', { overlay: true })

const rsiLen = input.int('rsiLen', 14, 'RSI Length')
const slMult = input.float('slMult', 2.0, 'SL Multiplier')
const tpMult = input.float('tpMult', 3.0, 'TP Multiplier')

const rsi = ta.rsi(close, rsiLen)
const atr = ta.atr(14)

// Enter long when oversold
if (rsi < 30 && strategy.position_size === 0) {
  strategy.entry('Long', 'long', {
    sl: close - atr * slMult,
    tp: close + atr * tpMult,
  })
}

// Exit when overbought
if (rsi > 70) strategy.close('Long')

How it works: When RSI drops below 30 and no position is open, a long entry is placed with ATR-based stop-loss and take-profit. The position is also closed if RSI reaches overbought territory. Backtest stats (win rate, PnL, Sharpe, etc.) are printed after execution.


Bollinger Breakout

Enter long on upper band breakout, enter short on lower band breakdown.

ts
strategy('Bollinger Breakout', { overlay: true })

const len = input.int('length', 20, 'BB Length')
const mult = input.float('mult', 2.0, 'StdDev')

const bb = ta.bollinger(close, len, mult)
plot(bb.upper, 'Upper', '#f44336')
plot(bb.lower, 'Lower', '#4caf50')
fill(bb.upper, bb.lower, 'rgba(33,150,243,0.05)')

if (close > bb.upper) strategy.entry('Long', 'long')
if (close < bb.lower) strategy.entry('Short', 'short')

// Exit on mean reversion
if (strategy.position_size !== 0 && close > bb.middle && close < bb.upper) {
  strategy.close()
}

How it works: When price breaks above the upper Bollinger Band, a long position opens. Below the lower band, a short opens (automatically closing the long). Positions close when price returns to the middle band area.


Dual Stochastic Filter

Use slow stochastic for trend direction, fast stochastic for entries.

ts
strategy('Stochastic Filter', { overlay: false })

const slowStoch = ta.stochastic(21, 7, 7)
const fastStoch = ta.stochastic(14, 3, 3)

plot(fastStoch.k, 'Fast %K', '#2962ff')
plot(fastStoch.d, 'Fast %D', '#ff6d00')
hline(80, 'OB', '#EF5350')
hline(20, 'OS', '#26A69A')

// Long: slow uptrend + fast oversold cross
if (slowStoch.k > 50 && ta.crossover(fastStoch.k, fastStoch.d) && fastStoch.k < 30) {
  strategy.entry('Long', 'long')
}

// Short: slow downtrend + fast overbought cross
if (slowStoch.k < 50 && ta.crossunder(fastStoch.k, fastStoch.d) && fastStoch.k > 70) {
  strategy.entry('Short', 'short')
}

How it works: The slow stochastic filters for trend direction (above/below 50). Entries only trigger when the fast stochastic gives an oversold/overbought crossover signal in the direction of the trend.


MTF Trend Filter

Use the 1-hour EMA trend to filter entries on a lower-timeframe chart.

ts
strategy('MTF Trend Filter', { overlay: true })

const h1 = request.timeframe('1H')
const h1Trend = ta.ema(h1.close, input.int('h1Len', 20, '1H EMA Length'))

const fast = ta.ema(close, input.int('fast', 9, 'Fast EMA'))
const slow = ta.ema(close, input.int('slow', 21, 'Slow EMA'))

plot(fast, 'Fast', '#26A69A')
plot(slow, 'Slow', '#EF5350')

// Only long when 1H trend is up
if (h1.close > h1Trend && ta.crossover(fast, slow)) {
  strategy.entry('Long', 'long')
}

// Only short when 1H trend is down
if (h1.close < h1Trend && ta.crossunder(fast, slow)) {
  strategy.entry('Short', 'short')
}

How it works: request.timeframe('1H') fetches 1-hour candles, forward-filled to the chart timeframe. The 1-hour EMA acts as a trend filter — EMA cross entries only fire in the direction of the higher-timeframe trend. This reduces false signals in choppy markets.


CHoCH (Change of Character)

Built-in Strategy

This strategy is available as a built-in strategy — open the Indicators panel, switch to the Strategies tab, and toggle it on. No scripting required.

ICT market structure reversal strategy. Tracks swing highs and swing lows on a higher timeframe to determine the prevailing structure (bullish or bearish), then signals when price breaks the structure.

  • Bullish CHoCH: Structure is bearish (lower highs / lower lows) and price closes above the most recent swing high — trend shifts bullish
  • Bearish CHoCH: Structure is bullish (higher highs / higher lows) and price closes below the most recent swing low — trend shifts bearish

Signal-only (no SL/TP/position management). After a signal fires, structure updates to the new direction to avoid repeat signals.

Parameters:

ParamDefaultDescription
Pivot Left/Right Bars5Lookback/lookahead for HTF swing detection
Higher TF15mTimeframe for swing structure detection

Candle Range Theory (CRT)

Built-in Strategy

This strategy is available as a built-in strategy — open the Indicators panel, switch to the Strategies tab, and toggle it on. No scripting required.

ICT-style liquidity sweep reversal strategy. Identifies a dealing range from higher-timeframe swing highs/lows, then signals when price sweeps beyond the range and closes back inside.

  • Bullish CRT: Price wicks below the range low (grabbing buy-side liquidity) but closes back above it
  • Bearish CRT: Price wicks above the range high (grabbing sell-side liquidity) but closes back below it

The sweep must exceed a configurable ATR-based threshold to filter noise.

Parameters:

ParamDefaultDescription
Pivot Left/Right Bars5Lookback/lookahead for HTF swing detection
ATR Length14Period for ATR calculation (sweep threshold)
Sweep Threshold (ATR×)0.1Minimum sweep distance as a multiple of ATR
Higher TF15mTimeframe for pivot/dealing range detection

Martingale (CHoCH + Fib Levels)

Built-in Strategy

This strategy is available as a built-in strategy — open the Indicators panel, switch to the Strategies tab, and toggle it on. No scripting required.

Martingale position-layering strategy built on CHoCH (Change of Character) entries. When a CHoCH signal fires, the strategy opens an initial position and places additional orders at Fibonacci retracement levels below (for longs) or above (for shorts) the entry price. Each subsequent level increases the lot multiplier.

  • Bullish CHoCH entry: Bearish structure breaks → buy at close. Additional buy levels placed at 50%, 100%, 168%, and 216% of the swing range below entry.
  • Bearish CHoCH entry: Bullish structure breaks → sell at close. Additional sell levels placed at 50%, 100%, 168%, and 216% of the swing range above entry.

Horizontal dashed lines are drawn at each Fibonacci level showing the price and lot multiplier.

LevelFib %Lots
1st (entry)0%
2nd50%
3rd100%
4th168%
5th216%

Parameters:

ParamDefaultDescription
Pivot Left/Right Bars5Lookback/lookahead for HTF swing detection
Higher TF15mTimeframe for swing structure detection

Fibonacci Zone Divergence

Built-in Strategy

This strategy is available as a built-in strategy — open the Indicators panel, switch to the Strategies tab, and toggle it on. No scripting required. The code below shows the equivalent script version for reference/customization.

Uses 5-minute pivot highs/lows to define Fibonacci extension zones (1.0–1.382), then enters on RSI divergence within those zones. Designed for 15-second charts.

ts
strategy('Fib Zone Divergence', { overlay: true })

// ─── Inputs ───────────────────────────────────────────────
const pivotLeft  = input.int('pivotLeft', 5, 'Pivot Left Bars', { min: 2, max: 20 })
const pivotRight = input.int('pivotRight', 5, 'Pivot Right Bars', { min: 2, max: 20 })
const rsiLen     = input.int('rsiLen', 14, 'RSI Length', { min: 2, max: 50 })
const rsiPivL    = input.int('rsiPivLeft', 3, 'RSI Pivot Left', { min: 1, max: 10 })
const rsiPivR    = input.int('rsiPivRight', 3, 'RSI Pivot Right', { min: 1, max: 10 })
const fibExt     = input.float('fibExt', 0.382, 'Fib Extension', { min: 0.1, max: 1.0 })

// ─── 5m Pivot Detection ──────────────────────────────────
const tf5 = request.timeframe('5m')
const pivHigh = ta.pivotHigh(tf5.high, pivotLeft, pivotRight)
const pivLow  = ta.pivotLow(tf5.low, pivotLeft, pivotRight)

// ─── 15s RSI + RSI Pivots ────────────────────────────────
const rsi      = ta.rsi(close, rsiLen)
const rsiPivHi = ta.pivotHigh(rsi, rsiPivL, rsiPivR)
const rsiPivLo = ta.pivotLow(rsi, rsiPivL, rsiPivR)

// ─── Helpers: scan back for most recent pivot ────────────
function findLastPivot(pivSeries: Series, maxLookback: number) {
  for (let k = 0; k <= maxLookback; k++) {
    const v = pivSeries[k]
    if (!isNaN(v)) return { offset: k, price: v }
  }
  return null
}

function findPrevPivot(pivSeries: Series, afterOffset: number, maxLookback: number) {
  for (let k = afterOffset + 1; k <= maxLookback; k++) {
    const v = pivSeries[k]
    if (!isNaN(v)) return { offset: k, price: v }
  }
  return null
}

// ─── Bar-by-bar logic ────────────────────────────────────
const lookback = 200

const lastSwingHigh = findLastPivot(pivHigh, lookback)
const lastSwingLow  = findLastPivot(pivLow, lookback)

// --- Fibonacci Zones ---
let fibHighTop = NaN, fibHighBot = NaN
let fibLowBot  = NaN, fibLowTop  = NaN
let swingHighSL = NaN, swingLowSL = NaN

if (lastSwingHigh) {
  const barsSince = lastSwingHigh.offset
  const recentLow = ta.lowest(low, Math.max(barsSince, 1))
  const range = lastSwingHigh.price - +recentLow
  fibHighBot = lastSwingHigh.price
  fibHighTop = lastSwingHigh.price + range * fibExt
  swingHighSL = +tf5.high
}

if (lastSwingLow) {
  const barsSince = lastSwingLow.offset
  const recentHigh = ta.highest(high, Math.max(barsSince, 1))
  const range = +recentHigh - lastSwingLow.price
  fibLowTop = lastSwingLow.price
  fibLowBot = lastSwingLow.price - range * fibExt
  swingLowSL = +tf5.low
}

// --- Divergence Detection ---
const rsiH1 = findLastPivot(rsiPivHi, lookback)
const rsiH2 = rsiH1 ? findPrevPivot(rsiPivHi, rsiH1.offset, lookback) : null

const rsiL1 = findLastPivot(rsiPivLo, lookback)
const rsiL2 = rsiL1 ? findPrevPivot(rsiPivLo, rsiL1.offset, lookback) : null

let bearishDiv = false
if (rsiH1 && rsiH2) {
  const priceAtH1 = high[rsiH1.offset]
  const priceAtH2 = high[rsiH2.offset]
  if (!isNaN(priceAtH1) && !isNaN(priceAtH2) && priceAtH1 > priceAtH2 && rsiH1.price < rsiH2.price)
    bearishDiv = true
}

let bullishDiv = false
if (rsiL1 && rsiL2) {
  const priceAtL1 = low[rsiL1.offset]
  const priceAtL2 = low[rsiL2.offset]
  if (!isNaN(priceAtL1) && !isNaN(priceAtL2) && priceAtL1 < priceAtL2 && rsiL1.price > rsiL2.price)
    bullishDiv = true
}

// --- Entry Conditions ---
const inShortZone = !isNaN(fibHighBot) && +close >= fibHighBot && +close <= fibHighTop
const inLongZone  = !isNaN(fibLowTop)  && +close <= fibLowTop  && +close >= fibLowBot

if (inShortZone && bearishDiv && strategy.position_size === 0)
  strategy.entry('Short', 'short', { sl: swingHighSL, tp: fibHighBot })

if (inLongZone && bullishDiv && strategy.position_size === 0)
  strategy.entry('Long', 'long', { sl: swingLowSL, tp: fibLowTop })

// ─── Visualization ───────────────────────────────────────
plot(pivHigh, 'Swing High', '#EF5350')
plot(pivLow, 'Swing Low', '#26A69A')

if (inShortZone && bearishDiv) bgcolor('rgba(239,83,80,0.25)')
if (inLongZone && bullishDiv)  bgcolor('rgba(38,166,154,0.25)')

if (!isNaN(fibHighBot)) hline(fibHighBot, 'Fib 1.0 High', '#EF5350')
if (!isNaN(fibHighTop)) hline(fibHighTop, 'Fib 1.382 High', 'rgba(239,83,80,0.5)')
if (!isNaN(fibLowTop))  hline(fibLowTop, 'Fib 1.0 Low', '#26A69A')
if (!isNaN(fibLowBot))  hline(fibLowBot, 'Fib 1.382 Low', 'rgba(38,166,154,0.5)')

How it works: request.timeframe('5m') fetches 5-minute candles forward-filled to the chart timeframe (e.g. 15s). ta.pivotHigh / ta.pivotLow detect swing points on the 5m data — they de-duplicate forward-filled runs automatically. Fibonacci extension zones are calculated from the swing point to the recent price range (1.0 = swing level, 1.382 = extension). RSI divergence is detected by comparing the two most recent RSI pivots with corresponding price: bearish divergence (higher price high + lower RSI high) triggers shorts in the upper fib zone, bullish divergence (lower price low + higher RSI low) triggers longs in the lower fib zone. SL is set at the 5m candle extreme, TP at the fib 1.0 level.

Tradify Charts Scripting Documentation