Aave V3 Interest Analysis

In this notebook we will show how to download interest events from Aave V3 to your computer as CSV files and use them to analyse interest rates in each reserve.

  • You need to understand Jupyter Notebook and Pandas basics

  • You need to understand Ethereum and Web3.py basics

  • You need to understand lending protocols and Aave

  • You will need to have Polygon node and its JSON-RPC URL in order to pull out the data from the blockchain. The notebook will interactively ask you for your JSON-RPC URL. Any free Polygon JSON-RPC node won’t work, because those do not allow historical event extraction in scale.

  • This notebook will download massive amount data from your JSON-RPC provider. By default, we limit the time period with max_block and the data is up until today.

  • After successful download the data is cached in a file 'aave-v3-polygon-rate-scan.json, so any subsequent runs of the notebook are fast

Aave V3 Networks and Tokens

The Aave V3 protocol runs on multiple networks and supports multiple tokens. To analyze interest rates, you must choose a network and token to analyze. The following networks and tokens are available:

  • Polygon: AAVE, DAI, USDT, LINK, WMATIC, USDC, AGEUR, EURS, WBTC, WETH, CRV, SUSHI, GHST, JEUR, DPI, BAL

  • Optimism, Arbitrum, Fantom, Avalance and Harmony networks not yet fully implemented - token definitions can be added to aave_v3/constants.py

For instance, to analyze interest rates for lending and borrowing ETH on the Polygon blockchain, you connect to the JSON-RPC URI of a Polygon node and use the WETH (Wrapped ETH) token.

Note that Aave V3 does not run on the native Ethereum blockchain at this time. You can find more information about available networks and tokens on the Polygon website.

Download the raw data from a blockchain

For simplicity, you can sign up for free access to a Polygon node for example at Infura, however we recommend you to run your own node.

The code below will connect to your JSON-RPC URL and auto-detect the network by its chain id.

[ ]:

from web3 import Web3, HTTPProvider from eth_defi.aave_v3.constants import aave_v3_get_json_rpc_url, aave_v3_get_network_by_chain_id from eth_defi.event_reader.fast_json_rpc import patch_web3 print("Starting notebook") # Get your node JSON-RPC URL json_rpc_url = aave_v3_get_json_rpc_url() or input("Please enter your JSON-RPC URL here") web3 = Web3(HTTPProvider(json_rpc_url)) aave_network = aave_v3_get_network_by_chain_id(web3.eth.chain_id) token = 'WMATIC' # use WMATIC token in the following examples stable_token = 'DAI' # use DAI token in the following examples for stable token operations # Up to the current block # max_block = web3.eth.block_number max_block = 32_000_000 print(f'Detected network {aave_network.name } chain {web3.eth.chain_id} start block {aave_network.pool_created_at_block} max block {max_block}')

Here we download raw events since the Aave v3 pool was created in the blockchain. The events will be stored in several CSV files at /tmp folder.

Depends on your internet connection and latency to the Ethereum node, the scan might take hours. However it can resume in case there is a crash, as we save the last scanned block in a JSON state file.

You can change start_block and end_block to download event data for a shorter range of history.

[ ]:
from eth_defi.aave_v3.events import aave_v3_fetch_events_to_csv
from eth_defi.event_reader.json_state import JSONFileScanState

start_block = aave_network.pool_created_at_block  # Read from creation of the Aave v3 pool
end_block = max_block  # Read until end of available blocks
max_workers = 8  # number of workers to use for parallel API request processing

# Stores the last block number of event data we store
state = JSONFileScanState(f'aave-v3-{aave_network.name.lower()}-rate-scan.json')

aave_v3_fetch_events_to_csv(json_rpc_url, state, aave_network.name, start_block=start_block, end_block=end_block, max_workers=max_workers)

Analyze Aave v3 rate information

Aave v3 rates can be read from the ReserveDataUpdated event history. Note that the dataset includes updates for all reserve currencies. See eth_defi/aave_v3/README.md for a more detailed description of the events.

[ ]:
import pandas

