Indicator

Fathom Opening

RTH opening range, breakout zones and statistical projections.

What the indicator shows

  • ORH and ORL: the high and low of the first openingMinutes minutes after the RTH open, at 9:30 am New York time. ORM: their midpoint.
  • After a breakout: imbalance zones in the breakout direction, and three projections (Protection, Ext. Avg, Ext Std-1) drawn from previous sessions.

How to use it

  • The breakout often sets the tone for the day: follow its direction as long as price does not cross back over the midpoint.
  • The projections show how far price usually travels after a breakout: price targets, not guarantees.
  • Imbalance zones left in the breakout direction are pullback zones if price comes back to them.
  • 60 minutes is the Initial Balance; a shorter duration gives a tighter frame, set earlier.

How it is computed

  • The range is tracked trade by trade from 9:30 am to 9:30 am + openingMinutes (New York time): high (ORH), low (ORL) and their midpoint ORM = (high + low) / 2.
  • Afterwards, between 9:30 am and 5:00 pm, a timeframe-second bar closing above the high (or below the low) sets the breakout direction; a close back across the range midpoint cancels it. The session’s breakouts are counted trade by trade, each time price crosses an edge of the range other than the previous one.
  • Zones (zones): at the close of each bar, at least 3 consecutive levels where, diagonally (the ask of a price against the bid of the price just below), one side reaches at least 3 times the other, in the breakout direction.
  • Projections (projections): three prices beyond the range, in the breakout direction, equal to range edge + (high − low) × an extension, rounded to the tick (ties to even). A past session’s extension is its largest move beyond the range between its formation and 5:00 pm, relative to the range height. Complete sessions of the previous 365 days are grouped by their breakouts, counted on 1-minute closes: no lasting breakout (none, or a last close before 5:00 pm back inside the range), one breakout, several breakouts. Protection is the average extension of sessions with no lasting breakout; Ext. Avg, that of sessions with as many breakouts as the current one (one, or several); Ext Std-1, that average + 1 standard deviation.
  • The four object kinds are told apart by their id prefix (range:, zone:, proj:, statistics:); <session> is the session’s YYYY-MM-DD closing date.

When it updates

  • The range is sent in progress while it forms, then final; it is removed at the next session.
  • A zone is sent in progress when the bar that creates it closes, then final when a bar body crosses it or at the end of the session.
  • The projections and the statistics object stay in progress: they are sent again with final: false on every change, then removed (remove) when the breakout is canceled or at the next session; they never have a final: true version. In confirmed mode they never arrive: only their remove does (ignore it).
  • With no complete previous session, the statistics:<session> object reports STATISTICS_UNAVAILABLE. The statistics carry over from one week to the next.
  • Reset at every session open (6:00 pm New York time): the previous session’s range and projections are removed, its zones still in progress are ended.
Identifier
opening-range
Objects received
range, zone, projection, statistics
Historical cost
1 compute unit per trade

Subscribe

The same params work in real time, on historical data and in exports. Parameters you omit take their default values.

Subscribe message
{
  "t": "subscribe",
  "sub": "opening-range",
  "instrument": "NQ.front",
  "indicator": "opening-range",
  "params": {
    "extendBars": 10,
    "openingMinutes": 60,
    "projections": true,
    "timeframe": 300,
    "zones": true
  },
  "mode": "live",
  "from": "live"
}

Parameters

As soon as a parameter differs from its default, the subscription counts as a custom configuration. To change a parameter, open a new subscription.

ParameterTypeDefaultAllowed valuesUnitDescription
extendBarsinteger101 to 500barsInitial zone length, in bars; a zone stays active beyond it, until a bar body crosses it or until the next session.
openingMinutesinteger601 to 240—Length of the opening range from 9:30 am New York time, in minutes (60 = Initial Balance).
projectionsbooleantruetrue · false—Publish statistical projections after the range breakout.
timeframeinteger3001 · 5 · 10 · 15 · 30 · 60 · 120 · 180 · 300 · 600 · 900 · 1800 · 3600—Bar duration in seconds: from 1 second (1) to 1 hour (3600).
zonesbooleantruetrue · false—Publish imbalance zones in the direction of the range breakout.

Objects received

Each object keeps the same id from one update to the next. Its data is in the data field. Prices are in ticks: multiply them by the instrument’s tick size (GET /v1/instruments) to get points.

range sent in progress

Range; in progress while it forms, final afterwards, removed at the next session.

