# Fathom Imbalances

Stacked buyer and seller imbalances, turned into zones.

## What the indicator shows

- An imbalance is a price level where one side overwhelms the other in the bar’s footprint, that is its bid and ask volume price by price. In `diagonal` mode, the default, aggressive buying (ask) at a level is compared with aggressive selling (bid) one level below: with `minimumPercent: 300`, the ask must be at least 3 times that bid for a buy imbalance; conversely, the bid must be at least 3 times the ask one level above for a sell imbalance.
- When at least `consecutive` consecutive levels (3 by default) are imbalanced on the same side, they are called stacked imbalances: the indicator turns them into a zone. The footprint is not published; only the resulting zone is, when the bar closes.
- A buy zone marks where buyers dominated across those levels; a sell zone, where sellers dominated. On the Fathom chart, buy zones are blue and sell zones coral.
- The zone extends to the right while it is active. With `triggeredZones`, a retest zone (`triggered: true`) takes over after triggering.

## How to use it

- Price often comes back to these zones: a buy zone then acts as support, a sell zone as resistance.
- A close on the other side triggers the zone: the level gave way. The retest zone follows that broken level, which can then play the opposite role.
- Raise `consecutive` to keep only the clearest imbalances.

## How it is computed

- The bar’s levels are first grouped by `groupTicks` ticks. `mode` sets the comparison: `diagonal` compares a level’s ask with the bid of the level just below (buy imbalance), and a level’s bid with the ask of the level just above (sell imbalance); `horizontal` compares the ask and bid of the same level; `delta-percentage` relates the level’s delta to its volume.
- With `diagonal` and `horizontal`, a level is imbalanced when the dominant side is not empty, reaches `minimumPercent`% of the other (300 = 3 to 1) and exceeds it by at least `minimumVolume` contracts; an empty opposite side (0 contracts) only counts with `includeZero`. With `delta-percentage`, the difference must strictly exceed `minimumVolume` and be at least `minimumPercent`% of both sides’ volume; `minimumPercent` must then be at most 100 (the default, 300, is refused there).
- At least `consecutive` consecutive levels imbalanced on the same side form a zone, bounded by the bar and widened by `extraTicks` ticks on each side.
- A buy zone is triggered when a bar closes below it (a sell zone, above it); with `triggerOnlyTouch`, a wick going past it is enough. With `triggeredZones`, a retest zone (`triggered: true`) then takes over, until a bar moves back to the other side the same way.
- A zone and its retest zone live at most `extendedBars` bars after the bar that created the original zone.

## When it updates

- A zone is sent in progress when the bar that creates it closes, sent again on every change, then sent final (`active: false`, with its `end`) when it ends.

- Zones stay active from bar to bar and from session to session. With `resetSession: true`, zones in progress are ended when the first bar of the new session closes (6:05 pm New York time with `timeframe: 300`).

- `<weekSunday>` is the `YYYY-MM-DD` date of the week’s Sunday reopen; `<ordinal>` numbers the week’s zones in creation order.

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

- **Identifier**: `imbalances`

- **Objects received**: `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": "imbalances",
  "instrument": "NQ.front",
  "indicator": "imbalances",
  "params": {
    "consecutive": 3,
    "extendedBars": 10,
    "extraTicks": 0,
    "groupTicks": 1,
    "includeZero": false,
    "minimumPercent": 300,
    "minimumVolume": 0,
    "mode": "diagonal",
    "resetSession": false,
    "timeframe": 300,
    "triggerOnlyTouch": false,
    "triggeredZones": 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                                                                                                                                                                                                                                                                            |
| ------------------ | ------- | ---------- | ----------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `consecutive`      | integer | `3`        | 1 to 20                                                                                         | —         | Minimum number of consecutive imbalanced levels to form a zone.                                                                                                                                                                                                                        |
| `extendedBars`     | integer | `10`       | 1 to 500                                                                                        | bars      | Maximum lifetime of a zone (and of its retest zone), in bars after the bar that created it.                                                                                                                                                                                            |
| `extraTicks`       | integer | `0`        | 0 to 100                                                                                        | ticks     | Padding added on each side of the zone, above the top and below the bottom.                                                                                                                                                                                                            |
| `groupTicks`       | integer | `1`        | 1 to 100                                                                                        | ticks     | Number of ticks grouped into each level before the comparison.                                                                                                                                                                                                                         |
| `includeZero`      | boolean | `false`    | true · false                                                                                    | —         | Count a level whose opposite side is empty (0 contracts) as an imbalance.                                                                                                                                                                                                              |
| `minimumPercent`   | integer | `300`      | 1 to 10,000                                                                                     | %         | Imbalance threshold, in percent. With `diagonal` and `horizontal`, ratio of the dominant side to the other (300 = 3 to 1). With `delta-percentage`, the delta’s share of the level’s volume: this mode requires an explicit value of at most 100 (the default, 300, is refused there). |
| `minimumVolume`    | integer | `0`        | 0 to 1,000,000                                                                                  | contracts | Minimum volume difference between the two sides of a level (strictly greater with `delta-percentage`).                                                                                                                                                                                 |
| `mode`             | choice  | `diagonal` | `diagonal` · `horizontal` · `delta-percentage`                                                  | —         | How volumes are compared: `diagonal` (a level’s ask against the bid of the level just below), `horizontal` (ask and bid of the same level) or `delta-percentage` (the level’s delta relative to its volume). Unrelated to the subscription `mode` (`live` or `confirmed`).             |
| `resetSession`     | boolean | `false`    | true · false                                                                                    | —         | End every zone when the first bar of each new session closes.                                                                                                                                                                                                                          |
| `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`).                                                                                                                                                                                                                       |
| `triggerOnlyTouch` | boolean | `false`    | true · false                                                                                    | —         | Trigger a zone as soon as a wick goes past it, without waiting for a close beyond it.                                                                                                                                                                                                  |
| `triggeredZones`   | boolean | `true`     | true · false                                                                                    | —         | Create a retest zone when a zone is triggered.                                                                                                                                                                                                                                         |

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

### `zone` (sent in progress)

Zone; in progress while it is active.

`id` format: `<weekSunday>:<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 is extended; `false` once it is final.                                                                                                                                                                                                                                                                                                                                                         |
| `bar`       | string                   | timestamp (ns) | always    | Start of the bar that created the zone (for a retest zone, the bar that triggered the original zone).                                                                                                                                                                                                                                                                                                                |
| `end`       | number                   | bars           | sometimes | End of the zone, in bars relative to `bar`; present once the zone is final. Original zone: index of the bar that ends it (trigger, session open, end of week) minus 1, that is `extendedBars` on expiry; it is 0, so less than `start`, for a zone triggered by the very next bar. Retest zone: index of the bar that ends it plus 1 after a retest, minus 1 on expiry, at a session open or at the end of the week. |
| `high`      | integer                  | ticks          | always    | Top of the zone.                                                                                                                                                                                                                                                                                                                                                                                                     |
| `low`       | integer                  | ticks          | always    | Bottom of the zone (equal to the top for a line).                                                                                                                                                                                                                                                                                                                                                                    |
| `side`      | string · `buy` \| `sell` | —              | always    | Buy or sell imbalance.                                                                                                                                                                                                                                                                                                                                                                                               |
| `start`     | number                   | bars           | always    | Start of the zone, in bars relative to `bar` (0 = start of `bar`): `0.5` for an original zone, `-0.5` for a retest zone. Matching time, in nanoseconds: `BigInt(bar) + BigInt(Math.round(start × timeframe × 1e9))` (`bar` is in nanoseconds, `timeframe` in seconds).                                                                                                                                               |
| `triggered` | boolean                  | —              | always    | `true`: retest zone, created when a bar triggered a zone; `false`: original zone.                                                                                                                                                                                                                                                                                                                                    |

## 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": "imbalances",
  "cursor": "20720.0.0",
  "t": "upsert",
  "id": "2026-09-20:750",
  "final": true,
  "ts": "1790208000058746563",
  "data": {
    "side": "sell",
    "high": 123052,
    "low": 123050,
    "triggered": true,
    "active": false,
    "bar": "1790207400000000000",
    "start": -0.5,
    "end": 2
  }
}
```

**Object in progress**

```json
{
  "sub": "imbalances",
  "cursor": "20720.0.1",
  "t": "upsert",
  "id": "2026-09-20:751",
  "final": false,
  "ts": "1790208000058746563",
  "data": {
    "side": "sell",
    "high": 123040,
    "low": 123038,
    "triggered": false,
    "active": true,
    "bar": "1790207700000000000",
    "start": 0.5
  }
}
```

## 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/imbalances/params.json",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "consecutive": {
      "description": "Minimum number of consecutive imbalanced levels to form a zone.",
      "type": "integer",
      "default": 3,
      "minimum": 1,
      "maximum": 20
    },
    "extendedBars": {
      "description": "Maximum lifetime of a zone (and of its retest zone), in bars after the bar that created it.",
      "type": "integer",
      "default": 10,
      "minimum": 1,
      "maximum": 500
    },
    "extraTicks": {
      "description": "Padding added on each side of the zone, above the top and below the bottom.",
      "type": "integer",
      "default": 0,
      "minimum": 0,
      "maximum": 100
    },
    "groupTicks": {
      "description": "Number of ticks grouped into each level before the comparison.",
      "type": "integer",
      "default": 1,
      "minimum": 1,
      "maximum": 100
    },
    "includeZero": {
      "description": "Count a level whose opposite side is empty (0 contracts) as an imbalance.",
      "type": "boolean",
      "default": false
    },
    "minimumPercent": {
      "description": "Imbalance threshold, in percent. With `diagonal` and `horizontal`, ratio of the dominant side to the other (300 = 3 to 1). With `delta-percentage`, the delta’s share of the level’s volume: this mode requires an explicit value of at most 100 (the default, 300, is refused there).",
      "type": "integer",
      "default": 300,
      "minimum": 1,
      "maximum": 10000
    },
    "minimumVolume": {
      "description": "Minimum volume difference between the two sides of a level (strictly greater with `delta-percentage`).",
      "type": "integer",
      "default": 0,
      "minimum": 0,
      "maximum": 1000000
    },
    "mode": {
      "description": "How volumes are compared: `diagonal` (a level’s ask against the bid of the level just below), `horizontal` (ask and bid of the same level) or `delta-percentage` (the level’s delta relative to its volume). Unrelated to the subscription `mode` (`live` or `confirmed`).",
      "type": "string",
      "default": "diagonal",
      "enum": [
        "diagonal",
        "horizontal",
        "delta-percentage"
      ]
    },
    "resetSession": {
      "description": "End every zone when the first bar of each new session closes.",
      "type": "boolean",
      "default": false
    },
    "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
      ]
    },
    "triggerOnlyTouch": {
      "description": "Trigger a zone as soon as a wick goes past it, without waiting for a close beyond it.",
      "type": "boolean",
      "default": false
    },
    "triggeredZones": {
      "description": "Create a retest zone when a zone is triggered.",
      "type": "boolean",
      "default": true
    }
  },
  "allOf": [
    {
      "description": "In `delta-percentage` mode, `minimumPercent` must be given and be at most 100 (the default, 300, is refused there).",
      "if": {
        "properties": {
          "mode": {
            "const": "delta-percentage"
          }
        },
        "required": [
          "mode"
        ]
      },
      "then": {
        "properties": {
          "minimumPercent": {
            "maximum": 100
          }
        },
        "required": [
          "minimumPercent"
        ]
      }
    }
  ]
}
```

**`data` field schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://fathomcharts.com/schemas/imbalances/data.json",
  "title": "zone",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "active": {
      "description": "`true` while the zone is extended; `false` once it is final.",
      "type": "boolean"
    },
    "bar": {
      "description": "Start of the bar that created the zone (for a retest zone, the bar that triggered the original zone).",
      "type": "string",
      "pattern": "^[0-9]+$"
    },
    "end": {
      "description": "End of the zone, in bars relative to `bar`; present once the zone is final. Original zone: index of the bar that ends it (trigger, session open, end of week) minus 1, that is `extendedBars` on expiry; it is 0, so less than `start`, for a zone triggered by the very next bar. Retest zone: index of the bar that ends it plus 1 after a retest, minus 1 on expiry, at a session open or at the end of the week.",
      "type": "number"
    },
    "high": {
      "description": "Top of the zone.",
      "type": "integer"
    },
    "low": {
      "description": "Bottom of the zone (equal to the top for a line).",
      "type": "integer"
    },
    "side": {
      "description": "Buy or sell imbalance.",
      "type": "string",
      "enum": [
        "buy",
        "sell"
      ]
    },
    "start": {
      "description": "Start of the zone, in bars relative to `bar` (0 = start of `bar`): `0.5` for an original zone, `-0.5` for a retest zone. Matching time, in nanoseconds: `BigInt(bar) + BigInt(Math.round(start × timeframe × 1e9))` (`bar` is in nanoseconds, `timeframe` in seconds).",
      "type": "number"
    },
    "triggered": {
      "description": "`true`: retest zone, created when a bar triggered a zone; `false`: original zone.",
      "type": "boolean"
    }
  },
  "required": [
    "side",
    "high",
    "low",
    "triggered",
    "active",
    "bar",
    "start"
  ]
}
```
