Created
April 6, 2026 00:02
-
-
Save bthaile/27481a9cd95b157643980dc4bdb45b58 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //@version=6 | |
| strategy("30min ORB + OR Box + Gap Filter [v6]", overlay=true, | |
| default_qty_type=strategy.percent_of_equity, | |
| default_qty_value=100, | |
| commission_type=strategy.commission.percent, | |
| commission_value=0.1) | |
| // ── Inputs ───────────────────────────────────────────────────────────────────── | |
| orbMinutes = input.int(30, "Opening Range Minutes", minval=5, maxval=60) | |
| gapMovePct = input.float(5.0, "Gap Threshold %", minval=0.1) | |
| useVWAPFilter = input.bool(true, "Require VWAP Confirmation") | |
| useConfirmBar = input.bool(true, "Require Next Bar Confirmation") | |
| maFastLen = input.int(4, "Profit MA Fast Length") | |
| maSlowLen = input.int(9, "Profit MA Slow Length") | |
| maType = input.string("EMA", "MA Type", options=["SMA", "EMA"]) | |
| // ── Optional safety: built for 5m charts ────────────────────────────────────── | |
| if timeframe.period != "5" | |
| runtime.error("This strategy is intended for 5-minute charts only.") | |
| // ── Session filters ──────────────────────────────────────────────────────────── | |
| bool inSession = not na(time(timeframe.period, "0930-1600")) | |
| bool before11 = hour < 11 | |
| // ── Gap filter ───────────────────────────────────────────────────────────────── | |
| // Note: This is an overnight gap proxy, not true after-hours move. | |
| prevClose = request.security(syminfo.tickerid, "1D", close[1], lookahead=barmerge.lookahead_on) | |
| gapPct = prevClose != 0 ? (open - prevClose) / prevClose * 100 : 0 | |
| isInPlay = math.abs(gapPct) >= gapMovePct | |
| // ── New day detection ────────────────────────────────────────────────────────── | |
| bool isNewDay = ta.change(time("1D")) != 0 | |
| // ── Opening Range state ──────────────────────────────────────────────────────── | |
| var float orHigh = na | |
| var float orLow = na | |
| var bool orSet = false | |
| var int orStartBar = na | |
| var int orEndBar = na | |
| var box orBox = na | |
| if isNewDay | |
| orHigh := na | |
| orLow := na | |
| orSet := false | |
| orStartBar := na | |
| orEndBar := na | |
| orBox := na | |
| barsInOR = ta.barssince(isNewDay) | |
| orBars = math.round(orbMinutes / 5) | |
| inOrbPeriod = inSession and not orSet and barsInOR < orBars | |
| // ── Build OR high/low ────────────────────────────────────────────────────────── | |
| if inOrbPeriod | |
| orStartBar := na(orStartBar) ? bar_index : orStartBar | |
| orHigh := na(orHigh) ? high : math.max(orHigh, high) | |
| orLow := na(orLow) ? low : math.min(orLow, low) | |
| // ── Finalize OR after window closes ──────────────────────────────────────────── | |
| if not orSet and not inOrbPeriod and not na(orHigh) and not na(orLow) | |
| orSet := true | |
| orEndBar := bar_index - 1 | |
| // Create a full box covering the OR candles | |
| orBox := box.new( | |
| left=orStartBar, | |
| top=orHigh, | |
| right=orEndBar, | |
| bottom=orLow, | |
| border_color=color.new(color.yellow, 0), | |
| bgcolor=color.new(color.yellow, 85) | |
| ) | |
| // Extend the box to the right all day so you can see the breakout level | |
| if orSet and not na(orBox) | |
| box.set_right(orBox, bar_index) | |
| box.set_top(orBox, orHigh) | |
| box.set_bottom(orBox, orLow) | |
| // ── Plot OR levels ───────────────────────────────────────────────────────────── | |
| plot(orSet ? orHigh : na, "ORB High", color.red, 2, plot.style_linebr) | |
| plot(orSet ? orLow : na, "ORB Low", color.lime, 2, plot.style_linebr) | |
| // ── VWAP ─────────────────────────────────────────────────────────────────────── | |
| vwapValue = ta.vwap(hlc3) | |
| plot(useVWAPFilter ? vwapValue : na, "VWAP", color.purple, 2) | |
| // ── Profit MAs ───────────────────────────────────────────────────────────────── | |
| maFast = maType == "EMA" ? ta.ema(close, maFastLen) : ta.sma(close, maFastLen) | |
| maSlow = maType == "EMA" ? ta.ema(close, maSlowLen) : ta.sma(close, maSlowLen) | |
| plot(maFast, "Fast MA", color.orange, 1) | |
| plot(maSlow, "Slow MA", color.blue, 1) | |
| // ── Breakout logic ───────────────────────────────────────────────────────────── | |
| bullBreak = orSet and close > orHigh and close[1] <= orHigh | |
| bearBreak = orSet and close < orLow and close[1] >= orLow | |
| bullSignal = useConfirmBar ? (bullBreak[1] and close > orHigh) : bullBreak | |
| bearSignal = useConfirmBar ? (bearBreak[1] and close < orLow) : bearBreak | |
| longCondition = isInPlay and bullSignal and (not useVWAPFilter or close > vwapValue) and before11 and inSession | |
| shortCondition = isInPlay and bearSignal and (not useVWAPFilter or close < vwapValue) and before11 and inSession | |
| // ── Entries ──────────────────────────────────────────────────────────────────── | |
| if longCondition and strategy.position_size <= 0 | |
| strategy.entry("Long", strategy.long) | |
| if shortCondition and strategy.position_size >= 0 | |
| strategy.entry("Short", strategy.short) | |
| // ── Exits ────────────────────────────────────────────────────────────────────── | |
| strategy.exit("Long Stop", "Long", stop=low[1]) | |
| strategy.exit("Short Stop", "Short", stop=high[1]) | |
| longProfit = ta.crossunder(maFast, maSlow) | |
| shortProfit = ta.crossover(maFast, maSlow) | |
| if longProfit and strategy.position_size > 0 | |
| strategy.close("Long") | |
| if shortProfit and strategy.position_size < 0 | |
| strategy.close("Short") | |
| // ── Visuals ──────────────────────────────────────────────────────────────────── | |
| plotshape(longCondition, title="Long Signal", location=location.belowbar, color=color.green, style=shape.triangleup, size=size.large, text="LONG") | |
| plotshape(shortCondition, title="Short Signal", location=location.abovebar, color=color.red, style=shape.triangledown, size=size.large, text="SHORT") | |
| bgcolor(isInPlay ? color.new(color.yellow, 92) : na, title="In Play Day") | |
| // ── Debug plots ──────────────────────────────────────────────────────────────── | |
| plotchar(isInPlay, title="isInPlay", char="G", location=location.top, color=color.yellow, size=size.tiny) | |
| plotchar(orSet, title="orSet", char="R", location=location.top, color=color.white, size=size.tiny) | |
| plotchar(bullBreak, title="bullBreak", char="B", location=location.top, color=color.green, size=size.tiny) | |
| plotchar(bearBreak, title="bearBreak", char="S", location=location.top, color=color.red, size=size.tiny) | |
| // ── Alerts ───────────────────────────────────────────────────────────────────── | |
| alertcondition(longCondition, title="Long ORB Alert", message="ORB LONG confirmed • {{ticker}} @ {{close}}") | |
| alertcondition(shortCondition, title="Short ORB Alert", message="ORB SHORT confirmed • {{ticker}} @ {{close}}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment