Impermanent Loss

[1]:
from uniswappy import *
import numpy as np
import datetime
import matplotlib.pyplot as plt

GBM

[2]:
# Instantiation Parameters
n_steps = 500     # Number of steps
start_price = 10 # Initial price SYS/USD
mu = 0.1; sigma = 0.5
n_paths = 1      # Number of simulationed paths
seconds_year = 31536000

# Brownian Model
bm = BrownianModel(start_price)
p_arr = bm.gen_gbms(mu, sigma, n_steps-1, n_paths).flatten()

dt = datetime.timedelta(seconds=seconds_year/n_steps)
dates = [datetime.datetime.strptime("2024-09-01", '%Y-%m-%d') + k*dt for k in range(n_steps)]
[8]:
user_nm = 'user0'
tkn1_amount = 1000
tkn2_amount = p_arr[0]*tkn1_amount

fee = UniV3Utils.FeeAmount.MEDIUM
tick_spacing = UniV3Utils.TICK_SPACINGS[fee]
lwr_tick = UniV3Utils.getMinTick(tick_spacing)
upr_tick = UniV3Utils.getMaxTick(tick_spacing)
[4]:
tkn1_nm = 'TKN1'
tkn2_nm = 'TKN2'

tkn1 = ERC20(tkn1_nm, "0x09")
tkn2 = ERC20(tkn2_nm, "0x111")

exchg_data = UniswapExchangeData(tkn0 = tkn1, tkn1 = tkn2, symbol="LP",
                                   address="0x011", version = 'V3',
                                   tick_spacing = tick_spacing,
                                   fee = fee)

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)

Join().apply(lp, user_nm, tkn1_amount, tkn2_amount, lwr_tick, upr_tick)
lp.summary()
Exchange TKN1-TKN2 (LP)
Real Reserves:   TKN1 = 999.9999999999999, TKN2 = 10000.0
Gross Liquidity: 3162.277660168379

[5]:
tick_size = 10000
lwr_tick = UniV3Helper().get_price_tick(lp, -1, lp.get_price(tkn1), tick_size)
upr_tick = UniV3Helper().get_price_tick(lp, 1, lp.get_price(tkn1), tick_size)
out = AddLiquidity().apply(lp, tkn1, user_nm, 10, lwr_tick, upr_tick)
lp.summary()

dL = lp.last_liquidity_deposit
iLoss = UniswapImpLoss(lp, dL, lwr_tick, upr_tick)
Exchange TKN1-TKN2 (LP)
Real Reserves:   TKN1 = 1009.9999999999999, TKN2 = 10100.262769858964
Gross Liquidity: 3242.81759104987

[6]:
tkn1_init_amt = iLoss.get_init_amt(tkn1)
tkn2_init_amt = iLoss.get_init_amt(tkn2)
tkn1_hold_amt = iLoss.hold_value(tkn1)
tkn2_hold_amt = iLoss.hold_value(tkn2)

print(f'Initial {tkn1.token_name} amount: {tkn1_init_amt:.2f}, Initial {tkn2.token_name} amount: {tkn2_init_amt:.2f}')
print(f'Hold {tkn1.token_name} amount: {tkn1_hold_amt:.2f}, Hold {tkn2.token_name} amount: {tkn2_hold_amt:.2f}')
Initial TKN1 amount: 10.00, Initial TKN2 amount: 100.26
Hold TKN1 amount: 20.03, Hold TKN2 amount: 200.26

Simulate AMM

[7]:
tick_size = 100000
arb = CorrectReserves(lp, x0 = p_arr[0])
TKN_amt = TokenDeltaModel(100)
pTKN1_TKN2_arr = [];

for k in range(n_steps):

    # *****************************
    # ***** Random Swapping ******
    # *****************************
    Swap().apply(lp, tkn1, user_nm, TKN_amt.delta())
    Swap().apply(lp, tkn2, user_nm, p_arr[k]*TKN_amt.delta())

    # *****************************
    # ***** Rebalance ******
    # *****************************

    lwr_tick = UniV3Helper().get_price_tick(lp, -1, lp.get_price(tkn1), tick_size)
    upr_tick = UniV3Helper().get_price_tick(lp, 1, lp.get_price(tkn1), tick_size)
    arb.apply(p_arr[k], lwr_tick, upr_tick)

    # *****************************
    # ******* Data Capture ********
    # *****************************
    pTKN1_TKN2_arr.append(LPQuote().get_price(lp, tkn1))