id format: range:<session>. The object is sent in progress (final: false), then sent again on every change; each version fully replaces the previous one. Once it can no longer change, its last version carries final: true, except for objects whose description says they are removed without a final version.

FieldTypeUnitPresentDescription
barstringtimestamp (ns)alwaysStart of the bar where the range began.
highintegerticksalwaysHigh (ORH).
lowintegerticksalwaysLow (ORL); midpoint ORM = (high + low) / 2.

zone sent in progress

Zone; in progress while it is active.

id format: zone:<session>:<ordinal>. The object is sent in progress (final: false), then sent again on every change; each version fully replaces the previous one. Once it can no longer change, its last version carries final: true, except for objects whose description says they are removed without a final version.

FieldTypeUnitPresentDescription
activeboolean—alwaystrue while the zone holds; false once it is final.
barstringtimestamp (ns)alwaysStart of the bar that created the zone.
endnumberbarsalwaysEnd of the zone, in the same unit as start: start + extendBars while the zone is active (a drawing length, not an expiry), then the index of the bar whose body crossed it, or, at the end of the session, the index of its last bar.
highintegerticksalwaysTop of the zone.
lowintegerticksalwaysBottom of the zone.
sidestring · buy | sell—alwaysDirection of the range breakout.
startnumberbarsalwaysStart of the zone, in bars relative to bar (0 = start of bar): always -0.4, the zone is drawn from 0.4 bar before the bar that created it. Matching time, in nanoseconds: BigInt(bar) + BigInt(Math.round(start × timeframe × 1e9)) (bar is in nanoseconds, timeframe in seconds).

projection sent in progress

Session projection; its price follows the breakout direction and the number of breakouts, which the object does not carry. Never final: sent again with final: false on every change, then removed when the breakout is canceled and at the next session.

id format: proj:<session>:<protection|ext-avg|ext-std-1>. The object is sent in progress (final: false), then sent again on every change; each version fully replaces the previous one. Once it can no longer change, its last version carries final: true, except for objects whose description says they are removed without a final version.

FieldTypeUnitPresentDescription
labelstring · Protection | Ext. Avg | Ext Std-1—alwaysProtection: average extension of previous sessions with no lasting breakout of the range; Ext. Avg (with a period): average extension of previous sessions with as many breakouts as the current one; Ext Std-1 (no period): that average + 1 standard deviation. Compare these labels exactly. In the id: protection, ext-avg and ext-std-1.
priceintegerticksalwaysProjected price.

statistics sent in progress

Reports that no statistics are available for the session. Never final: removed at the next session.

id format: statistics:<session>. The object is sent in progress (final: false), then sent again on every change; each version fully replaces the previous one. Once it can no longer change, its last version carries final: true, except for objects whose description says they are removed without a final version.

FieldTypeUnitPresentDescription
statusstring · STATISTICS_UNAVAILABLE—alwaysAlways STATISTICS_UNAVAILABLE: projections are requested, but no complete previous session is available to compute them.

Example messages

Messages as the WebSocket sends them to the subscription above, taken from a real NQ session. Historical data returns the same objects, with the same cursor, without the sub field.

Object in progress
{
  "sub": "opening-range",
  "cursor": "20720.111121.0",
  "t": "upsert",
  "id": "range:2026-09-24",
  "final": false,
  "ts": "1790256600000077031",
  "data": {
    "bar": "1790256600000000000",
    "high": 122076,
    "low": 122076
  }
}
Final object
{
  "sub": "opening-range",
  "cursor": "20720.189499.0",
  "t": "upsert",
  "id": "range:2026-09-24",
  "final": true,
  "ts": "1790260200003097647",
  "data": {
    "bar": "1790256600000000000",
    "high": 122554,
    "low": 121973
  }
}
Object in progress
{
  "sub": "opening-range",
  "cursor": "20720.282713.0",
  "t": "upsert",
  "id": "zone:2026-09-24:0",
  "final": false,
  "ts": "1790266800003513995",
  "data": {
    "side": "buy",
    "high": 122373,
    "low": 122371,
    "active": true,
    "bar": "1790266500000000000",
    "start": -0.4,
    "end": 9.6
  }
}
Object in progress
{
  "sub": "opening-range",
  "cursor": "20720.282713.1",
  "t": "upsert",
  "id": "proj:2026-09-24:protection",
  "final": false,
  "ts": "1790266800003513995",
  "data": {
    "label": "Protection",
    "price": 122662
  }
}
Final object
{
  "sub": "opening-range",
  "cursor": "20720.343631.0",
  "t": "upsert",
  "id": "zone:2026-09-24:2",
  "final": true,
  "ts": "1790270400021675849",
  "data": {
    "side": "buy",
    "high": 123010,
    "low": 123008,
    "active": false,
    "bar": "1790268300000000000",
    "start": -0.4,
    "end": 6
  }
}

