> For the complete documentation index, see [llms.txt](https://docs.hydromancer.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hydromancer.xyz/readme/builder-guides/tpsl-heatmap-streaming.md).

# TP/SL heatmap streaming

Stream real-time trigger order (take profit / stop loss) updates and build heatmaps showing where TP/SL orders cluster at different price levels.

### How it works

1. Subscribe to [tpslUpdates](/readme/websocket/tpslupdates.md) and buffer incoming updates
2. Fetch [tpslBook](/readme/rest-api/market-data/tpslbook.md) snapshot with block height
3. Apply buffered updates where `height > snapshot_height`
4. Continue applying live updates

Trigger orders are **immutable** — only `add` and `remove` diffs, no modifications. This makes the sync model simpler than L4 orderbooks (no partial fills, no matching required).

### Use cases

* **TP/SL heatmaps** — visualize where stop losses and take profits cluster relative to current price. Dense clusters = potential liquidation cascades or profit-taking zones.
* **Liquidation risk analysis** — large clusters of stop losses below support indicate potential cascade risk
* **Smart money tracking** — monitor where large accounts place their TP/SL levels
* **Position-level TP/SL monitoring** — filter `isPositionTpsl: true` to see position-attached orders vs standalone triggers
* **Market microstructure research** — study how TP/SL placement evolves relative to price action

### SDK

The [Hydromancer Python SDK](https://github.com/hydromancerxyz/hydromancer-sdk) handles sync automatically:

```python
from hydromancer_sdk import TpslClient

def on_update(client, height, adds, removes):
    book = client.get_book("BTC")
    if book:
        print(f"BTC: {book.order_count} orders "
              f"(TP={len(book.take_profits)}, SL={len(book.stop_losses)})")

        # Get orders near current price
        near = book.orders_near_price(105000, pct=5)
        print(f"  Within 5% of $105K: {len(near)} orders")

        # Price level heatmap data
        levels = book.price_levels()
        for px, count in levels.items():
            if count >= 3:
                print(f"  ${px}: {count} orders")

client = TpslClient(coins=["BTC", "ETH"], on_update=on_update)
await client.run()
```

See [examples/stream\_tpsl.py](https://github.com/hydromancerxyz/hydromancer-sdk/blob/main/examples/stream_tpsl.py) for a complete streaming example.

### Manual implementation

If implementing without the SDK, maintain a map of `oid → order` per market:

* **add** — insert or replace order by `oid` (idempotent upsert)
* **remove** — delete order by `oid`

No matching or modification logic is needed. Every `remove` diff is terminal — just drop the order from your map. The `reason` field is informational (see [tpslUpdates](/readme/websocket/tpslupdates.md#remove-handling)) and doesn't affect handling.

### Building a heatmap

To build a trigger-order heatmap:

1. **Bucket by price** — group orders by `triggerPx` into price buckets (e.g., $500 intervals for BTC)
2. **Weight by size** — sum `sz` within each bucket for volume-weighted heatmap
3. **Split by type** — separate take profits (`orderType` contains "Take Profit") from stop losses ("Stop") for distinct color layers
4. **Split by side** — `"A"` (sell-side triggers, typically stop losses below price) vs `"B"` (buy-side triggers, typically take profits above price)

### Key details

* Updates arrive per-block (\~70ms block time on Hyperliquid)
* \~110K trigger orders across \~330 markets (typical for mainnet)
* Block processing latency: \~20µs p50 (near-instant)
* Orders are fully immutable — no size or price changes after placement
* `isPositionTpsl: true` = attached to a position (auto-cancel when position closes)
* `reduceOnly: true` = almost all trigger orders (reduce-only by nature)

### Differences from L4 orderbook

|                     | L4 orderbook                     | TPSL book                |
| ------------------- | -------------------------------- | ------------------------ |
| **What**            | Resting limit orders on the book | Untriggered TP/SL orders |
| **Diffs**           | `new`, `update`, `remove`        | `add`, `remove` only     |
| **Modifications**   | Yes (partial fills change size)  | No (immutable)           |
| **Matching**        | Required (crossed orders)        | Not needed               |
| **Snapshot format** | Binary (multi-zstd msgpack)      | JSON                     |
| **Typical count**   | \~460K orders                    | \~110K orders            |
