Skip to content

Instantly share code, notes, and snippets.

@Philogy
Created May 20, 2024 13:23
Show Gist options
  • Save Philogy/9aece76cf777ddb0f7a5e39b339f718b to your computer and use it in GitHub Desktop.
Save Philogy/9aece76cf777ddb0f7a5e39b339f718b to your computer and use it in GitHub Desktop.
Uniswap V3 Model
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()
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