# Fathom Zones

Directional effort zones on 40-tick range bars, with the 20-period EMA of the closes.

## What the indicator shows

- A buy zone marks a fast upward push backed by buying delta; a sell zone, a fast downward push backed by selling delta. On the Fathom chart, buy zones are blue and sell zones coral.
- The range bars and the 20-period EMA of their closes are published with the zones, to put them in the context of the trend.

## How to use it

- When price comes back into a zone, watch how it reacts: a buy zone often acts as support, a sell zone as resistance.
- Place the zone against the EMA: a buy zone above the EMA goes with the trend, and so does a sell zone below it.
- A zone crossed by a bar body ends: the level did not hold.

## How it is computed

- 40-tick range bars, with no parameter: a bar runs until the trade that would take its high-low spread beyond 40 ticks, and that trade opens the next bar. A new bar also opens at every session open (6:00 pm New York time). Bars are numbered from 0 from the start of the week.
- A finished bar is an effort bar when four conditions hold: its body goes the way of its delta (ask − bid; close ≥ open for a positive or zero delta, close < open for a negative one); the absolute delta is 10% to 40% of the bar’s volume; the delta per body tick, `delta / (|close − open| + 1)` truncated, is at most 14 contracts in absolute value; the bar formed in 45 seconds at most (duration truncated to the second), from its first trade to the trade that opens the next one.
- As soon as two consecutive effort bars go the same way, a push starts: its bounds start from the high and low of these two bars, then widen with each further effort bar the same way.
- The first bar that is not an effort bar that way ends the push and creates the zone, whose `start` is that bar’s index. The zone covers the half of the push where it started: the lower half for a buy zone, the upper half for a sell zone, so its bounds can fall on a half tick.
- The 20-period EMA of the closes starts at the bar with index 21: its first value is that bar’s close, then `EMA = previous EMA + (close − previous EMA) × 2/21`.
- The three object kinds have no field telling them apart in `data`: read the `id` prefix (`bar:`, `ema:`, `zone:`). `<weekSunday>` is the `YYYY-MM-DD` date of the week’s Sunday reopen; the index of a bar or an EMA value is the last segment of its `id`.

## When it updates

- Each range bar is sent once, final, when it closes, with its EMA value from index 21 on.

- A zone is sent in progress as soon as it is created, then final (`active: false`, with its `end`) on the first of these events: a bar body crosses it (below its bottom for a buy zone, above its top for a sell zone), it has covered 21 bars (`start` to `start` + 20), or a new session opens (6:00 pm New York time). A zone can end on the very bar that creates it.

- The snapshot keeps the last 32 final objects of all kinds together: the more numerous bars and EMA values can leave no room for any final zone.

- Weekly reset: on the first trade of the Sunday reopen (6:00 pm New York time), the week’s last bar is sent, zones still in progress are sent final (`active: false`, with their `end`), then bars, indices and the EMA start over; nothing carries over to the new week.

- **Identifier**: `effort-zones`

- **Objects received**: `bar`, `ema`, `zone`

- **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": "effort-zones",
  "instrument": "NQ.front",
  "indicator": "effort-zones",
  "params": {},
  "mode": "live",
  "from": "live"
}
```

## Parameters

This indicator has no parameters: omit `params` or send `{}`.

## 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.

### `bar` (sent final)

Finished range bar. `<weekSunday>`: `YYYY-MM-DD` date of the week’s Sunday reopen; `<index>`: number of the bar from the start of the week (0, 1, 2…), read from the `id` (`data` has no `index` field).

`id` format: `bar:<weekSunday>:<index>`. Each object is sent once, final (`final: true`).

| Field   | Type    | Unit           | Present | Description                                                                                                                                                                                       |
| ------- | ------- | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `close` | integer | ticks          | always  | Close.                                                                                                                                                                                            |
| `high`  | integer | ticks          | always  | High.                                                                                                                                                                                             |
| `low`   | integer | ticks          | always  | Low.                                                                                                                                                                                              |
| `open`  | integer | ticks          | always  | Open.                                                                                                                                                                                             |
| `start` | string  | timestamp (ns) | always  | Timestamp of the bar’s first trade. To place a zone in time, read the `start` of the bar whose index equals the zone’s `start` (or `end`); the bar of the `end` is only published when it closes. |

### `ema` (sent final)

EMA value at the close of bar `<index>` (the same index as in that bar’s `id`).

`id` format: `ema:<weekSunday>:<index>`. Each object is sent once, final (`final: true`).

| Field   | Type   | Unit  | Present | Description                                                                                                                                                                                                                   |
| ------- | ------ | ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `value` | number | ticks | always  | 20-period EMA of the closes, at the close of bar `<index>`. It starts at the bar with index 21, on its close, then `EMA = previous EMA + (close − previous EMA) × 2/21`. No `ema` object exists for bars 0 to 20 of the week. |

### `zone` (sent in progress)

Zone; in progress while it is active. `<startIndex>` is its `start`.

`id` format: `zone:<weekSunday>:<startIndex>`. 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 is extended; `false` once it is final.                                                                                                                                                                                                                                    |
| `end`    | integer                  | bars  | sometimes | Index of the range bar after the end of the zone: the bar still in progress when the zone ends, only published when it closes; at the end of the week, the index that would follow the week’s last bar, which no bar carries. On expiry, `end` = `start` + 21. Absent while the zone is active. |
| `high`   | number                   | ticks | always    | Top of the zone; can fall on a half tick.                                                                                                                                                                                                                                                       |
| `low`    | number                   | ticks | always    | Bottom of the zone; can fall on a half tick.                                                                                                                                                                                                                                                    |
| `side`   | string · `buy` \| `sell` | —     | always    | Direction of the push that formed the zone: `buy` for a positive or zero delta, `sell` for a negative delta.                                                                                                                                                                                    |
| `start`  | integer                  | bars  | always    | Index of the range bar that created the zone, counted from the start of the week like the index of bar `id`s (not a timestamp).                                                                                                                                                                 |

## 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.

**Final object**

```json
{
  "sub": "effort-zones",
  "cursor": "20720.110.0",
  "t": "upsert",
  "id": "bar:2026-09-20:2939",
  "final": true,
  "ts": "1790208062570437091",
  "data": {
    "start": "1790207999116757165",
    "open": 123012,
    "high": 123044,
    "low": 123004,
    "close": 123006
  }
}
```

**Final object**

```json
{
  "sub": "effort-zones",
  "cursor": "20720.110.1",
  "t": "upsert",
  "id": "ema:2026-09-20:2939",
  "final": true,
  "ts": "1790208062570437091",
  "data": {
    "value": 123066.85128768138
  }
}
```

**Object in progress**

```json
{
  "sub": "effort-zones",
  "cursor": "20720.32179.2",
  "t": "upsert",
  "id": "zone:2026-09-20:3114",
  "final": false,
  "ts": "1790230140362667235",
  "data": {
    "side": "buy",
    "high": 122357,
    "low": 122258,
    "active": true,
    "start": 3114
  }
}
```

**Final object**

```json
{
  "sub": "effort-zones",
  "cursor": "20720.34641.2",
  "t": "upsert",
  "id": "zone:2026-09-20:3114",
  "final": true,
  "ts": "1790231072263560767",
  "data": {
    "side": "buy",
    "high": 122357,
    "low": 122258,
    "active": false,
    "start": 3114,
    "end": 3135
  }
}
```

## 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/effort-zones/params.json",
  "type": "object",
  "additionalProperties": false,
  "properties": {}
}
```

**`data` field schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://fathomcharts.com/schemas/effort-zones/data.json",
  "anyOf": [
    {
      "title": "bar",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "close": {
          "description": "Close.",
          "type": "integer"
        },
        "high": {
          "description": "High.",
          "type": "integer"
        },
        "low": {
          "description": "Low.",
          "type": "integer"
        },
        "open": {
          "description": "Open.",
          "type": "integer"
        },
        "start": {
          "description": "Timestamp of the bar’s first trade. To place a zone in time, read the `start` of the bar whose index equals the zone’s `start` (or `end`); the bar of the `end` is only published when it closes.",
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "required": [
        "start",
        "open",
        "high",
        "low",
        "close"
      ]
    },
    {
      "title": "ema",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "value": {
          "description": "20-period EMA of the closes, at the close of bar `<index>`. It starts at the bar with index 21, on its close, then `EMA = previous EMA + (close − previous EMA) × 2/21`. No `ema` object exists for bars 0 to 20 of the week.",
          "type": "number"
        }
      },
      "required": [
        "value"
      ]
    },
    {
      "title": "zone",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "active": {
          "description": "`true` while the zone is extended; `false` once it is final.",
          "type": "boolean"
        },
        "end": {
          "description": "Index of the range bar after the end of the zone: the bar still in progress when the zone ends, only published when it closes; at the end of the week, the index that would follow the week’s last bar, which no bar carries. On expiry, `end` = `start` + 21. Absent while the zone is active.",
          "type": "integer"
        },
        "high": {
          "description": "Top of the zone; can fall on a half tick.",
          "type": "number"
        },
        "low": {
          "description": "Bottom of the zone; can fall on a half tick.",
          "type": "number"
        },
        "side": {
          "description": "Direction of the push that formed the zone: `buy` for a positive or zero delta, `sell` for a negative delta.",
          "type": "string",
          "enum": [
            "buy",
            "sell"
          ]
        },
        "start": {
          "description": "Index of the range bar that created the zone, counted from the start of the week like the index of bar `id`s (not a timestamp).",
          "type": "integer"
        }
      },
      "required": [
        "side",
        "high",
        "low",
        "active",
        "start"
      ]
    }
  ]
}
```
