Skip to content

Trading

Liquidation Engine Design: From Index Price to Insurance Fund

How exchange liquidation engines actually work - maintenance margin, mark price triggers, the insurance fund, auto-deleveraging, and what your risk system must track to never get caught.

11 min
#liquidation #risk-management #perpetual-futures #binance #market-microstructure #margin

Understanding how liquidation engines work from the exchange’s perspective fundamentally changed how I design risk systems. Most traders think about liquidation as “price hits X and you lose everything.” That’s not what happens. There’s an entire pipeline - margin calculation, mark price checks, engine queuing, insurance fund absorption, and auto-deleveraging as last resort - and each step has implications for how your infrastructure should behave.

I spent time studying Binance’s liquidation mechanics in detail when building ZeroCopy’s risk layer. This is what I found.

The Liquidation Trigger: Maintenance Margin

Binance BTCUSDT perpetual futures uses a tiered maintenance margin structure based on notional position size:

Notional ValueInitial Margin (max leverage)Maintenance Margin
Up to $50,0000.40% (125x)0.50%
50K50K - 250K0.50% (100x)0.50%
250K250K - 1M1.00% (50x)0.50%
1M1M - 10M2.00% (25x)1.00%
10M10M - 20M5.00% (10x)2.50%
20M20M - 50M10.00% (5x)5.00%

The maintenance margin rate is the minimum margin ratio required to keep a position open. When your margin ratio falls below this threshold, Binance’s liquidation engine takes over.

Margin Ratio = Maintenance Margin / Account Equity

More precisely:

Margin Ratio = (Maintenance Margin Amount) / (Position Notional × Mark Price)

Where Maintenance Margin Amount is calculated position-by-position based on the notional tiers above. Your position gets liquidated when:

Account Equity ≤ Sum of all (Notional Position × Maintenance Margin Rate)

Critically: this calculation uses mark price, not last price. The exchange makes the liquidation decision based on the mark price feed, which is anchored to the index and protected against short-term manipulation. If someone dumps the perp order book to create a momentary price spike, the mark price barely moves because it’s tracking a weighted average of multiple spot venues.

The Mark Price Requirement: Why It Matters

During the March 2020 crash and multiple times in 2021-2022, traders reported being liquidated at prices far below where they “saw” the market trading. In many cases, they were looking at the last traded price on the perp - which had been artificially moved by thin liquidity or cascading liquidations - while their margin was being evaluated against mark price.

The mark price formula on Binance:

Mark Price = Index Price + 8h EMA of (Last Price - Index Price)

This EMA-smoothed basis means the mark price tracks the index but with a lag proportional to recent perp-spot divergence. During normal markets the difference is small. During cascading liquidations, the last price can crash much faster than the mark price because:

  1. The spot markets (which compose the index) don’t experience the same cascade
  2. The EMA smooths out short-term perp price dislocations

In practice during a hard crash: if BTCUSDT spot drops from 60Kto60K to 50K, both mark price and last price follow. But if BTCUSDT perp gets slammed to 48Kbycascadesellingwhilespotstaysat48K by cascade selling while spot stays at 50K, the mark price stays closer to $50K - protecting you from artificial cascade liquidations while still liquidating you correctly if the spot markets themselves are falling.

The infrastructure lesson: always compare mark price vs last price on your risk dashboards. A large divergence (>0.5%) in a falling market is a sign that cascade liquidations are in progress and your position is closer to the margin threshold than it appears from the chart.

The Liquidation Pipeline

When Binance’s system determines a position must be liquidated, the engine follows a specific pipeline:

Step 1: Partial vs Full Liquidation

Modern Binance liquidation is not binary. For large positions, the engine first attempts a partial liquidation - reducing the position to a size where the account equity is safely above maintenance margin again. This is better for traders (preserves part of the position and residual equity) and better for markets (less sudden volume hitting the order book).

The partial liquidation threshold: if closing part of the position would bring margin ratio back above maintenance margin + a buffer, only that portion gets liquidated.

Step 2: Close at Bankruptcy Price

The full liquidation target is the bankruptcy price - the price at which total account equity (including unrealized PnL) reaches exactly zero. This is distinct from the liquidation trigger price.

For a long position, bankruptcy price:

Bankruptcy Price = Entry Price × (1 - Initial Margin Rate)

Example: Entry at $80,000 with 10x leverage (10% initial margin):

Bankruptcy Price = $80,000 × (1 - 0.10) = $72,000

The liquidation engine attempts to close the position before mark price reaches the bankruptcy price. The difference between the liquidation trigger price and the bankruptcy price is the liquidation buffer - Binance typically has 0.5-1% of notional as buffer.

Step 3: Insurance Fund

If the position cannot be closed before mark price reaches the bankruptcy price (i.e., slippage during execution exceeds the buffer), the insurance fund absorbs the loss.

