<jupyter_start><jupyter_text>TORN $^2$ Tokenomics Analyzis<jupyter_code>import numpy as np
import pandas as pd
import scipy as scp
from matplotlib import pyplot as plt
import os<jupyter_output><empty_output><jupyter_text>Unique Users per Week Deposits<jupyter_code>with open('./Data/TornadoCash Unique Users Per Week Deposits.csv') as f:
    UniqueUsers = pd.read_csv(f)
UniqueUsers['time'] = pd.to_datetime(UniqueUsers['time'])
plt.bar(UniqueUsers['time'], UniqueUsers['users'], width=5)
plt.xlabel('Date')
plt.ylabel('Unique Users per Week')
plt.gcf().set_size_inches(20, 5)
plt.grid()
plt.title('Unique Users per Week')<jupyter_output><empty_output><jupyter_text>TVL in Tornado Pools<jupyter_code>TVL = {}

for file in os.listdir('./Data/TVL'):
    TVL[file.replace(' ETH TVL Daily (90d).csv', '')] = pd.read_csv('./Data/TVL/' + str(file))

i = 0
fig, axes = plt.subplots(len(TVL)+1, 1)
for k in TVL:
    date = pd.to_datetime(TVL[k]['day'])
    tvl = TVL[k]['daily_tvl']
    relative_tvl = tvl/float(k)
    axes[i].plot(date, tvl)#, label='TVL')
    axes[-1].plot(date, relative_tvl, label=k + ' ETH Pool')
    axes[i].set_title(k + ' ETH Pool TVL')
    axes[i].set_xlabel('Date')
    axes[i].set_ylabel('TVL (ETH)')
    axes[i].grid()

    i += 1

axes[-1].set_title('TVL Per 1 ETH')
axes[-1].set_xlabel('Date')
axes[-1].set_ylabel('Relative TVL')
axes[-1].legend()
axes[-1].grid()

plt.gcf().set_size_inches(20, 25)<jupyter_output><empty_output><jupyter_text>Deposits & Withdrawals<jupyter_code>DW = {}

for file in os.listdir('./Data/DepositsWithdrawals'):
    DW[file.replace(' ETH DepositsWithdrawals Daily (30d).csv', '')] = pd.read_csv('./Data/DepositsWithdrawals/' + str(file))

i = 0
fig, axes = plt.subplots(len(DW), 1)
for k in DW:
    date = pd.to_datetime(DW[k]['time'])
    dep = DW[k]['deposits_eth']
    wit = DW[k]['withdrawals_eth']
    axes[i].bar(date, dep, label='Deposits')
    axes[i].bar(date, wit, label='Withdrawals', alpha=0.5)
    #axes[-1].plot(date, relative_tvl, label=k + ' ETH Pool')
    axes[i].set_title(k + ' ETH Pool Deopsits & Withdrawals')
    axes[i].set_xlabel('Date')
    axes[i].set_ylabel('Amount (ETH)')
    axes[i].grid()
    axes[i].legend()

    i += 1

'''axes[-1].set_title('TVL Per 1 ETH')
axes[-1].set_xlabel('Date')
axes[-1].set_ylabel('Relative TVL')
axes[-1].legend()
axes[-1].grid()'''

plt.gcf().set_size_inches(20, 25)<jupyter_output><empty_output><jupyter_text>Fees<jupyter_code>Fees = {}

for file in os.listdir('./Data/Fees'):
    Fees[file.replace(' ETH Fees Daily.csv', '')] = pd.read_csv('./Data/Fees/' + str(file))

for k in Fees:
    Fees[k] = Fees[k][pd.to_datetime(Fees[k]['day']) >= pd.to_datetime('2021-11-18', utc=True)]
    Fees[k] = Fees[k][pd.to_datetime(Fees[k]['day']) <= pd.to_datetime('2021-12-17', utc=True)]

i = 0
fig, axes = plt.subplots(len(Fees), 1)
for k in Fees:
    date = pd.to_datetime(Fees[k]['day'])
    fees = Fees[k]['relayer_total_fee_eth']
    gas = Fees[k]['relayer_gas_cost_eth']
    profits = fees - gas
    profits2 = Fees[k]['relayer_profit_eth']
    axes[i].bar(date, fees, label='Fees', width=0.5)
    axes[i].bar(date, gas, label='Gas Spendings', alpha=0.5, width=0.5)
    #axes[i].plot(date, profits, label='Profits', color='yellow', alpha=1)
    axes[i].plot(date, profits2, label='Profits', color='red')
    #axes[-1].plot(date, relative_tvl, label=k + ' ETH Pool')
    axes[i].set_title(k + ' ETH Pool Fees')
    axes[i].set_xlabel('Date')
    axes[i].set_ylabel('Amount (ETH)')
    axes[i].grid()
    axes[i].legend()

    i += 1

'''axes[-1].set_title('TVL Per 1 ETH')
axes[-1].set_xlabel('Date')
axes[-1].set_ylabel('Relative TVL')
axes[-1].legend()
axes[-1].grid()'''

plt.gcf().set_size_inches(20, 25)<jupyter_output><empty_output><jupyter_text>TORN Circulating Supply<jupyter_code>with open('./Data/TORN Circulating Supply v03.csv') as file:
    CS = pd.read_csv(file)

plt.plot(pd.to_datetime(CS['day']), CS['Circulating Supply'])
plt.title('TORN Circulating Supply')
plt.xlabel('Date')
plt.ylabel('TORN')
plt.grid()
plt.gcf().set_size_inches(20, 5)<jupyter_output><empty_output><jupyter_text>TORN Locked in Governance<jupyter_code>with open('./Data/TORN locked in governance - daily v01.csv') as file:
    TGov = pd.read_csv(file)

plt.plot(pd.to_datetime(TGov['day']), TGov['amount'])
plt.title('TORN Locked in Governance')
plt.xlabel('Date')
plt.ylabel('TORN')
plt.grid()
plt.gcf().set_size_inches(20, 5)<jupyter_output><empty_output><jupyter_text>TORN Price<jupyter_code>with open('./Data/TORN Price.csv') as file:
    TORN_Price = pd.read_csv(file)

TORN_Price = TORN_Price.rename(columns={'snapped_at':'date'})
TORN_Price['date'] = pd.to_datetime(TORN_Price['date'])

plt.plot(TORN_Price['date'], TORN_Price['price'])
plt.title('TORN Price')
plt.xlabel('date')
plt.ylabel('$')
plt.grid()
plt.gcf().set_size_inches(20, 5)<jupyter_output><empty_output><jupyter_text>ETH Price<jupyter_code>with open('./Data/ETH Price.csv') as file:
    ETH_Price = pd.read_csv(file)

ETH_Price = ETH_Price.rename(columns={'snapped_at':'date'})
ETH_Price['date'] = pd.to_datetime(ETH_Price['date'])

plt.plot(ETH_Price['date'], ETH_Price['price'])
plt.title('ETH Price')
plt.xlabel('date')
plt.ylabel('$')
plt.grid()
plt.gcf().set_size_inches(20, 5)<jupyter_output><empty_output><jupyter_text>Cutting data from 2021-11-18<jupyter_code>start_date = pd.to_datetime('2021-11-18', utc=True)
end_date = pd.to_datetime('2021-12-17', utc=True)
with open('./Data/TORN Price.csv') as torn_file, open('./Data/ETH Price.csv') as eth_file:
    TORN_price = pd.read_csv(torn_file).rename(columns={'price':'torn'})
    ETH_price = pd.read_csv(eth_file).rename(columns={'price':'eth'})
    Price = ETH_price.merge(TORN_price, on='snapped_at')[['snapped_at', 'eth', 'torn']].rename(columns={'snapped_at':'date'})
    Price['date'] = pd.to_datetime(Price['date'])
    Price = Price[(Price['date'] >= start_date) & (Price['date'] <= end_date)].sort_values(by='date').reset_index(drop=True)

Price.head()
DW = {}
for file in os.listdir('./Data/DepositsWithdrawals'):
    DW[file.replace(' ETH DepositsWithdrawals Daily (30d).csv', '')] = pd.read_csv('./Data/DepositsWithdrawals/' + str(file))

for k in DW:
    DW[k]['time'] = pd.to_datetime(DW[k]['time'])
    DW[k] = DW[k].rename(columns={'time': 'date'})
    DW[k] = DW[k][(DW[k]['date'] >= start_date) & (DW[k]['date'] <= end_date)].sort_values(by='date').reset_index(drop=True)

DW['1'].head()
Fees = {}
for file in os.listdir('./Data/Fees'):
    Fees[file.replace(' Fees Daily.csv', '')] = pd.read_csv('./Data/Fees/' + str(file))
    
for k in Fees:
    Fees[k]['day'] = pd.to_datetime(Fees[k]['day'])
    Fees[k] = Fees[k].rename(columns={'day' : 'date'})
    Fees[k] = Fees[k][(Fees[k]['date'] >= start_date) & (Fees[k]['date'] <= end_date)]
    Fees[k] = Fees[k].sort_values(by='date').reset_index(drop=True)

Fees['1'].head()<jupyter_output><empty_output><jupyter_text>To determine the quantities of TORN token required to perform a withdrawal, let's calculate the profitability of the relayer as a percentage of total withdrawal amount:$$Profit = \frac{\sum{profits}}{\sum{withdrawals}}\times 100\%$$ Based on this metric we can derive what percentage of the profit will be converted into $TORN$ staked back into $TORN$ pool by the Relayers:$$TORN = Profit \times A \%$$We can analyze for different $A$ and obtain the buying pressure of the $TORN$ token in the outer market, the TVL growth of the $TORN$ pool and the APR of $TORN$ stakers.<jupyter_code>for k in Fees:
    k_profit = Fees[k]['relayer_profit_eth'].sum() / DW[k]['withdrawals_eth'].sum()
    print(k + ' ETH Pool Relayer Profit = {}%'.format(k_profit*100))<jupyter_output>0.1 ETH Pool Relayer Profit = 19.152471210803885%
1 ETH Pool Relayer Profit = 1.420402888454761%
10 ETH Pool Relayer Profit = 0.24232959000598575%
100 ETH Pool Relayer Profit = 0.03020600676676755%<jupyter_text>It is also interesting to see the spendings of the user withdrawing funds:<jupyter_code>for k in Fees:
    k_profit = Fees[k]['relayer_total_fee_eth'].sum() / DW[k]['withdrawals_eth'].sum()
    print(k + ' ETH Pool Relayer Fees = {}%'.format(k_profit*100))<jupyter_output>0.1 ETH Pool Relayer Fees = 45.85143295733881%
1 ETH Pool Relayer Fees = 3.363556057890783%
10 ETH Pool Relayer Fees = 0.5536110973172016%
100 ETH Pool Relayer Fees = 0.0523502920740059%<jupyter_text>We see the distribution of Relayers' profits per single withdrawal based on the withdrawal size.Turns out the 0.1 ETH pool is the most expensive one. Let's start with conservative $A$ of $20 \%$ (i.e. $20 \%$ of Relayers' profits actually be the necessary TORN deposit in the Vault).The initial TORN Pool will consist of those tokens locked in governance at the moment:<jupyter_code>with open('./data/TORN locked in governance - addresses v01.csv') as file:
    GOV = pd.read_csv(file)

init_TORN = GOV['amount'].sum()
init_TORN
A = 0.05

TORN_pool = pd.DataFrame()
TORN_pool['torn'] = np.ones(len(Price)) * init_TORN
TORN_pool['torn_deposit'] = np.zeros(len(Price))
TORN_pool['date'] = Price['date']

for k in Fees:
    Fees[k]['torn_required'] = Fees[k]['relayer_profit_eth'] * A * Price['eth'] / Price['torn']
    TORN_pool['torn_deposit'] += Fees[k]['torn_required']

for i in range(len(TORN_pool)):
    TORN_pool.loc[i, 'torn'] += TORN_pool[TORN_pool['date'] <= TORN_pool.loc[i, 'date']]['torn_deposit'].sum()

TORN_pool
plt.rcParams['text.color'] = 'white'
plt.rcParams['axes.labelcolor'] = 'white'
plt.rcParams['xtick.color'] = 'white'
plt.rcParams['ytick.color'] = 'white'
a = plt.plot(TORN_pool['date'], TORN_pool['torn'], label='TORN in pool')
plt.title('TORN Pool Composition', fontsize=32)
plt.xlabel('Date', fontsize=20)
plt.ylabel('TORN', fontsize=20)
plt.grid()
plt.twinx()
b = plt.bar(TORN_pool['date'], TORN_pool['torn_deposit'], alpha=0.2, color='green', width=0.8, label='TORN deposit')
plt.ylabel('TORN Deposit', fontsize=20)
plt.gcf().set_size_inches(20, 10)
l = [a[0], b]
labels = [x.get_label() for x in l]
plt.rcParams['text.color'] = 'black'
plt.legend(l, labels, fontsize=20, loc=9)

final_TORN = TORN_pool.iloc[-1]['torn']

gain = final_TORN - init_TORN
rate = gain/init_TORN
apr = rate / 30 * 365
apr
final_TORN<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1875122141880008, 'fraction_numerical': 0.031268321282001174, 'mean_word_length': 5.531589023611997, 'pattern_counts': {'":': 0, '<': 56, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '419568', 'license': 'MIT', 'n_tokens_mistral': 4424, 'n_tokens_neox': 4004, 'n_words': 776, 'path': 'TornadoAutocompoundResearch/TORN.ipynb', 'repo_name': 'powerpool-finance/powerpool-research', 'size': '10234'}