Building a Crypto Liquidation Cascade Detection System

Building a Crypto Liquidation Cascade Detection System

Liquidation cascades in DeFi happen in minutes. When collateral prices fall, undercollateralized positions trigger liquidations, which sell collateral into the market, driving prices further down and triggering more liquidations. Detecting the cascade as it begins—not after it ends—requires sub-second streaming analytics.

Why Liquidation Cascade Detection Matters

The March 2020 "Black Thursday" event on MakerDAO saw ETH price drop 50% in hours, triggering cascading liquidations that exhausted the protocol's auction system. More recently, the collapse of LUNA/UST in May 2022 triggered cascading liquidations across Aave, Compound, and dozens of smaller protocols simultaneously.

A liquidation cascade forms when several conditions align:

  1. Cascade trigger: a sharp asset price decline brings multiple positions to their liquidation threshold simultaneously.
  2. Collateral contagion: the liquidated collateral (e.g., ETH) is sold on-chain, increasing sell pressure and pushing prices further down.
  3. Cross-protocol stress: the same collateral asset is used across multiple protocols; a price drop affects health factors everywhere simultaneously.
  4. Liquidation queue depth: if liquidatable positions exceed what keepers can process, the protocol accumulates bad debt.

Early detection—ideally when positions approach 90% of the liquidation threshold, before they cross it—allows risk managers to circuit-break, protocols to pause borrowing, and users to add collateral.

Key metrics for cascade detection:

  • At-risk TVL: total value of positions within 5–10% of liquidation threshold.
  • Liquidation queue depth: number of positions currently below health factor 1.0.
  • Cascade trigger price: the ETH/BTC price at which the next 10%/25%/50% of collateral becomes liquidatable.
  • Protocol stress score: a weighted sum of at-risk TVL as a percentage of total TVL.

How Streaming SQL Solves This

RisingWave maintains continuously updated materialized views of protocol health factors, at-risk TVL, and cascade trigger prices. As oracle prices update every block, the health factor calculations update automatically. When a cascade begins, the liquidation_queue view materializes within seconds—long before a batch system would complete its next cycle.

Building It Step by Step

Step 1: Connect the Data Source

CREATE SOURCE lending_positions (
    position_id         VARCHAR,
    protocol_id         VARCHAR,
    user_address        VARCHAR,
    collateral_token    VARCHAR,
    collateral_amount   NUMERIC,
    collateral_usd      NUMERIC,
    debt_token          VARCHAR,
    debt_amount         NUMERIC,
    debt_usd            NUMERIC,
    health_factor       NUMERIC,   -- 1.0 = at liquidation threshold
    liquidation_threshold NUMERIC, -- e.g. 0.825 for Aave ETH
    block_ts            TIMESTAMPTZ
) WITH (
    connector     = 'kafka',
    topic         = 'lending.position.updates',
    properties.bootstrap.server = 'kafka:9092'
) FORMAT PLAIN ENCODE JSON;

CREATE SOURCE liquidation_events (
    tx_hash             VARCHAR,
    protocol_id         VARCHAR,
    liquidator          VARCHAR,
    borrower            VARCHAR,
    collateral_token    VARCHAR,
    collateral_seized_usd NUMERIC,
    debt_repaid_usd     NUMERIC,
    liquidation_bonus_pct NUMERIC,
    block_ts            TIMESTAMPTZ
) WITH (
    connector     = 'kafka',
    topic         = 'lending.liquidation.events',
    properties.bootstrap.server = 'kafka:9092'
) FORMAT PLAIN ENCODE JSON;

Step 2: Build the Core Materialized View

Segment positions by health factor proximity to liquidation:

CREATE MATERIALIZED VIEW position_risk_tiers AS
SELECT
    protocol_id,
    collateral_token,
    COUNT(*)                                            AS position_count,
    SUM(collateral_usd)                                 AS total_collateral_usd,
    SUM(debt_usd)                                       AS total_debt_usd,
    -- At-risk buckets by health factor
    SUM(CASE WHEN health_factor < 1.0  THEN collateral_usd ELSE 0 END)
                                                        AS liquidatable_usd,
    SUM(CASE WHEN health_factor BETWEEN 1.0 AND 1.05 THEN collateral_usd ELSE 0 END)
                                                        AS critical_risk_usd,
    SUM(CASE WHEN health_factor BETWEEN 1.05 AND 1.15 THEN collateral_usd ELSE 0 END)
                                                        AS high_risk_usd,
    SUM(CASE WHEN health_factor BETWEEN 1.15 AND 1.25 THEN collateral_usd ELSE 0 END)
                                                        AS moderate_risk_usd,
    -- Cascade stress: at-risk as % of total
    SUM(CASE WHEN health_factor < 1.15 THEN collateral_usd ELSE 0 END)
    / NULLIF(SUM(collateral_usd), 0) * 100              AS at_risk_pct
FROM lending_positions
WHERE block_ts > NOW() - INTERVAL '2 minutes'
GROUP BY protocol_id, collateral_token;

CREATE MATERIALIZED VIEW liquidation_rate AS
SELECT
    protocol_id,
    collateral_token,
    COUNT(*)                            AS liquidation_count_1h,
    SUM(collateral_seized_usd)          AS seized_usd_1h,
    SUM(debt_repaid_usd)                AS debt_cleared_usd_1h,
    AVG(liquidation_bonus_pct)          AS avg_bonus_pct
FROM liquidation_events
WHERE block_ts > NOW() - INTERVAL '1 hour'
GROUP BY protocol_id, collateral_token;

Step 3: Detection Logic and Alerts

Emit cascade warnings when at-risk TVL crosses danger thresholds:

CREATE MATERIALIZED VIEW cascade_alerts AS
SELECT
    prt.protocol_id,
    prt.collateral_token,
    prt.liquidatable_usd,
    prt.critical_risk_usd,
    prt.high_risk_usd,
    prt.at_risk_pct,
    lr.liquidation_count_1h,
    lr.seized_usd_1h,
    CASE
        WHEN prt.liquidatable_usd > 10000000 AND lr.liquidation_count_1h > 50
            THEN 'CASCADE_IN_PROGRESS'
        WHEN prt.critical_risk_usd > 50000000
            THEN 'CASCADE_IMMINENT'
        WHEN prt.at_risk_pct > 20
            THEN 'ELEVATED_SYSTEMIC_RISK'
        WHEN prt.at_risk_pct > 10
            THEN 'MODERATE_RISK'
        ELSE
            'NORMAL'
    END AS risk_status,
    NOW() AS evaluated_at
FROM position_risk_tiers prt
LEFT JOIN liquidation_rate lr
  ON prt.protocol_id = lr.protocol_id
 AND prt.collateral_token = lr.collateral_token
WHERE prt.at_risk_pct > 5
   OR prt.liquidatable_usd > 1000000;

CREATE SINK cascade_alert_sink AS
SELECT * FROM cascade_alerts
WHERE risk_status IN ('CASCADE_IN_PROGRESS', 'CASCADE_IMMINENT', 'ELEVATED_SYSTEMIC_RISK')
WITH (
    connector                   = 'kafka',
    topic                       = 'alerts.defi.liquidation-cascade',
    properties.bootstrap.server = 'kafka:9092'
) FORMAT PLAIN ENCODE JSON;

Step 4: Querying Live Results

View the current systemic risk snapshot across all monitored protocols:

SELECT
    protocol_id,
    collateral_token,
    ROUND(liquidatable_usd / 1e6, 2)      AS liquidatable_m_usd,
    ROUND(critical_risk_usd / 1e6, 2)     AS critical_risk_m_usd,
    ROUND(at_risk_pct, 2)                 AS at_risk_pct,
    liquidation_count_1h,
    ROUND(seized_usd_1h / 1e6, 2)         AS seized_1h_m_usd,
    risk_status,
    evaluated_at
FROM cascade_alerts
ORDER BY at_risk_pct DESC
LIMIT 10;

Comparison Table

Detection MethodLatencyCascade Stage DetectedProtocol Coverage
Protocol health endpoint polling1–5 minMid-cascadePer-protocol
Liquidation event monitoringPer-blockAlready happeningPer-protocol
Streaming SQL (health factor view)< 1sPre-cascade (HF approaching 1.0)Cross-protocol
Batch warehouse query15–60 minPost-cascadeHistorical only

FAQ

How do you compute the cascade trigger price—the asset price that would cause a wave of liquidations? You need the current collateral amount and liquidation threshold per position. The liquidation trigger price per position is debt_usd / (collateral_amount * liquidation_threshold). Group these prices into buckets and sum the collateral at risk per price bucket to build a waterfall chart. This can be computed as a materialized view directly from lending_positions, giving you a live view of where liquidation cliffs exist in the market.

How does collateral contagion propagate in this model? Contagion is indirect: liquidated collateral is sold on-chain, increasing sell pressure and reducing the oracle price for that asset. This causes the health factors of other positions using the same collateral to deteriorate further. To model contagion, monitor the liquidation_rate view alongside the oracle price feed. When seized_usd_1h is high and the oracle price is falling simultaneously, contagion is underway.

Can this system monitor cross-protocol risk across Aave, Compound, and Euler simultaneously? Yes. The protocol_id field captures which protocol each position belongs to. The position_risk_tiers view groups by both protocol_id and collateral_token, giving you a per-protocol, per-asset breakdown. A cross-protocol aggregate view simply drops the protocol_id grouping dimension to show total at-risk TVL for a given collateral asset regardless of which protocol holds it.

Key Takeaways

  • Liquidation cascades develop in minutes; detecting positions approaching the health factor threshold before they cross it allows preventative action.
  • RisingWave maintains health factor segmentation (liquidatable, critical, high, moderate risk) as a continuously updated materialized view that refreshes with every oracle price update and position change.
  • Cross-protocol cascade risk is visible in a single streaming SQL query, eliminating the need to poll individual protocol APIs.
  • Cascade alerts differentiate between normal liquidation activity, elevated systemic risk, and an active cascade in progress—giving risk managers proportional response options.

Best-in-Class Event Streaming
for Agents, Apps, and Analytics
GitHubXLinkedInSlackYouTube
Sign up for our to stay updated.