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.pyto download the dataYou 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()