//@version=6 indicator("Bullmania Breaker v1", shorttitle="Breaker v1", overlay=true) // Fresh concept: squeeze-and-release breakout with trend bias and volatility/volume confirmation. // Trend filters emaFastLen = input.int(50, "EMA Fast", minval=1, group="Trend") emaSlowLen = input.int(200, "EMA Slow", minval=1, group="Trend") trendBuffer = input.float(0.10, "Trend buffer %", minval=0.0, step=0.05, group="Trend", tooltip="Optional gap between fast/slow EMAs to avoid flat cross noise.") // Squeeze and release bbLen = input.int(20, "BB Length", minval=1, group="Squeeze") bbMult = input.float(2.0, "BB Multiplier", minval=0.1, step=0.1, group="Squeeze") squeezeLookback = input.int(120, "Squeeze lookback", minval=10, group="Squeeze", tooltip="Bars to normalize band width. Larger = more stable percentile range.") squeezeThreshold = input.float(0.25, "Squeeze threshold (0-1)", minval=0.0, maxval=1.0, step=0.05, group="Squeeze", tooltip="Normalized band width must be below this to count as a squeeze.") releaseThreshold = input.float(0.35, "Release threshold (0-1)", minval=0.0, maxval=1.0, step=0.05, group="Squeeze", tooltip="Normalized band width must expand above this after a squeeze.") releaseWindow = input.int(30, "Release window bars", minval=1, group="Squeeze", tooltip="How many bars after a squeeze we still allow a release breakout.") // Confirmation and filters confirmPct = input.float(0.25, "Confirm move % (next bar)", minval=0.0, maxval=3.0, step=0.05, group="Confirmation", tooltip="Additional move required on the next bar after breakout. Set to 0 to disable.") volLookback = input.int(20, "Volume MA length", minval=1, group="Confirmation") volSpike = input.float(1.2, "Volume spike x", minval=0.5, step=0.05, group="Confirmation", tooltip="Volume ratio vs MA required on breakout.") adxLen = input.int(14, "ADX length", minval=1, group="Confirmation") adxMin = input.int(18, "ADX minimum", minval=0, maxval=100, group="Confirmation") rsiLen = input.int(14, "RSI length", minval=2, group="Confirmation") rsiLongMin = input.float(48, "RSI long min", minval=0, maxval=100, group="Confirmation") rsiLongMax = input.float(68, "RSI long max", minval=0, maxval=100, group="Confirmation") rsiShortMin = input.float(32, "RSI short min", minval=0, maxval=100, group="Confirmation") rsiShortMax = input.float(60, "RSI short max", minval=0, maxval=100, group="Confirmation") priceRangeLookback = input.int(100, "Price position lookback", minval=10, group="Confirmation") longPosMax = input.float(88, "Long max position %", minval=0, maxval=100, group="Confirmation", tooltip="Cap longs when price is near top of range.") shortPosMin = input.float(12, "Short min position %", minval=0, maxval=100, group="Confirmation", tooltip="Floor shorts when price is near bottom of range.") cooldownBars = input.int(5, "Bars between signals", minval=0, maxval=100, group="Confirmation") // Core series emaFast = ta.ema(close, emaFastLen) emaSlow = ta.ema(close, emaSlowLen) trendGap = (emaSlow == 0.0) ? 0.0 : math.abs((emaFast - emaSlow) / emaSlow) * 100 trendLong = emaFast > emaSlow and trendGap >= trendBuffer trendShort = emaFast < emaSlow and trendGap >= trendBuffer basis = ta.sma(close, bbLen) dev = bbMult * ta.stdev(close, bbLen) upper = basis + dev lower = basis - dev widthPct = basis == 0.0 ? 0.0 : (upper - lower) / basis * 100.0 lowW = ta.lowest(widthPct, squeezeLookback) highW = ta.highest(widthPct, squeezeLookback) denom = math.max(highW - lowW, 0.0001) normWidth = (widthPct - lowW) / denom squeeze = normWidth < squeezeThreshold barsSinceSqueeze = ta.barssince(squeeze) recentSqueeze = not na(barsSinceSqueeze) and barsSinceSqueeze <= releaseWindow release = normWidth > releaseThreshold and recentSqueeze volMA = ta.sma(volume, volLookback) volumeRatio = volMA == 0.0 ? 0.0 : volume / volMA rsi = ta.rsi(close, rsiLen) [_, _, adxVal] = ta.dmi(adxLen, adxLen) highestRange = ta.highest(high, priceRangeLookback) lowestRange = ta.lowest(low, priceRangeLookback) rangeSpan = math.max(highestRange - lowestRange, 0.0001) pricePos = ((close - lowestRange) / rangeSpan) * 100 // Breakout candidates breakoutLong = trendLong and release and close > upper and volumeRatio >= volSpike and adxVal >= adxMin and rsi >= rsiLongMin and rsi <= rsiLongMax and pricePos <= longPosMax breakoutShort = trendShort and release and close < lower and volumeRatio >= volSpike and adxVal >= adxMin and rsi >= rsiShortMin and rsi <= rsiShortMax and pricePos >= shortPosMin // Cooldown gate var int lastSignalBar = na cooldownOk = na(lastSignalBar) or (bar_index - lastSignalBar > cooldownBars) // Two-stage confirmation var int pendingDir = 0 var float pendingPrice = na var int pendingBar = na var int pendingCount = na finalLong = false finalShort = false if cooldownOk if breakoutLong pendingDir := 1 pendingPrice := close pendingBar := bar_index pendingCount := nz(pendingCount, 0) + 1 if breakoutShort pendingDir := -1 pendingPrice := close pendingBar := bar_index pendingCount := nz(pendingCount, 0) + 1 if pendingDir != 0 and bar_index == pendingBar + 1 longPass = pendingDir == 1 and (confirmPct <= 0 or close >= pendingPrice * (1 + confirmPct / 100.0)) shortPass = pendingDir == -1 and (confirmPct <= 0 or close <= pendingPrice * (1 - confirmPct / 100.0)) if longPass finalLong := true lastSignalBar := bar_index if shortPass finalShort := true lastSignalBar := bar_index pendingDir := 0 pendingPrice := na pendingBar := na // Plots plot(emaFast, "EMA Fast", color=color.new(color.green, 0), linewidth=1) plot(emaSlow, "EMA Slow", color=color.new(color.red, 0), linewidth=1) plot(basis, "BB Basis", color=color.new(color.gray, 50)) plot(upper, "BB Upper", color=color.new(color.teal, 40)) plot(lower, "BB Lower", color=color.new(color.teal, 40)) plotshape(squeeze, title="Squeeze", location=location.bottom, color=color.new(color.blue, 10), style=shape.square, size=size.tiny) plotshape(release, title="Release", location=location.bottom, color=color.new(color.orange, 0), style=shape.triangleup, size=size.tiny) plotshape(finalLong, title="Buy", location=location.belowbar, color=color.lime, style=shape.circle, size=size.small, text="B") plotshape(finalShort, title="Sell", location=location.abovebar, color=color.red, style=shape.circle, size=size.small, text="S") // Alerts baseCurrency = str.replace(syminfo.ticker, "USDT", "") baseCurrency := str.replace(baseCurrency, "USD", "") baseCurrency := str.replace(baseCurrency, "PERP", "") ver = "breaker_v1" common = " | ADX:" + str.tostring(adxVal, "#.##") + " | RSI:" + str.tostring(rsi, "#.##") + " | VOL:" + str.tostring(volumeRatio, "#.##") + " | POS:" + str.tostring(pricePos, "#.##") + " | IND:" + ver longMsg = baseCurrency + " buy " + timeframe.period + common shortMsg = baseCurrency + " sell " + timeframe.period + common if finalLong alert(longMsg, alert.freq_once_per_bar_close) if finalShort alert(shortMsg, alert.freq_once_per_bar_close)