//@version=6 // ───────────────────────────────────────────────────────────────────────────── // Pre-Market High / Low + Prior Day Levels // • Marks the pre-market range each day and extends it into the session // • Prior-day high / low / close drawn as anchored lines with end labels // • Time-of-day relative volume "glow" on abnormal-volume bars // • Break alerts for both sets of levels, optionally gated on a glowing candle // Requires an intraday timeframe. // ───────────────────────────────────────────────────────────────────────────── indicator("Pre-Market High / Low + Prior Day", "PMH/PML+PD", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500) // ── Inputs ─────────────────────────────────────────────────────────────────── gSess = "Session" pmSess = input.session("0400-0930", "Pre-Market Window", group = gSess, tooltip = "The window whose high and low get marked. Needs Extended Trading Hours enabled on the chart or there are no bars here to measure. Handles windows that cross midnight, so futures overnight ranges work too.") rthSess = input.session("0930-1600", "Regular Session", group = gSess, tooltip = "Defines the cash session. Used for the 'Until Session End' extend mode and to build the prior-day high/low/close, which are RTH-only by construction.") tzIn = input.string("America/New_York", "Timezone", options = ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "Europe/London", "Asia/Tokyo", "UTC"], group = gSess, tooltip = "Timezone the session strings and the volume minute-of-day buckets are evaluated in. Leave on New York for US equities; DST is handled for you.") gDraw = "Pre-Market Display" extendMode = input.string("Until Session End", "Extend Levels", options = ["Pre-Market Only", "Until Session End", "Until Right Edge"], group = gDraw, tooltip = "How far the PM high/low lines run. Pre-Market Only stops them at 9:30, Until Session End carries them to 16:00, Until Right Edge keeps extending to the newest bar.") histDays = input.int(3, "Days To Keep", minval = 1, maxval = 20, group = gDraw, tooltip = "How many past days of PM ranges and prior-day levels stay drawn. Older drawings are deleted to stay inside TradingView's 500-object limit.") showZone = input.bool(true, "Shade Pre-Market Range", group = gDraw, tooltip = "Fills the box between the PM high and low.") showMid = input.bool(false, "Show Mid / EQ Line", group = gDraw, tooltip = "Dotted line at the 50% of the pre-market range.") showLabels = input.bool(true, "Show Labels", group = gDraw, tooltip = "Price tags riding the right end of the PM high and low lines.") showTable = input.bool(true, "Show Info Panel", group = gDraw, tooltip = "Top-right readout: PM range, prior-day levels, relative volume, and the live setup checklist.") gPrior = "Prior Day Levels" showPDH = input.bool(true, "Prior Day High", group = gPrior, tooltip = "Yesterday's regular-session high. Built from the chart's own RTH bars, so pre/post-market spikes never contaminate it.") showPDL = input.bool(true, "Prior Day Low", group = gPrior, tooltip = "Yesterday's regular-session low.") showPDC = input.bool(true, "Prior Day Close", group = gPrior, tooltip = "Yesterday's last regular-session close. The reference for the 'vs PDC' gap reading in the panel.") pdLabels = input.bool(true, "Label Prior Levels", group = gPrior, tooltip = "Price tags on the right end of the prior-day lines.") pdStyleIn = input.string("Solid", "Prior Line Style", options = ["Solid", "Dashed", "Dotted"], group = gPrior, tooltip = "Dashed or dotted separates these visually from the solid pre-market lines.") gVol = "Volume Glow" volOn = input.bool(true, "Enable Volume Glow", group = gVol, tooltip = "Compares each bar's volume to the same minute of the day over the last N sessions, so 09:31 is judged against 09:31 rather than a flat all-day average. Required for the composite long setup alert.") volMode = input.string("Relative Volume (x)", "Threshold Mode", options = ["Relative Volume (x)", "Std Dev (z-score)"], group = gVol, tooltip = "RVOL 3x and 3 sigma are NOT the same measurement. Intraday volume is right-skewed, so 3x the average is often only ~1.5-2 sigma, while a true 3 sigma event can be 6x or more. Pick the one you actually mean.") volThresh = input.float(3.0, "Threshold", minval = 0.5, step = 0.1, group = gVol, tooltip = "The number a bar must reach to glow, read in whatever mode is selected above. Watch the Rel Vol row in the panel for a few sessions to calibrate this per symbol.") volSessions = input.int(5, "Lookback Sessions", minval = 2, maxval = 20, group = gVol, tooltip = "How many past sessions of history each minute-of-day bucket holds. Five is roughly a trading week.") volMinSamp = input.int(3, "Min Sessions Required", minval = 1, maxval = 20, group = gVol, tooltip = "No glow until this many sessions exist for the current minute slot. Expect silence on the first days of a freshly loaded chart; the panel shows 'building 1/3' while it fills.") volMinRaw = input.float(0, "Min Raw Volume", minval = 0, group = gVol, tooltip = "Absolute volume floor. A pre-market slot averaging 400 shares posts a 3x ratio on 1,200 shares, which is true but useless. Set a floor and those disappear.") volProject = input.bool(false, "Project Live Bar Volume", group = gVol, tooltip = "Scales the forming bar's volume up by how much of the bar has elapsed so a live spike registers before the close. Jumpy in the first seconds of a bar. Does not affect the setup alert, which always waits for the close.") glowLayers = input.int(2, "Glow Layers", minval = 0, maxval = 3, group = gVol, tooltip = "Stacked translucent halos behind the candle. Pine has no real blur, so this fakes it. Zero gives you just the recolored bar.") glowCol = input.color(#FFEB3B, "Glow Color", group = gVol, tooltip = "Used for the bar, the halo, and the setup marker.") glowPad = input.float(0.18, "Halo Spread", minval = 0.05, maxval = 1.0, step = 0.01, group = gVol, tooltip = "How far each halo layer extends past the wicks, as a fraction of the bar's range.") colorBar = input.bool(true, "Color The Candle", group = gVol, tooltip = "Off leaves the candle its normal color and shows only the halo.") volLabels = input.bool(false, "Label Volume Value", group = gVol, tooltip = "Prints the reading under each glowing bar.") gStyle = "Style" hiCol = input.color(#26A69A, "PM High", inline = "c1", group = gStyle, tooltip = "Colors for the pre-market high, low, and mid lines.") loCol = input.color(#EF5350, "PM Low", inline = "c1", group = gStyle) midCol = input.color(#9598A1, "PM Mid", inline = "c1", group = gStyle) pdhCol = input.color(#FF9800, "PDH", inline = "c2", group = gStyle, tooltip = "Colors for the prior-day high, low, and close lines.") pdlCol = input.color(#29B6F6, "PDL", inline = "c2", group = gStyle) pdcCol = input.color(#B2B5BE, "PDC", inline = "c2", group = gStyle) zoneCol = input.color(color.new(#787B86, 92), "Zone Fill", inline = "c3", group = gStyle, tooltip = "Fill color for the pre-market range box, and the width of the PM lines.") lWidth = input.int(2, "PM Width", minval = 1, maxval = 5, inline = "c3", group = gStyle) lStyleIn = input.string("Solid", "PM Line Style", options = ["Solid", "Dashed", "Dotted"], group = gStyle, tooltip = "Line style for the pre-market high and low.") labSizeIn = input.string("Small", "Label Size", options = ["Tiny", "Small", "Normal", "Large"], group = gStyle, tooltip = "Applies to every price tag on the chart.") labText = input.color(#131722, "Label Text", group = gStyle, tooltip = "Text color inside the prior-day price tags. Dark reads better on the light orange and grey backgrounds.") gAlert = "Alerts" alertScope = input.string("Setup Only", "Which Events Send alert()", options = ["Setup Only", "All Events"], group = gAlert, tooltip = "Controls what a TradingView alert built on 'Any alert() function call' will fire on. Setup Only sends nothing but the composite long setup. All Events also sends the individual break events and the PM range-complete event, which fires every morning at the open on every symbol regardless of price.") rangeAlert = input.bool(false, "PM Range Complete Event", group = gAlert, tooltip = "A notification the moment the pre-market window closes. No price condition attached, so it fires daily on every symbol. Only relevant with All Events selected.") setupOn = input.bool(true, "Long Setup Alert (all conditions)", group = gAlert, tooltip = "The composite signal. Fires once per day on a confirmed bar only when ALL of these are true together: close above the prior-day high, a crossover that closes above the pre-market high, a volume-glow bar, and the bar opening inside the break window. Needs Volume Glow enabled.") setupMark = input.bool(true, "Plot Setup Marker", group = gAlert, tooltip = "Drops a labeled marker under the bar that completes the setup.") alertsOn = input.bool(true, "Pre-Market Break Alerts", group = gAlert, tooltip = "The individual PM high/low break events, separate from the composite setup above.") pdAlertsOn = input.bool(false, "Prior Day Break Alerts", group = gAlert, tooltip = "Individual prior-day high/low break events. Off by default because this level is live 24 hours and fires often.") firstOnly = input.bool(true, "First Break Only (per day)", group = gAlert, tooltip = "One signal per level per day. With this off you get every re-cross, which on a chopping open can be several.") useWick = input.bool(false, "Trigger On Wick (else close)", group = gAlert, tooltip = "Applies to the individual break events only. The composite long setup always requires a close through the level regardless of this setting.") showMarks = input.bool(true, "Plot Break Markers", group = gAlert, tooltip = "Triangles for PM breaks, diamonds for prior-day breaks.") reqClear = input.bool(true, "PM Break Must Clear Prior Day", group = gAlert, tooltip = "Long side: the PM break only counts if the pre-market HIGH is already above yesterday's high, so taking it out puts price above both levels at once. Short side mirrors it.") useBreakWin = input.bool(true, "PM Break Window Only", group = gAlert, tooltip = "Restricts PM break events to the opening window below.") breakWin = input.session("0930-0945", "Break Window", group = gAlert, tooltip = "Bars whose OPEN falls inside this window. On a 5-minute chart 0930-0945 covers the 9:30, 9:35 and 9:40 bars; the 9:45 bar is outside. Widen to 0930-0950 if you want the 9:45 bar included.") reqGlow = input.bool(true, "Break Must Be A Glowing Candle", group = gAlert, tooltip = "Applies the volume requirement to the individual break events too. Skipped automatically if Volume Glow is switched off, so turning volume off never silently kills every alert.") volAlerts = input.bool(false, "Standalone Volume Spike Alerts", group = gAlert, tooltip = "Fires on any glowing candle, break or not.") gShort = "Short Setup" shortOn = input.bool(false, "Short Setup Alert (all conditions)", group = gShort, tooltip = "The mirror image of the long. Fires once per day on a confirmed bar only when ALL of these land together: close BELOW the prior-day low, a crossunder that closes below the pre-market low, a volume-glow bar, and the bar opening inside the break window. Off by default.") shortMark = input.bool(true, "Plot Short Marker", group = gShort, tooltip = "Drops a labeled marker above the bar that completes the short setup.") shortCol = input.color(#FF5252, "Marker", group = gShort, tooltip = "Fill color of the SHORT label.") gStats = "Setup History" statsOn = input.bool(false, "Show Setup History Panel", group = gStats, tooltip = "A second table counting how many entries the script generated over the last 30, 60 and 90 calendar days of loaded chart history. Counts only what this symbol and timeframe produced under your current settings, so the numbers move if you change the break window, the volume threshold or any other setup input.") statPos = input.string("Bottom Right", "Panel Position", options = ["Bottom Right", "Bottom Left", "Middle Right", "Top Left"], group = gStats, tooltip = "Keep it clear of the main info panel in the top right.") statSide = input.string("Both", "Show Entries", options = ["Both", "Long Only", "Short Only"], group = gStats, tooltip = "Which side the panel counts. The short column stays at zero unless the Short Setup group is switched on, since nothing generates short entries otherwise.") lStyle = switch lStyleIn "Dashed" => line.style_dashed "Dotted" => line.style_dotted => line.style_solid pdStyle = switch pdStyleIn "Dashed" => line.style_dashed "Dotted" => line.style_dotted => line.style_solid labSize = switch labSizeIn "Tiny" => size.tiny "Normal" => size.normal "Large" => size.large => size.small // ── Session state ──────────────────────────────────────────────────────────── isIntra = timeframe.isintraday inPM = isIntra and not na(time(timeframe.period, pmSess, tzIn)) inRTH = isIntra and not na(time(timeframe.period, rthSess, tzIn)) pmBegin = inPM and not inPM[1] pmEnd = not inPM and inPM[1] rthOpen = inRTH and not inRTH[1] rthShut = not inRTH and inRTH[1] extendOK = switch extendMode "Pre-Market Only" => inPM "Until Session End" => inPM or inRTH => true // ── Time-of-day relative volume ────────────────────────────────────────────── // Each minute-of-day keeps its own rolling history of the last N sessions' // volume for that same slot. Pine won't nest a collection inside another // collection's type template, so the array is wrapped in a UDT field. type VolSlot array v = na var map volMap = map.new() var array glowBoxes = array.new() var array