The Binance USDT-margined futures insurance fund was over 400Matitspeak.Itsfundedbycollectingtheresidualequitywhenliquidationsexecutebetterthanthebankruptcypricei.e.,whentheengineclosesapositionat400M at its peak. It's funded by collecting the residual equity when liquidations execute better than the bankruptcy price - i.e., when the engine closes a position at 71,500 against a bankruptcy price of 70,000,the70,000, the 1,500 surplus goes into the insurance fund.

The engineering implication: your infrastructure never directly interacts with the insurance fund, but you need to monitor its size. If the fund depletes during a major market event, the exchange activates ADL.

Auto-Deleveraging (ADL): The Invisible Risk

When the insurance fund is insufficient to cover losses from a liquidated position, Binance activates Auto-Deleveraging (ADL). The exchange forcibly closes positions of profitable traders on the opposing side to offset the deficit.

ADL priority is determined by a queue based on two factors:

  1. Profit %: How profitable is the opposing position?
  2. Leverage: How much leverage is being used?

The ADL priority indicator is shown as a 5-bar indicator in Binance’s UI (and available via API). If your position shows all 5 bars lit, you’re at the front of the ADL queue - the most likely to be auto-deleveraged.

For infrastructure engineers, ADL creates a terrifying edge case: your position can be involuntarily closed without a fill event you initiated. You might be holding a profitable short during a cascade event, and suddenly your position is gone - not because you got stopped out, but because an exchange-level event triggered forced closure.

How to handle ADL in your systems:

  1. Subscribe to the user data stream and handle ORDER_TRADE_UPDATE events with order type ADL. These are the notifications you’ll receive.

  2. Monitor your ADL indicator. The Binance API returns this in position risk data. If you’re at high ADL risk and in a highly profitable opposing position, consider reducing size to lower priority.

  3. Never assume your position exists. Always reconcile your internal position state against the exchange’s position via the REST API on startup and periodically during operation. ADL can reduce your position asynchronously.

import asyncio
import json
import websockets
from enum import Enum

class PositionEventType(Enum):
    FILL = "fill"
    LIQUIDATION = "liquidation"
    ADL = "adl"
    FUNDING = "funding"

async def handle_position_event(event_data: dict) -> PositionEventType | None:
    """
    Process user data stream events for position changes.
    Returns the event type so the position manager can route appropriately.
    """
    event_type = event_data.get("e")

    if event_type == "ORDER_TRADE_UPDATE":
        order_data = event_data.get("o", {})
        order_status = order_data.get("X")  # Order status
        order_type = order_data.get("o")    # Order type

        # ADL events have specific order type
        if order_type == "ADL":
            symbol = order_data.get("s")
            side = order_data.get("S")
            qty = float(order_data.get("q", 0))
            price = float(order_data.get("ap", 0))  # avg price

            print(f"WARNING: ADL triggered on {symbol}: {side} {qty} @ {price}")
            return PositionEventType.ADL

        # Normal liquidation (your own position being liquidated)
        if order_type == "LIQUIDATION":
            symbol = order_data.get("s")
            print(f"WARNING: Position liquidated on {symbol}")
            return PositionEventType.LIQUIDATION

    return None

The Liquidation Price Calculator

Your risk system needs to calculate the current liquidation price in real-time. For a cross-margin account (where all positions share a single margin pool), the calculation is more complex than isolated margin.

For isolated margin (simpler case):

def liquidation_price_isolated_long(
    entry_price: float,
    position_qty: float,
    margin: float,
    maintenance_margin_rate: float = 0.005,  # 0.5% for small positions
) -> float:
    """
    Calculate liquidation price for an isolated-margin long position.

    Args:
        entry_price: Position entry price in USDT
        position_qty: Position size in BTC (positive = long)
        margin: Isolated margin amount in USDT
        maintenance_margin_rate: Exchange maintenance margin rate (0.5% default)

    Returns:
        Price at which position will be liquidated
    """
    # Position notional
    notional = entry_price * position_qty

    # Cumulative maintenance margin (simplified, ignoring tiering for now)
    maintenance_margin = notional * maintenance_margin_rate

    # Liquidation occurs when unrealized PnL = -(margin - maintenance_margin)
    # For long: (liq_price - entry_price) * qty = -(margin - maintenance_margin)
    # Solving for liq_price:
    liq_price = entry_price - (margin - maintenance_margin) / position_qty

    return liq_price


def liquidation_price_isolated_short(
    entry_price: float,
    position_qty: float,  # positive even for short position
    margin: float,
    maintenance_margin_rate: float = 0.005,
) -> float:
    """Calculate liquidation price for an isolated-margin short position."""
    notional = entry_price * position_qty
    maintenance_margin = notional * maintenance_margin_rate

    # For short: (entry_price - liq_price) * qty = -(margin - maintenance_margin)
    liq_price = entry_price + (margin - maintenance_margin) / position_qty

    return liq_price


