From 04bac79e863e3b38ea52dcc7c1a937a4fcd0d80b Mon Sep 17 00:00:00 2001 From: poliwop Date: Thu, 24 Oct 2024 10:43:59 -0500 Subject: [PATCH] - Fixed bug in agent.is_holding - Added validation for intents with partial = False flag in validate_intents - Added tests with LRNA to single trade tests - Fixed various solver tests to run with find_solution_outer_approx --- hydradx/model/amm/agents.py | 2 + hydradx/model/amm/omnix.py | 20 +++-- hydradx/tests/test_solver.py | 170 +++++++++++++---------------------- 3 files changed, 78 insertions(+), 114 deletions(-) diff --git a/hydradx/model/amm/agents.py b/hydradx/model/amm/agents.py index edfa4620..66ecd9c6 100644 --- a/hydradx/model/amm/agents.py +++ b/hydradx/model/amm/agents.py @@ -65,6 +65,8 @@ def copy(self): def is_holding(self, tkn, amt=None): if amt is None: return tkn in self.holdings and self.holdings[tkn] > 0 + elif amt == 0: + return True else: return tkn in self.holdings and self.holdings[tkn] >= amt diff --git a/hydradx/model/amm/omnix.py b/hydradx/model/amm/omnix.py index 772ebdcf..a9ecd760 100644 --- a/hydradx/model/amm/omnix.py +++ b/hydradx/model/amm/omnix.py @@ -66,10 +66,15 @@ def validate_intents(intents: list, intent_deltas: list): for i in range(len(intents)): intent = intents[i] sell_amt = -intent_deltas[i][0] + buy_amt = intent_deltas[i][1] if not 0 <= sell_amt <= intent['sell_quantity']: raise Exception("amt processed is not in range") + if intent['partial'] == False and sell_amt not in [0, intent['sell_quantity']]: + raise Exception("intent should not be partially executed") + if intent['partial'] == False and 0 < buy_amt < intent['buy_quantity']: + raise Exception("intent should not be partially executed") tolerance = 1e-6 # temporarily allowing some tolerance, only for testing purposes - if sell_amt > 0 and intent_deltas[i][1] / sell_amt < (1 - tolerance) * intent['buy_quantity'] / intent['sell_quantity']: + if sell_amt > 0 and buy_amt / sell_amt < (1 - tolerance) * intent['buy_quantity'] / intent['sell_quantity']: raise Exception("price is not within tolerance") return True @@ -92,10 +97,13 @@ def execute_solution( # transfer assets in from agents whose intents are being executed for transfer in transfers: - if transfer['tkn_sell'] not in pool_agent.holdings: - pool_agent.holdings[transfer['tkn_sell']] = 0 - pool_agent.holdings[transfer['tkn_sell']] += transfer['sell_quantity'] - transfer['agent'].holdings[transfer['tkn_sell']] -= transfer['sell_quantity'] + if transfer['sell_quantity'] > 0: + if transfer['tkn_sell'] not in pool_agent.holdings: + pool_agent.holdings[transfer['tkn_sell']] = 0 + pool_agent.holdings[transfer['tkn_sell']] += transfer['sell_quantity'] + transfer['agent'].holdings[transfer['tkn_sell']] -= transfer['sell_quantity'] + elif transfer['sell_quantity'] < 0: + raise Exception("sell quantity is negative") # execute swaps against Omnipool lrna_deltas = {tkn: 0 for tkn in deltas} @@ -117,6 +125,8 @@ def execute_solution( if transfer['tkn_buy'] not in transfer['agent'].holdings: transfer['agent'].holdings[transfer['tkn_buy']] = 0 transfer['agent'].holdings[transfer['tkn_buy']] += transfer['buy_quantity'] + elif transfer['buy_quantity'] < 0: + raise Exception("buy quantity is negative") return pool_agent, lrna_deltas diff --git a/hydradx/tests/test_solver.py b/hydradx/tests/test_solver.py index 03b40d6f..95a497b5 100644 --- a/hydradx/tests/test_solver.py +++ b/hydradx/tests/test_solver.py @@ -13,7 +13,7 @@ def test_single_trade_settles(): - agents = [Agent(holdings={'DOT': 100})] + agents = [Agent(holdings={'DOT': 100, 'LRNA': 750})] init_intents_partial = [ # selling DOT for $7 {'sell_quantity': mpf(100), 'buy_quantity': mpf(700), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0], 'partial': True} @@ -21,6 +21,14 @@ def test_single_trade_settles(): init_intents_full = [ # selling DOT for $7 {'sell_quantity': mpf(100), 'buy_quantity': mpf(700), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0], 'partial': False} ] + init_intents_partial_lrna = [ + {'sell_quantity': mpf(750), 'buy_quantity': mpf(700), 'tkn_sell': 'LRNA', 'tkn_buy': 'USDT', 'agent': agents[0], + 'partial': True} + ] + init_intents_full_lrna = [ + {'sell_quantity': mpf(750), 'buy_quantity': mpf(700), 'tkn_sell': 'LRNA', 'tkn_buy': 'USDT', 'agent': agents[0], + 'partial': False} + ] liquidity = {'HDX': mpf(100000000), 'USDT': mpf(10000000), 'DOT': mpf(10000000/7.5)} # DOT price is $7.50 lrna = {'HDX': mpf(1000000), 'USDT': mpf(10000000), 'DOT': mpf(10000000)} @@ -46,6 +54,18 @@ def test_single_trade_settles(): assert intent_deltas[0][0] == -init_intents_full[0]['sell_quantity'] assert intent_deltas[0][1] == init_intents_full[0]['buy_quantity'] + intents = copy.deepcopy(init_intents_partial_lrna) + intent_deltas = find_solution_outer_approx(initial_state, intents) + assert validate_and_execute_solution(initial_state.copy(), intents, intent_deltas) + assert intent_deltas[0][0] == -init_intents_partial_lrna[0]['sell_quantity'] + assert intent_deltas[0][1] == init_intents_partial_lrna[0]['buy_quantity'] + + intents = copy.deepcopy(init_intents_full_lrna) + intent_deltas = find_solution_outer_approx(initial_state, intents) + assert validate_and_execute_solution(initial_state.copy(), intents, intent_deltas) + assert intent_deltas[0][0] == -init_intents_full_lrna[0]['sell_quantity'] + assert intent_deltas[0][1] == init_intents_full_lrna[0]['buy_quantity'] + def test_single_trade_does_not_settle(): agents = [Agent(holdings={'DOT': 100, 'USDT': 0})] @@ -56,6 +76,14 @@ def test_single_trade_does_not_settle(): init_intents_full = [ # selling DOT for $8 {'sell_quantity': mpf(100), 'buy_quantity': mpf(800), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0], 'partial': False} ] + init_intents_partial_lrna = [ + {'sell_quantity': mpf(650), 'buy_quantity': mpf(700), 'tkn_sell': 'LRNA', 'tkn_buy': 'USDT', 'agent': agents[0], + 'partial': True} + ] + init_intents_full_lrna = [ + {'sell_quantity': mpf(650), 'buy_quantity': mpf(700), 'tkn_sell': 'LRNA', 'tkn_buy': 'USDT', 'agent': agents[0], + 'partial': False} + ] liquidity = {'HDX': mpf(100000000), 'USDT': mpf(10000000), 'DOT': mpf(10000000/7.5)} # DOT price is $7.50 lrna = {'HDX': mpf(1000000), 'USDT': mpf(10000000), 'DOT': mpf(10000000)} @@ -81,16 +109,28 @@ def test_single_trade_does_not_settle(): assert intent_deltas[0][0] == 0 assert intent_deltas[0][1] == 0 + intents = copy.deepcopy(init_intents_partial_lrna) + intent_deltas = find_solution_outer_approx(initial_state, intents) + assert validate_and_execute_solution(initial_state.copy(), intents, intent_deltas) + assert intent_deltas[0][0] == 0 + assert intent_deltas[0][1] == 0 + + intents = copy.deepcopy(init_intents_full_lrna) + intent_deltas = find_solution_outer_approx(initial_state, intents) + assert validate_and_execute_solution(initial_state.copy(), intents, intent_deltas) + assert intent_deltas[0][0] == 0 + assert intent_deltas[0][1] == 0 + def test_matching_trades_execute_more(): agents = [Agent(holdings={'DOT': 1000}), Agent(holdings={'USDT': 7600})] intent1 = { # selling DOT for $7.49 - 'sell_quantity': mpf(1000), 'buy_quantity': mpf(7470), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0] + 'sell_quantity': mpf(1000), 'buy_quantity': mpf(7470), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0], 'partial': True } intent2 = { # buying DOT for $7.51 - 'sell_quantity': mpf(7530), 'buy_quantity': mpf(1000), 'tkn_sell': 'USDT', 'tkn_buy': 'DOT', 'agent': agents[1] + 'sell_quantity': mpf(7530), 'buy_quantity': mpf(1000), 'tkn_sell': 'USDT', 'tkn_buy': 'DOT', 'agent': agents[1], 'partial': True } liquidity = {'HDX': mpf(100000000), 'USDT': mpf(10000000), 'DOT': mpf(10000000/7.5)} # DOT price is $7.50 @@ -108,19 +148,19 @@ def test_matching_trades_execute_more(): # do the DOT sale alone state_sale = initial_state.copy() intents_sale = [copy.deepcopy(intent1)] - sale_deltas = find_solution2(state_sale, intents_sale) + sale_deltas = find_solution_outer_approx(state_sale, intents_sale) assert validate_and_execute_solution(state_sale, intents_sale, sale_deltas) # do the DOT buy alone state_buy = initial_state.copy() intents_buy = [copy.deepcopy(intent2)] - buy_deltas = find_solution2(state_buy, intents_buy) + buy_deltas = find_solution_outer_approx(state_buy, intents_buy) assert validate_and_execute_solution(state_buy, intents_buy, buy_deltas) # do both trades together state_match = initial_state.copy() intents_match = [copy.deepcopy(intent1), copy.deepcopy(intent2)] - match_deltas = find_solution2(state_match, intents_match) + match_deltas = find_solution_outer_approx(state_match, intents_match) assert validate_and_execute_solution(state_match, intents_match, match_deltas) # check that matching trades caused more execution than executing either alone @@ -138,45 +178,10 @@ def test_convex(): ] intents = [ - {'sell_quantity': mpf(100), 'buy_quantity': mpf(700), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0]}, # selling DOT for $7 - {'sell_quantity': mpf(1500), 'buy_quantity': mpf(100000), 'tkn_sell': 'USDT', 'tkn_buy': 'HDX', 'agent': agents[1]}, # buying HDX for $0.015 - {'sell_quantity': mpf(400), 'buy_quantity': mpf(50), 'tkn_sell': 'USDT', 'tkn_buy': 'DOT', 'agent': agents[2]}, # buying DOT for $8 - {'sell_quantity': mpf(100), 'buy_quantity': mpf(100), 'tkn_sell': 'HDX', 'tkn_buy': 'USDT', 'agent': agents[3]}, # selling HDX for $1 - ] - - liquidity = {'HDX': mpf(100000000), 'USDT': mpf(10000000), 'DOT': mpf(10000000/7.5)} - lrna = {'HDX': mpf(1000000), 'USDT': mpf(10000000), 'DOT': mpf(10000000)} - initial_state = OmnipoolState( - tokens={ - tkn: {'liquidity': liquidity[tkn], 'LRNA': lrna[tkn]} for tkn in lrna - }, - asset_fee=mpf(0.0025), - lrna_fee=mpf(0.0005) - ) - initial_state.last_fee = {tkn: mpf(0.0025) for tkn in lrna} - initial_state.last_lrna_fee = {tkn: mpf(0.0005) for tkn in lrna} - - intent_deltas = find_solution(initial_state, intents) - - assert validate_and_execute_solution(initial_state, intents, intent_deltas) - - pprint(intent_deltas) - - -def test_convex2(): - - agents = [ - Agent(holdings={'DOT': 100}), - Agent(holdings={'USDT': 1500}), - Agent(holdings={'USDT': 400}), - Agent(holdings={'HDX': 100}), - ] - - intents = [ - {'sell_quantity': mpf(100), 'buy_quantity': mpf(700), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0]}, # selling DOT for $7 - {'sell_quantity': mpf(1500), 'buy_quantity': mpf(100000), 'tkn_sell': 'USDT', 'tkn_buy': 'HDX', 'agent': agents[1]}, # buying HDX for $0.015 - {'sell_quantity': mpf(400), 'buy_quantity': mpf(50), 'tkn_sell': 'USDT', 'tkn_buy': 'DOT', 'agent': agents[2]}, # buying DOT for $8 - {'sell_quantity': mpf(100), 'buy_quantity': mpf(100), 'tkn_sell': 'HDX', 'tkn_buy': 'USDT', 'agent': agents[3]}, # selling HDX for $1 + {'sell_quantity': mpf(100), 'buy_quantity': mpf(700), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0], 'partial': True}, # selling DOT for $7 + {'sell_quantity': mpf(1500), 'buy_quantity': mpf(100000), 'tkn_sell': 'USDT', 'tkn_buy': 'HDX', 'agent': agents[1], 'partial': True}, # buying HDX for $0.015 + {'sell_quantity': mpf(400), 'buy_quantity': mpf(50), 'tkn_sell': 'USDT', 'tkn_buy': 'DOT', 'agent': agents[2], 'partial': True}, # buying DOT for $8 + {'sell_quantity': mpf(100), 'buy_quantity': mpf(100), 'tkn_sell': 'HDX', 'tkn_buy': 'USDT', 'agent': agents[3], 'partial': True}, # selling HDX for $1 ] liquidity = {'HDX': mpf(100000000), 'USDT': mpf(10000000), 'DOT': mpf(10000000/7.5)} @@ -191,7 +196,7 @@ def test_convex2(): initial_state.last_fee = {tkn: mpf(0.0025) for tkn in lrna} initial_state.last_lrna_fee = {tkn: mpf(0.0005) for tkn in lrna} - intent_deltas = find_solution2(initial_state, intents) + intent_deltas = find_solution_outer_approx(initial_state, intents) assert validate_and_execute_solution(initial_state, intents, intent_deltas) @@ -209,11 +214,11 @@ def test_with_lrna_intent(): ] intents = [ - {'sell_quantity': mpf(100), 'buy_quantity': mpf(700), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0]}, # selling DOT for $7 - {'sell_quantity': mpf(1500), 'buy_quantity': mpf(100000), 'tkn_sell': 'USDT', 'tkn_buy': 'HDX', 'agent': agents[1]}, # buying HDX for $0.015 - {'sell_quantity': mpf(400), 'buy_quantity': mpf(50), 'tkn_sell': 'USDT', 'tkn_buy': 'DOT', 'agent': agents[2]}, # buying DOT for $8 - {'sell_quantity': mpf(100), 'buy_quantity': mpf(100), 'tkn_sell': 'HDX', 'tkn_buy': 'USDT', 'agent': agents[3]}, # selling HDX for $1 - {'sell_quantity': mpf(1000), 'buy_quantity': mpf(100), 'tkn_sell': 'LRNA', 'tkn_buy': 'DOT', 'agent': agents[4]}, # buying DOT for $10 + {'sell_quantity': mpf(100), 'buy_quantity': mpf(700), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0], 'partial': True}, # selling DOT for $7 + {'sell_quantity': mpf(1500), 'buy_quantity': mpf(100000), 'tkn_sell': 'USDT', 'tkn_buy': 'HDX', 'agent': agents[1], 'partial': True}, # buying HDX for $0.015 + {'sell_quantity': mpf(400), 'buy_quantity': mpf(50), 'tkn_sell': 'USDT', 'tkn_buy': 'DOT', 'agent': agents[2], 'partial': True}, # buying DOT for $8 + {'sell_quantity': mpf(100), 'buy_quantity': mpf(100), 'tkn_sell': 'HDX', 'tkn_buy': 'USDT', 'agent': agents[3], 'partial': True}, # selling HDX for $1 + {'sell_quantity': mpf(1000), 'buy_quantity': mpf(100), 'tkn_sell': 'LRNA', 'tkn_buy': 'DOT', 'agent': agents[4], 'partial': True}, # buying DOT for $10 {'sell_quantity': mpf(100), 'buy_quantity': mpf(700), 'tkn_sell': 'DOT', 'tkn_buy': 'USDT', 'agent': agents[0], 'partial': False}, # selling DOT for $7 ] @@ -229,7 +234,7 @@ def test_with_lrna_intent(): initial_state.last_fee = {tkn: mpf(0.0025) for tkn in lrna} initial_state.last_lrna_fee = {tkn: mpf(0.0005) for tkn in lrna} - intent_deltas = find_solution2(initial_state, intents) + intent_deltas = find_solution_outer_approx(initial_state, intents) assert validate_and_execute_solution(initial_state, intents, intent_deltas) @@ -245,8 +250,8 @@ def test_solver_with_real_omnipool(): intents = [ # {'sell_quantity': mpf(100), 'buy_quantity': mpf(1.149711278057), 'tkn_sell': 'HDX', 'tkn_buy': 'CRU', 'agent': agents[0]}, # {'sell_quantity': mpf(1.149711278057), 'buy_quantity': mpf(100), 'tkn_sell': 'CRU', 'tkn_buy': 'HDX', 'agent': agents[1]}, - {'sell_quantity': mpf(100), 'buy_quantity': mpf(1.149), 'tkn_sell': 'HDX', 'tkn_buy': 'CRU', 'agent': agents[0]}, - {'sell_quantity': mpf(1.150), 'buy_quantity': mpf(100), 'tkn_sell': 'CRU', 'tkn_buy': 'HDX', 'agent': agents[1]}, + {'sell_quantity': mpf(100), 'buy_quantity': mpf(1.149), 'tkn_sell': 'HDX', 'tkn_buy': 'CRU', 'agent': agents[0], 'partial': True}, + {'sell_quantity': mpf(1.150), 'buy_quantity': mpf(100), 'tkn_sell': 'CRU', 'tkn_buy': 'HDX', 'agent': agents[1], 'partial': True}, # {'sell_quantity': mpf(100), 'buy_quantity': mpf(1.25359), 'tkn_sell': 'HDX', 'tkn_buy': 'CRU', # 'agent': agents[0]}, # {'sell_quantity': mpf(1.25361), 'buy_quantity': mpf(100), 'tkn_sell': 'CRU', 'tkn_buy': 'HDX', @@ -276,11 +281,9 @@ def test_solver_with_real_omnipool(): initial_state.last_fee = {tkn: mpf(0.0025) for tkn in lrna} initial_state.last_lrna_fee = {tkn: mpf(0.0005) for tkn in lrna} - intent_deltas = find_solution3(initial_state, intents) + intent_deltas = find_solution_outer_approx(initial_state, intents) assert validate_and_execute_solution(initial_state.copy(), copy.deepcopy(intents), intent_deltas) - intent_deltas2 = [[-mpf(100), mpf(1.149)], [0, 0]] - assert validate_and_execute_solution(initial_state.copy(), copy.deepcopy(intents), intent_deltas2) pprint(intent_deltas) @@ -331,7 +334,7 @@ def test_solver_with_real_omnipool_one_full(): full_intent_indicators = [1] # full_intent_indicators = [] - amm_deltas, sell_deltas = _find_solution_unrounded3(initial_state, partial_intents, full_intents, I=full_intent_indicators) + amm_deltas, sell_deltas, _, _ = _find_solution_unrounded3(initial_state, partial_intents, full_intents, I=full_intent_indicators) for i in full_intents: if full_intent_indicators.pop(0) == 1: sell_deltas.append(-i['sell_quantity']) @@ -345,57 +348,6 @@ def test_solver_with_real_omnipool_one_full(): pprint(intent_deltas) -def test_MIP_solver_with_real_omnipool(): - agents = [ - Agent(holdings={'HDX': 100}), - Agent(holdings={'HDX': 100}), - ] - - intents = [ - # {'sell_quantity': mpf(100), 'buy_quantity': mpf(1.149711278057), 'tkn_sell': 'HDX', 'tkn_buy': 'CRU', 'agent': agents[0]}, - # {'sell_quantity': mpf(1.149711278057), 'buy_quantity': mpf(100), 'tkn_sell': 'CRU', 'tkn_buy': 'HDX', 'agent': agents[1]}, - {'sell_quantity': mpf(100), 'buy_quantity': mpf(1.149), 'tkn_sell': 'HDX', 'tkn_buy': 'CRU', 'agent': agents[0], 'partial': False}, - {'sell_quantity': mpf(100), 'buy_quantity': mpf(1.149), 'tkn_sell': 'CRU', 'tkn_buy': 'HDX', 'agent': agents[1], 'partial': True}, - # {'sell_quantity': mpf(100), 'buy_quantity': mpf(200.0), 'tkn_sell': 'HDX', 'tkn_buy': 'CRU', 'agent': agents[1], - # 'partial': True}, - # {'sell_quantity': mpf(100), 'buy_quantity': mpf(1.25359), 'tkn_sell': 'HDX', 'tkn_buy': 'CRU', - # 'agent': agents[0]}, - # {'sell_quantity': mpf(1.25361), 'buy_quantity': mpf(100), 'tkn_sell': 'CRU', 'tkn_buy': 'HDX', - # 'agent': agents[1]} - ] - - liquidity = {'4-Pool': mpf(1392263.9295618401), 'HDX': mpf(140474254.46393022), 'KILT': mpf(1941765.8700688032), - 'WETH': mpf(897.820372708098), '2-Pool': mpf(80.37640742108785), 'GLMR': mpf(7389788.325282889), - 'BNC': mpf(5294190.655262755), 'RING': mpf(30608622.54045291), 'vASTR': mpf(1709768.9093601815), - 'vDOT': mpf(851755.7840315843), 'CFG': mpf(3497639.0397717496), 'CRU': mpf(337868.26827475097), - '2-Pool': mpf(14626788.977583803), 'DOT': mpf(2369965.4990946855), 'PHA': mpf(6002455.470581388), - 'ZTG': mpf(9707643.829161936), 'INTR': mpf(52756928.48950746), 'ASTR': mpf(31837859.71273387), } - lrna = {'4-Pool': mpf(50483.454258911326), 'HDX': mpf(24725.8021660851), 'KILT': mpf(10802.301353604526), - 'WETH': mpf(82979.9927924809), '2-Pool': mpf(197326.54331209575), 'GLMR': mpf(44400.11377262768), - 'BNC': mpf(35968.10763198863), 'RING': mpf(1996.48438233777), 'vASTR': mpf(4292.819030020081), - 'vDOT': mpf(182410.99000727307), 'CFG': mpf(41595.57689216696), 'CRU': mpf(4744.442135139952), - '2-Pool': mpf(523282.70722423657), 'DOT': mpf(363516.4838824808), 'PHA': mpf(24099.247547699764), - 'ZTG': mpf(4208.90365804613), 'INTR': mpf(19516.483401186168), 'ASTR': mpf(68571.5237579274), } - - initial_state = OmnipoolState( - tokens={ - tkn: {'liquidity': liquidity[tkn], 'LRNA': lrna[tkn]} for tkn in lrna - }, - asset_fee=mpf(0.0025), - lrna_fee=mpf(0.0005) - ) - initial_state.last_fee = {tkn: mpf(0.0025) for tkn in lrna} - initial_state.last_lrna_fee = {tkn: mpf(0.0005) for tkn in lrna} - - intent_deltas = _solve_inclusion_problem(initial_state, intents) - - assert validate_and_execute_solution(initial_state.copy(), copy.deepcopy(intents), intent_deltas) - intent_deltas2 = [[-mpf(100), mpf(1.149)], [0, 0]] - assert validate_and_execute_solution(initial_state.copy(), copy.deepcopy(intents), intent_deltas2) - - pprint(intent_deltas) - - def test_full_solver(): agents = [ Agent(holdings={'HDX': 100}),