ChartnautDocs

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 ctx.session({...}) calls

One for the 30-minute range window, one for the London session. Both end by the clock, never by counting bars.

Range state in ctx.accum

Resets on orWin.didOpen, widens while orWin.isOpen, then holds.

warmup((w) => w.day())

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.

runOn

Limits the definition to timeframes that fit inside the range window.

sessionKey in the payload and in oncePer

One up break and one down break per day, with no hand-rolled dedupe.

ctx.barConfirmed guard

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.