def distance_to_liquidation(
    entry_price: float,
    mark_price: float,
    position_side: str,  # "LONG" or "SHORT"
    position_qty: float,
    margin: float,
    maintenance_margin_rate: float = 0.005,
) -> dict:
    """
    Compute distance from current mark price to liquidation price.
    Returns both absolute and percentage distance.
    """
    if position_side == "LONG":
        liq_price = liquidation_price_isolated_long(
            entry_price, position_qty, margin, maintenance_margin_rate
        )
        distance_usd = mark_price - liq_price
        distance_pct = distance_usd / mark_price * 100
    else:
        liq_price = liquidation_price_isolated_short(
            entry_price, position_qty, margin, maintenance_margin_rate
        )
        distance_usd = liq_price - mark_price
        distance_pct = distance_usd / mark_price * 100

    return {
        "liquidation_price": liq_price,
        "current_mark_price": mark_price,
        "distance_usd": distance_usd,
        "distance_pct": distance_pct,
        "is_safe": distance_pct > 2.0,  # Alert if within 2% of liquidation
        "alert_level": "CRITICAL" if distance_pct < 1.0 else
                       "WARNING" if distance_pct < 3.0 else "OK"
    }


# Example usage:
result = distance_to_liquidation(
    entry_price=80000,
    mark_price=78500,
    position_side="LONG",
    position_qty=1.0,
    margin=4000,  # $4,000 isolated margin (approximately 20x leverage)
    maintenance_margin_rate=0.005
)
print(f"Liquidation price: ${result['liquidation_price']:,.0f}")
print(f"Distance: {result['distance_pct']:.2f}% ({result['alert_level']})")

For Cross-Margin Accounts

Cross-margin makes the calculation significantly more complex because all open positions and all account equity contribute to margin maintenance simultaneously. The formula requires summing maintenance requirements across all positions and comparing to total account equity.

For cross-margin, the practical approach in production:

  1. Subscribe to account balance updates from the user data stream - these fire whenever account equity changes
  2. Maintain a local position/balance model updated from fill events
  3. Periodically reconcile with GET /fapi/v2/account to catch any drift
  4. Use margin ratio directly from the account endpoint rather than computing it yourself - Binance calculates and returns maintMargin and totalMarginBalance which gives you the ratio directly
def compute_cross_margin_ratio(account_data: dict) -> dict:
    """
    Extract margin health from Binance account endpoint response.
    GET /fapi/v2/account
    """
    total_maint_margin = float(account_data.get("totalMaintMargin", 0))
    total_margin_balance = float(account_data.get("totalMarginBalance", 0))
    total_unrealized_pnl = float(account_data.get("totalUnrealizedProfit", 0))

    if total_margin_balance <= 0:
        return {"margin_ratio": float("inf"), "status": "NO_POSITION"}

    margin_ratio = total_maint_margin / total_margin_balance * 100

    return {
        "margin_ratio_pct": margin_ratio,
        "maint_margin_usd": total_maint_margin,
        "margin_balance_usd": total_margin_balance,
        "unrealized_pnl_usd": total_unrealized_pnl,
        "status": "CRITICAL" if margin_ratio > 80 else
                  "WARNING" if margin_ratio > 50 else "OK",
        "estimated_time_to_liquidation": None,  # Requires price velocity model
    }

The Mistakes Most Engineers Make

Mistake 1: Using last price for risk calculations. This is the most common error and the most dangerous. Always use mark price for margin and distance-to-liquidation calculations. Subscribe to the @markPrice stream separately from your @trade stream.

Mistake 2: Not handling ADL events. ADL is rare but catastrophic if you don’t handle it. Your user data stream callback needs to explicitly check for ADL order type and notify your position manager to reconcile against the exchange.

Mistake 3: Calculating liquidation price only on position open. As mark price moves, your unrealized PnL changes, which changes your available margin for cross-margin accounts. Your liquidation price calculator must run continuously, not just at position entry.

Mistake 4: Ignoring the tiered maintenance margin. For positions over $1M notional, the maintenance margin rate doubles. If your system hardcodes 0.5%, you’ll miscalculate liquidation prices for large positions.

Mistake 5: Not monitoring insurance fund health. The Binance insurance fund endpoint (/fapi/v1/insuranceFund) is publicly accessible. During extreme market events, monitoring fund size tells you how close the exchange is to activating ADL - giving your system early warning to reduce exposure.

The liquidation engine is one of the few systems in crypto trading where understanding the mechanism deeply and directly protects your capital. Build the calculations into your risk layer from day one.

Continue Reading

Enjoyed this?

Get one deep infrastructure insight per week.

Free forever. Unsubscribe anytime.

You're in. Check your inbox.