Strategy · Pine Script v6 · On the chart

Seykota Alt 14: Pullback Pyramid

by trustdan· code credited to danieltuckerrust

This strategy implements a trend-following Donchian breakout system with pullback-based pyramiding.

View on GitHub

GitHub stars16on the repository
Pine versionv6current
Lines of code265
Settings24inputs
LicenceNone statedfile: MPL-2.0
Last update11 months ago3 Nov 2025

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new strategy, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart. The trades and results appear in the Strategy Tester tab; adjust the settings with the gear icon.

Source Code

265 lines of Pine Script v6 from pine-scripts/27_PF-0.822_SPY_seykota_alt14_pullback_pyramid.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

27_PF-0.822_SPY_seykota_alt14_pullback_pyramid.pine
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © danieltuckerrust
// ALTERNATIVE 14: Pullback Pyramid
// THEORY: Add to positions on strength pullbacks, not melt-ups, to get better prices.
// The baseline adds immediately when price extends by 0.5N. But adding into parabolic moves
// often means bad fill prices. This alternative waits for a pullback to the 10 EMA after the
// add threshold is hit. This ensures we're "buying the dip" in an uptrend rather than chasing.
// Trade-off: Better add prices and reduced chase risk, but miss runaway trends without pullbacks.

//@version=6
strategy("Seykota Alt 14: Pullback Pyramid v1.0",
     overlay=true,
     initial_capital=100000,
     commission_type=strategy.commission.percent, commission_value=0.005,
     slippage=2,
     default_qty_type=strategy.fixed, default_qty_value=0,
     pyramiding=10,
     max_bars_back=5000)

//================ Inputs
allowLong   = input.bool(true,  "Allow LONGs?")
allowShort  = input.bool(true,  "Allow SHORTs?")

entryLen    = input.int(55,     "Donchian ENTRY lookback",            minval=10)
exitLen     = input.int(10,     "Donchian EXIT lookback",             minval=5)
nLen        = input.int(20,     "N = ATR length",                     minval=5)
stopN       = input.float(2.0,  "Initial stop (in N)",                minval=0.5, step=0.25)
addStepN    = input.float(0.5,  "Add every X * N",                    minval=0.25, step=0.25)
maxUnits    = input.int(4,      "Max units (incl. initial)",          minval=1, maxval=10)
riskPct     = input.float(1.0,  "Risk % of equity PER UNIT",          minval=0.1, maxval=5, step=0.1)

// CHANGED: Pullback add parameters
pullbackLen = input.int(10,     "Pullback EMA length",                minval=3)
waitForPullback = input.bool(true, "Wait for pullback to EMA?")

useMarket   = input.bool(false, "Use market regime filter?")
marketSym   = input.symbol("SPY", "Market symbol for regime (if used)")
marketTF    = input.timeframe("D", "Regime timeframe (if used)")
marketLen   = input.int(200,    "Market MA length (if used)",         minval=50)

timeExitMode= input.string("None","Time exit", options=["None","Close","Roll"])
timeExitBars= input.int(60,     "Time exit bars (if used)",           minval=5)

minVol      = input.int(0,      "Min 20-bar avg volume (chart TF)",   minval=0)
showSignals = input.bool(true,  "Plot signals & stops?")
plotDon     = input.bool(true,  "Plot Donchian bands?")
plotEMA     = input.bool(true,  "Plot pullback EMA?")

// Date-range filter
fromDate = input.time(defval=timestamp("2022-01-01T00:00:00"), title="Backtest FROM (yyyy-mm-dd)", confirm=true)
toDate   = input.time(defval=timestamp("2099-12-31T23:59:59"), title="Backtest TO (yyyy-mm-dd)",   confirm=true)
flatAtFrom = input.bool(true, "Force FLAT at range start?")
isRangeStart = (time[1] < fromDate) and (time >= fromDate)
if flatAtFrom and isRangeStart and strategy.position_size != 0
    strategy.close_all(comment="Flat at FROM")

//================ Helpers
sharesForUnit(_equity, _Nentry) =>
    riskDollars   = _equity * (riskPct/100.0)
    perShareRisk  = math.max(stopN * _Nentry, syminfo.mintick)
    math.max(1, math.floor(riskDollars / perShareRisk))

inRange = (time >= fromDate) and (time <= toDate)

//================ Core calcs
N        = ta.atr(nLen)
volMA    = ta.sma(volume, 20)
liqOK    = volMA >= minVol

// CHANGED: Pullback EMA for add timing
pullbackEMA = ta.ema(close, pullbackLen)

// Donchian levels
donHi    = ta.highest(high, entryLen)
donLo    = ta.lowest(low,  entryLen)
donHiPrev= donHi[1]
donLoPrev= donLo[1]

exitHiPrev = ta.highest(high, exitLen)[1]
exitLoPrev = ta.lowest(low,  exitLen)[1]

// Market regime (optional)
mClose = request.security(marketSym, marketTF, close)
mMA    = request.security(marketSym, marketTF, ta.sma(close, marketLen))
longRegOK  = not useMarket or (mClose > mMA)
shortRegOK = not useMarket or (mClose < mMA)

//================ Signals
longBreak  = allowLong  and liqOK and longRegOK  and (close > donHiPrev)
shortBreak = allowShort and liqOK and shortRegOK and (close < donLoPrev)

//================ Position state
var float N_entry       = na
var float lastAddLong   = na
var float lastAddShort  = na
var int   units         = 0
var int   barsInPos     = 0
var bool  wantReenterL  = false
var bool  wantReenterS  = false
var bool  addThresholdHitLong  = false  // Track if we hit add threshold and are waiting for pullback
var bool  addThresholdHitShort = false

inPos     = strategy.position_size != 0
inLong    = strategy.position_size > 0
inShort   = strategy.position_size < 0

if inPos
    barsInPos += 1
else
    barsInPos    := 0
    units        := 0
    lastAddLong  := na
    lastAddShort := na
    N_entry      := na
    wantReenterL := false
    wantReenterS := false
    addThresholdHitLong  := false
    addThresholdHitShort := false

//================ Entries
if inRange and not inPos and longBreak
    N_entry := N
    float sh = sharesForUnit(strategy.equity, N_entry)
    strategy.entry("L", strategy.long, qty=sh)
    units       := 1
    lastAddLong := close

if inRange and not inPos and shortBreak
    N_entry := N
    float sh = sharesForUnit(strategy.equity, N_entry)
    strategy.entry("S", strategy.short, qty=sh)
    units        := 1
    lastAddShort := close

//================ PULLBACK PYRAMID LOGIC
// Step 1: Check if price has extended enough for an add
priceExtendedLong  = inRange and inLong  and units < maxUnits and high >= nz(lastAddLong)  + addStepN * N_entry
priceExtendedShort = inRange and inShort and units < maxUnits and low  <= nz(lastAddShort) - addStepN * N_entry

// Step 2: Mark that threshold was hit
if priceExtendedLong
    addThresholdHitLong := true

if priceExtendedShort
    addThresholdHitShort := true

// Step 3: Wait for pullback to EMA before adding
// For longs: pullback = close <= pullbackEMA (after being above)
// For shorts: pullback = close >= pullbackEMA (after being below)
pullbackConditionLong  = addThresholdHitLong  and close <= pullbackEMA
pullbackConditionShort = addThresholdHitShort and close >= pullbackEMA

// CHANGED: Only add if pullback condition is met (or if waiting is disabled)
canAddLong  = addThresholdHitLong  and (not waitForPullback or pullbackConditionLong)
canAddShort = addThresholdHitShort and (not waitForPullback or pullbackConditionShort)

if canAddLong
    float sh = sharesForUnit(strategy.equity, N_entry)
    strategy.entry("L", strategy.long, qty=sh)
    units       += 1
    lastAddLong := close
    addThresholdHitLong := false  // Reset for next add

if canAddShort
    float sh = sharesForUnit(strategy.equity, N_entry)
    strategy.entry("S", strategy.short, qty=sh)
    units        += 1
    lastAddShort := close
    addThresholdHitShort := false  // Reset for next add

//================ Exits
var float stopL = na
var float stopS = na

if inRange and inLong
    initStopL = strategy.position_avg_price - stopN * N_entry
    stopL     := math.max(initStopL, exitLoPrev)
    strategy.exit("L-EXIT", from_entry="L", stop=stopL)
else
    stopL := na

if inRange and inShort
    initStopS = strategy.position_avg_price + stopN * N_entry
    stopS     := math.min(initStopS, exitHiPrev)
    strategy.exit("S-EXIT", from_entry="S", stop=stopS)
