Strategy · Pine Script v5 · On the chart

ETH Momentum Breakout

by grinay

ETH Momentum Breakout is a momentum-based strategy overlay for crypto trading.

View on GitHub

GitHub stars3on the repository
Pine versionv5runs as is
Lines of code712
Settings40inputs
LicenceMIT
Last update7 months ago3 Mar 2026

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

712 lines of Pine Script v5 from strategies/eth-momentum-breakout/strategy.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

strategy.pine
//@version=5
strategy("ETH Momentum Breakout v15.0",
     shorttitle   = "ETH Breakout v15",
     overlay      = true,
     default_qty_type   = strategy.percent_of_equity,
     default_qty_value  = 100,
     initial_capital    = 10000,
     commission_type    = strategy.commission.percent,
     commission_value   = 0.1,
     slippage           = 2,
     pyramiding         = 0,
     calc_on_every_tick = false,
     process_orders_on_close = false)

// ═══════════════════════════════════════════════════════════════
// INPUTS — tuned for 2H timeframe
// ═══════════════════════════════════════════════════════════════

// --- Direction ---
GRP_DIR     = "Direction"
dirMode     = input.string("Long Only", "Trade Direction", options=["Long Only", "Short Only", "Both"], group=GRP_DIR)

// --- Regime Filter ---
GRP_REG     = "Regime Filter"
useRegime   = input.bool(true, "Enable Regime Filter", group=GRP_REG)
regimeTF    = input.timeframe("D", "Regime Timeframe", group=GRP_REG)

useAdxFilter = input.bool(false, "ADX Filter", group=GRP_REG,
                 tooltip="OFF by default — squeeze detection already handles consolidation filtering")
adxDiLen    = input.int(14, "ADX DI Length", minval=5, group=GRP_REG)
adxSmooth   = input.int(14, "ADX Smoothing", minval=5, group=GRP_REG)
adxMin      = input.float(18.0, "Min ADX", step=1.0, minval=0.0, group=GRP_REG)
adxMax      = input.float(40.0, "Max ADX", step=1.0, minval=0.0, group=GRP_REG)

useEmaDistFilter = input.bool(true, "EMA Distance Filter", group=GRP_REG)
emaDistLen  = input.int(200, "Long-term EMA Length", minval=50, group=GRP_REG)
emaDistMax  = input.float(35.0, "Max Distance From EMA %", step=1.0, minval=5.0, group=GRP_REG)

useVolRegime = input.bool(true, "Volatility Regime Filter", group=GRP_REG)
volRegimeLen = input.int(100, "Vol History Length", minval=20, group=GRP_REG)
volRegimeMax = input.float(2.0, "Max Vol Ratio", step=0.1, minval=1.0, group=GRP_REG)
volRegimeMin = input.float(0.4, "Min Vol Ratio", step=0.1, minval=0.0, maxval=1.0, group=GRP_REG)

// --- HTF Trend Filter ---
GRP_TREND   = "Trend Filter"
useTrend    = input.bool(true, "Enable HTF Trend Filter", group=GRP_TREND)
trendTF     = input.timeframe("60", "Trend Timeframe", group=GRP_TREND,
                 tooltip="1H micro-trend on 2H chart — responsive, granular trend confirmation")
trendEmaLen = input.int(50, "Trend EMA Length", minval=10, group=GRP_TREND)
trendAtrLen = input.int(14, "Trend ATR Length", minval=5, group=GRP_TREND)
trendAtrMul = input.float(0.5, "EMA Buffer (ATR x)", step=0.1, group=GRP_TREND)
useSlope    = input.bool(true, "Require EMA Slope Confirmation", group=GRP_TREND)
slopeBars   = input.int(5, "Slope Lookback (HTF bars)", minval=1, maxval=20, group=GRP_TREND)

// --- Squeeze Detection ---
GRP_SQZ     = "Squeeze Detection"
bbLen       = input.int(20, "BB Length", minval=5, group=GRP_SQZ)
bbMult      = input.float(2.0, "BB Multiplier", step=0.1, group=GRP_SQZ)
kcLen       = input.int(20, "KC Length", minval=5, group=GRP_SQZ)
kcMult      = input.float(1.5, "KC Multiplier", step=0.1, group=GRP_SQZ)
sqzMaxBars  = input.int(6, "Max Bars After Squeeze", minval=1, maxval=20, group=GRP_SQZ)
sqzMinBars  = input.int(3, "Min Squeeze Duration (bars)", minval=1, maxval=50, group=GRP_SQZ,
                 tooltip="3 bars on 2H = 6 hours of compression")

// --- Breakout Quality ---
GRP_BRK     = "Breakout Quality"
brkMargin   = input.float(0.3, "Breakout Margin (ATR x)", step=0.05, minval=0.0, group=GRP_BRK)
minBodyPct  = input.float(40.0, "Min Candle Body %", step=5.0, minval=0.0, maxval=100.0, group=GRP_BRK)

// --- RSI Extreme Filter ---
GRP_RSI     = "RSI Filter"
useRsiFilter = input.bool(true, "Enable RSI Extreme Filter", group=GRP_RSI,
                 tooltip="Prevents entries at overbought/oversold extremes where reversals are likely")
rsiLen      = input.int(14, "RSI Length", minval=5, maxval=50, group=GRP_RSI)
rsiOBLevel  = input.float(75.0, "Overbought Level (skip longs above)", step=1.0, minval=60.0, maxval=95.0, group=GRP_RSI)
rsiOSLevel  = input.float(25.0, "Oversold Level (skip shorts below)", step=1.0, minval=5.0, maxval=40.0, group=GRP_RSI)

// --- Failed Breakout Exit ---
GRP_FAILBRK = "Failed Breakout Exit"
useFailBrk  = input.bool(false, "Enable Failed Breakout Exit", group=GRP_FAILBRK,
                 tooltip="OFF by default — v7 data showed this cost $4,565 on longs. BE stop handles failures better.")
