Skip to content

Commit 2e4db0a

Browse files
committed
Added linear approximation for XYK pools
1 parent 2c02a0e commit 2e4db0a

3 files changed

Lines changed: 75 additions & 70 deletions

File tree

hydradx/model/solver/amm_constraints.py

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -179,34 +179,35 @@ def __init__(self, amm: ConstantProductPoolState):
179179
self.liquidity = {tkn: amm.liquidity[tkn] for tkn in amm.asset_list}
180180

181181
def get_amm_bounds(self, approx: str, scaling: dict) -> tuple:
182-
# TODO implement linear, quadratic approximations
183182
amm_i = self.amm_i
184183
coef = [scaling[self.tkn_share] / self.shares] + [scaling[tkn] / self.liquidity[tkn] for tkn in self.asset_list]
185-
# if approx == "linear": # linearize the AMM constraint
186-
# c1 = 1 / (1 + epsilon_tkn[tkn])
187-
# c2 = 1 / (1 - epsilon_tkn[tkn]) if epsilon_tkn[tkn] < 1 else 1e15
188-
# A5j2 = np.zeros((2, k))
189-
# b5j2 = np.zeros(2)
190-
# A5j2[0, amm_i.asset_net[0]] = -B[1]
191-
# A5j2[0, amm_i.asset_net[1]] = -B[2] * c1
192-
# A5j2[1, amm_i.asset_net[0]] = -B[1]
193-
# A5j2[1, amm_i.asset_net[1]] = -B[2] * c2
194-
# cones5j.append(cb.NonnegativeConeT(2))
195-
# cones_count5j.append(2)
196-
# else: # full constraint
197-
# A5j2 = np.zeros((3, k))
198-
# b5j2 = np.ones(3)
199-
# A5j2[0, amm_i.asset_net[0]] = -B[1]
200-
# A5j2[1, amm_i.asset_net[1]] = -B[2]
201-
# cones5j.append(cb.PowerConeT(0.5))
202-
# cones_count5j.append(3)
203-
A = np.zeros((3, self.k))
204-
b = np.ones(3)
205-
A[0, amm_i.asset_net[0]] = -coef[1]
206-
A[1, amm_i.asset_net[1]] = -coef[2]
207-
A[2, amm_i.shares_net] = -coef[0]
208-
cones = [cb.PowerConeT(0.5)]
209-
cone_sizes = [3]
184+
epsilon = 1e-6 # largest delta/balance for which we still use linear approximation
185+
if approx == "linear": # linearize the AMM constraint
186+
A = np.zeros((6, self.k))
187+
b = np.zeros((6))
188+
cones = [cb.NonnegativeConeT(6)]
189+
cone_sizes = [6]
190+
share_cs = [coef[0] * (2 - coef[0] * epsilon), coef[0] * (2 + coef[0] * epsilon)]
191+
asset_cs = [
192+
[-coef[1], -coef[2]],
193+
[-coef[1], -coef[2] * (1 + epsilon * coef[1])],
194+
[-coef[1] * (1 + epsilon * coef[2]), -coef[2]]
195+
]
196+
row = 0
197+
for share_c in share_cs:
198+
for asset_c in asset_cs:
199+
A[row, amm_i.shares_net] = share_c
200+
A[row, amm_i.asset_net[0]] = asset_c[0]
201+
A[row, amm_i.asset_net[1]] = asset_c[1]
202+
row += 1
203+
else: # full constraint
204+
A = np.zeros((3, self.k))
205+
b = np.ones(3)
206+
A[0, amm_i.asset_net[0]] = -coef[1]
207+
A[1, amm_i.asset_net[1]] = -coef[2]
208+
A[2, amm_i.shares_net] = -coef[0]
209+
cones = [cb.PowerConeT(0.5)]
210+
cone_sizes = [3]
210211

211212
return A, b, cones, cone_sizes
212213

