Skip to main content

Insurance Fund & ADL

ZTDX's USDⓈ-M futures market protects users from socialised loss using a two-stage waterfall:

  1. Insurance fund — a per-symbol pool that absorbs bad debt when a liquidated position's remaining collateral goes negative.
  2. Auto-Deleveraging (ADL) — only kicks in when the insurance fund cannot fully cover the shortfall; reduces top-ranked profitable opposing positions to make the books whole.

This page documents the mechanism, the public REST endpoints, and how to detect when an account has been touched by either system.

Bad-Debt Waterfall

When a position breaches its maintenance margin and is force-closed at mark_price:

remaining_collateral = collateral_amount
+ realized_pnl(mark_price)
- accumulated_funding_fee
- accumulated_borrowing_fee
- liquidation_fee (size_in_usd × 0.5%)
- insurance_contribution (size_in_usd × 0.1%)
- liquidator_reward (size_in_usd × 0.1%, if any)

Stage 1 — User collateral

If remaining_collateral ≥ 0, the user is fully bankrupt-proof: the position closes, fees are paid, and any positive remainder is credited back to available_balance. Insurance fund receives the insurance_contribution slice and ADL is not engaged.

Stage 2 — Insurance fund payout

If remaining_collateral < 0, the insurance fund covers the shortfall up to:

insurance_payout = min( |remaining_collateral|,
fund.balance × max_insurance_payout_rate )