failBrkBars = input.int(3, "Check Within (bars)", minval=1, maxval=10, group=GRP_FAILBRK)
failBrkRef  = input.string("Entry Price", "Exit Below", options=["BB Upper", "BB Basis", "Entry Price"], group=GRP_FAILBRK,
                 tooltip="Entry Price is safest — only exits true reversals, not normal pullbacks")

// --- Volume ---
GRP_VOL     = "Volume Filter"
volLen      = input.int(20, "Volume SMA Length", minval=5, group=GRP_VOL)
volMult     = input.float(1.8, "Spike Multiplier", step=0.1, minval=1.0, group=GRP_VOL)

// --- Momentum ---
GRP_MOM     = "Momentum"
momLen      = input.int(12, "Momentum Length", minval=1, group=GRP_MOM)
momThresh   = input.float(0.0, "Momentum Threshold", step=0.1, group=GRP_MOM)

// --- Risk Management ---
GRP_RISK    = "Risk Management"
atrLen      = input.int(14, "ATR Length", minval=1, group=GRP_RISK)
slMult      = input.float(2.0, "Stop Loss (ATR x)", step=0.1, group=GRP_RISK)
usePartialTP = input.bool(true, "Enable Partial Take-Profit", group=GRP_RISK,
                 tooltip="Take profit at TP1 on a portion, let remainder ride to TP2 with tighter trail")
tp1Mult     = input.float(1.5, "TP1 (ATR x) — Partial Exit", step=0.1, group=GRP_RISK)
tp1Pct      = input.float(50.0, "TP1 Exit %", step=5.0, minval=10.0, maxval=90.0, group=GRP_RISK)
tp2Mult     = input.float(3.5, "TP2 (ATR x) — Final Exit", step=0.1, group=GRP_RISK)
useTrail    = input.bool(true, "Enable Trailing Stop", group=GRP_RISK)
trailMult   = input.float(2.5, "Trail Distance (ATR x)", step=0.1, group=GRP_RISK)
trailTightMult = input.float(1.5, "Trail After Partial (ATR x)", step=0.1, group=GRP_RISK,
                 tooltip="Tighter trail after first profit locked in")
useRunnerMode  = input.bool(true, "Runner Mode (trail-only after TP1)", group=GRP_RISK,
                 tooltip="After partial TP1 profit is taken, removes the TP2 ceiling — remaining position rides the trail indefinitely. Captures big multi-day trends.")
runnerTrailBars = input.int(8, "Runner Structure Trail (N-bar low)", minval=2, maxval=20, group=GRP_RISK,
                 tooltip="After TP1, also trail using the N-bar low. Adapts to market structure — tighter in smooth trends, wider in volatile ones. 8 bars = 16h on 2H.")

// --- Breakeven Stop ---
GRP_BE      = "Breakeven Stop"
useBE       = input.bool(true, "Enable Breakeven Stop", group=GRP_BE)
beThresh    = input.float(1.2, "Activate After (ATR x)", step=0.1, minval=0.1, group=GRP_BE)
beOffset    = input.float(0.2, "BE Offset (ATR x)", step=0.05, group=GRP_BE,
                 tooltip="0.2 ATR buffer above entry — wider than v7 to survive 2H bar noise")

// --- Trade Management ---
GRP_MGMT    = "Trade Management"
cooldownLen = input.int(12, "Cooldown Bars After Exit", minval=0, maxval=100, group=GRP_MGMT)
useSmartCool = input.bool(true, "Smart Cooldown (faster after runners)", group=GRP_MGMT,
                 tooltip="Halves cooldown after a runner trade exits — re-enter trending markets faster")
minAtrPerc  = input.float(0.3, "Min ATR % of Price", step=0.05, minval=0.0, group=GRP_MGMT)
maxBarsInTrade = input.int(100, "Max Bars In Trade (0=off)", minval=0, maxval=500, group=GRP_MGMT,
                 tooltip="100 bars on 2H = ~8 days. Only applies to trades that haven't hit TP1. Runner mode trades have no timeout.")
useStagnation  = input.bool(true, "Enable Stagnation Exit", group=GRP_MGMT)
stagnBars   = input.int(10, "Stagnation Check After (bars)", minval=5, maxval=100, group=GRP_MGMT,
                 tooltip="10 bars on 2H = 20 hours. Tightened from v10 (15) — stagnation trades never recover, cut sooner to free capital")
stagnThresh = input.float(0.5, "Min Progress (ATR x)", step=0.1, minval=0.0, group=GRP_MGMT)

// --- Crypto Fundamentals ---
GRP_FUND    = "Crypto Fundamentals"
useFund     = input.bool(false, "Enable Fundamental Filters", group=GRP_FUND)
addrTicker  = input.string("INTOTHEBLOCK:ETH_ACTIVEADDRESSES", "Active Addresses", group=GRP_FUND)
lgTxTicker  = input.string("INTOTHEBLOCK:ETH_LARGETXCOUNT",    "Large Tx Count",   group=GRP_FUND)
sentTicker  = input.string("INTOTHEBLOCK:ETH_SENTIMENT",       "Sentiment",        group=GRP_FUND)
fundTF      = input.timeframe("D", "Fundamental Timeframe", group=GRP_FUND)
fundSmaLen  = input.int(14, "Fundamental SMA Length", minval=3, group=GRP_FUND)
sentBull    = input.float(55.0, "Bullish Sentiment Threshold", minval=0, maxval=100, step=1.0, group=GRP_FUND)

// --- Adaptive Regime (ML-lite) ---
GRP_ADAPT   = "Adaptive Regime"
useAdaptive = input.bool(true, "Enable Adaptive Parameters", group=GRP_ADAPT,
                 tooltip="Classifies market into Hot/Normal/Cold regimes at entry time and scales TP, SL, trail, and TP1% accordingly.")
