Skip to content

Commit

Permalink
- Fixed bug relating to LRNA sales
Browse files Browse the repository at this point in the history
- Added logic to only look at net direction of known trades in determining direction of flow
- Solver starts by excluding all trades that cannot be partially executed
- Added a fuzz test for solver
  • Loading branch information
poliwop committed Oct 23, 2024
1 parent 4e36c05 commit c12a27a
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 21 deletions.
61 changes: 44 additions & 17 deletions hydradx/model/amm/omnix_solver_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,15 @@ def _find_solution_unrounded3(
fee_buffer: float = 0.0001
) -> (dict, list):

intent_directions = {}
asset_list = []
for intent in partial_intents + full_intents: # TODO update direction logic to take advantage of known full intents
for intent in partial_intents + full_intents:
if intent['tkn_sell'] != "LRNA" and intent['tkn_sell'] not in asset_list:
asset_list.append(intent['tkn_sell'])
if intent['tkn_sell'] != "LRNA" and intent['tkn_buy'] not in asset_list:
if intent['tkn_buy'] != "LRNA" and intent['tkn_buy'] not in asset_list:
asset_list.append(intent['tkn_buy'])

intent_directions = {}
for intent in partial_intents:
if intent['tkn_sell'] not in intent_directions:
intent_directions[intent['tkn_sell']] = "sell"
elif intent_directions[intent['tkn_sell']] == "buy":
Expand All @@ -395,6 +397,24 @@ def _find_solution_unrounded3(
elif intent_directions[intent['tkn_buy']] == "sell":
intent_directions[intent['tkn_buy']] = "both"

known_flow = {tkn: 0 for tkn in ["LRNA"] + asset_list}
for i, intent in enumerate(full_intents):
if I[i] > 0.5:
known_flow[intent['tkn_sell']] += intent["sell_quantity"]
known_flow[intent['tkn_buy']] -= intent["buy_quantity"]
for tkn in asset_list:
if known_flow[tkn] > 0: # net agent is selling tkn
if tkn not in intent_directions:
intent_directions[tkn] = "sell"
elif intent_directions[tkn] == "buy":
intent_directions[tkn] = "both"
elif known_flow[tkn] < 0: # net agent is buying tkn
if tkn not in intent_directions:
intent_directions[tkn] = "buy"
elif intent_directions[tkn] == "sell":
intent_directions[tkn] = "both"


n = len(asset_list)
if I is None: # run all intents as though they are partial
assert full_intents == []
Expand Down Expand Up @@ -429,7 +449,8 @@ def _find_solution_unrounded3(
fees = [float(state.last_fee[tkn]) for tkn in asset_list] # f_i
lrna_fees = [float(state.last_lrna_fee[tkn]) for tkn in asset_list] # l_i
fee_match = 0.0005
assert fee_match <= min(fees) # breaks otherwise
if len(fees) > 0:
assert fee_match <= min(fees) # breaks otherwise

# calculate tau, phi
scaling, net_supply, net_demand = _calculate_scaling(partial_intents + full_intents, state, asset_list)
Expand Down Expand Up @@ -625,7 +646,7 @@ def _solve_inclusion_problem(
for intent in intents:
if intent['tkn_sell'] != "LRNA" and intent['tkn_sell'] not in asset_list:
asset_list.append(intent['tkn_sell'])
if intent['tkn_sell'] != "LRNA" and intent['tkn_buy'] not in asset_list:
if intent['tkn_buy'] != "LRNA" and intent['tkn_buy'] not in asset_list:
asset_list.append(intent['tkn_buy'])
tkn_list = ["LRNA"] + asset_list

Expand Down Expand Up @@ -785,8 +806,8 @@ def _solve_inclusion_problem(

for i in range(m):
exec_partial_intent_deltas[i] = -x_expanded[4 * n + i] * scaling[intents[i]['tkn_sell']]
for i in range(r):
exec_full_intent_flags[i] = x_expanded[4 * n + m + i]

exec_full_intent_flags = [1 if x_expanded[4 * n + m + i] > 0.5 else 0 for i in range(r)]

save_A = np.vstack([old_A, S])
save_A_upper = np.concatenate([old_A_upper, S_upper])
Expand Down Expand Up @@ -853,27 +874,34 @@ def find_solution3(state: OmnipoolState, intents: list, epsilon: float = 1e-5) -


def find_solution_outer_approx(state: OmnipoolState, intents: list, epsilon: float = 1e-5) -> list:
if len(intents) == 0:
return []
partial_intent_indices = [i for i in range(len(intents)) if intents[i]['partial']]
full_intent_indices = [i for i in range(len(intents)) if not intents[i]['partial']]
partial_intents = [intents[i] for i in partial_intent_indices]
full_intents = [intents[i] for i in full_intent_indices]

asset_list = []
for intent in intents:
if intent['tkn_sell'] != "LRNA" and intent['tkn_sell'] not in asset_list:
asset_list.append(intent['tkn_sell'])
if intent['tkn_buy'] != "LRNA" and intent['tkn_buy'] not in asset_list:
asset_list.append(intent['tkn_buy'])

m = len(partial_intents)
r = len(full_intents)
inf = highspy.kHighsInf
# find initial solution as though all intents are partial
amm_deltas, intent_deltas, x, obj = _find_solution_unrounded3(state, partial_intents + full_intents, epsilon=epsilon)
n = len(amm_deltas)
n = len(asset_list)
k_milp = 4 * n + m + r
# get initial I values
indicators = [1 if -intent_deltas[m + l] >= full_intents[l]['sell_quantity'] / 2 else 0 for l in range(r)]
indicators = [0]*r
# set Z_L = -inf, Z_U = inf
Z_L = -inf
Z_U = inf
milp_obj = -inf
new_A, new_A_upper, new_A_lower = np.zeros((0, k_milp)), np.array([]), np.array([])
# loop until linearization has no solution:
for _i in range(10):
for _i in range(50):
# - update I^(K+1), Z_L
Z_L = max(Z_L, milp_obj)
# - do NLP solve given I values, update x^K
Expand Down Expand Up @@ -903,8 +931,11 @@ def find_solution_outer_approx(state: OmnipoolState, intents: list, epsilon: flo
if not valid:
break

if valid == True: # this means we did not get to a solution
return [[0,0]]*len(intents)

flags = get_directional_flags(best_amm_deltas)
best_amm_deltas, best_intent_deltas, x, obj = _find_solution_unrounded3(state, partial_intents, full_intents, I=y_best, epsilon = epsilon)
best_amm_deltas, best_intent_deltas, x, obj = _find_solution_unrounded3(state, partial_intents, full_intents, I=y_best, flags=flags, epsilon = epsilon)
sell_deltas = round_solution(partial_intents, best_intent_deltas)
partial_deltas_with_buys = add_buy_deltas(partial_intents, sell_deltas)
full_deltas_with_buys = [[-full_intents[l]['sell_quantity'], full_intents[l]['buy_quantity']] if y_best[l] == 1 else [0,0] for l in range(r)]
Expand All @@ -914,7 +945,3 @@ def find_solution_outer_approx(state: OmnipoolState, intents: list, epsilon: flo
for i in range(len(full_intent_indices)):
deltas[full_intent_indices[i]] = full_deltas_with_buys[i]
return deltas

print(Z_U)
print(x_best)
print(y_best)
61 changes: 57 additions & 4 deletions hydradx/tests/test_solver.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import copy
from pprint import pprint

from hypothesis import given, strategies as st, assume, settings, Verbosity

from hydradx.model.amm.agents import Agent
from hydradx.model.amm.omnipool_amm import OmnipoolState
from mpmath import mp, mpf

from hydradx.model.amm.omnix import validate_and_execute_solution
from hydradx.model.amm.omnix_solver_simple import find_solution, find_solution2, find_solution3, \
_find_solution_unrounded3, add_buy_deltas, round_solution, find_solution_outer_approx
_find_solution_unrounded3, add_buy_deltas, round_solution, find_solution_outer_approx, _solve_inclusion_problem


def test_single_trade_settles():
agents = [Agent(holdings={'DOT': 100})]
Expand Down Expand Up @@ -343,7 +346,6 @@ def test_solver_with_real_omnipool_one_full():


def test_MIP_solver_with_real_omnipool():
from hydradx.model.amm.omnix_solver_simple import _solve_inclusion_problem
agents = [
Agent(holdings={'HDX': 100}),
Agent(holdings={'HDX': 100}),
Expand Down Expand Up @@ -395,7 +397,6 @@ def test_MIP_solver_with_real_omnipool():


def test_full_solver():
from hydradx.model.amm.omnix_solver_simple import _solve_inclusion_problem
agents = [
Agent(holdings={'HDX': 100}),
Agent(holdings={'HDX': 100}),
Expand Down Expand Up @@ -441,4 +442,56 @@ def test_full_solver():

assert validate_and_execute_solution(initial_state.copy(), copy.deepcopy(intents), intent_deltas)

pprint(intent_deltas)
pprint(intent_deltas)

@given(st.lists(st.floats(min_value=1e-10, max_value=0.5), min_size=3, max_size=3),
st.lists(st.floats(min_value=0.9, max_value=1.1), min_size=3, max_size=3),
st.lists(st.integers(min_value=0, max_value=18), min_size=3, max_size=3),
st.lists(st.integers(min_value=0, max_value=17), min_size=3, max_size=3),
st.lists(st.booleans(), min_size=3, max_size=3)
)
@settings(print_blob=True, verbosity=Verbosity.verbose, deadline=None)
def test_solver_random_intents(sell_ratios, price_ratios, sell_is, buy_is, partial_flags):

liquidity = {'4-Pool': mpf(1392263.9295618401), 'HDX': mpf(140474254.46393022), 'KILT': mpf(1941765.8700688032),
'WETH': mpf(897.820372708098), '2-Pool-btc': 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-btc': 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}

good_indices = [i for i in range(len(sell_is)) if sell_is[i]-1 != buy_is[i]]
intents = []
for i in good_indices:
sell_tkn = initial_state.asset_list[sell_is[i]-1] if sell_is[i] > 0 else "LRNA"
buy_tkn = initial_state.asset_list[buy_is[i]]
if sell_tkn != "LRNA":
sell_quantity = sell_ratios[i] * liquidity[sell_tkn]
else:
sell_quantity = sell_ratios[i] * lrna[buy_tkn]
buy_quantity = sell_quantity * initial_state.price(initial_state, sell_tkn, buy_tkn) * price_ratios[i]
agent = Agent(holdings={sell_tkn: sell_quantity})
intents.append({'sell_quantity': sell_quantity, 'buy_quantity': buy_quantity, 'tkn_sell': sell_tkn,
'tkn_buy': buy_tkn, 'agent': agent, 'partial': partial_flags[i]})

intent_deltas = find_solution_outer_approx(initial_state, intents)

assert validate_and_execute_solution(initial_state.copy(), copy.deepcopy(intents), intent_deltas)

pprint(intent_deltas)

0 comments on commit c12a27a

Please sign in to comment.