# Fathom Walls

Liquidity walls defending repeated highs or lows.

## What the indicator shows

- A sell wall sits on a high tested several times, where aggressive buying stalls without breaking the level; the Fathom chart marks it with an S.
- A buy wall sits on a low tested several times, where aggressive selling stalls; the Fathom chart marks it with a B.
- In both cases, passive orders absorb the aggression: the level is defended.

## How to use it

- A wall gives a level to trade for a bounce while it holds, or to watch for a breakout if it gives way.
- Start with the looser settings described below (about one signal per hour on NQ), then tighten the criteria to keep only the clearest walls. Stricter criteria do not guarantee that a level will hold.

## How it is computed

- The test runs when each `timeframe`-second bar closes. A bar is a candidate for a sell wall when its high reaches those of the two previous bars (its low, for a buy wall).
- The tested group (cluster) starts at a recent candidate bar and gathers every later candidate up to the closed bar, at least `minimumBars` bars in all. Its first bar is less than `nearnessBars` bars old, and the closed bar does not exceed that bar’s high by more than `tickBreakoutMargin` ticks: a candidate that no longer meets these conditions is dropped. The closed bar’s high must be less than `tickGrouping` ticks from the group’s high. Groups are tried from the oldest candidate to the most recent.
- A cell is one price level of one bar of the group, within the `tickGrouping` ticks below the group’s high (above its low, for a buy wall).
- A wall is signaled when at least `minimumBars` cells each reach `minPerBarVolume` contracts, the cells’ total volume exceeds `minClusterVolume` and the share of aggressive volume stalling on the wall (`sideVolume / volume`) exceeds `minDeltaPercent`%; these last two thresholds are exclusive, so with `minDeltaPercent: 100` no wall is possible. Despite their names, `minimumBars` also counts cells, `minPerBarVolume` applies to each cell, and `minDeltaPercent` is not a delta but that share of aggressive volume.
- The defaults are strict: on NQ or ES, they give few walls, sometimes none over a whole session. For about one signal per hour on NQ, set `minPerBarVolume: 10`, `minClusterVolume: 30`, `minDeltaPercent: 40` and `tickGrouping: 4`, keeping the other parameters at their defaults (these are the parameters of the examples). The stricter the parameters, the fewer the walls.

## When it updates

- At most one signal per bar, sent final when the bar closes; the sell wall is tested first.

- Weekly reset: at the Sunday reopen (6:00 pm New York time), the previous week’s highs and lows no longer count.

- **Identifier**: `liquidity-walls`

- **Objects received**: `signal`

- **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": "liquidity-walls",
  "instrument": "NQ.front",
  "indicator": "liquidity-walls",
  "params": {
    "minClusterVolume": 30,
    "minDeltaPercent": 40,
    "minPerBarVolume": 10,
    "minimumBars": 2,
    "nearnessBars": 20,
    "tickBreakoutMargin": 1,
    "tickGrouping": 4,
    "timeframe": 60
  },
  "mode": "live",
  "from": "live"
}
```

These example parameters differ from the defaults: `minClusterVolume` `30` (default `400`), `minDeltaPercent` `40` (default `70`), `minPerBarVolume` `10` (default `100`), `tickGrouping` `4` (default `1`). This subscription therefore counts as a [custom configuration](https://fathomcharts.com/docs/limits.md#how-limits-are-counted); the Sandbox allows 1.

## 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                                                                                                                                           |
| -------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `minClusterVolume`   | integer | `400`   | 0 to 10,000,000                                                                                 | contracts | Total volume the wall’s cells must exceed (exclusive).                                                                                                |
| `minDeltaPercent`    | integer | `70`    | 0 to 100                                                                                        | %         | Minimum share (exclusive) of the wall’s cell volume made up of aggressive volume stalling against it: buying for a sell wall, selling for a buy wall. |
| `minPerBarVolume`    | integer | `100`   | 0 to 1,000,000                                                                                  | contracts | Minimum volume for a cell (one price level of one bar of the wall) to count.                                                                          |
| `minimumBars`        | integer | `2`     | 2 to 100                                                                                        | —         | Minimum number of cells whose volume reaches `minPerBarVolume`, and of candidate bars in the cluster.                                                 |
| `nearnessBars`       | integer | `20`    | 1 to 1,000                                                                                      | bars      | Maximum age of a high or low in the cluster, in bars.                                                                                                 |
| `tickBreakoutMargin` | integer | `1`     | 0 to 100                                                                                        | ticks     | How far price may go past a high (or a low) before it stops counting for the wall.                                                                    |
| `tickGrouping`       | integer | `1`     | 1 to 100                                                                                        | ticks     | Depth of the wall, below the cluster’s high (or above its low); the tested bar must reach this band.                                                  |
| `timeframe`          | integer | `60`    | `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`).                                                                                      |

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

### `signal` (sent final)

Signal of the finished bar that starts at `barStartNs`.

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

| Field        | Type                     | Unit      | Present | Description                                                                                                             |
| ------------ | ------------------------ | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `cells`      | integer                  | —         | always  | Number of cells (one price level of one bar of the wall) whose volume reaches `minPerBarVolume`.                        |
| `price`      | integer                  | ticks     | always  | High (sell wall) or low (buy wall) of the bar that tests the wall.                                                      |
| `side`       | string · `buy` \| `sell` | —         | always  | `sell`: sell wall at the high; `buy`: buy wall at the low.                                                              |
| `sideVolume` | integer                  | contracts | always  | Aggressive volume that stalled on the wall, in those cells: buying (ask) for a sell wall, selling (bid) for a buy wall. |
| `volume`     | integer                  | contracts | always  | Total volume of the wall’s cells.                                                                                       |

## 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": "liquidity-walls",
  "cursor": "20720.13976.0",
  "t": "upsert",
  "id": "1790217900000000000",
  "final": true,
  "ts": "1790217960047941663",
  "data": {
    "price": 122988,
    "side": "sell",
    "volume": 186,
    "sideVolume": 162,
    "cells": 4
  }
}
```

## 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/liquidity-walls/params.json",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "minClusterVolume": {
      "description": "Total volume the wall’s cells must exceed (exclusive).",
      "type": "integer",
      "default": 400,
      "minimum": 0,
      "maximum": 10000000
    },
    "minDeltaPercent": {
      "description": "Minimum share (exclusive) of the wall’s cell volume made up of aggressive volume stalling against it: buying for a sell wall, selling for a buy wall.",
      "type": "integer",
      "default": 70,
      "minimum": 0,
      "maximum": 100
    },
    "minPerBarVolume": {
      "description": "Minimum volume for a cell (one price level of one bar of the wall) to count.",
      "type": "integer",
      "default": 100,
      "minimum": 0,
      "maximum": 1000000
    },
    "minimumBars": {
      "description": "Minimum number of cells whose volume reaches `minPerBarVolume`, and of candidate bars in the cluster.",
      "type": "integer",
      "default": 2,
      "minimum": 2,
      "maximum": 100
    },
    "nearnessBars": {
      "description": "Maximum age of a high or low in the cluster, in bars.",
      "type": "integer",
      "default": 20,
      "minimum": 1,
      "maximum": 1000
    },
    "tickBreakoutMargin": {
      "description": "How far price may go past a high (or a low) before it stops counting for the wall.",
      "type": "integer",
      "default": 1,
      "minimum": 0,
      "maximum": 100
    },
    "tickGrouping": {
      "description": "Depth of the wall, below the cluster’s high (or above its low); the tested bar must reach this band.",
      "type": "integer",
      "default": 1,
      "minimum": 1,
      "maximum": 100
    },
    "timeframe": {
      "description": "Bar duration in seconds: from 1 second (`1`) to 1 hour (`3600`).",
      "type": "integer",
      "default": 60,
      "enum": [
        1,
        5,
        10,
        15,
        30,
        60,
        120,
        180,
        300,
        600,
        900,
        1800,
        3600
      ]
    }
  }
}
```

**`data` field schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://fathomcharts.com/schemas/liquidity-walls/data.json",
  "title": "signal",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "cells": {
      "description": "Number of cells (one price level of one bar of the wall) whose volume reaches `minPerBarVolume`.",
      "type": "integer"
    },
    "price": {
      "description": "High (sell wall) or low (buy wall) of the bar that tests the wall.",
      "type": "integer"
    },
    "side": {
      "description": "`sell`: sell wall at the high; `buy`: buy wall at the low.",
      "type": "string",
      "enum": [
        "buy",
        "sell"
      ]
    },
    "sideVolume": {
      "description": "Aggressive volume that stalled on the wall, in those cells: buying (ask) for a sell wall, selling (bid) for a buy wall.",
      "type": "integer"
    },
    "volume": {
      "description": "Total volume of the wall’s cells.",
      "type": "integer"
    }
  },
  "required": [
    "price",
    "side",
    "volume",
    "sideVolume",
    "cells"
  ]
}
```