adaptVolLen = input.int(100, "Vol Rank Lookback", minval=20, maxval=500, group=GRP_ADAPT)
adaptTrendLen = input.int(50, "Trend EMA Length", minval=10, maxval=200, group=GRP_ADAPT)
hotVolThresh = input.float(60.0, "Hot: Min Vol Percentile", step=5.0, minval=30.0, maxval=90.0, group=GRP_ADAPT,
                 tooltip="Volatility must be above this percentile to qualify as Hot regime.")
hotTrendThresh = input.float(1.5, "Hot: Min Trend Score (ATR)", step=0.1, minval=0.5, maxval=5.0, group=GRP_ADAPT,
                 tooltip="Price distance from trend EMA in ATR units. >1.5 means strong trend.")
coldVolThresh = input.float(30.0, "Cold: Max Vol Percentile", step=5.0, minval=10.0, maxval=60.0, group=GRP_ADAPT)
coldTrendThresh = input.float(0.5, "Cold: Max Trend Score (ATR)", step=0.1, minval=0.1, maxval=2.0, group=GRP_ADAPT,
                 tooltip="Price near the EMA with low vol = ranging market.")
hotScale    = input.float(1.35, "Hot: Parameter Scale", step=0.05, minval=1.0, maxval=2.0, group=GRP_ADAPT,
                 tooltip="TP/SL/Trail multiplied by this in hot regimes. 1.35 = 35% wider targets.")
coldScale   = input.float(0.75, "Cold: Parameter Scale", step=0.05, minval=0.3, maxval=1.0, group=GRP_ADAPT,
                 tooltip="TP/SL/Trail multiplied by this in cold regimes. 0.75 = 25% tighter targets.")
hotTP1Pct   = input.float(35.0, "Hot: TP1 Exit %", step=5.0, minval=10.0, maxval=80.0, group=GRP_ADAPT,
                 tooltip="In hot regimes, take LESS at TP1 — let more ride the runner.")
coldTP1Pct  = input.float(60.0, "Cold: TP1 Exit %", step=5.0, minval=20.0, maxval=90.0, group=GRP_ADAPT,
                 tooltip="In cold regimes, take MORE at TP1 — lock profit before chop erodes it.")

// --- Session ---
GRP_TIME    = "Session Filter"
useSess     = input.bool(false, "Enable Session Filter", group=GRP_TIME)
sess        = input.session("0800-2000", "Session (Exchange TZ)", group=GRP_TIME)

// ═══════════════════════════════════════════════════════════════
// DIRECTION FLAGS
// ═══════════════════════════════════════════════════════════════

allowLong  = dirMode == "Long Only" or dirMode == "Both"
allowShort = dirMode == "Short Only" or dirMode == "Both"

// ═══════════════════════════════════════════════════════════════
// REGIME FILTER
// ═══════════════════════════════════════════════════════════════

f_adx(diLen, smooth) =>
    [_p, _m, adxVal] = ta.dmi(diLen, smooth)
    adxVal

f_atrRatio(atrL, histL) =>
    curAtr = ta.atr(atrL)
    avgAtr = ta.sma(curAtr, histL)
    avgAtr > 0 ? curAtr / avgAtr : 1.0

regimeAdx      = request.security(syminfo.tickerid, regimeTF, f_adx(adxDiLen, adxSmooth))
regimeLtEma    = request.security(syminfo.tickerid, regimeTF, ta.ema(close, emaDistLen))
regimeVolRatio = request.security(syminfo.tickerid, regimeTF, f_atrRatio(14, volRegimeLen))

emaDist = regimeLtEma > 0 ? math.abs(close - regimeLtEma) / regimeLtEma * 100.0 : 0.0

adxOk     = not useRegime or not useAdxFilter     or (not na(regimeAdx) and regimeAdx >= adxMin and regimeAdx <= adxMax)
emaDistOk = not useRegime or not useEmaDistFilter  or emaDist <= emaDistMax
volRegOk  = not useRegime or not useVolRegime      or (not na(regimeVolRatio) and regimeVolRatio >= volRegimeMin and regimeVolRatio <= volRegimeMax)

regimeOk = adxOk and emaDistOk and volRegOk

// ═══════════════════════════════════════════════════════════════
// HTF TREND FILTER + SLOPE
// ═══════════════════════════════════════════════════════════════

htfEma     = request.security(syminfo.tickerid, trendTF, ta.ema(close, trendEmaLen))
htfAtr     = request.security(syminfo.tickerid, trendTF, ta.atr(trendAtrLen))
htfEmaLag  = request.security(syminfo.tickerid, trendTF, ta.ema(close, trendEmaLen)[slopeBars])

emaRising  = not useSlope or htfEma > htfEmaLag
emaFalling = not useSlope or htfEma < htfEmaLag

bullTrend = not useTrend or (close > htfEma + htfAtr * trendAtrMul and emaRising)
bearTrend = not useTrend or (close < htfEma - htfAtr * trendAtrMul and emaFalling)

// ═══════════════════════════════════════════════════════════════
// SQUEEZE DETECTION
// ═══════════════════════════════════════════════════════════════

bbBasis  = ta.sma(close, bbLen)
bbDev    = bbMult * ta.stdev(close, bbLen)
bbUpper  = bbBasis + bbDev
bbLower  = bbBasis - bbDev

kcBasis  = ta.sma(close, kcLen)
kcRange  = ta.atr(kcLen) * kcMult
kcUpper  = kcBasis + kcRange
kcLower  = kcBasis - kcRange

sqzOn = bbLower > kcLower and bbUpper < kcUpper

var int sqzDuration = 0
sqzDuration := sqzOn ? sqzDuration + 1 : 0

var int barsSinceSqz = 999
var bool lastSqzValid = false

if sqzOn
    barsSinceSqz := 0
else
    if barsSinceSqz == 0
        lastSqzValid := sqzDuration[1] >= sqzMinBars
    barsSinceSqz += 1

recentSqz = barsSinceSqz > 0 and barsSinceSqz <= sqzMaxBars and lastSqzValid

// ═══════════════════════════════════════════════════════════════
// BREAKOUT QUALITY
// ═══════════════════════════════════════════════════════════════

atr = ta.atr(atrLen)

runnerLowRef  = ta.lowest(low, runnerTrailBars)
runnerHighRef = ta.highest(high, runnerTrailBars)

// ═══════════════════════════════════════════════════════════════
// ADAPTIVE REGIME DETECTION
// ═══════════════════════════════════════════════════════════════

volRank    = ta.percentrank(atr, adaptVolLen)
adaptEMA   = ta.ema(close, adaptTrendLen)
trendLong  = atr > 0 ? (close - adaptEMA) / atr : 0.0
trendShort = atr > 0 ? (adaptEMA - close) / atr : 0.0

bool hotLong   = volRank > hotVolThresh and trendLong > hotTrendThresh
bool hotShort  = volRank > hotVolThresh and trendShort > hotTrendThresh
bool coldLong  = volRank < coldVolThresh or trendLong < coldTrendThresh
bool coldShort = volRank < coldVolThresh or trendShort < coldTrendThresh

int  curRegimeLong  = hotLong ? 1 : coldLong ? -1 : 0
int  curRegimeShort = hotShort ? 1 : coldShort ? -1 : 0

var int   entryRegime     = 0
var float dynTP1Mult      = na
var float dynTP2Mult      = na
var float dynSLMult       = na
var float dynTP1ExitPct   = na
var float dynTrailTightM  = na
var float dynTrailM       = na

candleBody  = math.abs(close - open)
candleRange = high - low
bodyRatio   = candleRange > 0 ? candleBody / candleRange * 100.0 : 0.0
strongBody  = bodyRatio >= minBodyPct

longBreakout  = close > bbUpper + atr * brkMargin
shortBreakout = close < bbLower - atr * brkMargin

// ═══════════════════════════════════════════════════════════════
// RSI EXTREME FILTER
// ═══════════════════════════════════════════════════════════════

rsiVal      = ta.rsi(close, rsiLen)
rsiLongOk   = not useRsiFilter or rsiVal < rsiOBLevel
rsiShortOk  = not useRsiFilter or rsiVal > rsiOSLevel

// ═══════════════════════════════════════════════════════════════
// MOMENTUM
// ═══════════════════════════════════════════════════════════════

mom     = ta.linreg(close - ta.sma(close, momLen), momLen, 0)
momUp   = mom > momThresh and mom > mom[1]
momDown = mom < -momThresh and mom < mom[1]

// ═══════════════════════════════════════════════════════════════
// VOLUME FILTER
// ═══════════════════════════════════════════════════════════════

volSma   = ta.sma(volume, volLen)
volSpike = volume > volSma * volMult

// ═══════════════════════════════════════════════════════════════
// VOLATILITY FILTER
// ═══════════════════════════════════════════════════════════════

atrPercent = atr / close * 100.0
volOk = atrPercent >= minAtrPerc

// ═══════════════════════════════════════════════════════════════
// TRADE TRACKING (vars declared early for cooldown reference)
// ═══════════════════════════════════════════════════════════════

var int   barsInTrade     = 0
var float maxFavExcursion = 0.0
var float entryATR        = na
var float entryQty        = na
var float entryPrice      = na
var bool  partialFilled   = false

// ═══════════════════════════════════════════════════════════════
// COOLDOWN
// ═══════════════════════════════════════════════════════════════

var int barsSinceExit = 999
var bool lastWasRunner = false
wasInTrade = strategy.position_size[1] != 0
isFlat     = strategy.position_size == 0

if wasInTrade and isFlat
    lastWasRunner := partialFilled ? true : false
    barsSinceExit := 0
else if isFlat
    barsSinceExit += 1

int dynCooldown = useSmartCool and lastWasRunner ? math.round(cooldownLen * 0.5) : cooldownLen
cooldownOk = barsSinceExit >= dynCooldown

// ═══════════════════════════════════════════════════════════════
// TRADE TRACKING (logic)
// ═══════════════════════════════════════════════════════════════

if strategy.position_size != 0
    barsInTrade += 1
    if strategy.position_size > 0
        maxFavExcursion := math.max(maxFavExcursion, close - strategy.position_avg_price)
    else
        maxFavExcursion := math.max(maxFavExcursion, strategy.position_avg_price - close)
    if not na(entryQty) and not partialFilled and strategy.position_size < entryQty * 0.75
        partialFilled := true
else
    barsInTrade     := 0
    maxFavExcursion := 0.0
    entryATR        := na
    entryQty        := na
    entryPrice      := na
    partialFilled   := false

timeoutExit    = maxBarsInTrade > 0 and barsInTrade >= maxBarsInTrade and not partialFilled
stagnationExit = useStagnation and barsInTrade >= stagnBars and maxFavExcursion < nz(entryATR, atr) * stagnThresh

// ═══════════════════════════════════════════════════════════════
// FAILED BREAKOUT DETECTION
// ═══════════════════════════════════════════════════════════════

float failRefLong = switch failBrkRef
    "BB Upper"    => bbUpper
    "BB Basis"    => bbBasis
    "Entry Price" => nz(entryPrice, close)
    => bbUpper

float failRefShort = switch failBrkRef
    "BB Upper"    => bbLower
    "BB Basis"    => bbBasis
    "Entry Price" => nz(entryPrice, close)
    => bbLower

failedBrkLong  = useFailBrk and strategy.position_size > 0 and barsInTrade >= 1 and barsInTrade <= failBrkBars and close < failRefLong
failedBrkShort = useFailBrk and strategy.position_size < 0 and barsInTrade >= 1 and barsInTrade <= failBrkBars and close > failRefShort

// ═══════════════════════════════════════════════════════════════
// CRYPTO FUNDAMENTALS
// ═══════════════════════════════════════════════════════════════

activeAddr = request.security(addrTicker,  fundTF, close, ignore_invalid_symbol=true)
largeTx    = request.security(lgTxTicker,  fundTF, close, ignore_invalid_symbol=true)
sentiment  = request.security(sentTicker,  fundTF, close, ignore_invalid_symbol=true)

addrSma  = ta.sma(nz(activeAddr), fundSmaLen)
lgTxSma  = ta.sma(nz(largeTx),    fundSmaLen)

addrOk      = not useFund or na(activeAddr) or activeAddr > addrSma
lgTxOk      = not useFund or na(largeTx)    or largeTx    > lgTxSma
sentLongOk  = not useFund or na(sentiment)  or sentiment  > sentBull
sentShortOk = not useFund or na(sentiment)  or sentiment  < (100.0 - sentBull)

fundLong  = addrOk and lgTxOk and sentLongOk
fundShort = addrOk and lgTxOk and sentShortOk

// ═══════════════════════════════════════════════════════════════
// SESSION FILTER
// ═══════════════════════════════════════════════════════════════

inSession = not useSess or not na(time(timeframe.period, sess))

// ═══════════════════════════════════════════════════════════════
// ENTRY CONDITIONS
// ═══════════════════════════════════════════════════════════════

longCond  = allowLong  and regimeOk and recentSqz and momUp   and volSpike and longBreakout  and strongBody and bullTrend and rsiLongOk  and volOk and cooldownOk and fundLong  and inSession
shortCond = allowShort and regimeOk and recentSqz and momDown and volSpike and shortBreakout and strongBody and bearTrend and rsiShortOk and volOk and cooldownOk and fundShort and inSession

// ═══════════════════════════════════════════════════════════════
// ORDER EXECUTION
// ═══════════════════════════════════════════════════════════════

var float stopL = na
var float tp1L  = na
var float tp2L  = na
var float stopS = na
var float tp1S  = na
var float tp2S  = na
var bool  beActiveL = false
var bool  beActiveS = false

// ---- LOCK REGIME + ENTRY ----
if (longCond or shortCond) and strategy.position_size == 0
    if useAdaptive
        entryRegime     := longCond ? curRegimeLong : curRegimeShort
        float sc = entryRegime == 1 ? hotScale : entryRegime == -1 ? coldScale : 1.0
        dynTP1Mult      := tp1Mult * sc
        dynTP2Mult      := tp2Mult * sc
        dynSLMult       := slMult * sc
        dynTrailTightM  := trailTightMult * sc
        dynTrailM       := trailMult * sc
        dynTP1ExitPct   := entryRegime == 1 ? hotTP1Pct : entryRegime == -1 ? coldTP1Pct : tp1Pct
    else
        entryRegime     := 0
        dynTP1Mult      := tp1Mult
        dynTP2Mult      := tp2Mult
        dynSLMult       := slMult
        dynTrailTightM  := trailTightMult
        dynTrailM       := trailMult
        dynTP1ExitPct   := tp1Pct

    entryATR      := atr
    entryPrice    := close
    beActiveL     := false
    beActiveS     := false
    partialFilled := false

    if longCond
        stopL := close - atr * dynSLMult
        tp1L  := close + atr * dynTP1Mult
        tp2L  := close + atr * dynTP2Mult
        strategy.entry("Long", strategy.long)
        if usePartialTP
            strategy.exit("XL1", "Long", qty_percent=dynTP1ExitPct, stop=stopL, limit=tp1L)
            strategy.exit("XL2", "Long", qty_percent=100,    stop=stopL, limit=tp2L)
        else
            strategy.exit("XL", "Long", stop=stopL, limit=tp2L)

    if shortCond
        stopS := close + atr * dynSLMult
        tp1S  := close - atr * dynTP1Mult
        tp2S  := close - atr * dynTP2Mult
        strategy.entry("Short", strategy.short)
        if usePartialTP
            strategy.exit("XS1", "Short", qty_percent=dynTP1ExitPct, stop=stopS, limit=tp1S)
            strategy.exit("XS2", "Short", qty_percent=100,    stop=stopS, limit=tp2S)
        else
            strategy.exit("XS", "Short", stop=stopS, limit=tp2S)

// ---- Record entry qty on first bar of trade ----
if strategy.position_size != 0 and na(entryQty)
    entryQty := math.abs(strategy.position_size)

// ---- LONG EXIT MANAGEMENT ----
if strategy.position_size > 0
    float ep     = strategy.position_avg_price
    float refATR = nz(entryATR, atr)

    if useBE and not beActiveL and (close - ep) >= refATR * beThresh
        beActiveL := true
        stopL := math.max(nz(stopL), ep + refATR * beOffset)

    if useTrail
        float _tTight = nz(dynTrailTightM, trailTightMult)
        float _tWide  = nz(dynTrailM, trailMult)
        float trailDist = partialFilled ? atr * _tTight : atr * _tWide
        float atrTrail  = close - trailDist
        if partialFilled and useRunnerMode
            stopL := math.max(nz(stopL), math.max(atrTrail, runnerLowRef))
        else
            stopL := math.max(nz(stopL), atrTrail)

    if usePartialTP
        float _pct = nz(dynTP1ExitPct, tp1Pct)
        if not partialFilled
            strategy.exit("XL1", "Long", qty_percent=_pct, stop=stopL, limit=tp1L)
            strategy.exit("XL2", "Long", qty_percent=100, stop=stopL, limit=tp2L)
        else if useRunnerMode
            strategy.exit("XL2", "Long", qty_percent=100, stop=stopL)
        else
            strategy.exit("XL2", "Long", qty_percent=100, stop=stopL, limit=tp2L)
    else
        strategy.exit("XL", "Long", stop=stopL, limit=tp2L)

// ---- SHORT EXIT MANAGEMENT ----
if strategy.position_size < 0
    float ep     = strategy.position_avg_price
    float refATR = nz(entryATR, atr)

    if useBE and not beActiveS and (ep - close) >= refATR * beThresh
        beActiveS := true
        stopS := math.min(nz(stopS), ep - refATR * beOffset)

    if useTrail
        float _tTight = nz(dynTrailTightM, trailTightMult)
        float _tWide  = nz(dynTrailM, trailMult)
        float trailDist = partialFilled ? atr * _tTight : atr * _tWide
        float atrTrail  = close + trailDist
        if partialFilled and useRunnerMode
            stopS := math.min(nz(stopS), math.min(atrTrail, runnerHighRef))
        else
            stopS := math.min(nz(stopS), atrTrail)

    if usePartialTP
        float _pctS = nz(dynTP1ExitPct, tp1Pct)
        if not partialFilled
            strategy.exit("XS1", "Short", qty_percent=_pctS, stop=stopS, limit=tp1S)
            strategy.exit("XS2", "Short", qty_percent=100, stop=stopS, limit=tp2S)
        else if useRunnerMode
            strategy.exit("XS2", "Short", qty_percent=100, stop=stopS)
        else
            strategy.exit("XS2", "Short", qty_percent=100, stop=stopS, limit=tp2S)
    else
        strategy.exit("XS", "Short", stop=stopS, limit=tp2S)

// ---- FAILED BREAKOUT EXIT ----
if failedBrkLong
    strategy.close("Long", comment="FailBrk")
if failedBrkShort
    strategy.close("Short", comment="FailBrk")

// ---- TIMEOUT EXIT ----
if timeoutExit and strategy.position_size > 0
    strategy.close("Long", comment="Timeout")
if timeoutExit and strategy.position_size < 0
    strategy.close("Short", comment="Timeout")

// ---- STAGNATION EXIT ----
if stagnationExit and strategy.position_size > 0
    strategy.close("Long", comment="Stagnant")
if stagnationExit and strategy.position_size < 0
    strategy.close("Short", comment="Stagnant")

// ---- RESET ON FLAT ----
if strategy.position_size == 0
    stopL     := na
    tp1L      := na
    tp2L      := na
    stopS     := na
    tp1S      := na
    tp2S      := na
    beActiveL := false
    beActiveS := false

// ═══════════════════════════════════════════════════════════════
// VISUALS
// ═══════════════════════════════════════════════════════════════

plot(useTrend ? htfEma : na, "HTF EMA", color.new(#FF9800, 30), linewidth=2)
plot(useTrend ? htfEma + htfAtr * trendAtrMul : na, "HTF Upper", color.new(#FF9800, 70), style=plot.style_circles)
plot(useTrend ? htfEma - htfAtr * trendAtrMul : na, "HTF Lower", color.new(#FF9800, 70), style=plot.style_circles)

pU = plot(bbUpper, "BB Upper", color.new(#2196F3, 70))
pL = plot(bbLower, "BB Lower", color.new(#2196F3, 70))
plot(bbBasis, "Basis", color.new(#2196F3, 85))
fill(pU, pL, color=sqzOn ? color.new(#FFEB3B, 90) : color.new(#2196F3, 95))

plotshape(longCond  and strategy.position_size == 0, "Long",  shape.labelup,
     location.belowbar, color.new(#4CAF50, 0), text="L", textcolor=color.white, size=size.small)
plotshape(shortCond and strategy.position_size == 0, "Short", shape.labeldown,
     location.abovebar, color.new(#F44336, 0), text="S", textcolor=color.white, size=size.small)

plotshape(sqzOn and not sqzOn[1],  "Sqz Start",  shape.diamond, location.bottom, color.new(#FF9800, 60), size=size.tiny)
plotshape(not sqzOn and sqzOn[1] and lastSqzValid,      "Valid Release", shape.diamond, location.bottom, color.new(#4CAF50, 0),  size=size.tiny)
plotshape(not sqzOn and sqzOn[1] and not lastSqzValid,   "Weak Release",  shape.diamond, location.bottom, color.new(#F44336, 60), size=size.tiny)

plotshape(failedBrkLong  and strategy.position_size > 0, "FailBrk L", shape.cross, location.abovebar, color.new(#E91E63, 0), size=size.tiny)
plotshape(failedBrkShort and strategy.position_size < 0, "FailBrk S", shape.cross, location.belowbar, color.new(#E91E63, 0), size=size.tiny)
plotshape(timeoutExit    and strategy.position_size != 0, "Timeout",   shape.xcross, location.abovebar, color.new(#FF9800, 0), size=size.tiny)
plotshape(stagnationExit and strategy.position_size != 0, "Stagnant",  shape.xcross, location.belowbar, color.new(#9C27B0, 0), size=size.tiny)

plotshape(strategy.position_size > 0 and beActiveL and not beActiveL[1], "BE Long",  shape.flag, location.abovebar, color.new(#2196F3, 0), size=size.tiny)
plotshape(strategy.position_size < 0 and beActiveS and not beActiveS[1], "BE Short", shape.flag, location.belowbar, color.new(#2196F3, 0), size=size.tiny)

partialEvent = (partialFilled and not partialFilled[1])
plotshape(partialEvent and strategy.position_size > 0, "Partial L", shape.diamond, location.abovebar, color.new(#00BCD4, 0), size=size.tiny)
plotshape(partialEvent and strategy.position_size < 0, "Partial S", shape.diamond, location.belowbar, color.new(#00BCD4, 0), size=size.tiny)

plot(strategy.position_size > 0 ? stopL : na, "Long Stop",   color.new(#F44336, 30), style=plot.style_linebr)
plot(strategy.position_size > 0 and not partialFilled ? tp1L : na, "Long TP1", color.new(#00BCD4, 50), style=plot.style_linebr)
plot(strategy.position_size > 0 and not (useRunnerMode and partialFilled) ? tp2L : na, "Long TP2", color.new(#4CAF50, 30), style=plot.style_linebr)
plot(strategy.position_size < 0 ? stopS : na, "Short Stop",  color.new(#F44336, 30), style=plot.style_linebr)
plot(strategy.position_size < 0 and not partialFilled ? tp1S : na, "Short TP1", color.new(#00BCD4, 50), style=plot.style_linebr)
plot(strategy.position_size < 0 and not (useRunnerMode and partialFilled) ? tp2S : na, "Short TP2", color.new(#4CAF50, 30), style=plot.style_linebr)

rsiBlocked = useRsiFilter and ((strategy.position_size == 0 and allowLong and rsiVal >= rsiOBLevel) or (strategy.position_size == 0 and allowShort and rsiVal <= rsiOSLevel))
regimeBlocked = useRegime and not regimeOk
bgcolor(regimeBlocked ? color.new(#F44336, 93) : rsiBlocked ? color.new(#FF5722, 95) : sqzOn ? color.new(#FFEB3B, 95) : na, title="State BG")
trendBG = useTrend and not regimeBlocked ? (bullTrend ? color.new(#4CAF50, 96) : bearTrend ? color.new(#F44336, 96) : na) : na
bgcolor(trendBG, title="Trend BG")

// ═══════════════════════════════════════════════════════════════
// DASHBOARD
// ═══════════════════════════════════════════════════════════════

var table dash = table.new(position.top_right, 2, 16,
     bgcolor=color.new(#1a1a2e, 5), border_width=1, border_color=color.new(#333333, 0))

if barstate.islast
    table.cell(dash, 0, 0, "v15 / 2H", text_color=#aaaaaa, text_size=size.small)
    table.cell(dash, 1, 0, dirMode, text_color=#dddddd, text_size=size.small)

    table.cell(dash, 0, 1, "Regime",    text_color=#aaaaaa, text_size=size.small)
    table.cell(dash, 1, 1, regimeOk ? "OK" : "BLOCKED",
         text_color=regimeOk ? #4CAF50 : #F44336, text_size=size.small)

    table.cell(dash, 0, 2, "ADX",       text_color=#aaaaaa, text_size=size.small)
    adxText = not useAdxFilter ? "OFF" : na(regimeAdx) ? "N/A" : str.tostring(regimeAdx, "#.#")
    table.cell(dash, 1, 2, adxText,
         text_color=not useAdxFilter ? #666666 : adxOk ? #4CAF50 : #F44336, text_size=size.small)

    table.cell(dash, 0, 3, "EMA Dist",  text_color=#aaaaaa, text_size=size.small)
    table.cell(dash, 1, 3, str.tostring(emaDist, "#.#") + "%",
         text_color=emaDistOk ? #4CAF50 : #F44336, text_size=size.small)

    table.cell(dash, 0, 4, "Vol Ratio", text_color=#aaaaaa, text_size=size.small)
    vrText = na(regimeVolRatio) ? "N/A" : str.tostring(regimeVolRatio, "#.##") + "x"
    table.cell(dash, 1, 4, vrText,
         text_color=volRegOk ? #4CAF50 : #F44336, text_size=size.small)

    table.cell(dash, 0, 5, "RSI",       text_color=#aaaaaa, text_size=size.small)
    rsiColor = not useRsiFilter ? #666666 : rsiVal >= rsiOBLevel ? #F44336 : rsiVal <= rsiOSLevel ? #2196F3 : #4CAF50
    table.cell(dash, 1, 5, not useRsiFilter ? "OFF" : str.tostring(rsiVal, "#.#"),
         text_color=rsiColor, text_size=size.small)

    table.cell(dash, 0, 6, "Trend",     text_color=#aaaaaa, text_size=size.small)
    table.cell(dash, 1, 6, bullTrend ? "BULL" : bearTrend ? "BEAR" : "CHOP",
         text_color=bullTrend ? #4CAF50 : bearTrend ? #F44336 : #FF9800, text_size=size.small)

    table.cell(dash, 0, 7, "Squeeze",   text_color=#aaaaaa, text_size=size.small)
    table.cell(dash, 1, 7, sqzOn ? "ON (" + str.tostring(sqzDuration) + ")" : "OFF",
         text_color=sqzOn ? #FF9800 : #666666, text_size=size.small)

    table.cell(dash, 0, 8, "Momentum",  text_color=#aaaaaa, text_size=size.small)
    table.cell(dash, 1, 8, momUp ? "Bull" : momDown ? "Bear" : "Flat",
         text_color=momUp ? #4CAF50 : momDown ? #F44336 : #888888, text_size=size.small)

    table.cell(dash, 0, 9, "Cooldown",  text_color=#aaaaaa, text_size=size.small)
    cdLeft = dynCooldown - barsSinceExit
    cdText = cooldownOk ? "READY" : str.tostring(cdLeft) + (lastWasRunner and useSmartCool ? "f" : "")
    table.cell(dash, 1, 9, cdText,
         text_color=cooldownOk ? #4CAF50 : #FF9800, text_size=size.small)

    table.cell(dash, 0, 10, "Position", text_color=#aaaaaa, text_size=size.small)
    posText = strategy.position_size > 0 ? "LONG" : strategy.position_size < 0 ? "SHORT" : "FLAT"
    posClr  = strategy.position_size > 0 ? #4CAF50 : strategy.position_size < 0 ? #F44336 : #666666
    barsTxt = barsInTrade > 0 ? " (" + str.tostring(barsInTrade) + ")" : ""
    table.cell(dash, 1, 10, posText + barsTxt, text_color=posClr, text_size=size.small)

    table.cell(dash, 0, 11, "Partial TP", text_color=#aaaaaa, text_size=size.small)
    ptpStatus = not usePartialTP ? "OFF" : strategy.position_size == 0 ? "-" : partialFilled ? "TAKEN" : "PENDING"
    ptpClr = not usePartialTP ? #666666 : partialFilled ? #00BCD4 : #FF9800
    table.cell(dash, 1, 11, ptpStatus, text_color=ptpClr, text_size=size.small)

    table.cell(dash, 0, 12, "BE Stop",  text_color=#aaaaaa, text_size=size.small)
    beStatus = strategy.position_size == 0 ? "-" : (beActiveL or beActiveS) ? "ACTIVE" : "WAITING"
    table.cell(dash, 1, 12, beStatus,
         text_color=(beActiveL or beActiveS) ? #2196F3 : #666666, text_size=size.small)

    table.cell(dash, 0, 13, "Trail",    text_color=#aaaaaa, text_size=size.small)
    trailInfo = not useTrail ? "OFF" : partialFilled ? str.tostring(trailTightMult, "#.#") + " ATR" : str.tostring(trailMult, "#.#") + " ATR"
    table.cell(dash, 1, 13, trailInfo,
         text_color=partialFilled ? #00BCD4 : #aaaaaa, text_size=size.small)

    table.cell(dash, 0, 14, "Runner",    text_color=#aaaaaa, text_size=size.small)
    runText = not useRunnerMode ? "OFF" : strategy.position_size == 0 ? "-" : partialFilled ? "RUNNING" : "ARMED"
    runClr  = not useRunnerMode ? #666666 : partialFilled ? #00E676 : #FF9800
    table.cell(dash, 1, 14, runText, text_color=runClr, text_size=size.small)

    table.cell(dash, 0, 15, "Regime",    text_color=#aaaaaa, text_size=size.small)
    int _dispRegime = allowLong ? curRegimeLong : curRegimeShort
    regText = not useAdaptive ? "OFF" : _dispRegime == 1 ? "HOT" : _dispRegime == -1 ? "COLD" : "NORM"
    regClr  = not useAdaptive ? #666666 : _dispRegime == 1 ? #FF5722 : _dispRegime == -1 ? #2196F3 : #4CAF50
    table.cell(dash, 1, 15, regText, text_color=regClr, text_size=size.small)

    table.cell(dash, 0, 16, "Fav Excur", text_color=#aaaaaa, text_size=size.small)
    table.cell(dash, 1, 16, str.tostring(maxFavExcursion, "#.##"),
         text_color=maxFavExcursion > 0 ? #4CAF50 : #666666, text_size=size.small)

Overview

ETH Momentum Breakout identifies volatility compression by checking when Bollinger Bands fall within Keltner Channels for a minimum number of bars. It triggers breakout orders when price breaks out beyond a margin defined by ATR, provided market regime filters (such as long-term EMA distance, ADX, and higher-timeframe trend alignment) are satisfied. The strategy overlay plots directly on price charts. Inputs allow customization of squeeze thresholds, trend filters, ADX conditions, and trade direction.

Details

Published by

grinay on GitHub

Type

Strategy, Pine Script v5

Draws

On the price chart

Category

Breakout, Volatility & bands, Trend

Markets

Crypto

Timeframes

M15-H1

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

algorithmic-tradingbreakout-strategybybitcrypto-tradingcryptocurrencymomentum-strategypine-scripttrading-bottradingviewwebhook

Settings

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

SettingTypeDefaultVariable
Trade DirectionstringLong OnlydirMode
Enable Regime FilterbooltrueuseRegime
Regime TimeframetimeframeDregimeTF
ADX FilterboolfalseuseAdxFilter
ADX DI Lengthint14adxDiLen
ADX Smoothingint14adxSmooth
Min ADXfloat18.0adxMin
Max ADXfloat40.0adxMax
EMA Distance FilterbooltrueuseEmaDistFilter
Long-term EMA Lengthint200emaDistLen
Max Distance From EMA %float35.0emaDistMax
Volatility Regime FilterbooltrueuseVolRegime
Show all 40 settings
SettingTypeDefaultVariable
Vol History Lengthint100volRegimeLen
Max Vol Ratiofloat2.0volRegimeMax
Min Vol Ratiofloat0.4volRegimeMin
Enable HTF Trend FilterbooltrueuseTrend
Trend Timeframetimeframe60trendTF
Trend EMA Lengthint50trendEmaLen
Trend ATR Lengthint14trendAtrLen
EMA Buffer (ATR x)float0.5trendAtrMul
Require EMA Slope ConfirmationbooltrueuseSlope
Slope Lookback (HTF bars)int5slopeBars
BB Lengthint20bbLen
BB Multiplierfloat2.0bbMult
KC Lengthint20kcLen
KC Multiplierfloat1.5kcMult
Max Bars After Squeezeint6sqzMaxBars
Min Squeeze Duration (bars)int3sqzMinBars
Breakout Margin (ATR x)float0.3brkMargin
Min Candle Body %float40.0minBodyPct
Enable RSI Extreme FilterbooltrueuseRsiFilter
RSI Lengthint14rsiLen
Overbought Level (skip longs above)float75.0rsiOBLevel
Oversold Level (skip shorts below)float25.0rsiOSLevel
Enable Failed Breakout ExitboolfalseuseFailBrk
Check Within (bars)int3failBrkBars
Exit BelowstringEntry PricefailBrkRef
Volume SMA Lengthint20volLen
Spike Multiplierfloat1.8volMult
Momentum Lengthint12momLen
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

257 stars

This strategy plots VWMA-based Bollinger bands on the 1-minute ES futures price chart for volatility analysis and potential breakout identification. It calculates dynamic long and short stop loss and take profit levels using percentage inputs relative to entry

Strategy v6 Volatility & bandsSupport & resistance
Updated 6 weeks ago · None stated GitHub repository of V1.6 ES - Futures 1M No Repaint
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 均线突破交易系统
16 stars

This trend-following strategy adapts classic Donchian breakout channels according to market volatility regimes. It calculates an ATR percentile rank across a rolling lookback period, shifting dynamically between tighter breakout channels with smaller stops dur

Strategy v6 TrendBreakout
Updated 11 months ago · None stated GitHub repository of Seykota Alt 1: Adaptive Volatility

This strategy executes breakout trades using two Donchian Channels alongside an optional 200-period EMA trend filter. Entries trigger when the price sets a new highest high or lowest low over the primary Donchian Channel period. Exits are managed via a shorter

Strategy v5 BreakoutTrend
Updated 1 year ago · None stated GitHub repository of Ruckard Walcrypto DoubleDonchian B002

Breakout Strategy

by JoelPasapera
11 stars

This strategy implements a channel breakout system directly on the main price chart. It calculates highest highs and lowest lows over configurable entry and exit periods using historical candles. A long entry triggers when the close breaks above the entry chan

Strategy v6 BreakoutTrend
Updated 5 months ago · Apache-2.0 GitHub repository of Breakout 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