Aave V3 liquidations

In this notebook we will analyse liquidation events events from Aave V3 to a Parquet file.

  • Run the script scipts/aave-v3/scan-liquidations.py to download the data

    • You need JSON RPC endpoints for the chains you want to scan

python scripts/aave-v3/scan-liquidations.py
Reading Aave liquidations data on Binance: 1 - 64,473,706: 100%|████████████████████████████████████████████████████████████████| 64473705/64473705 [00:39<00:00, 1622236.90it/s, At=2025-10-13 08:43:20, Liquidations=9,353]
Chain binance done, total liquidation rows now 9,353, file size is 1.58 MiB
Reading Aave liquidations data on Arbitrum: 1 - 389,080,336: 100%|███████████████████████████████████████████████████████████| 389080335/389080335 [01:22<00:00, 4738462.79it/s, At=2025-10-12 15:42:05, Liquidations=43,989]
Chain arbitrum done, total liquidation rows now 53,342, file size is 7.28 MiB
...

Setup

  • Setup chains and where do we store the results

[14]:
from pathlib import Path
import pandas as pd
from eth_defi.research.notebook import setup_charting_and_output, OutputMode

PARQUET_PATH = Path.home() / ".tradingstrategy" / "liquidations" / "aave-v3-liquidations.parquet"

assert PARQUET_PATH.exists(), f"Run the script scripts/aave-v3/scan-liquidations.py to create the file {PARQUET_PATH}"

# Initialise the data file if it does not exist
df = pd.read_parquet(PARQUET_PATH)


# Setup static rendering so we can display this notebook on Github
setup_charting_and_output(
    OutputMode.static,
    image_format="png",
    height=800,
)

Data inspection

  • See what kind of data we have

[15]:
display(df.head(5))
chain_id chain_name contract block_number block_hash timestamp transaction_hash log_index collateral_asset debt_asset user debt_to_cover liquidated_collateral_amount liquidator receive_a_token
0 56 Binance 0x618bd91ebe2224b7cd433d92532730da10032e08 9319115 0xc7e4bac90a16da7f92d24c3717144a0a421539a250fd... 2021-07-20 10:05:43 0x55b70adde19900a8f58f32732a3aa06a423ae494b05b... 530 ETH XPO 0xF0eF8D2A9D2fd929c6FA5dAddF26923F16893A4C 100.000000 0.001185 0xc4e0ffe31153d30da9c166ffab9f1cffee7b3712 False
1 56 Binance 0x618bd91ebe2224b7cd433d92532730da10032e08 9320213 0xce6cbbd52fad9285bf26beb59c58f5b78582194479ec... 2021-07-20 11:00:37 0xe5717eb05f04e83d7b2cbd70a0adc7ddad5d48b31067... 391 ETH XPO 0x065003146991eC55515A419f264df5cD15A5Be14 8831.343459 0.104700 0xe92fa2ea7f734be486556ce341697707cb336e56 False
2 56 Binance 0x618bd91ebe2224b7cd433d92532730da10032e08 9320248 0x2228abe21c300ddf2fa94a2bec26af5b3d3ac35ff300... 2021-07-20 11:02:22 0x2cecc4a812d4a96c8bf7f86c7f8d1a1dbae82b206969... 153 ETH XPO 0x4Bd2514fe28dF23f88084007605cD37817F26Ed2 1180.000000 0.013989 0xe92fa2ea7f734be486556ce341697707cb336e56 False
3 56 Binance 0x618bd91ebe2224b7cd433d92532730da10032e08 9320321 0xbc0c6239aa5a2c974db09f32dce6b3e6714c17e75f77... 2021-07-20 11:06:01 0x1d26c7a093458ea2730b8773002c6c458837ac13a3ea... 543 ETH XPO 0x9b75A39CB03f00704C81feB9512524Fa91dA43d4 22210.468110 0.267400 0xe92fa2ea7f734be486556ce341697707cb336e56 False
4 56 Binance 0x618bd91ebe2224b7cd433d92532730da10032e08 9320366 0x9a5fe737929c7404d37656c79d17facb65033746157d... 2021-07-20 11:08:16 0x1c2edba679051243a793c5ac77fd89c657d23593a33c... 389 ETH XPO 0x60668466b255b56B3c85C18F657c8634De993f67 2590.000000 0.031182 0xe92fa2ea7f734be486556ce341697707cb336e56 False

Analyse liquidations

  • Do simple liquidations analysis on a single chain, single token

Inspect Aave v3 protocols firing liquidations

  • First check what are the contracts firing the liquidation events, and manually choose correct ones from test and scam deployments

[ ]:
 # Get WETH liquidations on the mainnet
target_chain = "Ethereum"
target_asset = "WETH"


mask = (df["chain_name"] == target_chain) & (df["collateral_asset"] == target_asset)