# Read the CSV data into a DataFrame
reserve_data_updated_df = pandas.read_csv(f'/tmp/aave-v3-{aave_network.name.lower()}-reservedataupdated.csv')

# Index the dataset by timestamp (convert to datetime objects)
reserve_data_updated_df['timestamp'] = pandas.to_datetime(reserve_data_updated_df['timestamp'])
reserve_data_updated_df.set_index('timestamp', drop=False, inplace=True)

print(f"We have total {len(reserve_data_updated_df):,} reserve data updates in the dataset")

We calculate APY (Annual Percentage Yield) and APR (Annual Percentage Rate) based Aave documentation: https://docs.aave.com/developers/v/2.0/guides/apy-and-apr.

The resulting dataframe also includes deposit and borrow rates as floating point variables for convenience.

You can access these calculated columns in the resulting dataframe:

  • deposit_apr (float)

  • variable_borrow_apr (float)

  • stable_borrow_apr (float)

  • deposit_apy (float)

  • variable_borrow_apy (float)

  • stable_borrow_apy (float)

  • liquidity_rate_float (float)

  • variable_borrow_rate_float (float)

  • stable_borrow_rate_float (float)

[ ]:
from eth_defi.aave_v3.rates import aave_v3_calculate_apr_apy_rates

apr_apy_df = aave_v3_calculate_apr_apy_rates(reserve_data_updated_df)
apr_apy_df

Draw Aave v3 deposit rate graph

We can now plot historical deposit rates into a graph. Here we show the daily mean liquidity rate, which determines the interest you gain for deposits.

[ ]:
from eth_defi.aave_v3.rates import aave_v3_calculate_mean
import plotly.graph_objects as go
from plotly.subplots import make_subplots

time_bucket = pandas.Timedelta("1D")
deposit_df = aave_v3_calculate_mean(apr_apy_df, time_bucket, 'liquidity_rate_float', token) * 100
deposit_rate_scatter = go.Scatter(
    x=deposit_df.index,
    y=deposit_df,
    showlegend=True,
    name='Deposit rate %s' % (token),
    # marker={ "color": "rgba(0,128,0,1.0)", }
)

fig = make_subplots(specs=[[{"secondary_y": False}]])
fig.add_trace(deposit_rate_scatter, secondary_y=False)
fig.update_layout(title="Aave v3 deposit rates", height=800)
fig.update_yaxes(title="Deposit rate %", secondary_y=False, showgrid=False)
fig.show()

Draw Aave v3 borrow rates graph

We can plot historical stable and variable borrow rates into a graph. Here we show the daily mean variable/stable borrow rates.

Note that we use a different currency token (DAI) for stable rates, because the WMATIC token does not have stable borrow rates available.

[ ]:
from eth_defi.aave_v3.rates import aave_v3_calculate_mean
import plotly.graph_objects as go
from plotly.subplots import make_subplots

time_bucket = pandas.Timedelta("1D")
variable_borrow_df = aave_v3_calculate_mean(apr_apy_df, time_bucket, 'variable_borrow_rate_float', token) * 100
stable_borrow_df = aave_v3_calculate_mean(apr_apy_df, time_bucket, 'stable_borrow_rate_float', stable_token) * 100

variable_borrow_rate_scatter = go.Scatter(
    x=variable_borrow_df.index,
    y=variable_borrow_df,
    showlegend=True,
    name='Variable rate %s' % (token),
    # marker={ "color": "rgba(0,128,0,1.0)", }
)

stable_borrow_rate_scatter = go.Scatter(
    x=stable_borrow_df.index,
    y=stable_borrow_df,
    showlegend=True,
    name='Stable rate %s' % (stable_token),
    # marker={ "color": "rgba(0,128,0,1.0)", }
)

fig = make_subplots(specs=[[{"secondary_y": False}]])
fig.add_trace(variable_borrow_rate_scatter, secondary_y=False)
fig.add_trace(stable_borrow_rate_scatter, secondary_y=False)
fig.update_layout(title="Aave v3 borrow rates", height=800)
fig.update_yaxes(title="Borrow rate %", secondary_y=False, showgrid=False)
fig.show()

Draw Aave v3 APR/APY graph

We can plot a historical APR/APY graph. The graph will show daily mean APR and APY rates for deposits, variable borrow debt and stable borrow debt.

Note that for stable debt, we use the DAI token, while the other lines in the graph use the WMATIC token.

[ ]:
from eth_defi.aave_v3.rates import aave_v3_calculate_mean
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# Calculate APR/APY mean values for each day
time_bucket = pandas.Timedelta("1D")

# Calculate deposit and variable borrow rates for MATIC.
deposit_apr_df, variable_borrow_apr_df = aave_v3_calculate_mean(apr_apy_df, time_bucket, ('deposit_apr', 'variable_borrow_apr'), token)
deposit_apy_df, variable_borrow_apy_df = aave_v3_calculate_mean(apr_apy_df, time_bucket, ('deposit_apy', 'variable_borrow_apy'), token)

# Calculate stable rates for stable currency (DAI).
stable_borrow_apr_df = aave_v3_calculate_mean(apr_apy_df, time_bucket, 'stable_borrow_apr', stable_token)
stable_borrow_apy_df = aave_v3_calculate_mean(apr_apy_df, time_bucket, 'stable_borrow_apy', stable_token)

deposit_apr_df_scatter = go.Scatter(
    x=deposit_apr_df.index,
    y=deposit_apr_df,
    showlegend=True,
    name='Deposit %s APR' % (token),
    # marker={ "color": "rgba(0,128,0,1.0)", }
)

deposit_apy_df_scatter = go.Scatter(
    x=deposit_apy_df.index,
    y=deposit_apy_df,
    showlegend=True,
    name='Deposit %s APY' % (token),
    # marker={ "color": "rgba(0,0,128,1.0)", }
)

variable_borrow_apr_df_scatter = go.Scatter(
    x=variable_borrow_apr_df.index,
    y=variable_borrow_apr_df,
    showlegend=True,
    name='Variable %s Borrow APR' % (token),
    # marker={ "color": "rgba(128,0,0,1.0)", }
)

variable_borrow_apy_df_scatter = go.Scatter(
    x=variable_borrow_apy_df.index,
    y=variable_borrow_apy_df,
    showlegend=True,
    name='Variable %s Borrow APY' % (token),
    # marker={ "color": "rgba(128,0,128,1.0)", }
)

stable_borrow_apr_df_scatter = go.Scatter(
    x=stable_borrow_apr_df.index,
    y=stable_borrow_apr_df,
    showlegend=True,
    name='Stable %s Borrow APR' % (stable_token),
    # marker={ "color": "rgba(128,0,0,1.0)", }
)

stable_borrow_apy_df_scatter = go.Scatter(
    x=stable_borrow_apy_df.index,
    y=stable_borrow_apy_df,
    showlegend=True,
    name='Stable %s Borrow APY' % (stable_token),
    # marker={ "color": "rgba(128,0,128,1.0)", }
)

fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(deposit_apr_df_scatter, secondary_y=False)
fig.add_trace(deposit_apy_df_scatter, secondary_y=False)
fig.add_trace(variable_borrow_apr_df_scatter, secondary_y=False)
fig.add_trace(variable_borrow_apy_df_scatter, secondary_y=False)
fig.add_trace(stable_borrow_apr_df_scatter, secondary_y=False)
fig.add_trace(stable_borrow_apy_df_scatter, secondary_y=False)
fig.update_layout(title="Aave v3 APR/APY", height=800)
fig.update_yaxes(title="APR/APY %", secondary_y=False, showgrid=False)
#fig.update_yaxes(title="Borrow APR/APY %", secondary_y=True, showgrid=False)
fig.show()

Calculate Aave v3 interest between given times

These functions will calculate how much interest you paid if you took debt for the given time period, or how much interest you gained if you deposited currency for the given time period.

You can compare these values to what the Aave v3 dashboard currently shows for your deposits/debt, if you set the start times, amounts and tokens correctly.

[ ]:
from eth_defi.aave_v3.rates import aave_v3_calculate_accrued_deposit_interest, aave_v3_calculate_accrued_variable_borrow_interest, aave_v3_calculate_accrued_stable_borrow_interest
from datetime import datetime
from decimal import Decimal
from importlib import reload

start_time = datetime.fromisoformat('2022-08-06T03:17:23')  # replace with start time of your deposit
start_time_var = datetime.fromisoformat('2022-08-18T02:19:30')  # replace with start time of your variable borrow debt
start_time_stable = datetime.fromisoformat('2022-08-18T02:19:30')  # replace with start time of your stable borrow debt

end_time = datetime.now()

amount = Decimal(50)  # replace with your amount of deposit
amount_var = Decimal(10)  # replace with your amount of variable borrow debt
amount_stable = Decimal(2)  # replace with your amount of stable borrow debt

deposit_result = aave_v3_calculate_accrued_deposit_interest(reserve_data_updated_df, start_time, end_time, amount, token)
variable_borrow_result = aave_v3_calculate_accrued_variable_borrow_interest(reserve_data_updated_df, start_time_var, end_time, amount_var, token)
stable_borrow_result = aave_v3_calculate_accrued_stable_borrow_interest(reserve_data_updated_df, start_time_stable, end_time, amount_stable, stable_token)

actual_elapsed = deposit_result.actual_end_time - deposit_result.actual_start_time
actual_elapsed_var = variable_borrow_result.actual_end_time - variable_borrow_result.actual_start_time
actual_elapsed_stable = stable_borrow_result.actual_end_time - stable_borrow_result.actual_start_time

print(f'{deposit_result.actual_start_time.isoformat()[0:19]} - {deposit_result.actual_end_time.isoformat()[0:19]} ({actual_elapsed.days}d) {amount} {token.upper()} deposit interest: {deposit_result.interest} {token.upper()}')
print(f'{variable_borrow_result.actual_start_time.isoformat()[0:19]} - {variable_borrow_result.actual_end_time.isoformat()[0:19]} ({actual_elapsed_var.days}d) {amount_var} {token.upper()} variable borrow interest: {variable_borrow_result.interest} {token.upper()}')
print(f'{stable_borrow_result.actual_start_time.isoformat()[0:19]} - {stable_borrow_result.actual_end_time.isoformat()[0:19]} ({actual_elapsed_stable.days}d) {amount_stable} {stable_token.upper()} stable borrow interest: {stable_borrow_result.interest} {stable_token.upper()}')

Check current deposit account balance

If you have an active deposit or debt in Aave v3, you can check your current balance (with accrued interest) in the blockchain using the below functions.

You’ll need to input your account address and check that the token matches the currency you are using.

[ ]:
from eth_defi.aave_v3.balances import aave_v3_get_deposit_balance, aave_v3_get_variable_borrow_balance, aave_v3_get_stable_borrow_balance
from eth_defi.aave_v3.constants import aave_v3_get_account_address

# Lookup the token addresses needed for querying balances
deposit_address = aave_network.token_contracts[token.upper()].deposit_address
variable_borrow_address = aave_network.token_contracts[token.upper()].variable_borrow_address
stable_deposit_address = aave_network.token_contracts[stable_token.upper()].deposit_address
stable_borrow_address = aave_network.token_contracts[stable_token.upper()].stable_borrow_address

# The account address whose balances we want to query
account_address = aave_v3_get_account_address() or input("Please enter your account address")

# Get the balances
deposit_balance = aave_v3_get_deposit_balance(web3, deposit_address, account_address)
variable_borrow_balance = aave_v3_get_variable_borrow_balance(web3, variable_borrow_address, account_address)
stable_borrow_balance = aave_v3_get_stable_borrow_balance(web3, stable_borrow_address, account_address)

print(f'{token.upper()} deposit balance: {deposit_balance}')
print(f'{token.upper()} variable borrow balance: {variable_borrow_balance}')
print(f'{stable_token.upper()} stable borrow balance: {stable_borrow_balance}')