# ============================================================ # MIZAN presents: Opening Statement (ORB) — thinkScript · v2.0 # mizanquant.com/free · free & open source · a QWERTY Options Inc. tool # # WHAT IT DOES # The market makes its opening statement in the first minutes of the # session; this holds it to it. It builds the opening range, draws the # high and low for the rest of the session, marks confirmed breakouts # (cyan up, magenta down), marks retests of the broken level (blue and # yellow), and optionally projects a measured target from the range. # Markers describe conditions; they do not advise. # # INSTALL (thinkorswim desktop) # Charts > Studies > Edit Studies > Create > delete the template, # paste this whole file, name it, OK, Apply. Intraday charts only, # with the chart timeframe at or below the range length. # # v2.0 CHANGES FROM THE OWNER'S ORIGINAL # 1. Signals could evaluate while the range was still forming; every # signal is now hard-gated on range completion. # 2. The four raw yes/no debug chips in the header are gone, replaced # by one status label: FORMING, then the set range with its width. # 3. Range lines no longer draw provisional values; they print only # once the range is locked, so history never repaints. # 4. Breakouts confirm on candle close beyond the level, not on a # wick poke; retests require a close back through after touching. # 5. One breakout and one retest per side per day, enforced with # session-reset flags; a bar cooldown separates retest prints. # 6. Timeframe guard: on aggregations above the range length the # study shows a plain warning label instead of nonsense lines. # 7. Alerts added for confirmed breaks and retests; hidden scan # exports at the bottom for the Scan tab. # 8. Case-insensitive identifier audit passed (thinkorswim treats # MidLine and midline as the same name; we learned that one live). # ============================================================ declare upper; # ----- INPUTS ----- input rangeStartTime = 930; # session open, exchange time (ET) input rangeMinutes = 15; input showRangeLines = yes; input showBreakArrows = yes; input showRetestArrows = yes; input showTargets = no; input targetMultiple = 1.5; # target = range width x this, from the broken level input retestCooldownBars = 10; input showStatusLabel = yes; # ----- SESSION & RANGE WINDOW ----- def aggMin = GetAggregationPeriod() / 60000; def tooHigh = aggMin > rangeMinutes; def newDay = GetDay() <> GetDay()[1]; def secsFromOpen = SecondsFromTime(rangeStartTime); def inWindow = secsFromOpen >= 0 and secsFromOpen < rangeMinutes * 60; def windowDone = secsFromOpen >= rangeMinutes * 60; # ----- BUILD THE OPENING RANGE ----- rec orHi = CompoundValue(1, if newDay then (if inWindow then high else Double.NaN) else if inWindow then (if IsNaN(orHi[1]) then high else Max(orHi[1], high)) else orHi[1], Double.NaN); rec orLo = CompoundValue(1, if newDay then (if inWindow then low else Double.NaN) else if inWindow then (if IsNaN(orLo[1]) then low else Min(orLo[1], low)) else orLo[1], Double.NaN); def rangeSet = windowDone and !IsNaN(orHi) and !IsNaN(orLo); def rangeWidth = orHi - orLo; # ----- BREAKOUTS: close beyond the level, once per side per day ----- def closedAbove = rangeSet and close > orHi and close[1] <= orHi; def closedBelow = rangeSet and close < orLo and close[1] >= orLo; rec brokeUp = CompoundValue(1, if newDay then 0 else if closedAbove then 1 else brokeUp[1], 0); rec brokeDn = CompoundValue(1, if newDay then 0 else if closedBelow then 1 else brokeDn[1], 0); def breakUpSignal = closedAbove and !brokeUp[1]; def breakDnSignal = closedBelow and !brokeDn[1]; # ----- RETESTS: touch the broken level, close back through it ----- def touchedHiFromAbove = brokeUp[1] and low <= orHi and close > orHi; def touchedLoFromBelow = brokeDn[1] and high >= orLo and close < orLo; rec lastRetestBar = CompoundValue(1, if newDay then -1000 else if (touchedHiFromAbove or touchedLoFromBelow) then BarNumber() else lastRetestBar[1], -1000); def cooled = BarNumber() - lastRetestBar[1] > retestCooldownBars; rec retestedUp = CompoundValue(1, if newDay then 0 else if (touchedHiFromAbove and cooled) then 1 else retestedUp[1], 0); rec retestedDn = CompoundValue(1, if newDay then 0 else if (touchedLoFromBelow and cooled) then 1 else retestedDn[1], 0); def retestUpSignal = touchedHiFromAbove and cooled and !retestedUp[1]; def retestDnSignal = touchedLoFromBelow and cooled and !retestedDn[1]; # ----- PLOTS ----- plot ORHigh = if showRangeLines and rangeSet and !tooHigh then orHi else Double.NaN; ORHigh.SetDefaultColor(Color.GREEN); ORHigh.SetLineWeight(2); ORHigh.SetPaintingStrategy(PaintingStrategy.HORIZONTAL); plot ORLow = if showRangeLines and rangeSet and !tooHigh then orLo else Double.NaN; ORLow.SetDefaultColor(Color.RED); ORLow.SetLineWeight(2); ORLow.SetPaintingStrategy(PaintingStrategy.HORIZONTAL); plot BreakUp = if showBreakArrows and breakUpSignal and !tooHigh then low - TickSize() * 8 else Double.NaN; BreakUp.SetPaintingStrategy(PaintingStrategy.ARROW_UP); BreakUp.SetDefaultColor(Color.CYAN); BreakUp.SetLineWeight(3); plot BreakDown = if showBreakArrows and breakDnSignal and !tooHigh then high + TickSize() * 8 else Double.NaN; BreakDown.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN); BreakDown.SetDefaultColor(Color.MAGENTA); BreakDown.SetLineWeight(3); plot RetestUp = if showRetestArrows and retestUpSignal and !tooHigh then low - TickSize() * 4 else Double.NaN; RetestUp.SetPaintingStrategy(PaintingStrategy.ARROW_UP); RetestUp.SetDefaultColor(Color.BLUE); RetestUp.SetLineWeight(2); plot RetestDown = if showRetestArrows and retestDnSignal and !tooHigh then high + TickSize() * 4 else Double.NaN; RetestDown.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN); RetestDown.SetDefaultColor(Color.YELLOW); RetestDown.SetLineWeight(2); plot TargetUp = if showTargets and rangeSet and brokeUp and !tooHigh then orHi + rangeWidth * targetMultiple else Double.NaN; TargetUp.SetDefaultColor(Color.DARK_GREEN); TargetUp.SetStyle(Curve.SHORT_DASH); TargetUp.SetPaintingStrategy(PaintingStrategy.HORIZONTAL); plot TargetDown = if showTargets and rangeSet and brokeDn and !tooHigh then orLo - rangeWidth * targetMultiple else Double.NaN; TargetDown.SetDefaultColor(Color.DARK_RED); TargetDown.SetStyle(Curve.SHORT_DASH); TargetDown.SetPaintingStrategy(PaintingStrategy.HORIZONTAL); # ----- STATUS ----- AddLabel(showStatusLabel and tooHigh, "OPENING STATEMENT: timeframe above range length - use " + rangeMinutes + "m or lower", Color.ORANGE); AddLabel(showStatusLabel and !tooHigh and !rangeSet, "OR: FORMING", Color.GRAY); AddLabel(showStatusLabel and !tooHigh and rangeSet, "OR: " + Round(orLo, 2) + " x " + Round(orHi, 2) + " (" + Round(rangeWidth, 2) + ")", if brokeUp and !brokeDn then Color.GREEN else if brokeDn and !brokeUp then Color.RED else Color.LIGHT_GRAY); # ----- ALERTS ----- Alert(breakUpSignal, "Opening Statement: closed above the opening range", Alert.BAR, Sound.Bell); Alert(breakDnSignal, "Opening Statement: closed below the opening range", Alert.BAR, Sound.Bell); Alert(retestUpSignal, "Opening Statement: OR high retest held", Alert.BAR, Sound.Ding); Alert(retestDnSignal, "Opening Statement: OR low retest held", Alert.BAR, Sound.Ding); # ----- SCAN EXPORTS ----- plot ScanBrokeUp = if brokeUp then 1 else 0; ScanBrokeUp.Hide(); plot ScanBrokeDown = if brokeDn then 1 else 0; ScanBrokeDown.Hide(); plot ScanRangeWidth = if rangeSet then rangeWidth else 0; ScanRangeWidth.Hide(); # Educational tool. It describes market conditions; it does not give # advice, and no output is a recommendation to buy or sell anything. # Trading involves substantial risk of loss. mizanquant.com/terms