Created
May 20, 2024 13:23
-
-
Save Philogy/9aece76cf777ddb0f7a5e39b339f718b to your computer and use it in GitHub Desktop.
Uniswap V3 Model
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from math import sqrt | |
def dy(c: float, l: float, u: float) -> float: | |
if c < l: | |
return 0 | |
if l <= c < u: | |
return sqrt(c) - sqrt(l) | |
return sqrt(u) - sqrt(l) | |
def dx(c: float, l: float, u: float) -> float: | |
if c < l: | |
return 1 / sqrt(l) - 1 / sqrt(u) | |
if l <= c < u: | |
return 1 / sqrt(c) - 1 / sqrt(u) | |
return 0 | |
def get_amounts(d_liq: float, c: float, l: float, u: float) -> tuple[float, float]: | |
y = d_liq * dy(c, l, u) | |
x = d_liq * dx(c, l, u) | |
return x, y | |
def main(): | |
real_price = 4000 | |
l = 3000 | |
u = 5000 | |
x, y = get_amounts(500.0, real_price, l, u) | |
print(f'x: {x}') | |
print(f'y: {y}') | |
print(f'value:', y + x * real_price) | |
x, y = get_amounts(500.0, l, l, u) | |
print(f'\nDOWN\nx: {x}') | |
print(f'y: {y}') | |
print(f'value:', y + x * real_price) | |
x, y = get_amounts(500.0, u, l, u) | |
print(f'\nUP\nx: {x}') | |
print(f'y: {y}') | |
print(f'value:', y + x * real_price) | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from attrs import define | |
@define | |
class Tick: | |
fee_growth_outside: float | |
net_liquidity: float | |
def cross(self, fee_growth_global: float): | |
self.fee_growth_outside = fee_growth_global - self.fee_growth_outside | |
@define | |
class Position: | |
start_tick: int | |
end_tick: int | |
liquidity: float | |
last_fee_growth_inside: float | |
class Pool: | |
current_tick: int | |
fee_growth_global: float | |
liquidity: float | |
ticks: dict[int, Tick] | |
positions: list[Position] | |
def __init__(self, current_tick: int) -> None: | |
self.current_tick = current_tick | |
self.fee_growth_global = 0 | |
self.liquidity = 0 | |
self.ticks = {} | |
self.positions = [] | |
def add_liquidity(self, start_tick: int, end_tick: int, liquidity: float): | |
assert start_tick < end_tick | |
self.get_or_init(start_tick).net_liquidity += liquidity | |
self.get_or_init(end_tick).net_liquidity -= liquidity | |
position = Position( | |
start_tick, | |
end_tick, | |
liquidity, | |
self.get_fee_growth_inside(start_tick, end_tick) | |
) | |
self.positions.append(position) | |
if start_tick <= self.current_tick and self.current_tick < end_tick: | |
self.liquidity += liquidity | |
def get_or_init(self, tick: int) -> Tick: | |
if (info := self.ticks.get(tick)) is not None: | |
return info | |
if self.current_tick >= tick: | |
fee_growth_outside = self.fee_growth_global | |
else: | |
fee_growth_outside = 0 | |
new_tick = Tick(fee_growth_outside, 0) | |
self.ticks[tick] = new_tick | |
return new_tick | |
def get_fee_growth_inside(self, lower: int, upper: int) -> float: | |
if self.current_tick < lower: | |
return self.ticks[lower].fee_growth_outside - self.ticks[upper].fee_growth_outside | |
if self.current_tick >= upper: | |
return self.ticks[upper].fee_growth_outside - self.ticks[lower].fee_growth_outside | |
return ( | |
self.fee_growth_global | |
- self.ticks[lower].fee_growth_outside | |
- self.ticks[upper].fee_growth_outside | |
) | |
def up(self): | |
self.current_tick += 1 | |
tick = self.ticks.get(self.current_tick) | |
if tick is None: | |
return | |
tick.cross(self.fee_growth_global) | |
self.liquidity += tick.net_liquidity | |
assert self.liquidity >= 0 | |
def down(self): | |
tick = self.ticks.get(self.current_tick) | |
self.current_tick -= 1 | |
if tick is None: | |
return | |
tick.cross(self.fee_growth_global) | |
self.liquidity -= tick.net_liquidity | |
assert self.liquidity >= 0 | |
def collect(self, fees: float): | |
assert self.liquidity > 0 | |
self.fee_growth_global += fees / self.liquidity | |
def get_earnings(self, i: int) -> float: | |
position = self.positions[i] | |
return ( | |
self.get_fee_growth_inside(position.start_tick, position.end_tick) | |
- position.last_fee_growth_inside | |
) * position.liquidity | |
def disp_positions(self): | |
print() | |
for i, pos in enumerate(self.positions): | |
print( | |
f'{i}: [{pos.liquidity: .2f}]({pos.start_tick: 3}, {pos.end_tick: 3}): {self.get_earnings(i):8.4f}' | |
) | |
def goto_tick(self, tick: int): | |
while self.current_tick > tick: | |
self.down() | |
while self.current_tick < tick: | |
self.up() | |
def multi_donate(self, donations: list[tuple[int, float]]): | |
start_tick = self.current_tick | |
for tick, amount in donations: | |
self.goto_tick(tick) | |
self.collect(amount) | |
self.goto_tick(start_tick) | |
def disp_ticks(self, start: int, end: int): | |
print() | |
for tick in range(start, end + 1): | |
info = self.ticks.get(tick) | |
if info is None: | |
print(f'{tick:3}: -') | |
continue | |
ind = ' <' if tick == self.current_tick else '' | |
print( | |
f'{tick:3}: {info.net_liquidity:7.2f} [{info.fee_growth_outside:8.2f}]{ind}' | |
) | |
def disp(self, start: int, end: int): | |
print(f'=' * 50) | |
print(f'global fee growth: {self.fee_growth_global:.2f}') | |
print(f'\nticks (L = {self.liquidity:.2f}):') | |
self.disp_ticks(start, end) | |
print(f'\npositions:') | |
self.disp_positions() | |
print( | |
f'total: {sum(self.get_earnings(i) for i, _ in enumerate(self.positions)):.2f}') | |
print(f'=' * 50) | |
def main(): | |
pool = Pool(0) | |
pool.add_liquidity(-3, +3, 1.0) | |
pool.add_liquidity(-2, +2, 0.0) | |
pool.add_liquidity(-1, +1, 0.0) | |
pool.add_liquidity(0, +1, 0.0) | |
pool.disp(-3, 3) | |
pool.multi_donate([ | |
(-2, 0.1), | |
(-1, 0.1), | |
(1, 0.1), | |
(2, 0.1), | |
]) | |
pool.disp(-3, 3) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment