Fathom Opening
RTH opening range, breakout zones and statistical projections.
What the indicator shows
- ORH and ORL: the high and low of the first
openingMinutesminutes 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.Protectionis 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
idprefix (range:,zone:,proj:,statistics:);<session>is the session’sYYYY-MM-DDclosing 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
statisticsobject stay in progress: they are sent again withfinal: falseon every change, then removed (remove) when the breakout is canceled or at the next session; they never have afinal: trueversion. Inconfirmedmode they never arrive: only theirremovedoes (ignore it). - With no complete previous session, the
statistics:<session>object reportsSTATISTICS_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.
{
"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.
| Parameter | Type | Default | Allowed values | Unit | Description |
|---|---|---|---|---|---|
extendBars | integer | 10 | 1 to 500 | bars | Initial zone length, in bars; a zone stays active beyond it, until a bar body crosses it or until the next session. |
openingMinutes | integer | 60 | 1 to 240 | — | Length of the opening range from 9:30 am New York time, in minutes (60 = Initial Balance). |
projections | boolean | true | true · false | — | Publish statistical projections after the range breakout. |
timeframe | integer | 300 | 1 · 5 · 10 · 15 · 30 · 60 · 120 · 180 · 300 · 600 · 900 · 1800 · 3600 | — | Bar duration in seconds: from 1 second (1) to 1 hour (3600). |
zones | boolean | true | true · 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.
| Field | Type | Unit | Present | Description |
|---|---|---|---|---|
bar | string | timestamp (ns) | always | Start of the bar where the range began. |
high | integer | ticks | always | High (ORH). |
low | integer | ticks | always | Low (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.
| Field | Type | Unit | Present | Description |
|---|---|---|---|---|
active | boolean | — | always | true while the zone holds; false once it is final. |
bar | string | timestamp (ns) | always | Start of the bar that created the zone. |
end | number | bars | always | 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. |
high | integer | ticks | always | Top of the zone. |
low | integer | ticks | always | Bottom of the zone. |
side | string · buy | sell | — | always | Direction of the range breakout. |
start | number | bars | always | 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). |
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.
| Field | Type | Unit | Present | Description |
|---|---|---|---|---|
label | string · Protection | Ext. Avg | Ext Std-1 | — | always | 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. |
price | integer | ticks | always | Projected 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.
| Field | Type | Unit | Present | Description |
|---|---|---|---|---|
status | string · STATISTICS_UNAVAILABLE | — | always | Always 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.
{
"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
}
}{
"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
}
}{
"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
}
}{
"sub": "opening-range",
"cursor": "20720.282713.1",
"t": "upsert",
"id": "proj:2026-09-24:protection",
"final": false,
"ts": "1790266800003513995",
"data": {
"label": "Protection",
"price": 122662
}
}{
"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
{
"$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
{
"$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"
]
}
]
}