Lending protocols and short/long leverage data#
Load lending market data for a specific chain (Polygon in this example) and diplay status of the lending market.
In this specific example we download interest rates and prices for all assets on Aave v3 on Polygon.
As this notebook always uses latest data, it does not cache any downloaded datasets and may be slow to run depending on the speed of your Internet.
TODO: Some adjustments to this notebook are needed later, as the server-side data format will be switched from percents to raw floating points.
Set up client and API keys to download dataset#
[20]:
import datetime
import pandas as pd
from tradingstrategy.client import Client
# You will be interactively prompted to register an API key if you do not have one yet
client = Client.create_jupyter_client()
Started Trading Strategy in Jupyter notebook environment, configuration is stored in /Users/moo/.tradingstrategy
We set the notebook output to static image mode, so anyone can view this notetbook results directly on Github link.
Set back to interactive mode if you want to have draggable and zoomable charts in your notebook.
[21]:
from tradeexecutor.utils.notebook import OutputMode, setup_charting_and_output
setup_charting_and_output(OutputMode.static)
Download datasets#
We use a shortcut method load_trading_and_lending_data
to grab everything we need.
This function can download current data
This function can download data for historical time periods
[22]:
from tradingstrategy.chain import ChainId
from tradeexecutor.strategy.universe_model import UniverseOptions
from tradeexecutor.strategy.execution_context import ExecutionMode, ExecutionContext
from tradeexecutor.strategy.trading_strategy_universe import load_trading_and_lending_data, TradingStrategyUniverse
# Download 30 days historical from today
lookback = datetime.timedelta(days=30)
# Even within Uniswap v3, there will be multiple trading pairs
# for each lending market asset, we need to specify
# which ones we are interested in.
# We pick USDC because it's most used stablecoin in DeFi
# environment.
#
# Even with USDC some assets are quoted
# with different Uniswap v3 fee tiers,
# like 30 BPS and 5 BPS.
# We deal with that later.
quote_token = "USDC"
# Load all trading and lending data on Polygon
# for all lending markets on a relevant time period
dataset = load_trading_and_lending_data(
client,
execution_context=ExecutionContext(mode=ExecutionMode.data_research),
universe_options=UniverseOptions(history_period=lookback),
# Ask for all Polygon data
chain_id=ChainId.polygon,
# We limit ourselves to price feeds on Uniswap v3 and Quickswap on Polygon,
# as there are multiple small or dead DEXes on Polygon
# which also have price feeds but not interesting liquidity
exchange_slugs={"uniswap-v3", "quickswap"},
reserve_asset_symbols={quote_token},
# We want to display stablecoin-stablecoin
# trading pairs
volatile_only=False,
)
# Construct a trading universe that covers all
# chains, DEXes, trading pairs and lending pools we specified
strategy_universe = TradingStrategyUniverse.create_from_dataset(dataset)
data_universe = strategy_universe.data_universe
print(f"We have\n"
f"- {data_universe.pairs.get_count()} trading pairs\n"
f"- {data_universe.lending_reserves.get_count()} lending reserves\n"
f"- {data_universe.candles.get_candle_count()} price candles ({data_universe.time_bucket.value})")
We have
- 19 trading pairs
- 20 lending reserves
- 373 price candles (1d)
Display available trading pair and lending asset data#
Display the status of the trading and lending markets.
We use get_pair_by_human_description()
to find the lowest fee USDC trading pair for each lending reserve asset.
We determine if a trading pair is long/short leveraged tradeable by checking if it has enough trading volume on Polygon on any (indexed) DEX. The threshold is either - USD 100k monthly trading volume (no liquidity available) - USD 100k liquidity
[23]:
from IPython.display import HTML
from tradingstrategy.candle import CandleSampleUnavailable
from tradingstrategy.stablecoin import is_stablecoin_like
from tradingstrategy.pair import PairNotFoundError
from tradingstrategy.utils.jupyter import format_links_for_html_output
rows = []
now = datetime.datetime.utcnow() # We always operate on naive UTC timesetamps
past = now - datetime.timedelta(days=29)
# We are still happy if we get 2 days old price
# (Some low liquidity pairs may see very few trades)
price_delay_tolerance = pd.Timedelta(days=2)
for reserve in data_universe.lending_reserves.iterate_reserves():
stablecoin = is_stablecoin_like(reserve.asset_symbol)
lending_link = reserve.get_link()
supply_apr_now, lag = data_universe.lending_candles.supply_apr.get_single_rate(
reserve,
now,
data_lag_tolerance=price_delay_tolerance,
)
borrow_apr_now, lag = data_universe.lending_candles.variable_borrow_apr.get_single_rate(
reserve,
now,
data_lag_tolerance=price_delay_tolerance,
)
try:
trading_pair = data_universe.pairs.get_pair_by_human_description((ChainId.polygon, None, reserve.asset_symbol, quote_token))
exchange = data_universe.pairs.get_exchange_for_pair(trading_pair)
trading_pair_label = f"{trading_pair.base_token_symbol}-{trading_pair.quote_token_symbol} at {trading_pair.fee} BPS fee tier on {exchange.name}"
tradeable = trading_pair.is_tradeable()
trading_pair_link = trading_pair.get_link()
try:
price_now, lag = data_universe.candles.get_price_with_tolerance(
trading_pair,
now,
tolerance=price_delay_tolerance,
)
price_past, lag = data_universe.candles.get_price_with_tolerance(
trading_pair,
past,
tolerance=price_delay_tolerance,
)
change = f"{(price_now - price_past) / price_past * 100:,.2f}"
except CandleSampleUnavailable as e:
price_now = "no trades"
price_past = "no trades"
change = ""
except PairNotFoundError as e:
# Some assets like AAVE do not have USDC quoted pair on Uniswap v3 on Polygon
trading_pair = None
trading_pair_label = "No markets available"
tradeable = False
price_now = ""
price_past = ""
trading_pair_link = ""
change = ""
rows.append({
"Lending asset": reserve.asset_symbol,
"Leveraged trading": "yes" if (not stablecoin and tradeable) else "no",
"Stablecoin": "yes" if stablecoin else "no",
"Trading volume": "yes" if tradeable else "no",
"Best trading pair": trading_pair_label,
"Price now (USD)": price_now,
"Price 30d ago (USD)": price_past,
"Price %": change,
"Supply APR": f"{supply_apr_now:,.2f}",
"Borrow APR": f"{borrow_apr_now:,.2f}",
"Price data page": trading_pair_link,
"Lending rate page": lending_link,
})
df = pd.DataFrame(rows)
df = format_links_for_html_output(df, ("Price data page", "Lending rate page",))
print(f"Price and lending market information for {now} UTC")
with pd.option_context('display.max_rows', 100):
display(HTML(df.to_html(escape=False)))
Price and lending market information for 2023-10-09 19:17:32.428722 UTC
Lending asset | Leveraged trading | Stablecoin | Trading volume | Best trading pair | Price now (USD) | Price 30d ago (USD) | Price % | Supply APR | Borrow APR | Price data page | Lending rate page | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | AAVE | no | no | no | AAVE-USDC at 30 BPS fee tier on Quickswap | 63.23903 | 54.18481 | 16.71 | 0.00 | 0.00 | View | View |
1 | agEUR | no | yes | no | No markets available | 4.04 | 6.26 | View | View | |||
2 | BAL | no | no | no | BAL-USDC at 30 BPS fee tier on Uniswap v3 | no trades | no trades | 4.77 | 15.54 | View | View | |
3 | CRV | no | no | no | CRV-USDC at 30 BPS fee tier on Quickswap | 0.447231 | 0.427878 | 4.52 | 0.33 | 4.88 | View | View |
4 | DAI | no | yes | no | No markets available | 4.36 | 6.03 | View | View | |||
5 | DPI | no | no | no | DPI-USDC at 30 BPS fee tier on Quickswap | no trades | no trades | 0.43 | 3.85 | View | View | |
6 | EURS | no | yes | no | No markets available | 2.66 | 4.60 | View | View | |||
7 | GHST | no | yes | no | No markets available | 0.00 | 0.19 | View | View | |||
8 | jEUR | no | yes | no | No markets available | 10.06 | 13.73 | View | View | |||
9 | LINK | yes | no | yes | LINK-USDC at 5 BPS fee tier on Uniswap v3 | 7.292186 | 6.012935 | 21.27 | 0.04 | 0.86 | View | View |
10 | MaticX | no | no | no | No markets available | 0.00 | 0.33 | View | View | |||
11 | miMATIC | yes | no | yes | miMATIC-USDC at 30 BPS fee tier on Quickswap | 0.872123 | 0.88318 | -1.25 | 20.45 | 29.45 | View | View |
12 | stMATIC | no | no | no | stMATIC-USDC at 30 BPS fee tier on Quickswap | no trades | no trades | 0.00 | 0.00 | View | View | |
13 | SUSHI | no | no | no | SUSHI-USDC at 30 BPS fee tier on Uniswap v3 | no trades | no trades | 1.91 | 6.10 | View | View | |
14 | USDC | no | yes | no | No markets available | 5.72 | 7.13 | View | View | |||
15 | USDT | no | yes | no | No markets available | 3.92 | 5.39 | View | View | |||
16 | WBTC | no | no | no | WBTC-USDC at 30 BPS fee tier on Quickswap | 27618.872905 | 25882.467764 | 6.71 | 0.01 | 0.31 | View | View |
17 | WETH | yes | no | yes | WETH-USDC at 5 BPS fee tier on Uniswap v3 | 1579.611806 | 1617.493841 | -2.34 | 0.58 | 2.25 | View | View |
18 | WMATIC | yes | no | yes | WMATIC-USDC at 5 BPS fee tier on Uniswap v3 | 0.528472 | 0.523162 | 1.02 | 2.07 | 4.59 | View | View |
19 | wstETH | no | no | no | No markets available | 0.01 | 0.43 | View | View |
Historical lending rates#
Display historical supply and borrow APR for a single asset.
We plot the historical 30 days rates for ETH, using daily candles to show any variance.
[24]:
reserve = data_universe.lending_reserves.get_by_chain_and_symbol(ChainId.polygon, "WETH")
print("We are examining the lending reserve", reserve)
supply = data_universe.lending_candles.supply_apr.get_rates_by_reserve(reserve)
borrow = data_universe.lending_candles.variable_borrow_apr.get_rates_by_reserve(reserve)
# Display last 6 days
display(supply.iloc[-7:])
We are examining the lending reserve <LendingReserve for asset WETH in protocol aave_v3 on Polygon >
reserve_id | open | high | low | close | timestamp | |
---|---|---|---|---|---|---|
timestamp | ||||||
2023-10-03 | 5 | 0.434257 | 0.594038 | 0.432984 | 0.584951 | 2023-10-03 |
2023-10-04 | 5 | 0.584952 | 0.596051 | 0.572082 | 0.572082 | 2023-10-04 |
2023-10-05 | 5 | 0.572082 | 0.588325 | 0.571232 | 0.578549 | 2023-10-05 |
2023-10-06 | 5 | 0.578546 | 0.589339 | 0.572514 | 0.573854 | 2023-10-06 |
2023-10-07 | 5 | 0.573851 | 0.582503 | 0.571838 | 0.574988 | 2023-10-07 |
2023-10-08 | 5 | 0.574953 | 0.583936 | 0.573015 | 0.574267 | 2023-10-08 |
2023-10-09 | 5 | 0.574236 | 0.592240 | 0.574218 | 0.580444 | 2023-10-09 |
First we draw the borrow APR as candles to show what the data format looks like.
Intra-month volatility in the lending rates is usually low, so candles do not look that interesting unless there has been lending rate spike events like high utilisation spikes due to flashloans.
[25]:
import plotly.graph_objects as go
borrow_candlestick = go.Candlestick(
x=borrow.index,
open=borrow['open'],
high=borrow['high'],
low=borrow['low'],
close=borrow['close'],
showlegend=False,
)
figure = go.Figure(
borrow_candlestick,
layout={
"title": f"Borrow APR % for {reserve.asset_symbol} on {reserve.protocol_slug} on {reserve.chain_id.get_name()}",
"xaxis_rangeslider_visible": False,
"height": 600,
"width": 1200,
}
)
figure.show()
Then draw both supply APR and borrow APR % on the same chart. Use the daily close value for the intraday movement.
[26]:
import plotly.express as px
chart_df = pd.DataFrame(
{
"Borrow APR": borrow["close"],
"Supply APR": supply["close"]
},
index=borrow.index,
)
figure = px.line(
chart_df,
title=f"Interest rates for {reserve.asset_symbol}",
labels={
"variable": "Rates",
"timestamp": "Date",
},
)
figure.show()
Wrap up#
For more examples see Trading Strategy developer documentation.
[ ]: