Skip to content

Commit

Permalink
code refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Liu authored and Ben Liu committed Dec 18, 2023
1 parent 73f1c00 commit 5169963
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 206 deletions.
Binary file modified .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ methodology explanation can be found in medium article: https://medium.com/@xben

Code:
* main: main.py
* data: use library_data.py. Data can be pre-saved in output folder for easier check.
* data: use library_data.py. This file will pre save data into csv in the output folder.
* logic (imp loss, range, coverage %): library_logic.py
31 changes: 31 additions & 0 deletions lib_const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@


pool_info_list = [
['0xcbcdf9626bc03e24f779434178a73a0b4bad62ed', 'WBTC', 'WETH', 0.003],
['0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640' , 'USDC', 'WETH', ],
['0x5777d92f208679db4b9778590fa3cab3ac9e2168' , 'DAI', 'USDC', ],
['0x109830a1aaad605bbf02a9dfa7b0b92ec2fb7daa', 'WSTETH', 'WETH',],
['0x4585fe77225b41b697c938b018e2ac67ac5a20c0' , 'WBTC' ,'WETH', 0.0005]
]

# data needed by coingecko API
price_token_list =[
['bitcoin', 'BTC' ],
['ethereum', 'ETH']
]

def get_pool_filename(pool_address, token0=None, token1=None):
if token0 is None or token1 is None:
for pool_info in pool_info_list:
if pool_info[0] == pool_address:
token0 = pool_info[1]
token1 = pool_info[2]
break

file_name_addon = "_".join([pool_address, token0, token1])
file_name = 'output/pool_data_' + file_name_addon + '.csv'
return file_name


# date_begin = datetime.strptime(date_begin_yyyymmdd, '%Y%m%d')
# date_end = datetime.strptime(date_end_yyyymmdd, '%Y%m%d')
153 changes: 153 additions & 0 deletions lib_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import requests
from datetime import datetime, timedelta
import pandas as pd


# API each time can only get 100 recoreds, hence break down the retrieve into year-month
def get_uniswap_v3_data_limit100(pool_address, from_timestamp, to_timestamp):
# Uniswap V3 Subgraph endpoint
endpoint = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'

# GraphQL query to get historical data
query = '''
{
poolDayDatas( orderBy: date,
where: { pool: "%s", date_gte: %d, date_lte: %d } ) {
date
liquidity
sqrtPrice
token0Price
token1Price
volumeToken0
volumeToken1
feesUSD
volumeUSD
tvlUSD
}
}
''' % (pool_address, from_timestamp, to_timestamp)
# print(query)
# Make the GraphQL request
response = requests.post(endpoint, json={'query': query})
data = response.json()
#print(data)
return data['data']['poolDayDatas']

def last_day_of_month(year, month):
# Calculate the first day of the next month
first_day_of_next_month = datetime(year, month, 1) + timedelta(days=32)

# Subtract one day to get the last day of the current month
last_day_of_month = first_day_of_next_month.replace(day=1) - timedelta(days=1)

return last_day_of_month


def get_uniswap_v3_data_year(pool_address, years):
pool_df=pd.DataFrame()

# Get Uniswap V3 data
for year in years:
for month in range(1, 13):

from_timestamp = int(datetime(year, month, 1).timestamp()) # Replace with your start date
next_mon_first_day = last_day_of_month(year, month)+ timedelta(days=1)
to_timestamp = int(next_mon_first_day.timestamp())
uniswap_v3_data_month = get_uniswap_v3_data_limit100(pool_address, from_timestamp, to_timestamp)
df = pd.DataFrame(uniswap_v3_data_month)
pool_df = pd.concat([pool_df, df], ignore_index=True)

return pool_df

def get_crypto_price(symbol, token, start_date, end_date, vs_currency='usd'):
url = f"https://api.coingecko.com/api/v3/coins/{symbol}/market_chart"
params = {
'vs_currency': vs_currency,
'from': int(start_date.timestamp()),
'to': int(end_date.timestamp()),
'interval': 'daily',
'days': 1200
}

response = requests.get(url, params=params)

if response.status_code == 200:
data = response.json()
prices = data.get('prices', [])
else:
print(f"Error: {response.status_code}")
print(response.text)
return None

columns = ['date', 'price']
df = pd.DataFrame(prices, columns=columns)
df.drop(df.index[-1], inplace=True) # last date has 2 records

df['date'] = df['date'].apply( lambda x: datetime.utcfromtimestamp(x / 1000).date() )
df['token'] = token
df['vs_currency'] = vs_currency

return df





# Check if the script is being run as the main program
if __name__ == "__main__":

import lib_const

load_all_pool_related_data = True
if load_all_pool_related_data: # getting pool fee/vol related data

years = [2022, 2023]

result_df = pd.DataFrame()
for pool_info in lib_const.pool_info_list:
pool_address = pool_info[0]
result_df = get_uniswap_v3_data_year(pool_address, years)
file_name = lib_const.get_pool_filename(pool_address, token0=pool_info[1], token1=pool_info[2])
print("save data:",file_name )
result_df.to_csv(file_name, index=False)
#print("will only run for the first pool during test. ")
#break

# if only wanna run individual pool
# pool_address = '0xcbcdf9626bc03e24f779434178a73a0b4bad62ed' #WBTC WETH 0.3%
# pool_address = '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640' #USDC WETH
# pool_address = '0x5777d92f208679db4b9778590fa3cab3ac9e2168' #DAI USDC
# pool_address = '0x109830a1aaad605bbf02a9dfa7b0b92ec2fb7daa' #WSTETH WETH
# pool_address = '0x4585fe77225b41b697c938b018e2ac67ac5a20c0' #WBTC WETH, 0.05%
# result_df = get_uniswap_v3_data_year(pool_address, years)
# file_name = lib_const.get_pool_filename(pool_address)
# result_df.to_csv(file_name, index=False)
# Display the DataFrame
# result_df.head()


load_price_related_data = False;
if load_price_related_data: # getting pool fee/vol related data
# Set the start and end date
start_date = datetime(2020, 11, 1)
end_date = start_date + timedelta(days=1)


df = pd.DataFrame()
for token in lib_const.price_token_list:
token_name = token[0]
token_ticker = token[1]
df_price = get_crypto_price(token_name, token_ticker , start_date, end_date)
print(f'get token {token_ticker} price in usd' )
df_price_btc = pd.DataFrame()
if(token_ticker != 'BTC'):
df_price_btc = get_crypto_price(token_name, token_ticker , start_date, end_date, vs_currency='btc')
print(f'get token {token_ticker} price in btc' )

df = pd.concat([df, df_price, df_price_btc], ignore_index=True)

price_file_name = 'output/price_data_all_token.csv'
df.to_csv(price_file_name, index=False)

print(f"DataFrame saved to {price_file_name}")
76 changes: 45 additions & 31 deletions library_logic.py → lib_logic.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# function for full range LP
import numpy as np
import pandas as pd
from datetime import datetime
import lib_const

# Get impermanent loss, qty_change_token0, and token1.
# code also works if input is array
def get_impermanent_loss(price_change, is_change_token0=True, b_return_df = False, ret_imp_loss_only = False ):
# code also works with array input
def get_impermanent_loss_without_range(price_change, is_change_token0=True, b_return_df = False, ret_imp_loss_only = False ):
#input value check ignored here
if(is_change_token0):
price_change_token0 = np.array(price_change)
Expand Down Expand Up @@ -41,20 +43,9 @@ def get_impermanent_loss(price_change, is_change_token0=True, b_return_df = Fals
return result_matrix


def get_bin_price_range_same_liquidity(price_change):
return -price_change/(1+price_change)

def get_liquidity_boost_by_range(prince_range, benchmark = -0.5):
if(benchmark > 0):
benchmark = get_bin_price_range_same_liquidity(benchmark)

boost = (np.sqrt(1+benchmark) -1 ) / (np.sqrt(1+prince_range) -1 )
return boost


def get_impermanent_loss_range_pos(price_change, price_range_down, is_change_token0=True, b_return_df = False):
def get_impermanent_loss_given_range(price_change, price_range_down):
#input value check ignored here
price_range_up = get_bin_price_range_same_liquidity(price_range_down)
price_range_up = get_opposite_bin_limit_with_same_liquidity(price_range_down)

#print(price_change,price_range_down, price_range_up )

Expand All @@ -78,33 +69,56 @@ def get_impermanent_loss_range_pos(price_change, price_range_down, is_change_tok
return imp_loss


def get_ETHBTC_poolyield_daily(date_begin_yyyymmdd = "20090101", date_end_yyyymmdd = "30000101"):



def get_opposite_bin_limit_with_same_liquidity(price_change):
return -price_change/(1+price_change)

def get_liquidity_boost_given_range(prince_range, benchmark = -0.5):
if(benchmark > 0):
benchmark = get_opposite_bin_limit_with_same_liquidity(benchmark)

boost = (np.sqrt(1+benchmark) -1 ) / (np.sqrt(1+prince_range) -1 )
return boost



def get_ETHBTC_poolyield_daily(date_begin_yyyymmdd = "2009-01-01", date_end_yyyymmdd = "3000-01-01"):
pool_address = '0xcbcdf9626bc03e24f779434178a73a0b4bad62ed'
data_file_name = 'output/output_' + pool_address + '.csv'
data_file_name = lib_const.get_pool_filename(pool_address)

df = pd.read_csv(data_file_name)

date_begin = datetime.strptime(date_begin_yyyymmdd, '%Y-%m-%d')
date_end = datetime.strptime(date_end_yyyymmdd, '%Y-%m-%d')


# Convert 'date' column to YYYYMMDD format
df['date_int'] = df['date']
df['date'] = pd.to_datetime(df['date'], unit='s').dt.strftime('%Y%m%d')
df['date'] = pd.to_datetime(df['date'], unit='s')
df = df.sort_values(by='date',ascending=False)

df = df[(df['date'] >= date_begin_yyyymmdd) & (df['date'] <= date_end_yyyymmdd)]
df = df[(df['date'] >= date_begin) & (df['date'] <= date_end)]

df['daily_fee_rate'] = df['feesUSD'] / df['tvlUSD']
return np.average(df['daily_fee_rate'])

def get_pool_performance_statistic(pool_address, token0, token1, fee_bps, year = -1):

data_file_name = 'output/output_' + pool_address + '.csv'
data_file_name = lib_const.get_pool_filename(pool_address)
# data_file_name = 'output/output_' + pool_address + '.csv'
df = pd.read_csv(data_file_name)

# Convert 'date' column to YYYYMMDD format
df['date_int'] = df['date']
df['date'] = pd.to_datetime(df['date'], unit='s').dt.strftime('%Y%m%d')
df = df.sort_values(by='date',ascending=False)
df['date'] = pd.to_datetime(df['date'], unit='s')
df = df.sort_values(by='date',ascending=False)

#df['date'] = pd.to_datetime(df['date'], unit='s').dt.strftime('%Y%m%d')


df['year'] = pd.to_datetime(df['date_int'], unit='s').dt.strftime('%Y')
df['YYYYMM'] = pd.to_datetime(df['date_int'], unit='s').dt.strftime('%Y%m')
df['year'] = df['date'].dt.strftime('%Y')
df['YYYYMM'] = df['date'].dt.strftime('%Y%m')

# Filter rows where 'year' is not equal to 2023
if (year != -1):
Expand Down Expand Up @@ -148,7 +162,7 @@ def get_pool_performance_statistic(pool_address, token0, token1, fee_bps, year =


price_change_array = [total_pct_price_change, price_change_7d_95th, price_change_7d_5th]
df_pool_imp_loss_stats = get_impermanent_loss(price_change_array, b_return_df=True)
df_pool_imp_loss_stats = get_impermanent_loss_without_range(price_change_array, b_return_df=True)


# Create a 1-row DataFrame
Expand Down Expand Up @@ -186,21 +200,21 @@ def get_pool_performance_statistic(pool_address, token0, token1, fee_bps, year =
# print("test with range: -----")

price_change = 0.05
loss = get_impermanent_loss_range_pos(price_change, price_range_down=-0.16)
loss = get_impermanent_loss_given_range(price_change, price_range_down=-0.16)
print("imp loss",price_change, loss)


price_change = -0.08
loss = get_impermanent_loss_range_pos(price_change, price_range_down=0.16)
loss = get_impermanent_loss_given_range(price_change, price_range_down=0.16)
print("imp loss, all y gone",price_change, loss)

your_range = -0.05
benchmark = -0.15
print('boost:',your_range,benchmark, "by", get_liquidity_boost_by_range (your_range,benchmark))
print('boost:',your_range,benchmark, "by", get_liquidity_boost_given_range (your_range,benchmark))

your_range =np.array([-0.05, -0.1])
benchmark = -0.15
print('boost:',your_range,benchmark, "by", get_liquidity_boost_by_range (your_range,benchmark))
print('boost:',your_range,benchmark, "by", get_liquidity_boost_given_range (your_range,benchmark))


# Load CSV into DataFrame
Expand Down Expand Up @@ -231,6 +245,6 @@ def get_pool_performance_statistic(pool_address, token0, token1, fee_bps, year =
df_pool_stats.to_csv('output/result_pools.csv', index=False)

print("pool daily yield", get_ETHBTC_poolyield_daily())
print("pool daily yield 2023:", get_ETHBTC_poolyield_daily(date_begin_yyyymmdd = "20230101"))
print("pool daily yield 2023:", get_ETHBTC_poolyield_daily(date_begin_yyyymmdd = "2023-01-01"))


Loading

0 comments on commit 5169963

Please sign in to comment.