Auto-deleverage (ADL)
Preview. T4 fires only when a T3 close leaves the account flat and still negative — rare in normal operations, and deterministic when it happens.
TL;DR
When a liquidation leaves bad debt, the protocol claws back from profitable counter-parties on the same instrument, pro-rata to their unrealised PnL. ADL runs before the insurance fund, and after the Metaliquidity vault has taken what it can. MetaFlux's allocation uses an online-learning ranking that aims to minimise excess haircut (haircut beyond what the deficit requires).
When ADL fires
The tiered ladder:
T0 yellow card → T1 partial → T2 full → T3 backstop → T4 ADL
T3 closes the dying position at the committed mark. On a core market the Metaliquidity vault takes the first bite; what it declines is netted against profitable counter-parties. If the account is then flat and its equity is still negative, T4 runs.
Read the order carefully — ADL comes BEFORE the insurance fund. The deleveraged winners' realized gains absorb first, which keeps the fund for genuine tail events. An earlier version of this page had the two the other way round.
deficit = |account_value| after the account is flat
deficit -= metaliquidity_vault_absorb(deficit) # core markets only
if deficit > 0:
fire_adl(asset, deficit) # then insurance, then treasury
For the whole order — vault, ADL, insurance fund, treasury queue — see the deficit waterfall.
How ADL is computed
Further reading: "Autodeleveraging as Online Learning" (arXiv:2602.15182).
MTF does not use a single ranking score. ADL splits into two independent sub-problems: a 1-D severity decision (how much to haircut this round) learned online, and a deterministic pro-rata allocation (who, by PnL capacity).
⚠️ Correction vs. prior text. The earlier doc described a single online-learning ranking
score = α·pnl% + β·leverage + γ·age. That is not the implemented algorithm. The real controller isθ ∈ [0,1]severity via projected OGD + capacity-pro-rata allocation. Theα/β/γranking formula was rejected ("2D OGD — dimension blow-up"). The classicalpnl% × leveragequeue is the HL baseline MTF replaces, not what MTF runs.
1. Severity — 1-D online gradient descent on θ
Each round picks a scalar θ_t ∈ [0,1] = the fraction of this round's deficit to haircut:
B_t = θ_t · D_t # budget for this round
θ_needed_t = clamp(B̂_needed / D_t, 0, 1) # ex-ante estimate of what's actually needed
grad = D_t · sign(θ_t − θ_needed_t)
θ_{t+1} = clamp(θ_t − η · grad, 0, 1) # projected OGD step
D_t = round deficit, B̂_needed = the execution-price estimator's guess of the true need.
Step size η:
- Default mode is Adaptive (paper Cor. 1):
η* = sqrt( (1 + 2·P_T^θ) / Σ D_t² ), recomputed each round from running telemetry (path_variation,cumulative_squared_deficit). - On the first round (
Σ D_t² == 0) it falls back to the governance-tunableη₀ = 0.01(default_eta). - A
Fixed(c)mode pinsη = c(governance kill-switch / reproducibility).
The controller carries a dynamic-regret bound (Prop 1):
Reg_T^dyn ≤ sqrt( (1 + 2·P_T^θ) · Σ D_t² )
exposed as analytical_bound(); check_bound(slack) asserts empirical regret ≤ slack · bound (default slack 4) in the chaos tests.
Every fractional field (θ, η, path_variation, …) is exact fixed-point, and the square root is an integer method — there is no floating point on this path. Every accumulator saturates instead of overflowing, so an extreme value cannot halt the chain. The controller state is committed, so all validators agree on it.
2. Allocation — deterministic capacity pro-rata
Given budget B_t, distribute across the profitable counter-parties W_t (each with haircut capacity u_i = haircut-able unrealised PnL, u128):
total_u = Σ u_i
x_i = floor( u_i · B_t / total_u ) capped at u_i # 128-bit mul then 128-bit div
Integer-division dust B_t − Σ x_i is redistributed one unit at a time in ascending AccountId order to any winner with remaining capacity (BTreeMap iterates in key order → byte-identical across nodes). If B_t > total_u the round is capacity-bound and Σ x_i = total_u.
This replaces the rejected vector-mirror-descent and ILP allocators (the ILP is optimal but a non-deterministic solver — can't go on-chain).
Why pro-rata (HL Oct-10 2025 replay):
| Algorithm | Oct-10 total objective (lower = better) |
|---|---|
| HL production (ROE heuristic) | ~$45M overshoot |
| MTF pro-rata | $3.40M (~13× better) |
| Vector mirror descent | $4.41M |
| Min-max ILP (optimal, off-chain only) | $106k |
Pro-rata also gives 0 % monotonicity violations (vs HL's 11.4 %) and rank stability ≈ 1.0 (vs HL's 0.34).
Quoting ADL price (read side)
The EVM precompile 0x0902 adl_pro_rata_price lets a Solidity helper quote the VWAP fill an ADL of size N would clear at, walking the queue in side-appropriate priority (long ADL: highest price first; short ADL: lowest first) — all prices on the 1e8 fixed-point plane (price_e8, capacity_e8). It is pro-rata-only; the severity OGD lives in core-state, not the stateless precompile (severity is one decision per round; price quoting is many calls/sec).
What "haircut" means mechanically
Haircut is not a position transfer — the counter-party's position size shrinks and their unrealised PnL is converted into a realised loss. The dying account's opposite-side position evaporates by the same amount.
Concretely: suppose account A is long 1 BTC at entry 100 and account B is short 1 BTC at entry 100, mark = 110.
- A is profitable (+10 USDC unrealised).
- B is the dying account, liquidated; the position resolves at mark 110 but B has only 5 USDC of equity. 5 USDC shortfall.
- Insurance pool: 0 (depleted).
- ADL fires against A:
- A's long is reduced to 0.5 BTC.
- A realises +5 USDC PnL (the part that got haircut).
- A's remaining 0.5 BTC long is at entry 100, mark 110, +5 USDC unrealised.
- B's short is fully closed.
A keeps the unrealised PnL on its remaining position; A only loses the closed portion's PnL.
Notification
ADL execution carries no dedicated event on any channel today — no
notifications kind, no
fills entry, and no ledger_updates record. The haircut is a direct
state mutation, so the only live signal is your position itself: the
affected account's position size and unrealised PnL change on the next
clearinghouse_state push (it
is change-driven — any position or PnL move triggers a frame).
For automated bots, subscribe to clearinghouse_state and diff your position set
between pushes; treat a shrink you did not order yourself as a forced event
(ADL haircut or liquidation) and re-evaluate your strategy.
Predicting ADL exposure
Read clearinghouse_state with detail: "adl".
Each position row then carries adl_lamps, an integer from 0 to 4. More
lamps means the position sits sooner in the queue.
What the lamps rank is step 1 — the netting at mark. That step closes the
dying leg against the most profitable OPPOSITE-side positions, ordered by return
on committed margin (unrealised PnL ÷ |entry notional|, highest first). The
lamps are the quartile of your seat in exactly that order: 4 = top quarter,
1 = bottom quarter. Hedge legs rank separately, because the step settles per
leg.
The lamps do not rank step 2, the deficit haircut. That step is capacity-pro-rata and has no queue at all — every winner gives up the same fraction of capacity, so there is nothing to rank.
It is a RANKING, not a probability. Four lamps with nobody being liquidated on the other side still means nothing happens. Do not render it as a percentage chance.
ZERO lamps is meaningful, not unknown. Zero says the position is not in the queue at all — no committed mark, no unrealised profit, no cost basis, or nobody on the opposite side to be deleveraged against. A hedge account whose only opposing leg is its OWN reads zero on both legs, because ADL never nets an account against itself.
The depth is opt-in: each lamp costs one pass over the market's positions, so
ask for detail: "adl" only on a screen that shows the column, and poll the
default shape otherwise. The
WS clearinghouse_state frame
always carries the default shape and never adl_lamps.
For market makers running large books, the headline risk is still concentration — one big winning position dominating the asset's profitable side; diversifying across assets reduces ADL exposure.
Edge cases
Show edge cases
- Multiple shortfalls in one block. Each is allocated independently against the then-current counter-party set. Ranks can move between events.
- Empty counter-party set. If literally no profitable counter-party on the same instrument exists, the shortfall is socialised to the insurance pool's "uncovered loss" register, payable at the next pool replenishment. Should never happen for a liquid asset; can theoretically happen on a long-tail MIP-3 market.
- PM-enrolled counter-party. ADL still targets unrealised PnL on the same instrument — PM enrollment doesn't change ADL's per-asset granularity. The PM scenario engine sees the post-haircut state at the next block.
- Spot markets. Spot doesn't have unrealised PnL in the perp sense. Spot ADL is not defined for V1; spot positions are excluded from ADL ranking.
Sequence — ADL on a thin tail asset
block T: account X liquidates on asset 42 (MIP-3 market), loss = 100 USDC
insurance pool on asset 42 = 60 USDC
D_t (deficit) = 40 USDC
severity controller: θ_t resolves to 1.0 (full deficit needed)
B_t = θ_t · D_t = 40 # budget this round
winners W_t on asset 42 (by haircut capacity u_i):
A: u_A = 30
B: u_B = 50 → total_u = 80
pro-rata: x_i = floor(u_i · B_t / total_u)
x_A = floor(30·40/80) = 15
x_B = floor(50·40/80) = 25 # Σ = 40, no dust
result:
A's position haircut by 15 USDC of PnL realised, 15 kept
B's position haircut by 25 USDC of PnL realised, 25 kept
(Allocation is capacity-pro-rata, not a score-ranked walk: every winner gives up the same fraction of capacity — here 50 % — which is exactly the min-max fairness property pro-rata buys. Compare this to the old "rank by score, drain top tier first" model, which is not what the code does.)
See also
- Tiered liquidation — full ladder
- Insurance pool — T3 mechanism
- Portfolio margin — how PM interacts with ADL
clearinghouse_stateWS — the only live signal that an ADL haircut changed your positionclearinghouse_statewithdetail: "adl"— theadl_lampsqueue indicator
FAQ
Show FAQ
Q: Can I opt out of ADL? A: No. ADL is a protocol-level loss-mutualisation mechanism; opting out would just push the loss onto someone else. The minimisation-of-excess-haircut objective is the protection.
Q: Why allocate pro-rata by PnL capacity instead of a score-ranked queue? A: Pro-rata haircuts every winner by the same fraction of their haircut-able PnL — built-in min-max fairness, no monotonicity violations (two accounts with the same capacity get the same fate), and rank stability ≈ 1.0. It measured ~13× better than HL's ROE-heuristic queue on the Oct-10 2025 replay and within ~30 % of an off-chain ILP optimum, while staying fully deterministic and on-chain. The severity (how much total to haircut) is the part that's learned online; who pays is plain pro-rata.
Q: Does ADL respect Strict-Iso? A: Yes. ADL is per-asset by construction; Strict-Iso positions are counter-party candidates if and only if they hold the same asset.
Q: Is the ranking deterministic across validators?
A: Yes — all inputs (each winner's PnL capacity u_i, the round deficit D_t) are read from committed state, and the severity controller's state (θ, path_variation, Σ D_t², η) lives in BOLE accumulator slot 5, folded into the LtHash so every node verifies byte-identical. Pro-rata uses 128-bit integer mul/div with ascending-AccountId dust handling — no float, no HashMap.