@@ -253,7 +254,7 @@ def get_linearized_amm_constraints(self, x_list, global_asset_list: list, scalin
253254
def get_approx(self, deltas: list) -> str:
254255
pcts = [abs(deltas[0]) / self.shares] # first shares size constraint, delta_s / s_0 <= epsilon
255256
pcts.extend([abs(deltas[j + 1]) / self.liquidity[tkn] for j, tkn in enumerate(self.asset_list)])
256-
approx = "linear" if max(pcts) <= 1e-5 else "full"
257+
approx = "linear" if max(pcts) <= 1e-6 else "full"
257258
return approx
258259

259260
def upgrade_approx(self, deltas: list, current_approx: str) -> str:

hydradx/tests/test_solver/test_amm_constraints.py

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -199,40 +199,41 @@ def test_get_xyk_bounds():
199199
scaling = {tkn: 1 for tkn in (amm.asset_list + [amm.unique_id])}
200200
amm_i = constraints.amm_i
201201

202-
A, b, cones, cones_sizes = constraints.get_amm_bounds("None", scaling)
203-
x = np.zeros(constraints.k)
204-
# selling 5 B for 1 A should work
205-
b_sell_amt, a_buy_amt = 5, 1
206-
x[amm_i.asset_net[0]] = -a_buy_amt
207-
x[amm_i.asset_net[1]] = b_sell_amt
208-
x[amm_i.asset_out[0]] = a_buy_amt
209-
s = b - A @ x
210-
if not check_all_cone_feasibility(s, cones, cones_sizes, tol=0):
211-
raise AssertionError("Cone feasibility check failed for valid XYK bounds")
212-
# selling 1 A for 1 5 should not work
213-
a_sell_amt, b_buy_amt = 1, 5
214-
x[amm_i.asset_net[1]] = -b_buy_amt
215-
x[amm_i.asset_net[0]] = a_sell_amt
216-
x[amm_i.asset_out[1]] = b_buy_amt
217-
s = b - A @ x
218-
if check_all_cone_feasibility(s, cones, cones_sizes, tol=0):
219-
raise AssertionError("Cone feasibility check should fail")
220-
# selling 1 A for 1 B should work
221-
a_sell_amt, b_buy_amt = 1, 1
222-
x[amm_i.asset_net[1]] = -b_buy_amt
223-
x[amm_i.asset_net[0]] = a_sell_amt
224-
x[amm_i.asset_out[1]] = b_buy_amt
225-
s = b - A @ x
226-
if not check_all_cone_feasibility(s, cones, cones_sizes, tol=0):
227-
raise AssertionError("Cone feasibility check should succeed")
228-
# selling 1 B for 1 A should not work
229-
b_sell_amt, a_buy_amt = 1, 1
230-
x[amm_i.asset_net[0]] = -a_buy_amt
231-
x[amm_i.asset_net[1]] = b_sell_amt
232-
x[amm_i.asset_out[0]] = a_buy_amt
233-
s = b - A @ x
234-
if check_all_cone_feasibility(s, cones, cones_sizes, tol=0):
235-
raise AssertionError("Cone feasibility check should fail")
202+
for approx in ["none", "linear"]: # mult = 10 should test full approximation, 0.1 should test linear approximation
203+
A, b, cones, cones_sizes = constraints.get_amm_bounds(approx, scaling)
204+
x = np.zeros(constraints.k)
205+
# selling 5 B for 1 A should work
206+
b_sell_amt, a_buy_amt = 5, 1
207+
x[amm_i.asset_net[0]] = -a_buy_amt
208+
x[amm_i.asset_net[1]] = b_sell_amt
209+
x[amm_i.asset_out[0]] = a_buy_amt
210+
s = b - A @ x
211+
if not check_all_cone_feasibility(s, cones, cones_sizes, tol=0):
212+
raise AssertionError("Cone feasibility check failed for valid XYK bounds")
213+
# selling 1 A for 1 5 should not work
214+
a_sell_amt, b_buy_amt = 1, 5
215+
x[amm_i.asset_net[1]] = -b_buy_amt
216+
x[amm_i.asset_net[0]] = a_sell_amt
217+
x[amm_i.asset_out[1]] = b_buy_amt
218+
s = b - A @ x
219+
if check_all_cone_feasibility(s, cones, cones_sizes, tol=0):
220+
raise AssertionError("Cone feasibility check should fail")
221+
# selling 1 A for 1 B should work
222+
a_sell_amt, b_buy_amt = 1, 1
223+
x[amm_i.asset_net[1]] = -b_buy_amt
224+
x[amm_i.asset_net[0]] = a_sell_amt
225+
x[amm_i.asset_out[1]] = b_buy_amt
226+
s = b - A @ x
227+
if not check_all_cone_feasibility(s, cones, cones_sizes, tol=0):
228+
raise AssertionError("Cone feasibility check should succeed")
229+
# selling 1 B for 1 A should not work
230+
b_sell_amt, a_buy_amt = 1, 1
231+
x[amm_i.asset_net[0]] = -a_buy_amt
232+
x[amm_i.asset_net[1]] = b_sell_amt
233+
x[amm_i.asset_out[0]] = a_buy_amt
234+
s = b - A @ x
235+
if check_all_cone_feasibility(s, cones, cones_sizes, tol=0):
236+
raise AssertionError("Cone feasibility check should fail")
236237

237238

238239
def test_xyk_upgrade_approx():
@@ -273,11 +274,11 @@ def test_stableswap_upgrade_approx():
273274
# current_approx is entirely linear
274275
current_approx = ["linear", "linear", "linear"]
275276
examples = [ # [delta_mults, expected_approx]
276-
[[1e-4, 1e-6, 1e-6], ["full", "linear", "linear"]],
277-
[[1e-6, 1e-4, 1e-6], ["full", "full", "linear"]],
278-
[[1e-6, 1e-6, 1e-4], ["full", "linear", "full"]],
277+
[[1e-4, 1e-7, 1e-7], ["full", "linear", "linear"]],
278+
[[1e-7, 1e-4, 1e-7], ["full", "full", "linear"]],
279+
[[1e-7, 1e-6, 1e-4], ["full", "linear", "full"]],
279280
[[1e-4, 1e-4, 1e-4], ["full", "full", "full"]],
280-
[[1e-6, 1e-6, 1e-6], ["linear", "linear", "linear"]]
281+
[[1e-7, 1e-7, 1e-7], ["linear", "linear", "linear"]]
281282
]
282283
for delta_mults, expected_approx in examples:
283284
deltas = [amm.shares * delta_mults[0], amm.liquidity["A"] * delta_mults[1], amm.liquidity["B"] * delta_mults[2]]
@@ -289,11 +290,11 @@ def test_stableswap_upgrade_approx():
289290
# current_approx is mixed
290291
current_approx = ["full", "linear", "full"]
291292
examples = [ # [delta_mults, expected_approx]
292-
[[1e-4, 1e-6, 1e-6], ["full", "linear", "full"]],
293-
[[1e-6, 1e-4, 1e-6], ["full", "full", "full"]],
294-
[[1e-6, 1e-6, 1e-4], ["full", "linear", "full"]],
293+
[[1e-4, 1e-7, 1e-7], ["full", "linear", "full"]],
294+
[[1e-7, 1e-4, 1e-7], ["full", "full", "full"]],
295+
[[1e-7, 1e-7, 1e-4], ["full", "linear", "full"]],
295296
[[1e-4, 1e-4, 1e-4], ["full", "full", "full"]],
296-
[[1e-6, 1e-6, 1e-6], ["full", "linear", "full"]]
297+
[[1e-7, 1e-7, 1e-7], ["full", "linear", "full"]]
297298
]
298299
for delta_mults, expected_approx in examples:
299300
deltas = [amm.shares * delta_mults[0], amm.liquidity["A"] * delta_mults[1], amm.liquidity["B"] * delta_mults[2]]

hydradx/tests/test_solver/test_solver.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
from hydradx.model.amm.stableswap_amm import StableSwapPoolState
1717
from hydradx.model.amm.xyk_amm import ConstantProductPoolState
1818

19+
settings.register_profile("ci", deadline=None, print_blob=True)
20+
settings.load_profile("ci")
21+
1922

2023
##################################
2124
# Functional tests #
@@ -855,7 +858,7 @@ def test_matching_trades_execute_more_full_execution():
855858
###############
856859
# Other tests #
857860
###############
858-
861+
@reproduce_failure('6.127.0', b'ACg/dZBsfh29sA==')
859862
@given(st.floats(min_value=1e-7, max_value=0.01))
860863
@settings(verbosity=Verbosity.verbose, print_blob=True)
861864
def test_fuzz_single_trade_settles(size_factor: float):

0 commit comments

Comments
 (0)