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
diagonalmode, the default, aggressive buying (ask) at a level is compared with aggressive selling (bid) one level below: withminimumPercent: 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
consecutiveconsecutive 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
consecutiveto keep only the clearest imbalances.
How it is computed
- The bar’s levels are first grouped by
groupTicksticks.modesets the comparison:diagonalcompares 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);horizontalcompares the ask and bid of the same level;delta-percentagerelates the level’s delta to its volume. - With
diagonalandhorizontal, a level is imbalanced when the dominant side is not empty, reachesminimumPercent% of the other (300 = 3 to 1) and exceeds it by at leastminimumVolumecontracts; an empty opposite side (0 contracts) only counts withincludeZero. Withdelta-percentage, the difference must strictly exceedminimumVolumeand be at leastminimumPercent% of both sides’ volume;minimumPercentmust then be at most 100 (the default, 300, is refused there). - At least
consecutiveconsecutive levels imbalanced on the same side form a zone, bounded by the bar and widened byextraTicksticks 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. WithtriggeredZones, 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
extendedBarsbars 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 itsend) 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 withtimeframe: 300). <weekSunday>is theYYYY-MM-DDdate 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 theirend); 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, on historical data and in exports. Parameters you omit take their default values.
{
"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. 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 returns the same objects, with the same cursor, without the sub field.
{
"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
}
}{
"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.
Parameters schema
{
"$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
{
"$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"
]
}