JSON schemas

To validate or type your data: the JSON schemas of the parameters and of data, also available from GET /v1/indicators. The SDK can generate types from them.

Parameters schema
JSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://fathomcharts.com/schemas/opening-range/params.json",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "extendBars": {
      "description": "Initial zone length, in bars; a zone stays active beyond it, until a bar body crosses it or until the next session.",
      "type": "integer",
      "default": 10,
      "minimum": 1,
      "maximum": 500
    },
    "openingMinutes": {
      "description": "Length of the opening range from 9:30 am New York time, in minutes (60 = Initial Balance).",
      "type": "integer",
      "default": 60,
      "minimum": 1,
      "maximum": 240
    },
    "projections": {
      "description": "Publish statistical projections after the range breakout.",
      "type": "boolean",
      "default": true
    },
    "timeframe": {
      "description": "Bar duration in seconds: from 1 second (`1`) to 1 hour (`3600`).",
      "type": "integer",
      "default": 300,
      "enum": [
        1,
        5,
        10,
        15,
        30,
        60,
        120,
        180,
        300,
        600,
        900,
        1800,
        3600
      ]
    },
    "zones": {
      "description": "Publish imbalance zones in the direction of the range breakout.",
      "type": "boolean",
      "default": true
    }
  }
}
data field schema
JSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://fathomcharts.com/schemas/opening-range/data.json",
  "anyOf": [
    {
      "title": "range",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "bar": {
          "description": "Start of the bar where the range began.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "high": {
          "description": "High (ORH).",
          "type": "integer"
        },
        "low": {
          "description": "Low (ORL); midpoint ORM = (high + low) / 2.",
          "type": "integer"
        }
      },
      "required": [
        "bar",
        "high",
        "low"
      ]
    },
    {
      "title": "zone",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "active": {
          "description": "`true` while the zone holds; `false` once it is final.",
          "type": "boolean"
        },
        "bar": {
          "description": "Start of the bar that created the zone.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "end": {
          "description": "End of the zone, in the same unit as `start`: `start` + `extendBars` while the zone is active (a drawing length, not an expiry), then the index of the bar whose body crossed it, or, at the end of the session, the index of its last bar.",
          "type": "number"
        },
        "high": {
          "description": "Top of the zone.",
          "type": "integer"
        },
        "low": {
          "description": "Bottom of the zone.",
          "type": "integer"
        },
        "side": {
          "description": "Direction of the range breakout.",
          "type": "string",
          "enum": [
            "buy",
            "sell"
          ]
        },
        "start": {
          "description": "Start of the zone, in bars relative to `bar` (0 = start of `bar`): always `-0.4`, the zone is drawn from 0.4 bar before the bar that created it. Matching time, in nanoseconds: `BigInt(bar) + BigInt(Math.round(start × timeframe × 1e9))` (`bar` is in nanoseconds, `timeframe` in seconds).",
          "type": "number"
        }
      },
      "required": [
        "side",
        "high",
        "low",
        "active",
        "bar",
        "start",
        "end"
      ]
    },
    {
      "title": "projection",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "label": {
          "description": "`Protection`: average extension of previous sessions with no lasting breakout of the range; `Ext. Avg` (with a period): average extension of previous sessions with as many breakouts as the current one; `Ext Std-1` (no period): that average + 1 standard deviation. Compare these labels exactly. In the `id`: `protection`, `ext-avg` and `ext-std-1`.",
          "type": "string",
          "enum": [
            "Protection",
            "Ext. Avg",
            "Ext Std-1"
          ]
        },
        "price": {
          "description": "Projected price.",
          "type": "integer"
        }
      },
      "required": [
        "label",
        "price"
      ]
    },
    {
      "title": "statistics",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "status": {
          "description": "Always `STATISTICS_UNAVAILABLE`: projections are requested, but no complete previous session is available to compute them.",
          "const": "STATISTICS_UNAVAILABLE"
        }
      },
      "required": [
        "status"
      ]
    }
  ]
}