# 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](https://fathomcharts.com/docs/websocket.md), on [historical data](https://fathomcharts.com/docs/history.md) and in exports. Parameters you omit take their default values.

**Subscribe message**

```json
{
  "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](https://fathomcharts.com/docs/limits.md#how-limits-are-counted). 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](https://fathomcharts.com/docs/history.md) returns the same objects, with the same `cursor`, without the `sub` field.

**Object in progress**

```json
{
  "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**

```json
{
  "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**

```json
{
  "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**

```json
{
  "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**

```json
{
  "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](https://fathomcharts.com/docs/sdk.md#indicator-types).

**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"
      ]
    }
  ]
}
```
