MarketTrace
M1PositioningM2FootprintM3LiquidationsM4FundingM5Volume Profile
Methodology · v1.0.0 · published 2026-05-21

Perpetuals Heat Index

How the per-asset percentile and the equal-weight global score are computed, what the breadth counters mean, and where the data does not yet reach.

See the live widget at /perpetuals/positioning.

Data sources

The Heat Index reads one upstream file: data/research/heat_index.json, written by the PPI shadow-log cron at every hourly :05 UTC firing. That cron also maintains the per-asset ppi_log.jsonlfiles. So the index inherits PPI's 8-hour data cadence (one new datum per Binance funding settle), even though the FE polls every five minutes.

PPI itself is a rolling 180-cycle z-score of Binance USDT-margined funding rates per asset (3 cycles/day × 60 days ≈ 60 days of context). The shadow logger has captured roughly 2,049 settlements per asset since 2024-07-07, which is the historical distribution the percentile maps against.

Per-asset percentile

For each tracked asset the index ranks the current PPI value against that asset's own 22-month history. The formula is straight bisect_right: count how many historical PPI samples are less than or equal to the current value, divide by the total count, multiply by 100.

percentile_pct = bisect_right(sorted(history), current_ppi)
                  / len(history) × 100

A BTC score of 92 means today's BTC funding-z sits higher than 92% of every BTC funding-z observed since 2024-07-07. Each asset is graded on its own curve, so a BTC 92 and a DOGE 12 on the same day is a statement about position-within-history per asset, not absolute funding rates being equal.

Global aggregate

The market-wide score is the equal-weight average of the six per-asset current PPI values, mapped to the percentile of the same equal-weight aggregate computed historically over every timestamp where all six assets have a value. The historical window has roughly 2,049 such timestamps, same as the per-asset depth.

current_agg     = mean([ppi.btc, ppi.eth, ppi.sol, ppi.bnb,
                        ppi.xrp, ppi.doge])
historical_agg  = [mean(per_asset_ppi(t)) for t in shared_timestamps]
global_pct      = bisect_right(sorted(historical_agg), current_agg)
                  / len(historical_agg) × 100

Equal weight, not OI weight. BTC dominates open interest at around 60% of total perp OI, so an OI-weighted composite would track BTC closely. Equal weight surfaces breadth instead. When five of six venues run hot together the score climbs, even if BTC alone hasn't broken out.

Breadth counters

Alongside the global percentile, the payload exposes two counters: how many of the six assets currently sit in Hot or Euphoric bands, and how many sit in Cold or Panic. The widget renders these as “1 of 6 perps in hot zone”.

Breadth separates a broad regime shift from a single-asset blowout. Global score 75 with breadth_hot=5 means most of the market is warm at once. Global score 75 with breadth_hot=1 means one venue is at an extreme high and is pulling the average up by itself. Same headline number, very different microstructure.

Caching and freshness

The endpoint reads heat_index.json on every request and returns 503 if the file is older than two hours. Beyond that something is wrong upstream (timer dead, PPI cron failing, disk full) and serving stale heat values would mislead the FE more than an honest error.

Cache-Control sets 5-minute max-age plus 10-minute stale-while-revalidate, so a CDN can absorb most traffic without ever stale-serving past the file's actual age. FE polling matches: SWR refresh every five minutes. Since the file updates hourly, 11 of every 12 client requests return identical bytes from the CDN.

Limitations

Versioning

Initial release v1.0.0 on 2026-05-21. Material changes to the compute path (new weighting, threshold tweaks, additional inputs) bump the version and update the published date. Wording fixes do not.