max_insurance_payout_rate defaults to 0.5 (50% of the fund's current balance per single liquidation event), preventing one outlier event from draining the fund.

Stage 3 — ADL (Auto-Deleveraging)

If a residual shortfall remains after the insurance fund payout, the engine emits an adl event. Profitable positions on the opposite side of the liquidated position are ranked and partially (or fully) reduced at the current mark price; the realized PnL slice they would have collected is redirected to cover the residual shortfall.

For unified-margin accounts, the per-step record's liquidation_type is set to "adl" whenever this branch fires (see Unified-Margin Liquidation History).

Insurance Fund Funding

The fund is per symbol, not global. Each symbol's pool is fed by a fixed 0.1% (10 bps) of position notional charged to every liquidated position (insurance_fund_fee_rate, configurable per market). Trading fees, funding fees, and borrow fees do not flow into the insurance fund — they go to protocol revenue.

Withdrawals from the fund occur only when Stage 2 (above) fires.

ADL Ranking

ADL only considers positions that are profitable at the current mark price (unrealized_pnl > 0) and on the opposite side of the position being liquidated. Each candidate gets a score:

adl_score = pnl_weight × pnl_percentage
+ leverage_weight × leverage
+ size_weight × (size / max_size_in_market)

Positions are ranked descending by score; rank 1 is reduced first. The authoritative ranking — including the actual rank, adl_score, and the inputs used to compute it — is exposed via GET /api/v1/adl/{symbol}/rankings?side=long|short.

Per-market ADL behaviour (weights, max positions per event, min/max reduction ratios, per-symbol enable flag) is exposed via GET /api/v1/adl/{symbol}/config.

Rankings are recomputed every 30 seconds by a background worker.

Note on /fapi/v1/adlQuantile — the Binance-compatible endpoint currently returns a coarse, simplified quantile derived only from each position's side (it does not yet read live adl_rankings data). For the precise ranking, use the native GET /api/v1/adl/{symbol}/rankings endpoint described below.

Public REST Endpoints

All endpoints under /api/v1/... are public (no auth, no signing). Endpoints under /fapi/v1/... are Binance-compatible and signed (HMAC SHA256).

EndpointReturns
GET /api/v1/insurance-fund/{symbol}Current per-symbol fund balance + lifetime totals
GET /api/v1/liquidations/{symbol}Recent liquidations on a market (incl. insurance_fund_contribution)
GET /api/v1/liquidations/{symbol}/configLiquidation parameters for a market (insurance_fund_fee_rate, max_insurance_payout_rate, etc.)
GET /api/v1/adl/{symbol}/rankingsLive ADL ranking snapshot (per side)
GET /api/v1/adl/{symbol}/eventsHistory of ADL events on a market
GET /api/v1/adl/{symbol}/configPer-market ADL parameters and enabled flag
GET /fapi/v1/adlQuantilePer-position ADL quantile (0–4); Binance-compatible, signed
GET /fapi/v1/forceOrdersCaller's liquidation orders; Binance-compatible, signed

For the full schema of liquidation history records, see forceOrders (Binance-compatible) and unified/liquidations (ZTDX-native, unified-margin only).

GET /api/v1/insurance-fund/{symbol}

Returns the live insurance fund state for a single symbol. Public; no authentication.

Path parameters

NameTypeRequiredDescription
symbolSTRINGYESe.g. BTCUSDT

Response

{
"fund": {
"id": "8e7c2a1b-4d3e-4f5a-9b0c-1d2e3f4a5b6c",
"symbol": "BTCUSDT",
"balance": "14448.566075868791505285",
"total_contributions": "27508.804093804413605641",
"total_payouts": "13060.238017935622100451",
"updated_at": "2026-05-07T08:12:52.919276Z",
"created_at": "2025-11-14T03:00:00.000000Z"
}
}
FieldMeaning
balanceCurrent funds available to cover bad debt for this symbol
total_contributionsLifetime sum credited from liquidations (0.1% × notional)
total_payoutsLifetime sum debited to cover bad debt
updated_atLast time the row was touched (i.e. last liquidation on this symbol)

If the symbol has never been touched, the endpoint creates a zero-balance row on demand and returns it.

GET /api/v1/liquidations/{symbol}/config

Returns the liquidation parameters used by the engine for this market.

{
"config": {
"symbol": "BTCUSDT",
"liquidation_fee_rate": "0.005",
"max_leverage": 50,
"maintenance_margin_rate": "0.005",
"min_collateral_usd": "10",
"insurance_fund_fee_rate": "0.001",
"max_insurance_payout_rate": "0.5",
"liquidator_reward_rate": "0.001"
}
}

These values are also stamped into every liquidations row, so downstream analytics never have to assume the live config is authoritative for past events.

GET /api/v1/adl/{symbol}/rankings

Returns the current ranking snapshot for one side, ordered by rank ascending (rank 1 = first to be reduced if ADL fires next). Rankings are recomputed every 30s.

Query parameters

NameTypeRequiredDefaultDescription
sideSTRINGYES"long" or "short"
limitINTNO50Page size

Response

{
"market_symbol": "BTCUSDT",
"side": "long",
"rankings": [
{
"id": "...",
"market_symbol": "BTCUSDT",
"side": "long",
"position_id": "...",
"user_address": "0x...",
"position_size": "50000.00",
"unrealized_pnl": "1234.56",
"pnl_percentage": "12.345",
"leverage": "10",
"adl_score": "1.7234",
"rank": 1,
"computed_at": "2026-05-07T08:12:30Z"
}
]
}

Only profitable positions appear in the ranking; unprofitable positions are never candidates.

GET /api/v1/adl/{symbol}/events

History of ADL executions for the market. An empty list means the insurance fund has covered every shortfall to date, which is the steady state.

{
"events": [
{
"id": "...",
"market_symbol": "BTCUSDT",
"liquidation_id": "...",
"insurance_fund_shortfall": "850.000000000000000000",
"total_reduced_size": "8500.000000000000000000",
"total_pnl_realized": "850.000000000000000000",
"positions_affected": 3,
"status": "completed",
"created_at": "2026-04-13T05:12:34.567890Z",
"completed_at": "2026-04-13T05:12:34.892011Z"
}
]
}

status is one of pending (in flight), completed (shortfall fully covered), partial (couldn't cover entire shortfall — exceptional), or failed (no profitable counterparties — exceptional).

Detecting Insurance / ADL Impact in Account Data

You want to know…Where to look
Was my position liquidated?/fapi/v1/forceOrders — Binance-compatible
Was my unified-margin account liquidated step-by-step?/api/v1/unified/liquidations. liquidation_type == "adl" ⇒ ADL was engaged for this step
Am I likely to be ADL'd next?GET /api/v1/adl/{symbol}/rankings?side=... — find your position_id; lower rank = earlier in the queue. (/fapi/v1/adlQuantile is currently a coarse stub — use the native endpoint.)
Was a specific position reduced by ADL?The position will appear in adl_reductions; check the user-side trade history for a synthetic close at the ADL execution price

Code Example — fetch all insurance fund balances

import requests

BASE_URL = "https://api.ztdx.io"

# 1. Get the list of active symbols
markets = requests.get(f"{BASE_URL}/api/v1/markets").json()
symbols = [m["symbol"] for m in markets]

# 2. Fan out
total_balance = 0.0
for symbol in symbols:
r = requests.get(f"{BASE_URL}/api/v1/insurance-fund/{symbol}")
fund = r.json()["fund"]
bal = float(fund["balance"])
total_balance += bal
print(f"{symbol:14s} balance={bal:>14.4f} "
f"contrib={float(fund['total_contributions']):>14.4f} "
f"payouts={float(fund['total_payouts']):>14.4f}")

print(f"\nTotal insurance fund across all markets: {total_balance:,.2f} USDT")

FAQ

Why is the insurance fund per-symbol and not global? Per-symbol funds prevent contagion: a flash crash on a thinly-traded altcoin can't drain the BTC market's reserve.

Can the insurance fund be topped up manually? Not via a public endpoint. The only credit path is the 0.1% liquidation contribution. Operational top-ups (e.g. a protocol-funded boost) are admin operations and not part of the public API surface.

What happens if the insurance fund goes to zero? Stage 2 simply pays out 0, and Stage 3 (ADL) covers the entire bankrupt slice. The market keeps running.

Why does the 50% cap exist? A single anomalous liquidation should not be able to wipe out the entire fund. With the cap, even a worst-case liquidation only consumes half of the available balance, leaving the other half to absorb the next event. The remaining shortfall flows into ADL, which is correctly designed to handle unbounded events.

Are insurance contributions visible in protocol_fee_ledger? Yes. Each liquidation writes three rows: liquidation_fee (positive, protocol revenue), insurance_contribution (positive, mirrors the credit to the insurance fund), and liquidator_reward (negative when paid to a keeper, since it physically leaves the vault).