forked from chrisckwong821/Curve_Finance_Slippage_Simulator
-
Notifications
You must be signed in to change notification settings - Fork 1
/
simulation.py
160 lines (144 loc) · 4.78 KB
/
simulation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
class Curve:
"""
Python model of Curve pool math.
"""
def __init__(self, A, D, n, fee=None, p=None, tokens=None):
"""
A: Amplification coefficient
D: Total deposit size
n: number of currencies
p: target prices
"""
self.A = A # actually A * n ** (n - 1) because it's an invariant
self.n = n
# 4000000 = 0.04% implemented in prod
if fee == None:
self.fee = 4000000
else:
self.fee = fee
if p:
self.p = p
else:
self.p = [10 ** 18] * n
if isinstance(D, list):
self.x = D
else:
self.x = [D // n * 10 ** 18 // _p for _p in self.p]
self.tokens = tokens
def xp(self):
return [x * p // 10 ** 18 for x, p in zip(self.x, self.p)]
def D(self):
"""
D invariant calculation in non-overflowing integer operations
iteratively
A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
Converging solution:
D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1)
"""
Dprev = 0
xp = self.xp()
S = sum(xp)
D = S
Ann = self.A * self.n
while abs(D - Dprev) > 1:
D_P = D
for x in xp:
D_P = D_P * D // (self.n * x)
Dprev = D
D = (Ann * S + D_P * self.n) * D // ((Ann - 1) * D + (self.n + 1) * D_P)
return D
def y(self, i, j, x):
"""
Calculate x[j] if one makes x[i] = x
Done by solving quadratic equation iteratively.
x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n+1)/(n ** (2 * n) * prod' * A)
x_1**2 + b*x_1 = c
x_1 = (x_1**2 + c) / (2*x_1 + b)
"""
D = self.D()
xx = self.xp()
xx[i] = x # x is quantity of underlying asset brought to 1e18 precision
xx = [xx[k] for k in range(self.n) if k != j]
Ann = self.A * self.n
c = D
for y in xx:
c = c * D // (y * self.n)
c = c * D // (self.n * Ann)
b = sum(xx) + D // Ann - D
y_prev = 0
y = D
while abs(y - y_prev) > 1:
y_prev = y
y = (y ** 2 + c) // (2 * y + b)
return y # the result is in underlying units too
def y_D(self, i, _D):
"""
Calculate x[j] if one makes x[i] = x
Done by solving quadratic equation iteratively.
x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n+1)/(n ** (2 * n) * prod' * A)
x_1**2 + b*x_1 = c
x_1 = (x_1**2 + c) / (2*x_1 + b)
"""
xx = self.xp()
xx = [xx[k] for k in range(self.n) if k != i]
S = sum(xx)
Ann = self.A * self.n
c = _D
for y in xx:
c = c * _D // (y * self.n)
c = c * _D // (self.n * Ann)
b = S + _D // Ann
y_prev = 0
y = _D
while abs(y - y_prev) > 1:
y_prev = y
y = (y ** 2 + c) // (2 * y + b - _D)
return y # the result is in underlying units too
# find swapped amount without acconting fee
def dy(self, i, j, dx):
# dx and dy are in underlying units
xp = self.xp()
return xp[j] - self.y(i, j, xp[i] + dx)
# dy - fee
def exchange(self, i, j, dx):
xp = self.xp()
x = xp[i] + dx
y = self.y(i, j, x)
dy = xp[j] - y
fee = dy * self.fee // 10 ** 10
assert dy > 0
self.x[i] = x * 10 ** 18 // self.p[i]
self.x[j] = (y + fee) * 10 ** 18 // self.p[j]
return dy - fee
# return the lp tokens needed to withdraw
def remove_liquidity_imbalance(self, amounts):
_fee = self.fee * self.n // (4 * (self.n - 1))
old_balances = self.x
new_balances = self.x[:]
D0 = self.D()
for i in range(self.n):
new_balances[i] -= amounts[i]
self.x = new_balances
D1 = self.D()
self.x = old_balances
fees = [0] * self.n
for i in range(self.n):
ideal_balance = D1 * old_balances[i] // D0
difference = abs(ideal_balance - new_balances[i])
fees[i] = _fee * difference // 10 ** 10
new_balances[i] -= fees[i]
self.x = new_balances
D2 = self.D()
self.x = old_balances
token_amount = (D0 - D2) * self.tokens // D0
return token_amount
def calc_withdraw_one_coin(self, token_amount, i):
xp = self.xp()
if self.fee:
fee = self.fee - self.fee * xp[i] // sum(xp) + 5 * 10 ** 5
else:
fee = 0
D0 = self.D()
D1 = D0 - token_amount * D0 // self.tokens
dy = xp[i] - self.y_D(i, D1)
return dy - dy * fee // 10 ** 10