Session OR break
Builds the London opening range on EURUSD from 08:00 to 08:30 London time, then emits when a bar closes above its high or below its low during the rest of the London session. Each direction fires at most once a day.
What it defines
Events or_break_up and or_break_dn, carrying the range high and low, the break close, and the session key.
Key decisions
Piece | Why it matters |
|---|---|
Two | One for the 30-minute range window, one for the London session. Both end by the clock, never by counting bars. |
Range state in | Resets on |
| The script keeps state, so it must declare how far back it remembers. The state resets daily, so it declares one day: 1,440 bars on 1m, 288 on 5m, 96 on 15m. |
| Limits the definition to timeframes that fit inside the range window. |
| One up break and one down break per day, with no hand-rolled dedupe. |
| On the chart, only a closed bar can break the range, the same as when the definition runs over history. |
Full script
meta({
name: "OR break",
kind: "flow",
slug: "or-break",
shortName: "ORBreak",
runOn: ["1m", "5m", "15m"],
runOnReason: "The opening range is 30 minutes of intraday bars",
});
warmup((w) => w.day());
events.declare({
id: "or_break_up",
intent: "signal",
payload: {
orHigh: { type: "number", description: "Opening range high, 08:00 to 08:30 London" },
orLow: { type: "number", description: "Opening range low, 08:00 to 08:30 London" },
close: { type: "number", description: "Close of the breaking bar" },
sessionKey: { type: "string", description: "Start of the opening range session (UTC ISO)" },
},
paint: "pin",
oncePer: { keys: ["sessionKey"] },
});
events.declare({
id: "or_break_dn",
intent: "signal",
payload: {
orHigh: { type: "number", description: "Opening range high, 08:00 to 08:30 London" },
orLow: { type: "number", description: "Opening range low, 08:00 to 08:30 London" },
close: { type: "number", description: "Close of the breaking bar" },
sessionKey: { type: "string", description: "Start of the opening range session (UTC ISO)" },
},
paint: "pin",
oncePer: { keys: ["sessionKey"] },
});
outputs.declare({ id: "or_high", kind: "line", name: "OR high", color: "#26a69a" });
outputs.declare({ id: "or_low", kind: "line", name: "OR low", color: "#ef5350" });
const LONDON = "Europe/London";
export function onBar(ctx) {
const orWin = ctx.session({
period: "daily",
open: { hour: 8, minute: 0 },
close: { hour: 8, minute: 30 },
tz: LONDON,
});
const london = ctx.session({
period: "daily",
open: { hour: 8, minute: 0 },
close: { hour: 16, minute: 30 },
tz: LONDON,
});
const st = ctx.accum("or", () => ({ key: "", high: 0, low: 0 }), (prev) => {
if (orWin.didOpen) return { key: orWin.key, high: ctx.high, low: ctx.low };
if (orWin.isOpen && prev.key === orWin.key) {
return {
key: prev.key,
high: Math.max(prev.high, ctx.high),
low: Math.min(prev.low, ctx.low),
};
}
return prev;
});
// Today's range is complete and the London session is still open.
if (st.key !== orWin.key || orWin.isOpen || !london.isOpen) return;
ctx.plot("or_high", st.high);
ctx.plot("or_low", st.low);
if (ctx.barConfirmed === false) return;
const payload = {
orHigh: st.high,
orLow: st.low,
close: ctx.close,
sessionKey: st.key,
};
if (ctx.close > st.high) {
ctx.emit("or_break_up", payload, {
label: "OR break ↑",
subtitle: `H ${st.high.toFixed(5)}`,
price: ctx.close,
});
} else if (ctx.close < st.low) {
ctx.emit("or_break_dn", payload, {
label: "OR break ↓",
subtitle: `L ${st.low.toFixed(5)}`,
price: ctx.close,
});
}
}The session key is the range window's start time, so both events for one day share it and oncePer keeps one of each. For a different market, change open, close and tz on both sessions, and the decimals in the subtitles. The options are on Sessions.
