Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //@version=6
- strategy("Black Edge Strategy V5.10.2b",
- overlay=true,
- pyramiding=0,
- calc_on_every_tick=true)
- // ============================================================
- // CHANGELOG V5.10.2b (vs V5.10.2)
- // ------------------------------------------------------------
- // CHANGE — EMA 9 added
- // New ema9Len input + ema9 series, plotted on-chart.
- // Informational only — not referenced by any setup's
- // detection logic. For Eddie's discretionary reading.
- //
- // REMOVE — S1 and S1+ removed entirely
- // Deleted the S1+ Hammer/Shooting Star structure block,
- // the SETUP 1 (Pullback + Rejection + Confirmation)
- // block, and the SETUP 1+ (Hammer/Shooting Star +
- // Volume + Confirmation) block. longS1/shortS1 and
- // longS1Plus/shortS1Plus no longer exist as variables.
- // Removed downstream: S1/S1+ from FINAL SIGNALS,
- // PREVIEW vs CONFIRMED state, on-chart LABELS, and
- // WEBHOOK ALERTS. Also removed the now-dangling
- // `not longS1 and not longS1Plus` / `not shortS1 and
- // not shortS1Plus` clauses from the S2R exclusion guard
- // — those referenced variables that no longer exist.
- // S2R ignition sources were already S2/S3 only (see
- // NOTE 3 in V5.9 changelog below), so this is a pure
- // cleanup with no change to S2R's firing behavior.
- // bullReject/bearReject (CANDLE STRUCTURE block) are
- // left in place even though S1 was their only consumer
- // — kept as reference building blocks for a future S1
- // redesign, not deleted by default. Flag if you want
- // them stripped too.
- //
- // REMOVE — S2R webhook alert removed
- // S2R keeps full detection, strategy entries, and
- // on-chart labels — only the outbound alert() firing
- // for confirmedLongS2R/confirmedShortS2R is gone.
- // Matches the 22/08 precedent already applied on the
- // n8n side (S2R removed from Secondary/Shadow/Webhook
- // Layer 3 assessment). Eddie reads S2R on-chart at his
- // discretion; it no longer reaches Telegram.
- //
- // CHANGE — Emojis removed from S2/S3/S2R on-chart labels
- // plotshape `text=` values stripped of emoji prefixes
- // (e.g. "🔥 S2" -> "S2", "PRE ⚡ S3" -> "PRE S3").
- // alert() JSON payloads never contained emojis, so
- // Telegram-side formatting is unaffected — Telegram
- // keeps its own icons per Format Telegram V1.
- //
- // CHANGE — strategy() title renamed to "Black Edge Strategy"
- // Was "Core Setup Strategy" — brand alignment with the
- // BlackDaddy Edge System naming used across Notion/docs.
- // File name on disk unchanged (setup-family naming).
- // ============================================================
- // ============================================================
- // CHANGELOG V5.10.2 (vs V5.10.1)
- // ------------------------------------------------------------
- // FIX — S2 takes priority over S3 when both conditions overlap
- // On bars where price breaks high[1] AND SMA8/SMA21 cross
- // fires simultaneously, V5.10.1 classified the bar as S3
- // (cross ignition) because S3's body threshold (0.4) is
- // lower than S2's (0.5). A medium-bodied breakout candle
- // would satisfy S3 but not S2, mislabelling a momentum
- // ignition as a cross ignition.
- // Observed on: XLMUSDT 2026-05-28 30m — S3 printed on a
- // clean reclaim of 0.1820 with expanding volume. Correct
- // classification is S2 (momentum ignition). Trader waited
- // for a pullback to MAs (S3 playbook) and missed the move.
- // Fix: S3 now excludes longS2/shortS2 — if price breaks
- // high[1]/low[1] with a body > 50% range on the same bar
- // as a cross, S2 fires. S3 only fires when no S2 is present.
- // Semantic rationale: a bar that breaks the prior high on
- // a MA cross is a momentum event, not a cross event.
- // ============================================================
- // ============================================================
- // CHANGELOG V5.10.1 (vs V5.10)
- // ------------------------------------------------------------
- // FIX — S2R mutual exclusion with all higher-priority setups
- // With maxLateATR increased to 2.0 in V5.10, S2R and S1
- // could both fire on the same bar — two labels printed
- // on the chart. This created a mismatch with N8N scanners
- // which use a priority chain (S1 > S1+ > S2 > S3 > S2R)
- // and only send one Telegram alert per bar.
- // Fix: S2R now excludes all four higher-priority setups:
- // and not longS1 and not longS1Plus and not longS2 and not longS3
- // S2/S3 overlap with S2R is structurally rare (breakout
- // candle vs. pause candle are contradictory) but guarded
- // for full alignment with the N8N priority chain.
- // ============================================================
- // ============================================================
- // CHANGELOG V5.10 (vs V5.9)
- // ------------------------------------------------------------
- // CHANGE — maxLateATR default increased from 1.5 to 2.0
- // V5.9 suppressed valid S2 and S2R signals on strong
- // trending coins where price moved beyond 1.5 ATR from
- // EMA50 before the setup confirmed. Observed on:
- // - BNBUSDT 2026-05-29: two S2s fired, relaunch at 21:00
- // suppressed by notLateLong despite clean consolidation
- // - ALGOUSDT 2026-05-29: launch candle at 19:30 suppressed
- // despite bullTrend confirmed and price above EMA50
- // Increasing to 2.0 ATR gives stronger trending coins more
- // room to fire signals during sustained momentum moves.
- // bullTrend definition intentionally unchanged — the lag
- // on SMA8/SMA21 cross is a feature that protects against
- // false breakouts into resistance zones.
- // TEST VERSION — validate on live charts before adopting
- // as permanent default. Revert to 1.5 if over-firing.
- // ============================================================
- // ============================================================
- // CHANGELOG V5.9 (vs V5.8)
- // ------------------------------------------------------------
- // FIX 1 — entryATR reset bug resolved
- // V5.8 had `if strategy.position_size == 0: entryATR := na`
- // running on the same bar as the entry signal. Because
- // Pine Script fills orders between bars (not mid-execution),
- // position_size is still 0 throughout that bar's script run.
- // This caused entryATR to be set then immediately reset to
- // na on the entry bar, so SL/TP exits never fired.
- // Guard now reads:
- // if strategy.position_size == 0 and not longSignal and not shortSignal
- // ensuring the reset only happens when truly flat with no
- // new entry firing on the current bar.
- //
- // NOTE 1 — pullbackLong / pullbackShort are intentionally identical
- // (see inline comment at that block — by design).
- //
- // NOTE 2 — pausingLong / pausingShort are intentionally identical
- // (see inline comment at that block — by design).
- //
- // NOTE 3 — S1 / S1+ are intentionally excluded from S2R ignitions
- // S2R is designed to continue momentum from a decisive
- // breakout candle (S2 = momentum ignition, S3 = MA cross
- // ignition). S1/S1+ are pullback-rejection setups — they
- // signal mean-reversion into the zone, not a momentum burst.
- // Including them as S2R ignition sources would fire S2R
- // on normal price action that doesn't carry ignition momentum.
- // (S1/S1+ were removed entirely in V5.10.2b — this note is
- // preserved as historical record of the original design intent.)
- // ============================================================
- // ============================================================
- // CHANGELOG V5.8 (vs V5.7)
- // ------------------------------------------------------------
- // FIX 1 — S2R one-signal-per-ignition guard added
- // V5.7 allowed S2R to fire on consecutive pause bars
- // after the same ignition event (e.g. 3 S2Rs from one
- // S2). A fired flag now blocks subsequent S2R signals
- // until a new S2 or S3 ignition resets it — matching
- // the same pattern used for S3 double-signal guard.
- //
- // FIX 2 — pausingLong/Short threshold relaxed from 0.8 to 0.9
- // Strong trending coins (APTUSDT) grind up with candles
- // slightly below average range but not below 80%.
- // 0.9 threshold catches these shallow pauses correctly.
- // ============================================================
- // ============================================================
- // CHANGELOG V5.7 (vs V5.6)
- // ------------------------------------------------------------
- // close > high[1] removed from S2R — contradicted pausingLong
- // ============================================================
- // ============================================================
- // CHANGELOG V5.6 (vs V5.5)
- // ------------------------------------------------------------
- // S2R consolidation detection redesigned — momentum pause
- // ============================================================
- // ============================================================
- // CHANGELOG V5.5 (vs V5.4)
- // ------------------------------------------------------------
- // S2R lookback increased to 15 bars default
- // ============================================================
- // ============================================================
- // CHANGELOG V5.1 (vs V5)
- // ------------------------------------------------------------
- // FIX 1 — Pullback zone upper AND lower bound
- // FIX 2 — S2 volume filter reverted (ignition = supply exhaustion)
- // FIX 3 — S3 double-signal guard
- // FIX 4 — Entry requires position_size == 0
- // FIX 5 — ATR locked at entry
- // ============================================================
- // ============================================================
- // INPUTS
- // ============================================================
- fastLen = input.int(8, "SMA 8")
- slowLen = input.int(21, "SMA 21")
- ema9Len = input.int(9, "EMA 9")
- ema50Len = input.int(50, "EMA 50")
- ema200Len = input.int(200, "EMA 200")
- atrLen = input.int(14, "ATR Length")
- pullbackATR = input.float(0.4, "Pullback tolerance ATR", step=0.1)
- maxLateATR = input.float(2.0, "Max distance from EMA50 ATR", step=0.1)
- stopATR = input.float(1.2, "Stop ATR", step=0.1)
- targetRR = input.float(2.0, "Target RR", step=0.1)
- s2rLookback = input.int(15, "S2R Lookback bars", minval=5, maxval=25)
- s2rMinBars = input.int(4, "S2R Min bars since ignition", minval=2, maxval=8)
- s2rPauseBars = input.int(4, "S2R Consolidation bars", minval=2, maxval=8)
- // ============================================================
- // MOVING AVERAGES
- // ============================================================
- sma8 = ta.sma(close, fastLen)
- sma21 = ta.sma(close, slowLen)
- ema9 = ta.ema(close, ema9Len)
- ema50 = ta.ema(close, ema50Len)
- ema200 = ta.ema(close, ema200Len)
- atr = ta.atr(atrLen)
- // ============================================================
- // TREND BIAS
- // ============================================================
- bullTrend = sma8 > sma21 and close > ema50
- bearTrend = sma8 < sma21 and close < ema50
- // ============================================================
- // PULLBACK ZONE (upper AND lower bound)
- // ------------------------------------------------------------
- // pullbackLong and pullbackShort are intentionally identical.
- // Both simply ask: "is the candle overlapping the MA zone?"
- // Directional filtering is handled downstream by bullTrend /
- // bearTrend and the bullReject / bearReject candle conditions.
- // Separate names are kept for readability at the callsite.
- // ============================================================
- zoneTop = math.max(sma8, sma21)
- zoneBot = math.min(sma8, sma21)
- pullbackLong = low <= zoneTop + atr * pullbackATR and high >= zoneBot - atr * pullbackATR
- pullbackShort = high >= zoneBot - atr * pullbackATR and low <= zoneTop + atr * pullbackATR
- // ============================================================
- // NOT TOO LATE (distance from EMA50 cap)
- // ============================================================
- notLateLong = (close - ema50) <= atr * maxLateATR
- notLateShort = (ema50 - close) <= atr * maxLateATR
- // ============================================================
- // VOLUME CONTEXT
- // ============================================================
- volMA = ta.sma(volume, 20)
- volumeSpike = not na(volMA) and volume > volMA * 1.5
- volumeExpansion = not na(volMA) and volume > volMA * 1.2
- volumeState = volumeSpike ? "spike" : "normal"
- // ============================================================
- // CANDLE STRUCTURE
- // ============================================================
- barRange = math.max(high - low, syminfo.mintick)
- body = math.abs(close - open)
- upperWick = high - math.max(open, close)
- lowerWick = math.min(open, close) - low
- bullReject = pullbackLong and close > open and close >= low + barRange * 0.6 and lowerWick > body * 0.3
- bearReject = pullbackShort and close < open and close <= high - barRange * 0.6 and upperWick > body * 0.3
- // ============================================================
- // SETUP 2 — MOMENTUM IGNITION (no volume filter — intentional)
- // ------------------------------------------------------------
- // Volume filter removed in V5.1 — ignition candles fire on
- // low/average volume (supply exhaustion). Volume expands after
- // the ignition bar, not on it.
- // ============================================================
- longS2 = bullTrend and close > high[1] and close > sma8 and notLateLong and body > barRange * 0.5
- shortS2 = bearTrend and close < low[1] and close < sma8 and notLateShort and body > barRange * 0.5
- // ============================================================
- // SETUP 3 — CROSS IGNITION (double-signal guard)
- // ============================================================
- crossUpNow = ta.crossover(sma8, sma21)
- crossDnNow = ta.crossunder(sma8, sma21)
- var bool s3LongFired = false
- var bool s3ShortFired = false
- if not (crossUpNow or crossUpNow[1])
- s3LongFired := false
- if not (crossDnNow or crossDnNow[1])
- s3ShortFired := false
- longS3Raw = (
- (crossUpNow or crossUpNow[1]) and
- close >= ema50 and
- close > sma8 and
- close > open and
- body > barRange * 0.4
- )
- shortS3Raw = (
- (crossDnNow or crossDnNow[1]) and
- close <= ema50 and
- close < sma8 and
- close < open and
- body > barRange * 0.4
- )
- longS3 = longS3Raw and not s3LongFired and not longS2
- shortS3 = shortS3Raw and not s3ShortFired and not shortS2
- if longS3
- s3LongFired := true
- if shortS3
- s3ShortFired := true
- // ============================================================
- // SETUP S2R — CONSOLIDATION BREAK
- // ------------------------------------------------------------
- // Fires when price momentum pauses after a confirmed S2/S3
- // ignition, then resumes with a conviction candle.
- //
- // Ignition sources: S2 and S3 only (intentional — see NOTE 3
- // in V5.9 changelog). S1/S1+ are pullback-rejection setups,
- // not momentum ignitions, and are excluded by design. (S1/S1+
- // were removed entirely in V5.10.2b — the S2/S3-only ignition
- // source is unchanged, this is just a cleanup note.)
- //
- // Momentum pause = current candle range < 90% of average range
- // over the last s2rPauseBars bars. Threshold relaxed from 0.8
- // to 0.9 in V5.8 to catch shallow pauses on strong trend coins.
- //
- // One-signal-per-ignition guard added in V5.8 — s2rLongFired
- // flag prevents multiple S2R signals from the same ignition
- // event. Resets when a new S2 or S3 fires.
- // ============================================================
- barsSinceIgnitionLong = ta.barssince(longS2 or longS3)
- barsSinceIgnitionShort = ta.barssince(shortS2 or shortS3)
- recentLongIgnition = barsSinceIgnitionLong >= s2rMinBars and barsSinceIgnitionLong <= s2rLookback
- recentShortIgnition = barsSinceIgnitionShort >= s2rMinBars and barsSinceIgnitionShort <= s2rLookback
- // Momentum pause — range below 90% of recent average.
- // pausingLong and pausingShort are intentionally identical: a
- // range contraction is direction-agnostic. Separate names are
- // kept so each signal's condition reads symmetrically at the
- // longS2R / shortS2R callsite below.
- avgRange = ta.sma(high - low, s2rPauseBars)
- pausingLong = (high - low) < avgRange * 0.9
- pausingShort = (high - low) < avgRange * 0.9
- // One-signal-per-ignition guard
- var bool s2rLongFired = false
- var bool s2rShortFired = false
- // Reset fired flag when a new ignition fires
- if longS2 or longS3
- s2rLongFired := false
- if shortS2 or shortS3
- s2rShortFired := false
- longS2R = bullTrend and recentLongIgnition and pausingLong and close > open and body > barRange * 0.4 and notLateLong and not s2rLongFired and not longS2 and not longS3
- shortS2R = bearTrend and recentShortIgnition and pausingShort and close < open and body > barRange * 0.4 and notLateShort and not s2rShortFired and not shortS2 and not shortS3
- if longS2R
- s2rLongFired := true
- if shortS2R
- s2rShortFired := true
- // ============================================================
- // FINAL SIGNALS
- // ============================================================
- longSignal = longS2 or longS3 or longS2R
- shortSignal = shortS2 or shortS3 or shortS2R
- // ============================================================
- // PREVIEW vs CONFIRMED
- // ============================================================
- isPreviewBar = barstate.isrealtime and not barstate.isconfirmed
- isConfirmedBar = barstate.isconfirmed
- previewLongS2 = longS2 and isPreviewBar
- previewShortS2 = shortS2 and isPreviewBar
- previewLongS3 = longS3 and isPreviewBar
- previewShortS3 = shortS3 and isPreviewBar
- previewLongS2R = longS2R and isPreviewBar
- previewShortS2R = shortS2R and isPreviewBar
- confirmedLongS2 = longS2 and isConfirmedBar
- confirmedShortS2 = shortS2 and isConfirmedBar
- confirmedLongS3 = longS3 and isConfirmedBar
- confirmedShortS3 = shortS3 and isConfirmedBar
- confirmedLongS2R = longS2R and isConfirmedBar
- confirmedShortS2R = shortS2R and isConfirmedBar
- // ============================================================
- // LABELS
- // ============================================================
- // Preview labels (semi-transparent — bar not yet closed)
- plotshape(previewLongS2, title="PREVIEW LONG S2", style=shape.labelup, location=location.belowbar, text="PRE S2", size=size.small, color=color.new(color.blue, 55), textcolor=color.white)
- plotshape(previewShortS2, title="PREVIEW SHORT S2", style=shape.labeldown, location=location.abovebar, text="PRE S2", size=size.small, color=color.new(color.orange, 55), textcolor=color.white)
- plotshape(previewLongS3, title="PREVIEW LONG S3", style=shape.labelup, location=location.belowbar, text="PRE S3", size=size.small, color=color.new(color.purple, 55), textcolor=color.white)
- plotshape(previewShortS3, title="PREVIEW SHORT S3", style=shape.labeldown, location=location.abovebar, text="PRE S3", size=size.small, color=color.new(color.fuchsia,55), textcolor=color.white)
- plotshape(previewLongS2R, title="PREVIEW LONG S2R", style=shape.labelup, location=location.belowbar, text="PRE S2R", size=size.small, color=color.new(color.teal, 55), textcolor=color.white)
- plotshape(previewShortS2R, title="PREVIEW SHORT S2R", style=shape.labeldown, location=location.abovebar, text="PRE S2R", size=size.small, color=color.new(color.teal, 55), textcolor=color.white)
- // Confirmed labels (fully opaque — bar closed)
- plotshape(confirmedLongS2, title="LONG S2", style=shape.labelup, location=location.belowbar, text="S2", size=size.small, color=color.new(color.blue, 0), textcolor=color.white)
- plotshape(confirmedShortS2, title="SHORT S2", style=shape.labeldown, location=location.abovebar, text="S2", size=size.small, color=color.new(color.orange, 0), textcolor=color.white)
- plotshape(confirmedLongS3, title="LONG S3", style=shape.labelup, location=location.belowbar, text="S3", size=size.small, color=color.new(color.purple, 0), textcolor=color.white)
- plotshape(confirmedShortS3, title="SHORT S3", style=shape.labeldown, location=location.abovebar, text="S3", size=size.small, color=color.new(color.fuchsia,0), textcolor=color.white)
- plotshape(confirmedLongS2R, title="LONG S2R", style=shape.labelup, location=location.belowbar, text="S2R", size=size.normal, color=color.new(color.teal, 0), textcolor=color.white)
- plotshape(confirmedShortS2R, title="SHORT S2R", style=shape.labeldown, location=location.abovebar, text="S2R", size=size.normal, color=color.new(color.teal, 0), textcolor=color.white)
- // ============================================================
- // WEBHOOK CONTEXT
- // ============================================================
- is30m = timeframe.period == "30"
- // Note: alerts only fire on 30m timeframe by design.
- // If you add this script to other timeframes, alerts will
- // silently not fire — this is intentional.
- trendAlignmentLong = close > ema200 ? "bullish" : "bearish"
- trendAlignmentShort = close < ema200 ? "bearish" : "bullish"
- // ============================================================
- // WEBHOOK ALERTS — CONFIRMED BARS, 30m ONLY
- // ------------------------------------------------------------
- // S1/S1+ removed entirely (setups deleted in V5.10.2b).
- // S2R removed from this section (V5.10.2b) — S2R still fires,
- // plots, and enters/exits per FINAL SIGNALS, it just no longer
- // sends a webhook alert. Only S2 and S3 reach Telegram.
- // ============================================================
- if confirmedLongS2 and is30m
- alert('{"setup":"S2","side":"LONG","symbol":"' + syminfo.ticker + '","timeframe":"30m","price":"' + str.tostring(close) + '","status":"confirmed","trend":"' + trendAlignmentLong + '","volume":"' + volumeState + '"}', alert.freq_once_per_bar_close)
- if confirmedShortS2 and is30m
- alert('{"setup":"S2","side":"SHORT","symbol":"' + syminfo.ticker + '","timeframe":"30m","price":"' + str.tostring(close) + '","status":"confirmed","trend":"' + trendAlignmentShort + '","volume":"' + volumeState + '"}', alert.freq_once_per_bar_close)
- if confirmedLongS3 and is30m
- alert('{"setup":"S3","side":"LONG","symbol":"' + syminfo.ticker + '","timeframe":"30m","price":"' + str.tostring(close) + '","status":"confirmed","trend":"' + trendAlignmentLong + '","volume":"' + volumeState + '"}', alert.freq_once_per_bar_close)
- if confirmedShortS3 and is30m
- alert('{"setup":"S3","side":"SHORT","symbol":"' + syminfo.ticker + '","timeframe":"30m","price":"' + str.tostring(close) + '","status":"confirmed","trend":"' + trendAlignmentShort + '","volume":"' + volumeState + '"}', alert.freq_once_per_bar_close)
- // ============================================================
- // ENTRIES: position must be flat before new entry
- // ============================================================
- if longSignal and isConfirmedBar and strategy.position_size == 0
- strategy.entry("Long", strategy.long)
- if shortSignal and isConfirmedBar and strategy.position_size == 0
- strategy.entry("Short", strategy.short)
- // ============================================================
- // EXITS: ATR locked at entry, not recalculated
- // ------------------------------------------------------------
- // entryATR is set on the signal bar (position_size still 0
- // because Pine fills orders between bars, not mid-execution).
- // The reset guard `not longSignal and not shortSignal` ensures
- // the na-reset does NOT run on the same bar as an entry,
- // which was the V5.8 bug that prevented exits from ever firing.
- // ============================================================
- var float entryATR = na
- if longSignal and isConfirmedBar and strategy.position_size == 0
- entryATR := atr
- if shortSignal and isConfirmedBar and strategy.position_size == 0
- entryATR := atr
- if strategy.position_size == 0 and not longSignal and not shortSignal
- entryATR := na
- if strategy.position_size > 0 and not na(entryATR)
- longStopPrice = strategy.position_avg_price - entryATR * stopATR
- longTakePrice = strategy.position_avg_price + (strategy.position_avg_price - longStopPrice) * targetRR
- strategy.exit("Long Exit", from_entry="Long", stop=longStopPrice, limit=longTakePrice)
- if strategy.position_size < 0 and not na(entryATR)
- shortStopPrice = strategy.position_avg_price + entryATR * stopATR
- shortTakePrice = strategy.position_avg_price - (shortStopPrice - strategy.position_avg_price) * targetRR
- strategy.exit("Short Exit", from_entry="Short", stop=shortStopPrice, limit=shortTakePrice)
- // ============================================================
- // PLOTS
- // ============================================================
- plot(sma8, "SMA 8", color=color.white, linewidth=2)
- plot(sma21, "SMA 21", color=color.yellow, linewidth=2)
- plot(ema9, "EMA 9", color=color.aqua, linewidth=1)
- plot(ema50, "EMA 50", color=color.orange, linewidth=2)
- plot(ema200, "EMA 200", color=color.blue, linewidth=2)
Advertisement
Add Comment
Please, Sign In to add comment