Simple Uni V2 Tree (Part 2)

[1]:
import os
import numpy as np
import pandas as pd
import datetime
import matplotlib.pyplot as plt
import scipy.stats as stats
import statsmodels.api as sm
import seaborn as sns

from uniswappy import *

Script params

[2]:
init_tkn_lp = 100000
tkn_delta_param = 1000
tkn_invest_amt = 100
tkn_nm = 'USDC'
itkn_nm = 'iUSDC'
usd_nm = 'USDT'
iusd_nm = 'iUSDT'

Simulate price data

[3]:
# *************************
# *** Simulation
# *************************
n_sim_runs = 2000
seconds_year = 31536000
shape = 2000
scale = 0.0005

p_arr = np.random.gamma(shape = shape, scale = scale, size = n_sim_runs)

n_runs = len(p_arr)-1
dt = datetime.timedelta(seconds=seconds_year/n_sim_runs)
dates = [datetime.datetime.strptime("2024-09-01", '%Y-%m-%d') + k*dt for k in range(n_sim_runs)]

x_val = np.arange(0,len(p_arr))
fig, (USD_ax) = plt.subplots(nrows=1, sharex=False, sharey=False, figsize=(18, 5))
USD_ax.plot(dates, p_arr, color = 'r',linestyle = 'dashdot', label='initial invest')
USD_ax.set_title(f'Price Chart ({tkn_nm}/{usd_nm})', fontsize=20)
USD_ax.set_ylabel('Price (USD)', size=20)
USD_ax.set_xlabel('Date', size=20)
[3]:
Text(0.5, 0, 'Date')
../_images/tutorials_simple_tree_pt2_5_1.png

Initialization Params

[4]:
user_nm = 'user0'
tkn_amount = init_tkn_lp
dai_amount = p_arr[0]*tkn_amount

Initialize DEX Tree

[5]:
dai1 = ERC20(usd_nm, "0x111")
tkn1 = ERC20(tkn_nm, "0x09")
exchg_data = UniswapExchangeData(tkn0 = tkn1, tkn1 = dai1, symbol="LP", address="0x011")

TKN_amt = TokenDeltaModel(tkn_delta_param)
TKN_amt_arb = TokenDeltaModel(100)

lp1_state = MarkovState(stochastic = True)
iVault1 = IndexVault('iVault1', "0x7")

factory = UniswapFactory(f"{tkn_nm} pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, tkn_amount, dai_amount)

tkn2 = ERC20(tkn_nm, "0x09")
itkn1 = IndexERC20(itkn_nm, "0x09", tkn1, lp)
exchg_data1 = UniswapExchangeData(tkn0 = tkn2, tkn1 = itkn1, symbol="LP1", address="0x012")
lp1 = factory.deploy(exchg_data1)
JoinTree().apply(lp1, user_nm, iVault1, 10000)

# Re-balance LP price after JoinTree
SwapDeposit().apply(lp, dai1, user_nm, lp.get_reserve(tkn1) - lp.get_reserve(dai1))

lp.summary()
lp1.summary()
Exchange USDC-USDT (LP)
Reserves: USDC = 110000.0, USDT = 110000.0
Liquidity: 109988.82515648747

Exchange USDC-iUSDC (LP1)
Reserves: USDC = 9972.071706380651, iUSDC = 5003.621418712864
Liquidity: 7063.743453650251

Take an investment position

[6]:
tkn_invest = 100
invested_user_nm = 'invested_user'

SwapIndexMint(iVault1, opposing = False).apply(lp, tkn1, invested_user_nm, tkn_invest)
mint_itkn1_deposit = lp1.convert_to_human(iVault1.index_tokens[itkn_nm]['last_lp_deposit'])
lp1_state.next_state(mint_itkn1_deposit)
SwapDeposit().apply(lp1, itkn1, invested_user_nm, mint_itkn1_deposit)

lp.summary()
lp1.summary()

lp_invest_track  = lp.get_liquidity_from_provider(invested_user_nm)
lp1_invest_track  = lp1.get_liquidity_from_provider(invested_user_nm)

# Redeem from parent
tkn_redeem_parent = LPQuote(False).get_amount_from_lp(lp, tkn1, lp_invest_track)

# Redeem from tree (child + parent)
itkn_redeem_child = LPQuote(False).get_amount_from_lp(lp1, itkn1, lp1_invest_track)
tkn_redeem_tree = LPQuote(False).get_amount_from_lp(lp, tkn1, itkn_redeem_child)

print(f'{tkn_redeem_parent:.3f} USDC redeemed from {lp_invest_track:.3f} LP tokens if {tkn_invest:.1f} invested USDC immediately pulled from parent')
print(f'{tkn_redeem_tree:.3f} USDC redeemed from {lp1_invest_track:.3f} LP1 tokens if {tkn_invest:.1f} invested USDC immediately pulled from tree')
Exchange USDC-USDT (LP)
Reserves: USDC = 110100.0, USDT = 110000.0
Liquidity: 110038.73363174185

Exchange USDC-iUSDC (LP1)
Reserves: USDC = 9972.071706380651, iUSDC = 5053.529893967241
Liquidity: 7098.831802880604

99.700 USDC redeemed from 49.908 LP tokens if 100.0 invested USDC immediately pulled from parent
99.403 USDC redeemed from 35.088 LP1 tokens if 100.0 invested USDC immediately pulled from tree

Simulate trading

[7]:
arb = CorrectReserves(lp, x0 = 1)
arb1 = Arbitrage(lp1, lp1_state)

TKN_amt = TokenDeltaModel(tkn_delta_param)

lp_direct_invest_arr = []; lp1_direct_invest_arr = []; lp1_tree_invest_arr = [];
pTKN_DAI_arr = []; pTKN_iTKN_arr = []
fee_lp_arr  = []; fee_lp1_arr  = [];

for k in range(n_sim_runs):

    #if(k % 100 == 0 and k != 0): print(f'Processing event {k}')

    # *****************************
    # ***** Parent Arbitrage ******
    # *****************************
    arb.apply(p_arr[k])

    # *****************************
    # ***** Child Arbitrage ******
    # *****************************
    amt_arb1 = TKN_amt_arb.delta()
    arb1.apply(1, user_nm, amt_arb1)
    arb1.update_state(itkn1)

    mint_tkn1_amt = 0.5*TKN_amt.delta()
    SwapIndexMint(iVault1, opposing = False).apply(lp, tkn1, user_nm, mint_tkn1_amt)
    mint_itkn1_deposit = lp1.convert_to_human(iVault1.index_tokens[itkn_nm]['last_lp_deposit'])
    lp1_state.next_state(mint_itkn1_deposit)
    vault_lp1_amt = lp1_state.get_current_state('dVault')
    burned_itkn1_amt = lp1_state.get_current_state('dBurned')

    ## WithdrawSwap burned token from parent LP
    if(burned_itkn1_amt > 0):
        total_tkn_w_swap = LPQuote(False).get_amount_from_lp(lp, tkn1, burned_itkn1_amt)
        amt_out = RemoveLiquidity().apply(lp, tkn1, user_nm, total_tkn_w_swap/2)

    ## Balance LP1: TKN/iTKN
    if(vault_lp1_amt > 0):
        # A portion of aquired token is coming from newly minted, while the remainder is coming from held
        amt_tkn = LPQuote(False).get_amount_from_lp(lp, tkn1, vault_lp1_amt)
        price_tkn = amt_tkn/vault_lp1_amt
        AddLiquidity(price_tkn).apply(lp1, itkn1, user_nm, vault_lp1_amt)
    elif(vault_lp1_amt < 0):
        # A portion of removed token is getting held, while the remainder is getting burned
        RemoveLiquidity().apply(lp1, itkn1, user_nm, abs(vault_lp1_amt))

    # *****************************
    # ***** Random Swapping ******
    # *****************************
    Swap().apply(lp, tkn1, user_nm, TKN_amt.delta())
    Swap().apply(lp, dai1, user_nm, TKN_amt.delta())

    # conservatively assume 10% of tokens held outside vault are traded
    held_tokens = lp1_state.get_current_state('Held')
    if(held_tokens > 0):
        tradable_itkn1_amt = 0.1*held_tokens
        Swap().apply(lp1, tkn2, user_nm, LPQuote(False).get_amount_from_lp(lp, tkn1, tradable_itkn1_amt))
        Swap().apply(lp1, itkn1, user_nm, tradable_itkn1_amt)

    # *****************************
    # ******* Data Capture ********
    # *****************************

    # price
    pTKN_DAI_arr.append(LPQuote().get_price(lp, tkn1))
    pTKN_iTKN_arr.append(LPQuote().get_price(lp1, tkn1))


    # investment performance
    tkn_redeem_parent = LPQuote(False).get_amount_from_lp(lp, tkn1, lp_invest_track)
    itkn_redeem_child = LPQuote(False).get_amount_from_lp(lp1, itkn1, lp1_invest_track)
    tkn_redeem_tree = LPQuote(False).get_amount_from_lp(lp, tkn1, itkn_redeem_child)

    lp_direct_invest_arr.append(tkn_redeem_parent)
    lp1_direct_invest_arr.append(LPQuote(True).get_amount_from_lp(lp1, itkn1, lp1_invest_track))
    lp1_tree_invest_arr.append(tkn_redeem_tree)

    # DEX Fees
    fee_lp_arr.append(TreeAmountQuote().get_tot_y(lp, lp.get_fee(tkn1), lp.get_fee(dai1)))
    fee_lp1_arr.append(TreeAmountQuote().get_tot_y(lp1, lp1.get_fee(tkn2), lp1.get_fee(itkn1)))

lp.summary()
lp1.summary()

# Redeem from parent
tkn_redeem_parent = LPQuote(False).get_amount_from_lp(lp, tkn1, lp_invest_track)

# Redeem from tree (child + parent)
itkn_redeem_child = LPQuote(False).get_amount_from_lp(lp1, itkn1, lp1_invest_track)
tkn_redeem_tree = LPQuote(False).get_amount_from_lp(lp, tkn1, itkn_redeem_child)

print(f'{tkn_redeem_parent:.3f} USDC redeemed from {lp_invest_track:.3f} LP tokens if {tkn_invest:.1f} invested USDC pulled from parent (lp)')
print(f'{tkn_redeem_tree:.3f} USDC redeemed from {lp1_invest_track:.3f} LP1 tokens if {tkn_invest:.1f} invested USDC pulled from tree (lp + lp1)')
Exchange USDC-USDT (LP)
Reserves: USDC = 171393.9925275301, USDT = 171267.56261415375
Liquidity: 163210.91402568494

Exchange USDC-iUSDC (LP1)
Reserves: USDC = 28546.788191959484, iUSDC = 14217.580396607946
Liquidity: 16978.544568119345

104.648 USDC redeemed from 49.908 LP tokens if 100.0 invested USDC pulled from parent (lp)
122.904 USDC redeemed from 35.088 LP1 tokens if 100.0 invested USDC pulled from tree (lp + lp1)
[8]:
lp1_state.check_states()
lp1_state.inspect_states(tail = True, num_states = 5)
Amount of tokens retained across states: PASS
[8]:
Mint Held Vault Burned dHeld dVault dBurned
1996 122.576565 5943.910541 15466.301221 79645.820358 1070.362693 -1153.353402 90.676558
1997 26.135150 4332.885622 17110.617010 79735.106052 -1611.024919 1644.315789 89.285695
1998 7.404537 5553.044202 15914.494886 79737.204747 1220.158580 -1196.122124 2.098695
1999 9.147774 5837.010759 15575.717343 79799.420270 283.966557 -338.777543 62.215523
2000 63.571901 5424.658124 15974.391541 79822.246482 -412.352635 398.674198 22.826212
[9]:
fig, (TKN_ax, DAI_ax) = plt.subplots(nrows=2, sharex=False, sharey=False, figsize=(15, 8))

strt_pt = 5

TKN_ax.plot(dates[strt_pt:], p_arr[strt_pt:], color = 'g',linestyle = 'dashed', linewidth=1, label=f'{tkn_nm} Price (Market)')
TKN_ax.plot(dates[strt_pt:], pTKN_DAI_arr[strt_pt:], color = 'b',linestyle = '-', linewidth=0.7, label=f'{tkn_nm}/{usd_nm} (LP)')

TKN_ax.set_title('Price comparison: parent vs child LPs', fontsize=20)
TKN_ax.set_ylabel('Price (USD)', size=20)
TKN_ax.legend(fontsize=12)
TKN_ax.grid()

DAI_ax.plot(dates[strt_pt:], pTKN_iTKN_arr[strt_pt:], color = 'b',linestyle = 'dashed', label=f'{tkn_nm}/{itkn_nm} (LP1)')
DAI_ax.set_ylabel('prices', size=20)
DAI_ax.set_ylabel('Price (USD)', size=20)
DAI_ax.legend(fontsize=12)
DAI_ax.grid()
../_images/tutorials_simple_tree_pt2_15_0.png
[10]:
y1_samp = stats.gamma.rvs(a=2000, scale=0.0005, size=10000)

fig, ax = plt.subplots(1, 2, figsize=(12,5))

sns.distplot(pTKN_DAI_arr, hist=True, kde=True, bins=int(30), color = 'darkblue',
             hist_kws={'edgecolor':'black'}, kde_kws={'linewidth': 2}, ax=ax[0])

sns.distplot(pTKN_iTKN_arr, hist=True, kde=True, bins=int(30), color = 'darkblue',
             hist_kws={'edgecolor':'black'}, kde_kws={'linewidth': 2}, ax=ax[1])

ax[0].set_title(f'Distribution: {tkn_nm}/{usd_nm} LP price (parent)')
ax[0].set_xlabel('Price')
ax[0].set_ylabel('Frequency')

ax[1].set_title(f'Distribution: {tkn_nm}/{itkn_nm} LP1 price (child)')
ax[1].set_xlabel('Price')
ax[1].set_ylabel('Frequency')
[10]:
Text(0, 0.5, 'Frequency')
../_images/tutorials_simple_tree_pt2_16_1.png
[11]:
lowess = sm.nonparametric.lowess
x = range(0,n_sim_runs)
res = lowess(lp_direct_invest_arr, x, frac=1/15); sm_lp_direct = res[:,1]
res = lowess(lp1_direct_invest_arr, x, frac=1/15); sm_lp1_direct = res[:,1]
res = lowess(lp1_tree_invest_arr, x, frac=1/15); sm_lp1_tree= res[:,1]

strt_ind = 3

fig, (p_ax) = plt.subplots(nrows=1, sharex=True, sharey=False, figsize=(15, 8))
fig.suptitle('Simple Tree (USDC / USDT) performance ', fontsize=20)
p_ax.plot(dates[strt_ind:], lp_direct_invest_arr[strt_ind:], linestyle='dashed', linewidth=0.5, color = 'g')
p_ax.plot(dates[strt_ind:], sm_lp_direct[strt_ind:], color = 'g', label = 'Expected return from parent (LP)')
p_ax.plot(dates[strt_ind:], lp1_direct_invest_arr[strt_ind:], linestyle='dashed', linewidth=0.5, color = 'b')
p_ax.plot(dates[strt_ind:], sm_lp1_direct[strt_ind:], color = 'b', label = 'Expected return from child (LP1)')
p_ax.plot(dates[strt_ind:], lp1_tree_invest_arr[strt_ind:], linestyle='dashed', linewidth=0.5, color = 'r')
p_ax.plot(dates[strt_ind:], sm_lp1_tree[strt_ind:], color = 'r', label = 'Expected return from tree (LP+LP1)')
p_ax.legend( fontsize=12)
p_ax.set_ylabel("$100 USD Investment", fontsize=14)
[11]:
Text(0, 0.5, '$100 USD Investment')
../_images/tutorials_simple_tree_pt2_17_1.png
[12]:
print(f'{tkn_invest:.3f} TKN before is worth {sm_lp_direct[-1]:.3f} TKN after direct investment into parent (lp)')
print(f'{tkn_invest:.3f} TKN before is worth {sm_lp1_direct[-1]:.3f} TKN after direct investment into child (lp1)')
print(f'{tkn_invest:.3f} TKN before is worth {sm_lp1_tree[-1]:.3f} TKN after investment into simple tree (lp + lp1)')
100.000 TKN before is worth 104.430 TKN after direct investment into parent (lp)
100.000 TKN before is worth 117.719 TKN after direct investment into child (lp1)
100.000 TKN before is worth 122.696 TKN after investment into simple tree (lp + lp1)
[13]:
t = np.arange(0,len(fee_lp_arr))

fee_lpB = np.array(fee_lp1_arr)
fee_lpA = fee_lpB+np.array(fee_lp_arr)

fig = plt.figure(figsize=(15, 5))

plt.plot(dates, fee_lpA, color = 'r', label = f'Parent LP ({tkn_nm}/{usd_nm})')
plt.fill_between(dates, fee_lpB, fee_lpA, alpha=0.3, color='r')

plt.plot(dates, fee_lpB, color = 'b', label = f'Child LP1 ({tkn_nm}/{itkn_nm})')
plt.fill_between(dates, np.repeat(0,len(fee_lp_arr)), fee_lpB, alpha=0.3, color='b')

plt.title('Cumulative Arbitrage Fees (Direct Investment, Simple Tree, Uni V2)', fontsize = 20)
plt.xlabel("Time unit", fontsize=12)
plt.ylabel("Collected Fees (USD)", fontsize=14)

plt.legend(fontsize=12)
[13]:
<matplotlib.legend.Legend at 0x2866aea10>
../_images/tutorials_simple_tree_pt2_19_1.png