Builtins: the footprint ladder
A footprint bar is a ladder of price rows, each carrying the volume that hit the bid and the volume that lifted the offer. These accessors read the same ladder the footprint draws. Rows are ordered from the lowest price up, and a row price is the BOTTOM of that row, so a price p belongs to the row r with r.price at or below p and p below r.price plus one tick.
| Signature | Returns |
|---|---|
| cell.available() -> bool | Whether this bar carries a ladder |
| cell.count() -> int | How many rows |
| cell.price(index: int) -> float | Price at the bottom of the row |
| cell.bid_volume(index: int) -> float | Volume that hit the bid on that row |
| cell.ask_volume(index: int) -> float | Volume that lifted the offer on that row |
| cell.volume(index: int) -> float | Both sides added |
| cell.delta(index: int) -> float | Ask volume minus bid volume on that row |
| cell.imbalance(index: int) -> float | Row delta divided by row volume, from -1 to 1 |
| cell.index_at(price: float) -> int | Index of the row that contains this price, or -1 |
| cell.volume_at(price: float) -> float | Row volume at a price |
| cell.bid_volume_at(price: float) -> float | Bid volume at a price |
| cell.ask_volume_at(price: float) -> float | Ask volume at a price |
| cell.delta_at(price: float) -> float | Row delta at a price |
| cell.poc_index() -> int | Index of the highest volume row, or -1 |
| cell.poc_price() -> float | Price of the highest volume row |
| cell.max_volume() -> float | Volume on that row |
| cell.max_delta() -> float | Largest absolute row delta on the bar |
| cell.high_price() -> float | Price of the top row |
| cell.low_price() -> float | Price of the bottom row |
| cell.va_high([percent: float]) -> float | Top of the value area, default 70 percent |
| cell.va_low([percent: float]) -> float | Bottom of the value area |
| cell.bid_imbalances(ratio: float) -> int | Rows whose bid beat the ask one row above by at least ratio |
| cell.ask_imbalances(ratio: float) -> int | Rows whose ask beat the bid one row below by at least ratio |
| cell.stacked_bid(ratio: float, min_count: int) -> bool | A run of at least min_count consecutive bid imbalances |
| cell.stacked_ask(ratio: float, min_count: int) -> bool | The same for ask imbalances |
| chart.tick_size() -> float | The instrument tick, or nan when the chart did not state one |
- Absent is not zero. A bar with no ladder answers cell.available() false, cell.count() zero, cell.index_at() and cell.poc_index() minus one, the two stacked tests false, and nan everywhere else. A bar that HAS a ladder answers zero for a price nothing traded at, because that is an observed zero.
- cell.index_at() and cell.poc_index() answer -1 when there is no row, and the row accessors answer nan for -1, so cell.price(cell.index_at(p)) needs no guard.
- The imbalance accessors compare a row with its diagonal neighbour: a bid against the ask one row above, an ask against the bid one row below.
- They need to know what one row apart means, so they use the instrument tick. Where the chart states no tick they answer nan, or false for the two stacked tests, rather than calling rows two ticks apart neighbours. The price lookups answer nan and -1 the same way.
- The heavier answers, the point of control, the value area and the imbalance counts, are computed once per bar however many times you call them.
- Calling any cell accessor tells the chart your indicator wants the ladder, the same way the print level tape accessors do.
let has_ladder: bool = cell.available()
let poc: float = cell.poc_price()
let value_high: float = cell.va_high(70)
let value_low: float = cell.va_low(70)
let stacked: bool = has_ladder && cell.stacked_ask(3, 3)
plot.hline(poc, "#F59E0B", 1, "dotted", "POC")
plot.marker(stacked, high, "#22C55E", "square", "above", 8, 2)