Indicator

Fathom Footprint

Bid and ask volume at each price level, bar by bar.

What the indicator shows

  • Each bar is split into price levels. At each level, the volume of aggressive selling, filled at the bid, and the volume of aggressive buying, filled at the ask; on the Fathom chart, the bid is on the left and the ask on the right.
  • The POC is the level with the most volume traded in the bar; the Fathom chart frames it.
  • A level’s delta (ask minus bid) shows which side was more aggressive at that price. The sum over the levels gives the bar’s delta.

How to use it

  • Spot absorption: heavy aggressive volume at an extreme without price moving on shows passive orders soaking up the aggression.
  • Follow where the POC sits: at the top of the bar, volume traded at the highs; at the bottom, at the lows.
  • Compare bid and ask on neighboring levels to see where one side takes over. The imbalances indicator keeps only those zones.
  • Raise groupTicks for a more condensed reading on a fast instrument. filterMin and filterMax only count trades of a given size, for example large lots.

How it is computed

  • Bars last timeframe seconds, from 1 second to 1 hour, and start on multiples of timeframe from midnight UTC (9:30, 9:35… with 5 minutes). A bar with no counted trade is not sent.
  • Levels are aligned on multiples of groupTicks ticks: a level’s price is the bottom of its group, so with groupTicks: 4 the first level can be below the bar’s low.
  • levels volumes only count trades whose volume is between filterMin and filterMax. The OHLC (open, high, low, close) is computed on all the bar’s trades, counted or not.
  • A level’s bid volume comes from aggressive sellers (hitting the bid), its ask volume from aggressive buyers. Level volume = l[1] + l[2] + (l[3] ?? 0); delta = l[2] − l[1].

When it updates

  • The bar is sent once, final, on the first trade of a later bar; nothing is sent while it is in progress. The last bar before the 5:00 pm New York break therefore only arrives at the 6:00 pm reopen, and Friday’s last bar on Sunday evening.
  • No reset: each bar is independent.
Identifier
footprint
Objects received
bar
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.

Subscribe message
{
  "t": "subscribe",
  "sub": "footprint",
  "instrument": "NQ.front",
  "indicator": "footprint",
  "params": {
    "filterMax": 0,
    "filterMin": 0,
    "groupTicks": 1,
    "timeframe": 15
  },
  "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.

ParameterTypeDefaultAllowed valuesUnitDescription
filterMaxinteger00 to 1,000,000contractsMaximum volume for a trade to be counted; 0 = unlimited, otherwise at least filterMin.
filterMininteger00 to 1,000,000contractsMinimum volume for a trade to be counted; 0 = no minimum.
groupTicksinteger11 to 100ticksNumber of ticks grouped into each price level.
timeframeinteger151 · 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.

bar sent final

Finished bar that starts at barStartNs, sent on the first trade of a later bar.

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

FieldTypeUnitPresentDescription
closeintegerticksalwaysClose: price of the bar’s last trade, counted or not.
highintegerticksalwaysHigh of the bar, over all its trades.
levelsarray—alwaysLevels by ascending price: [price (ticks), bid volume (contracts), ask volume (contracts)], plus a 4th element when the level holds trades whose aggressor is not identified (their volume, in contracts): [122008, 14, 3, 2] counts 14 contracts at the bid, 3 at the ask and 2 without an identified aggressor. Bid volume comes from aggressive sellers (hitting the bid), ask volume from aggressive buyers. A level’s price is the bottom of its groupTicks-tick group. Level volume = l[1] + l[2] + (l[3] ?? 0); delta = l[2] − l[1].
lowintegerticksalwaysLow of the bar, over all its trades.
openintegerticksalwaysOpen: price of the bar’s first trade, counted or not.
pocintegerticksalwaysPrice of the bar’s highest-volume level. On a tie: the lowest of the tied levels at or above the open’s level, otherwise the highest of those below it.

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.

Final object
{
  "sub": "footprint",
  "cursor": "20720.0.0",
  "t": "upsert",
  "id": "1790207985000000000",
  "final": true,
  "ts": "1790208000058746563",
  "data": {
    "open": 123032,
    "high": 123032,
    "low": 123012,
    "close": 123014,
    "levels": [
      [
        123012,
        1,
        0
      ],
      [
        123014,
        0,
        1
      ],
      [
        123015,
        1,
        0
      ],
      [
        123016,
        2,
        0
      ],
      [
        123017,
        1,
        0
      ],
      [
        123018,
        1,
        0
      ],
      [
        123020,
        3,
        1
      ],
      [
        123022,
        1,
        0
      ],
      [
        123024,
        1,
        0
      ],
      [
        123025,
        0,
        1
      ],
      [
        123026,
        0,
        1
      ],
      [
        123027,
        0,
        2
      ],
      [
        123032,
        0,
        1
      ]
    ],
    "poc": 123020
  }
}

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
JSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://fathomcharts.com/schemas/footprint/params.json",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "filterMax": {
      "description": "Maximum volume for a trade to be counted; 0 = unlimited, otherwise at least `filterMin`.",
      "type": "integer",
      "default": 0,
      "minimum": 0,
      "maximum": 1000000
    },
    "filterMin": {
      "description": "Minimum volume for a trade to be counted; 0 = no minimum.",
      "type": "integer",
      "default": 0,
      "minimum": 0,
      "maximum": 1000000
    },
    "groupTicks": {
      "description": "Number of ticks grouped into each price level.",
      "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": 15,
      "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/footprint/data.json",
  "title": "bar",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "close": {
      "description": "Close: price of the bar’s last trade, counted or not.",
      "type": "integer"
    },
    "high": {
      "description": "High of the bar, over all its trades.",
      "type": "integer"
    },
    "levels": {
      "description": "Levels by ascending price: `[price (ticks), bid volume (contracts), ask volume (contracts)]`, plus a 4th element when the level holds trades whose aggressor is not identified (their volume, in contracts): `[122008, 14, 3, 2]` counts 14 contracts at the bid, 3 at the ask and 2 without an identified aggressor. Bid volume comes from aggressive sellers (hitting the bid), ask volume from aggressive buyers. A level’s price is the bottom of its `groupTicks`-tick group. Level volume = `l[1] + l[2] + (l[3] ?? 0)`; delta = `l[2] − l[1]`.",
      "type": "array",
      "items": {
        "type": "array",
        "maxItems": 4,
        "minItems": 3,
        "prefixItems": [
          {
            "type": "integer"
          },
          {
            "type": "integer"
          },
          {
            "type": "integer"
          },
          {
            "type": "integer"
          }
        ]
      }
    },
    "low": {
      "description": "Low of the bar, over all its trades.",
      "type": "integer"
    },
    "open": {
      "description": "Open: price of the bar’s first trade, counted or not.",
      "type": "integer"
    },
    "poc": {
      "description": "Price of the bar’s highest-volume level. On a tie: the lowest of the tied levels at or above the open’s level, otherwise the highest of those below it.",
      "type": "integer"
    }
  },
  "required": [
    "open",
    "high",
    "low",
    "close",
    "levels",
    "poc"
  ]
}