lp.summary()
Exchange TKN1-TKN2 (LP)
Real Reserves:   TKN1 = 1146.3148635861073, TKN2 = 13671.93074026191
Gross Liquidity: 3865.0031366001244

Calculate Imp. Loss

[33]:
tkn1_hold_amt = iLoss.hold_value(tkn1)
tkn2_hold_amt = iLoss.hold_value(tkn2)
tkn1_pos_amt = iLoss.current_position_value(tkn1, lwr_tick, upr_tick)
tkn2_pos_amt = iLoss.current_position_value(tkn2, lwr_tick, upr_tick)

print(f'{tkn1.token_name} hold amt: {tkn1_hold_amt:.2f}, {tkn2.token_name} hold amt: {tkn2_hold_amt:.2f}')
print(f'{tkn1.token_name} position amt: {tkn1_pos_amt:.2f}, {tkn2.token_name} position amt: {tkn2_pos_amt:.2f}')
print(f'Start price: {p_arr[0]:.2f}, End price: {p_arr[-1]:.2f}')
TKN1 hold amt: 25.06, TKN2 hold amt: 166.85
TKN1 position amt: 61.26, TKN2 position amt: 407.92
Start price: 10.00, End price: 6.66
[34]:
iloss_calc = iLoss.apply(lwr_tick, upr_tick, fees = False)
returns_calc = iLoss.apply(lwr_tick, upr_tick, fees = True)

print(f'Imp Loss: {100*iloss_calc:.2f}%, returns: {100*returns_calc:.2f} %')
Imp Loss: -2.08%, returns: 144.49 %

Plot

[35]:
n_steps = 1000
alphas = np.linspace(0, 5, num=n_steps)
alpha_deltas = alphas-1

imp_losses = []
r_arr = np.exp(np.linspace(0.5, 4, num=5))
for r in r_arr:
    imp_loss = np.array([iLoss.calc_iloss(alpha, r) for alpha in alphas])
    imp_losses.append(imp_loss)

m0 = 20; m1 = 500
fig, (lp_ax) = plt.subplots(nrows=1, sharex=True, sharey=False, figsize=(12, 5))
fig.suptitle(f'Impermanent Loss: Uni V3', fontsize=20)

for k, imp_loss in enumerate(imp_losses):
    #lp_ax.plot(100*alphas[m0:-m1]-100, 100*imp_loss[m0:-m1]/max(imp_loss[m0:-m1])-1, label = f'Uni V3: r={r_arr[k]:.3f}')
    lp_ax.plot(100*alphas[m0:-m1]-100, 100*imp_loss[m0:-m1], label = f'Uni V3: r={r_arr[k]:.3f}')
    lp_ax.set_xlabel("Price Chg (%)", fontsize=12)
    lp_ax.set_ylabel("Impermanent Loss (%)", fontsize=14)
lp_ax.legend(fontsize=10, facecolor="lightgray", loc='lower right')
[35]:
<matplotlib.legend.Legend at 0x157c13f10>
../../_images/uniswapv3_tutorials_imp_loss_v3_14_1.png
[36]:
fig, (TKN_ax) = plt.subplots(nrows=1, sharex=False, sharey=False, figsize=(15, 8))

TKN_ax.plot(dates, p_arr, color = 'r',linestyle = 'dashed', linewidth=1, label=f'{tkn1_nm} Price (Global Market)')
TKN_ax.scatter(dates, pTKN1_TKN2_arr, s=10, marker='o', color = 'b',linestyle = '-', linewidth=0.7, label=f'{tkn1_nm} Price (LP)')

TKN_ax.set_title('Price comparison: Market vs LP price', fontsize=20)
TKN_ax.set_ylabel('Price (USD)', size=20)
TKN_ax.legend(fontsize=12)
TKN_ax.grid()
../../_images/uniswapv3_tutorials_imp_loss_v3_15_0.png