else
    stopS := na

//================ Optional time exit
timeExitTrig = inRange and inPos and (timeExitMode != "None") and (barsInPos >= timeExitBars)

if timeExitTrig
    if inLong
        if timeExitMode == "Close"
            strategy.close("L", comment="Time Exit")
        else if timeExitMode == "Roll"
            wantReenterL := true
            strategy.close("L", comment="Time Exit (Roll)")
    if inShort
        if timeExitMode == "Close"
            strategy.close("S", comment="Time Exit")
        else if timeExitMode == "Roll"
            wantReenterS := true
            strategy.close("S", comment="Time Exit (Roll)")

// Re-enter after Roll
if inRange and not inPos and wantReenterL and longRegOK and (close > donHiPrev)
    N_entry := N
    float sh = sharesForUnit(strategy.equity, N_entry)
    strategy.entry("L", strategy.long, qty=sh)
    units       := 1
    lastAddLong := close
    wantReenterL := false

if inRange and not inPos and wantReenterS and shortRegOK and (close < donLoPrev)
    N_entry := N
    float sh = sharesForUnit(strategy.equity, N_entry)
    strategy.entry("S", strategy.short, qty=sh)
    units        := 1
    lastAddShort := close
    wantReenterS := false

//================ Plots
float dHiPlot = plotDon ? donHi : na
float dLoPlot = plotDon ? donLo : na
plot(dHiPlot, "Donchian High", color=color.new(color.blue, 40))
plot(dLoPlot, "Donchian Low",  color=color.new(color.blue, 40))

// Plot pullback EMA
plot(plotEMA ? pullbackEMA : na, "Pullback EMA", color=color.new(color.orange, 0), linewidth=2)

// Background shading when waiting for pullback
waitingBg = waitForPullback and (addThresholdHitLong or addThresholdHitShort) ? color.new(color.yellow, 90) : na
bgcolor(waitingBg, title="Waiting for Pullback")

plotshape(showSignals and longBreak,  title="Long Breakout",  style=shape.triangleup,   location=location.belowbar, size=size.tiny,  color=color.new(color.green, 0), text="Long")
plotshape(showSignals and shortBreak, title="Short Breakout", style=shape.triangledown, location=location.abovebar, size=size.tiny,  color=color.new(color.red,   0), text="Short")

// Mark successful pullback adds
plotshape(showSignals and canAddLong,  title="Add Long (Pullback)",  style=shape.circle, location=location.belowbar, size=size.small, color=color.new(color.green, 0))
plotshape(showSignals and canAddShort, title="Add Short (Pullback)", style=shape.circle, location=location.abovebar, size=size.small, color=color.new(color.red,   0))

// Mark when threshold hit but waiting for pullback
plotshape(showSignals and addThresholdHitLong and not canAddLong,   title="Waiting for Pullback (Long)",  style=shape.diamond, location=location.belowbar, size=size.tiny, color=color.new(color.yellow, 30))
plotshape(showSignals and addThresholdHitShort and not canAddShort, title="Waiting for Pullback (Short)", style=shape.diamond, location=location.abovebar, size=size.tiny, color=color.new(color.yellow, 30))

plot(showSignals and inLong  ? stopL : na, "Long Stop",  color=color.new(color.red,  0), style=plot.style_linebr, linewidth=2)
plot(showSignals and inShort ? stopS : na, "Short Stop", color=color.new(color.red,  0), style=plot.style_linebr, linewidth=2)

// Info table
var table infoTable = table.new(position.top_right, 2, 4, border_width=1)
if barstate.islast and showSignals
    table.cell(infoTable, 0, 0, "Pyramid:", text_color=color.white, bgcolor=color.gray)
    table.cell(infoTable, 1, 0, "PULLBACK", text_color=color.white, bgcolor=color.orange)

    table.cell(infoTable, 0, 1, "EMA Length:", text_color=color.white, bgcolor=color.gray)
    table.cell(infoTable, 1, 1, str.tostring(pullbackLen), text_color=color.white, bgcolor=color.gray)

    table.cell(infoTable, 0, 2, "Status:", text_color=color.white, bgcolor=color.gray)
    waitStatus = (addThresholdHitLong or addThresholdHitShort) ? "Waiting for Pullback" : "Ready"
    waitColor = (addThresholdHitLong or addThresholdHitShort) ? color.yellow : color.green
    table.cell(infoTable, 1, 2, waitStatus, text_color=color.white, bgcolor=waitColor)

    table.cell(infoTable, 0, 3, "Units:", text_color=color.white, bgcolor=color.gray)
    table.cell(infoTable, 1, 3, str.tostring(units) + "/" + str.tostring(maxUnits), text_color=color.white, bgcolor=color.gray)

Overview

Seykota Alt 14: Pullback Pyramid initiates trades on Donchian channel breakouts and sizes positions based on ATR volatility risk. Instead of immediately adding units when the trend advances by a set ATR step, it waits for price to pull back to an exponential moving average before adding to the position. Exits execute via opposing Donchian channels, trailing stops, or optional time-based exits. The strategy plots channels, moving averages, and signals directly on the price chart.

Details

Published by

trustdan on GitHub

Code credit

danieltuckerrust, as written in the script's header

Type

Strategy, Pine Script v6

Draws

On the price chart

Category

Trend, Breakout, Moving averages, Volatility & bands

Markets

Any symbol

Timeframes

M15-H1, H4 and above

Risk Notes

  • The strategy can add several entries in the same direction

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Allow LONGs?booltrueallowLong
Allow SHORTs?booltrueallowShort
Donchian ENTRY lookbackint55entryLen
Donchian EXIT lookbackint10exitLen
N = ATR lengthint20nLen
Initial stop (in N)float2.0stopN
Add every X * Nfloat0.5addStepN
Max units (incl. initial)int4maxUnits
Risk % of equity PER UNITfloat1.0riskPct
Pullback EMA lengthint10pullbackLen
Wait for pullback to EMA?booltruewaitForPullback
Use market regime filter?boolfalseuseMarket
Show all 24 settings
SettingTypeDefaultVariable
Market symbol for regime (if used)symbolSPYmarketSym
Regime timeframe (if used)timeframeDmarketTF
Market MA length (if used)int200marketLen
Time exitstringNonetimeExitMode
Time exit bars (if used)int60timeExitBars
Min 20-bar avg volume (chart TF)int0minVol
Plot signals & stops?booltrueshowSignals
Plot Donchian bands?booltrueplotDon
Plot pullback EMA?booltrueplotEMA
Backtest FROM (yyyy-mm-dd)timetimestamp("2022-01-01T00:00:00")fromDate
Backtest TO (yyyy-mm-dd)timetimestamp("2099-12-31T23:59:59")toDate
Force FLAT at range start?booltrueflatAtFrom
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

106 stars

This strategy runs on the price chart and enters long when a fast simple moving average crosses above a slow one, and short on the opposite cross, while closing the other side. Entries also need ATR above a filter, a pullback check, a minimum candle-body chang

Strategy v5 TrendMoving averages
Updated 2 years ago · GPL-3.0 GitHub repository of 9:22 5 MIN 15 MIN BANKNIFTY

均线突破交易系统

by tangly1024
76 stars

This strategy trades long breakouts of a fast and a slow moving average on the price chart. It opens a long position when price crosses above either average, using a percentage of equity as size. It reduces the position by a set share when the high reaches the

Strategy v5 TrendMoving averages
Updated 4 months ago · None stated GitHub repository of 均线突破交易系统
4 stars

This strategy executes trend-following trades based on Bollinger Band breakouts confirmed by a long-term moving average filter. A long position opens when the price crosses above the upper Bollinger Band, provided the band spread meets a minimum threshold and

Strategy v5 Volatility & bandsTrend
Updated 2 years ago · None stated GitHub repository of Bollinger Trend - Strategy V1
4 stars

The Keltner Channel Strategy is a breakout system plotted directly on the main price chart. It calculates upper and lower channel bands around an exponential or simple moving average using ATR, True Range, or bar range multiplied by a specified factor. A long

Strategy v5 TrendVolatility & bands
Updated 5 months ago · None stated GitHub repository of Keltner Channel Strategy

This strategy combines Bollinger Bands and Bollinger Band Width to trigger breakout trades during periods of low volatility. It opens long positions when price crosses above the upper band while band width is below its 100-period moving average, and opens shor

Strategy v5 TrendVolatility & bands
Updated 4 years ago · CC0-1.0 GitHub repository of Greeffer's Bolinger Band Hedge Strategy

More strategys

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers