// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ // © GBB — "no hype, just data" //@version=6 indicator("Silver Bullet [GBB]", shorttitle = "SB [GBB]", overlay = true, behind_chart = false, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500, max_bars_back = 500) // --------------------------------------------------------------------------- // 1. Inputs // --------------------------------------------------------------------------- const string TZ = "America/New_York" g_win = "Windows (New York time)" g_liq = "Liquidity" g_swp = "Sweep" g_dsp = "Displacement" g_ent = "Entry" g_risk = "Risk" g_cost = "Costs (stats only)" g_look = "Display" g_tab = "Stats panel" g_alr = "Alerts" modeIn = input.string("Silver Bullet", "Mode", options = ["Silver Bullet", "Hour Scan"], group = g_win, tooltip = "Silver Bullet: only the windows below. Hour Scan: the identical rules run in every clock hour of the session, each hour on its own, so the Silver Bullet hours can be compared with all the others.") ldnOn = input.bool(true, "LDN 03:00-04:00", group = g_win) amOn = input.bool(true, "AM 10:00-11:00", group = g_win) pmOn = input.bool(true, "PM 14:00-15:00", group = g_win) cusOn = input.bool(false, "Custom", inline = "cus", group = g_win) cusSess = input.session("0930-1030", "", inline = "cus", group = g_win, tooltip = "Silver Bullet mode only. New York time; must not cross midnight. The study tested clock hours only.") useSwing = input.bool(true, "Swing highs / lows", group = g_liq) pivL = input.int(5, "Pivot left", minval = 1, inline = "piv", group = g_liq) pivR = input.int(5, "right", minval = 1, inline = "piv", group = g_liq) pivLook = input.int(300, "Swing lookback (bars)", minval = 10, group = g_liq) useAsia = input.bool(true, "Asia high / low (20:00-24:00)", group = g_liq) useLdn = input.bool(true, "London high / low (02:00-05:00)", group = g_liq) usePd = input.bool(true, "Previous day high / low", group = g_liq) usePre = input.bool(true, "Pre-window range (previous clock hour)", group = g_liq) swReqIn = input.string("Required", "Sweep", options = ["Required", "Not required"], group = g_swp, tooltip = "Required: the FVG must follow a sweep of a buy-side (for shorts) or sell-side (for longs) level in the same window.") closeBk = input.bool(false, "Sweep bar must close back", group = g_swp) atrLen = input.int(14, "ATR length", minval = 1, group = g_dsp) kBody = input.float(1.0, "Middle candle body >= ATR x", minval = 0, step = 0.1, group = g_dsp) gapAtr = input.float(0.1, "Min gap (ATR x)", minval = 0, step = 0.05, inline = "gap", group = g_dsp) gapTicks = input.int(2, "and ticks", minval = 1, inline = "gap", group = g_dsp) entryIn = input.string("Proximal edge", "Entry", options = ["Proximal edge", "CE (50%)"], group = g_ent) fillIn = input.string("Trade through", "Fill", options = ["Trade through", "Touch"], group = g_ent, tooltip = "Trade through: price must trade one tick beyond the limit (conservative, as in the study). Touch: reaching the price fills.") graceN = input.int(0, "Grace bars after the window", minval = 0, group = g_ent) perWin = input.string("One", "Trades per window", options = ["One", "One per direction"], group = g_ent) stopIn = input.string("Sweep extreme", "Stop beyond", options = ["Sweep extreme", "FVG candle 1"], group = g_risk, tooltip = "Sweep extreme is the default because it was the one variant that improved results out of sample on both ES and NQ in the study.") bufTicks = input.int(1, "Stop buffer (ticks)", minval = 0, group = g_risk) minPts = input.float(0.0, "Min target distance (points)", minval = 0, group = g_risk) minR = input.float(1.5, "Min target distance (R)", minval = 0, step = 0.1, group = g_risk, tooltip = "The target is the nearest unswept opposite liquidity. If it is closer than this, the setup is skipped (SKIP).") fbR = input.float(2.0, "Fallback target (R)", minval = 0.1, step = 0.1, group = g_risk, tooltip = "Used only when no opposite liquidity level exists.") tsIn = input.string("Window end + 60 min", "Time stop", options = ["Window end + 60 min", "Minutes after fill", "End of day (17:00)"], group = g_risk) tsMin = input.int(120, "Minutes after fill", minval = 1, group = g_risk) slipTk = input.float(2.0, "Slippage per round trip (ticks)", minval = 0, group = g_cost) commRT = input.float(4.50, "Commission per round trip", minval = 0, group = g_cost, tooltip = "In the symbol's currency, per contract. Converted to points with the point value.") palIn = input.string("Neon", "Palette", options = ["Neon", "GBB", "Mono"], group = g_look, tooltip = "Neon: each window gets its own color (LDN violet, AM cyan, PM gold). GBB: the v0.2 look. Mono: grey.") showBg = input.bool(true, "Window background glow", group = g_look, tooltip = "Full-height tint behind each Silver Bullet window, in the window's color.") showHud = input.bool(true, "Live status pill", group = g_look, tooltip = "A pill next to the last bar: window live and minutes left, armed setup, open trade with running R, or the next window.") showDisp = input.bool(true, "Paint displacement candle", group = g_look, tooltip = "Colors the middle candle of the FVG that armed a setup.") histDays = input.int(3, "Draw the last N days", minval = 1, maxval = 30, group = g_look, tooltip = "Older history is still computed for the stats panel but not drawn, so the chart never runs out of drawing objects.") txtSize = input.string("Normal", "Text size", options = ["Small", "Normal", "Large"], group = g_look, tooltip = "Size of all chart text: tags, level names, result chips and window titles. Large for video.") showWin = input.bool(true, "Window frames", group = g_look, tooltip = "A faint box around each Silver Bullet window's price range. In Hour Scan only the LDN / AM / PM hours are framed.") showLvl = input.bool(true, "Liquidity levels", group = g_look, tooltip = "Live session levels with a short name. A level disappears when it is swept, except the window's first sweep, which is marked.") lvlAtr = input.float(20, "Show levels within (ATR x)", minval = 0, step = 1, group = g_look, tooltip = "Levels farther than this from the close are tracked but not drawn, so they do not stretch the price scale. 0 = all.") showFvg = input.bool(true, "FVG boxes", group = g_look) showTrd = input.bool(true, "Trade zones", group = g_look, tooltip = "Risk (entry to stop) and reward (entry to target) zones from the fill to the exit.") showTag = input.bool(true, "Price tags and result chips", group = g_look) chartTxt = input.string("Auto", "Chart text", options = ["Auto", "Full", "Minimal"], group = g_look, tooltip = "Full: sweep names, entry / stop / target prices and result chips with the exit price. Minimal: the cross, the zones and a short result chip. Auto: Full in Silver Bullet mode, Minimal in Hour Scan, where setups every hour would overlap.") tabOn = input.bool(true, "Show", group = g_tab) tabPos = input.string("Top right", "Position", options = ["Top right", "Bottom right", "Top left", "Bottom left"], group = g_tab) tabScope = input.string("All loaded history", "Scope", options = ["All loaded history", "Last N days"], group = g_tab) tabDays = input.int(20, "N days", minval = 1, group = g_tab) tabRows = input.string("Auto", "Rows", options = ["Auto", "Windows", "Every hour"], group = g_tab, tooltip = "Auto: the windows in Silver Bullet mode, one row per clock hour in Hour Scan mode.") tabSize = input.string("Normal", "Panel text", options = ["Small", "Normal", "Large"], group = g_tab) alertOn = input.bool(true, "Send alert() messages", group = g_alr, tooltip = "Create an alert with 'Any alert() function call'. All messages fire on bar close.") // --------------------------------------------------------------------------- // 2. Constants // --------------------------------------------------------------------------- const color C_NEUTRAL = #787B86 const color C_BUY = #00E676 const color C_SELL = #FF3D71 const color C_CALM = #2979FF const color C_AMBER = #FFB000 const color C_PANEL = #1E222D const color C_TEXT = #D1D4DC const color C_BG = #131722 const color C_INK = #0B0E14 const color C_ROW = #12161F const color C_EDGE = #2E3445 const color N_UP = #00FFA3 const color N_DN = #FF2E63 const color N_LDN = #A78BFA const color N_AM = #22D3EE const color N_PM = #FFC53D const color N_CUS = #F472B6 const color N_ASIA = #60A5FA const int K_PIV = 0 const int K_ASIA = 1 const int K_LDN = 2 const int K_PDAY = 3 const int K_PRE = 4 const int NEVER = 2000000000 const int ST_PEND = 0 const int ST_TRADE = 1 const int ST_DONE = 2 const int OUT_TP = 1 const int OUT_SL = 2 const int OUT_TIME = 3 const int OUT_NOFILL = 4 const int OUT_SKIP = 5 const int OUT_CANCEL = 6 // target reached before the entry filled const int OUT_LOST = 7 // the window's one trade was taken by the other direction (not counted) const int CAP_L = 320 // finished lines kept (oldest deleted first) const int CAP_T = 400 // finished labels kept const int CAP_B = 380 // finished boxes kept const int ROWS = 25 // stats rows: clock hours 0-23, 24 = custom window bool hourScan = modeIn == "Hour Scan" bool fullTxt = chartTxt == "Full" or (chartTxt == "Auto" and not hourScan) bool sweepReq = swReqIn == "Required" bool fillTouch = fillIn == "Touch" float tick = syminfo.mintick float half = tick / 2.0 float costPts = slipTk * tick + commRT / syminfo.pointvalue int tfSec = timeframe.in_seconds() int tfMs = tfSec * 1000 bool tfOk = timeframe.isintraday and tfSec >= 60 and tfSec <= 900 bool drawOn = time >= last_bar_time - histDays * 86400000 bool neon = palIn == "Neon" color cUp = neon ? N_UP : palIn == "GBB" ? C_BUY : C_TEXT color cDn = neon ? N_DN : palIn == "GBB" ? C_SELL : C_NEUTRAL color cWin = palIn == "Mono" ? #9598A1 : C_CALM int cusS = int(str.tonumber(str.substring(cusSess, 0, 2))) * 60 + int(str.tonumber(str.substring(cusSess, 2, 4))) int cusE = int(str.tonumber(str.substring(cusSess, 5, 7))) * 60 + int(str.tonumber(str.substring(cusSess, 7, 9))) bool cusValid = cusE > cusS // --------------------------------------------------------------------------- // 3. Clock and window (New York wall clock; DST handled by the time zone) // --------------------------------------------------------------------------- int yNY = year(time, TZ) int moNY = month(time, TZ) int dNY = dayofmonth(time, TZ) int hNY = hour(time, TZ) int mNY = minute(time, TZ) int tod = hNY * 60 + mNY int hrStart = timestamp(TZ, yNY, moNY, dNY, hNY, 0) int t6 = time + 6 * 3600000 // the session opening at 18:00 belongs to the next date int tdk = year(t6, TZ) * 10000 + month(t6, TZ) * 100 + dayofmonth(t6, TZ) int dayEnd = timestamp(TZ, yNY, moNY, dNY, 17, 0) + (tod >= 1080 ? 86400000 : 0) bool sbHour = (ldnOn and hNY == 3) or (amOn and hNY == 10) or (pmOn and hNY == 14) int winStart = na int winEnd = na int winRow = -1 if hourScan if hNY != 17 winStart := hrStart winEnd := hrStart + 3600000 winRow := hNY else if sbHour winStart := hrStart winEnd := hrStart + 3600000 winRow := hNY else if cusOn and cusValid and tod >= cusS and tod < cusE winStart := timestamp(TZ, yNY, moNY, dNY, int(math.floor(cusS / 60)), cusS % 60) winEnd := timestamp(TZ, yNY, moNY, dNY, int(math.floor(cusE / 60)), cusE % 60) winRow := 24 bool inWin = tfOk and not na(winStart) int winKey = inWin ? winStart : -1 float atrV = ta.atr(atrLen) // regular-hours-only chart: no overnight bar in the loaded history (syminfo.session is "regular" for CME futures // even with electronic trading hours, so it cannot tell) var bool sawNight = false if hNY >= 18 or hNY < 8 sawNight := true // --------------------------------------------------------------------------- // 4. Types and state // --------------------------------------------------------------------------- type Level float price int side // +1 buy-side (a high), -1 sell-side (a low) int fromBar // first bar that can sweep it int knownBar // known at this bar's close (usable as a target) int expBar // swing levels expire after this bar int kind int originTime line ln // drawn only while near the price label lb type Setup int winStart int winEnd int row int dir // +1 long (bullish FVG), -1 short (bearish FVG) int armBar int armTime float entry float stop float target int tkind // level kind of the target, -1 = fallback R int fillEnd int dlWin int dayEnd int status = 0 int outcome = 0 int fillTime float fillPx int deadline bool draw = false box bx // FVG box zR // risk zone (entry to stop) box zW // reward zone (entry to target) line lE line lS line lT label tE label tS label tT type State int curWin = -1 int swBear int swBull bool armBear = false bool armBull = false bool winTraded = false int tdKey float dayHi float dayLo int dayMin = 0 float pdHi float pdLo float asiaHi float asiaLo bool asiaPub = true float ldnHi float ldnLo bool ldnPub = true int hourStart float hourHi float hourLo int firstTime int nBars = 0 int nDays = 0 int lastOutcome = 0 float lastR bool evWin = false bool evSweep = false bool evArm = false bool evFill = false bool evTP = false bool evSL = false bool evTime = false bool evCancel = false int pArmDir = 0 float pArmEntry float pArmStop float pArmTarget int pExit = 0 float pExitR box fBox // the current window's frame line fTop // its accent edge label fLbl float fHi float fLo var State S = State.new() var array levels = array.new() var array setups = array.new() var array keepL = array.new() var array