Lighter: benchmark pools
In this notebook, we examine native Lighter pools (perpetual trading pools on Lighter DEX)
These pools use Lighter’s ZK-rollup on Ethereum, identified by synthetic chain ID 9998
The main protocol pool is the LLP (Lighter Liquidity Pool) which provides market-making liquidity
Usage
This is an open source notebook based on open data - You can edit and remix this notebook yourself
To do your own data research:
Read general instructions how to run the tutorials
See
ERC-4626: scanning vaults' historical price and performanceexample in tutorials first how to buildvault-prices-1h.parquetfile.Run the Lighter daily metrics pipeline (
scripts/lighter/daily-pool-metrics.py) to populate Lighter pool data
For any questions, follow and contact Trading Strategy community.
Setup
Set up notebook rendering output mode
Use static image charts so this notebook is readable on Github / ReadTheDocs
[ ]:
import pandas as pd
from plotly.offline import init_notebook_mode
import plotly.io as pio
from eth_defi.vault.base import VaultSpec
from eth_defi.research.notebook import set_large_plotly_chart_font
# Fix X time axis bugs in Plotly charts
from eth_defi.monkeypatch import plotly
pd.options.display.float_format = "{:,.2f}".format
pd.options.display.max_columns = None
pd.options.display.max_rows = None
# Set up Plotly chart output as SVG
image_format = "png"
width = 1400
height = 800
# https://stackoverflow.com/a/52956402/315168
init_notebook_mode()
# https://plotly.com/python/renderers/#overriding-the-default-renderer
pio.renderers.default = image_format
current_renderer = pio.renderers[image_format]
# Have SVGs default pixel with
current_renderer.width = width
current_renderer.height = height
# Set all Plotly charts to use large font sizes for better readability,
# for sharing on mobile
set_large_plotly_chart_font(line_width=5, legend_font_size=16)
pio.templates.default = "custom"
Read and clean raw scanned vault price data
Read the Parquet file produced earlier with price scan
Lighter pool data is merged into the same files as ERC-4626 data
[ ]:
from pathlib import Path
from eth_defi.vault.vaultdb import VaultDatabase, read_default_vault_prices
vault_db = VaultDatabase.read()
prices_df = read_default_vault_prices()
print(f"We have {len(vault_db):,} vaults in the database and {len(prices_df):,} price rows.")
Choose Lighter chain
Lighter is a ZK-rollup perpetual DEX on Ethereum
Uses synthetic chain ID 9998 to avoid collision with real EVM chain IDs
[ ]:
from eth_defi.vault.base import VaultSpec
from eth_defi.chain import get_chain_name
from eth_defi.lighter.constants import LIGHTER_CHAIN_ID
selected_chain_id = LIGHTER_CHAIN_ID
chain_name = get_chain_name(selected_chain_id)
print(f"Examining chain {chain_name} ({selected_chain_id})")
Price data filtering
Filter prices for Lighter pools only
[ ]:
prices_df = prices_df[prices_df["chain"] == selected_chain_id]
print(f"Examined prices contain {len(prices_df):,} price rows across all pools on {chain_name}.")
prices_df.head(4)
Filter pools
Choose pools on Lighter
Filter out low TVL entries
[ ]:
min_tvl = 50_000
vault_db_chain = {spec: row for spec, row in vault_db.items() if spec.chain_id == selected_chain_id}
vault_db = {spec: row for spec, row in vault_db_chain.items() if (row["NAV"] or 0) >= min_tvl}
selected_vault_ids = {spec.as_string_id() for spec in vault_db.keys()}
prices_df = prices_df.loc[prices_df["id"].isin(selected_vault_ids)]
print(f"We have selected {len(vault_db)} pools out of total of {len(vault_db_chain):,} pools on chain {chain_name}, having {len(prices_df):,} price rows.")
print("An example pool metadata:")
example_vault = next(iter(vault_db.values()))
display(pd.DataFrame(list(example_vault.items()), columns=["Key", "Value"]))
Calculate pool lifetime metrics
Calculate the DataFrame of lifetime metrics for each pool
[ ]:
from eth_defi.research.vault_metrics import calculate_lifetime_metrics, clean_lifetime_metrics
from eth_defi.research.vault_metrics import format_lifetime_table
lifetime_data_df = calculate_lifetime_metrics(
prices_df,
vault_db,
)
print(f"Cleaning metrics for {len(lifetime_data_df):,} pools")
lifetime_data_df = clean_lifetime_metrics(
lifetime_data_df,
max_annualised_return=99.99, # 9999% max return
)
print(f"Calculated lifetime metrics for {len(lifetime_data_df):,} pools")
lifetime_data_df = lifetime_data_df.sort_values(["one_month_cagr"], ascending=False)
display(lifetime_data_df.head(2))
Top pool list
List top Lighter pools, formatted for readability
[ ]:
from eth_defi.research.vault_metrics import format_lifetime_table
min_tvl = 25_000
lifetime_data_filtered_df = lifetime_data_df[lifetime_data_df["current_nav"] >= min_tvl]
lifetime_data_filtered_df = lifetime_data_filtered_df.sort_values(["one_month_cagr"], ascending=False)
print(f"Pools filtered by min TVL of ${min_tvl:,}, remaining {len(lifetime_data_filtered_df):,} pools.")
formatted_df = format_lifetime_table(
lifetime_data_filtered_df,
add_index=True,
add_address=True,
)
print(f"Last update {lifetime_data_filtered_df['last_updated_at'].max()}")
cols_to_move = ["Name", "1M return ann. (net / gross)"]
other_cols = [col for col in formatted_df.columns if col not in cols_to_move]
formatted_df = formatted_df[cols_to_move + other_cols]
print(f"Formatted data for {len(formatted_df):,} pools.")
# Script output
max_address_dump = 300
head = formatted_df.head(max_address_dump)
pool_count = min(max_address_dump, len(head))
print(f"Top {pool_count} pools by 1 month annualised return are: {', '.join(head['Name'])}")
print(f"Top {pool_count} pools by 1 month annualised return are:\n{', '.join(head['Address'])}")
display(formatted_df)
Top pool equity curve comparison
Compare top pool equity curves
Compare net returns
Only pools with fee data included
Lookback 90 days
[ ]:
from eth_defi.research.vault_benchmark import visualise_vault_return_benchmark
top_count = 15
top_vaults_specs = lifetime_data_filtered_df.head(top_count)["id"].apply(VaultSpec.parse_string)
fig, net_returns_df = visualise_vault_return_benchmark(
top_vaults_specs,
prices_df=prices_df,
vault_db=vault_db,
)
fig.show()
Pool charts and performance tearsheets
Show rolling returns performance chart for N top pools
[ ]:
from eth_defi.research.vault_metrics import display_vault_chart_and_tearsheet
from eth_defi.vault.risk import VaultTechnicalRisk
examined_vaults_df = lifetime_data_filtered_df.loc[lifetime_data_filtered_df["risk"] != VaultTechnicalRisk.blacklisted]
interest_vault_specs = []
for idx, row in examined_vaults_df.head(10).iterrows():
vault_spec = VaultSpec.parse_string(row["id"])
# Used later
interest_vault_specs.append(vault_spec)
display_vault_chart_and_tearsheet(
vault_spec,
vault_db=vault_db,
prices_df=prices_df,
render=True,
)
Rolling returns comparison
Show rolling returns of all picked pools
[ ]:
from eth_defi.research.rolling_returns import calculate_rolling_returns, visualise_rolling_returns
rolling_returns_df = calculate_rolling_returns(
prices_df,
interesting_vaults=[spec.as_string_id() for spec in interest_vault_specs],
clip_up=100,
)
assert len(rolling_returns_df) > 0, "No rolling returns calculated"
fig = visualise_rolling_returns(rolling_returns_df)
fig.show()
All pools
List all Lighter pools found
[ ]:
from eth_defi.research.vault_metrics import format_lifetime_table, display_lifetime_table
min_tvl = 1_000
lifetime_data_filtered_df = lifetime_data_df[lifetime_data_df["current_nav"] >= min_tvl]
formatted = format_lifetime_table(
lifetime_data_filtered_df,
add_index=True,
html_links=True,
)
display_lifetime_table(formatted)