summary = (
    df.loc[mask]
      .groupby('contract', as_index=False)
      .agg(
          liquidation_events=('transaction_hash', 'count'),
          unique_users=('user', 'nunique'),
          total_collateral=('liquidated_collateral_amount', 'sum'),
          first_liquidation=('timestamp', 'min'),
          last_liquidation=('timestamp', 'max'),
      )
      .sort_values('liquidation_events', ascending=False)
)

display(summary)
contract liquidation_events unique_users total_collateral first_liquidation last_liquidation
5 0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9 21465 4139 3.327452e+05 2020-12-17 14:23:36 2025-10-13 09:39:59
6 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 9445 4119 2.534725e+05 2023-01-30 19:11:47 2025-10-13 09:43:23
9 0xc13e21b648a5ee794902342038ff3adab66be987 1190 500 2.223061e+04 2023-04-04 13:55:47 2025-10-12 06:58:47
4 0x7937d4799803fbbe595ed57278bc4ca21f3bffcb 341 69 9.085063e+02 2021-05-13 19:12:24 2025-09-25 09:23:35
0 0x2409af0251dcb89ee3dee572629291f9b087c668 113 22 1.657293e+06 2022-12-24 04:46:23 2025-09-26 11:07:47
1 0x3bc3d34c32cc98bf098d832364df8a222bbab4c0 55 34 3.379693e+00 2024-05-01 10:12:35 2025-10-12 05:03:59
11 0xd55b0b380a84b365bba7a9eed4dd6156eb0ccb1a 20 4 3.495829e+01 2024-05-12 23:43:59 2025-10-10 15:18:47
2 0x4e033931ad43597d96d6bcc25c280717730b58b1 15 15 2.059065e+01 2025-02-22 12:29:47 2025-09-25 17:46:11
13 0xea14474946c59dee1f103ad517132b3f19cef1be 10 3 4.241607e-01 2024-08-10 03:19:23 2025-05-07 23:47:11
8 0xb702ce183b4e1faa574834715e5d4a6378d0eed3 6 3 2.028200e-01 2022-09-15 22:41:47 2025-04-09 03:20:11
7 0x97e6afb7959888347a566736503c3bbc34da6a5f 5 1 6.184347e+01 2024-03-25 21:44:23 2024-03-25 21:49:23
12 0xd61afaaa8a69ba541bc4db9c9b40d4142b43b9a4 2 2 1.422526e+01 2022-06-14 04:25:39 2022-06-14 04:26:44
3 0x6447c4390457cad03ec1baa4254cee1a3d9e1bbd 1 1 2.485780e-02 2025-09-06 16:22:23 2025-09-06 16:22:23
10 0xd14a7c302051a0f1e9ce8e9a8c4845a45f41b46f 1 1 2.472364e-02 2023-05-21 18:22:23 2023-05-21 18:22:23
[18]:
# Filter out to well-known instances
target_contracts = [
    "0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9",
    "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
    "0xc13e21b648a5ee794902342038ff3adab66be987"
]

filtered_df = df[
    mask & (df["contract"].isin(target_contracts))
]

print(f"Total WETH liquidations on Ethereum: {len(filtered_df)}")

Total WETH liquidations on Ethereum: 32100

Liquidation value and event chart

  • Draw a liquidations on a timeline

[19]:
# Python
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import pandas as pd

# Prepare data
plot_df = filtered_df.copy()
plot_df["timestamp"] = pd.to_datetime(plot_df["timestamp"])
plot_df = plot_df.sort_values("timestamp")
plot_df["cum_weth_liquidated"] = plot_df["liquidated_collateral_amount"].cumsum()
plot_df["cum_events"] = pd.Series(1, index=plot_df.index).cumsum()

# Build dual‑axis figure
fig = make_subplots(specs=[[{"secondary_y": True}]])

# Left y‑axis: cumulative ETH liquidated
fig.add_trace(
    go.Scatter(
        x=plot_df["timestamp"],
        y=plot_df["cum_weth_liquidated"],
        name=f"Cumulative {target_asset} liquidated",
        mode="lines",
        line=dict(color="royalblue", width=2),

    ),
    secondary_y=False,
)

# Right y‑axis: cumulative events
fig.add_trace(
    go.Scatter(
        x=plot_df["timestamp"],
        y=plot_df["cum_events"],
        name="Cumulative events",
        mode="lines",
        line=dict(color="orangered", width=2),
        line_shape="hv",
    ),
    secondary_y=True,
)

# Layout
fig.update_layout(
    title=f"Cumulative {target_asset} liquidations and events on {target_chain} (Aave V3)",
    hovermode="x unified",
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0),
)

fig.update_xaxes(title_text="Date")

fig.update_yaxes(
    title_text=f"Total {target_asset} liquidated",
    secondary_y=False,
    rangemode="tozero",
)
fig.update_yaxes(
    title_text="Total events",
    secondary_y=True,
    rangemode="tozero",
)
fig.update_layout(
    template="plotly_white",
    autosize=False,
)
fig.show()
../_images/tutorials_aave-v3-liquidations_11_0.png