From afda7d3e929ec21a6d8f4bb6ae92ba83fca8a73a Mon Sep 17 00:00:00 2001 From: NIXBNT <88088888+NIXBNT@users.noreply.github.com> Date: Thu, 2 May 2024 15:37:59 +1000 Subject: [PATCH 1/4] overlapping order modifier --- fastlane_bot/helpers/poolandtokens.py | 70 +++++++++++++++++++++++++++ fastlane_bot/utils.py | 28 ++++++++--- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/fastlane_bot/helpers/poolandtokens.py b/fastlane_bot/helpers/poolandtokens.py index c6ac1f61d..4eac11a4c 100644 --- a/fastlane_bot/helpers/poolandtokens.py +++ b/fastlane_bot/helpers/poolandtokens.py @@ -407,6 +407,7 @@ def _carbon_to_cpc(self) -> ConstantProductCurve: # if idx == 0, use the first curve, otherwise use the second curve. change the numerical values to Decimal lst = [] errors = [] + strategy_typed_args = [] for i in [0, 1]: S = Decimal(self.A_1) if i == 0 else Decimal(self.A_0) @@ -450,6 +451,7 @@ def decimal_converter(idx): decimal_converter = decimal_converter(i) p_start = Decimal(encoded_order.p_start) * decimal_converter + p_marg = Decimal(encoded_order.p_marg) * decimal_converter p_end = Decimal(encoded_order.p_end) * decimal_converter yint = Decimal(yint) / ( Decimal("10") ** [self.tkn1_decimals, self.tkn0_decimals][i] @@ -457,6 +459,11 @@ def decimal_converter(idx): y = Decimal(y) / ( Decimal("10") ** [self.tkn1_decimals, self.tkn0_decimals][i] ) + is_limit_order = p_start==p_end + + # if (p_marg!=p_start) and (p_marg!=p_end): + # self.ConfigObj.logger.debug(f"[poolandtokens.py, _carbon_to_cpc] p_start, p_marg, p_end:, {p_start, p_marg, p_end}") + # assert (round(p_start,6)<=round(p_marg,6)<=round(p_end,6)) or (round(p_start,6)>=round(p_marg,6)>=round(p_end,6)), f"WARNING {p_start, p_marg, p_end}" tkny = 1 if i == 0 else 0 typed_args = { @@ -466,7 +473,9 @@ def decimal_converter(idx): "yint": yint, "y": y, "pb": p_end, + "p_marg": p_marg, # deleted later since not supported by from_carbon() "pa": p_start, + "is_limit_order": is_limit_order, # deleted later since not supported by from_carbon() "tkny": self.pair_name.split("/")[tkny].replace( self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS ), @@ -476,6 +485,67 @@ def decimal_converter(idx): "descr": self.descr, "params": self._params, } + + strategy_typed_args += [typed_args] + + #### MODIFICATION LOGIC HERE >>>>> + # Only overlapping strategies are selected for modification + if len(strategy_typed_args) == 2: + + is_overlapping = False + pmarg_threshold = Decimal("0.01") # 1% # WARNING using this condition alone can included stable/stable pairs incidently + + # evaluate that the marginal prices are within the pmarg_threshold + pmarg0, pmarg1 = [x['p_marg'] for x in strategy_typed_args] + pmarg0_inv = 1/pmarg0 # one of the orders must be flipped since prices are always dy/dx - but must flip same geomean_pmarg later + percent_component = pmarg_threshold * max(pmarg0_inv, pmarg1) + percent_component_met = abs(pmarg0_inv - pmarg1) <= percent_component + + # overlapping strategies by defintion cannot have A=0 i.e. there must be no limit orders + no_limit_orders = (strategy_typed_args[0]['is_limit_order'] == False) and (strategy_typed_args[1]['is_limit_order'] == False) + + # evaluate if the price boundaries pa/pb overlap at one end # TODO check logic and remove duplicate logic if necessary + prices_overlap = (strategy_typed_args[1]['pa']>(1/strategy_typed_args[0]['pa'])>strategy_typed_args[1]['pb']) # or (1/strategy_typed_args[0]['pa']<(strategy_typed_args[1]['pb'])) + + # if (percent_component_met and no_limit_orders) and not prices_overlap: + # print(percent_component_met, no_limit_orders, prices_overlap) + # print(strategy_typed_args) + + # if the threshold is met and neither is a limit order and prices overlap then likely to be overlapping + is_overlapping = percent_component_met and no_limit_orders and prices_overlap + + if is_overlapping: + # print(strategy_typed_args) + # calculate the geometric mean + geomean_p_marg = Decimal.sqrt(pmarg0_inv * pmarg1) + self.ConfigObj.logger.debug(f"[poolandtokens.py, _carbon_to_cpc] These cids are identified as overlapping: {[x['cid'] for x in strategy_typed_args]}") + self.ConfigObj.logger.debug(f"[poolandtokens.py, _carbon_to_cpc] pmarg0_inv, pmarg1, geomean_p_marg: {pmarg0_inv, pmarg1, geomean_p_marg}") + + # modify the y_int based on the new geomean to the limit of y #TODO check that this math is correct + typed_args0 = strategy_typed_args[0] + new_yint0 = typed_args0['y'] * (typed_args0['pa'] - typed_args0['pb']) / ((1/geomean_p_marg) - typed_args0['pb']) #this geomean is flipped because we flippend the pmarg from order 0 + if new_yint0 < typed_args0['y']: + new_yint0 = typed_args0['y'] + self.ConfigObj.logger.debug(f"[poolandtokens.py, _carbon_to_cpc] First order: typed_args0['yint'], new_yint0, , typed_args0['y']: {typed_args0['yint'], new_yint0, typed_args0['y']}") + typed_args0['yint'] = new_yint0 + + typed_args1 = strategy_typed_args[1] + new_yint1 = typed_args1['y'] * (typed_args1['pa'] - typed_args1['pb']) / (geomean_p_marg - typed_args1['pb']) + if new_yint1 < typed_args1['y']: + new_yint1 = typed_args1['y'] + self.ConfigObj.logger.debug(f"[poolandtokens.py, _carbon_to_cpc] Second order: typed_args1['yint'], new_yint1, typed_args1['y']: {typed_args1['yint'], new_yint1, typed_args1['y']} \n") + typed_args1['yint'] = new_yint1 + + # repack the strateg_typed_args + strategy_typed_args = [typed_args0, typed_args1] + + + #### <<<<< MODIFICATION LOGIC HERE + + for typed_args in strategy_typed_args: + # delete new args that arent supported by from_carbon() + del typed_args["p_marg"] + del typed_args["is_limit_order"] try: if typed_args["y"] > 0: lst.append( diff --git a/fastlane_bot/utils.py b/fastlane_bot/utils.py index e1692d086..715f95bdc 100644 --- a/fastlane_bot/utils.py +++ b/fastlane_bot/utils.py @@ -68,6 +68,10 @@ def __getitem__(self, item): def decodeFloat(cls, value): """undoes the mantisse/exponent encoding in A,B""" return (value % cls.ONE) << (value // cls.ONE) + + @classmethod + def decodeRate(cls, value): + return (value / cls.ONE) ** 2 @classmethod def decode(cls, value): @@ -179,15 +183,27 @@ def p_start(self): @property def p_marg(self): if self.y == self.z: + # try: + new_method = self.decodeRate(self.decodeFloat(int(self.B)) + self.decodeFloat(int(self.A))) + # except: + # print(self.B, self.A) + # print(type(self.B), type(self.A)) + # print(self.decodeFloat(int(self.B)) + self.decodeFloat(int(self.A))) + # print(new_method, self.p_start) + assert new_method == self.p_start, f"{new_method}, {self.p_start} **************************************" return self.p_start elif self.y == 0: return self.p_end - raise NotImplementedError("p_marg not implemented for non-full / empty orders") - A = self.decodeFloat(self.A) - B = self.decodeFloat(self.B) - return self.decode(B + A * self.y / self.z) ** 2 - # https://github.com/bancorprotocol/carbon-simulator/blob/beta/benchmark/core/trade/impl.py - # 'marginalRate' : decodeRate(B + A if y == z else B + A * y / z), + # return 0 + else: + return self.decodeRate(self.decodeFloat(int(self.B)) + (self.decodeFloat(int(self.A)) * self.y/self.z)) + + # raise NotImplementedError("p_marg not implemented for non-full / empty orders") + # A = self.decodeFloat(self.A) + # B = self.decodeFloat(self.B) + # return self.decode(B + A * self.y / self.z) ** 2 + # # https://github.com/bancorprotocol/carbon-simulator/blob/beta/benchmark/core/trade/impl.py + # # 'marginalRate' : decodeRate(B + A if y == z else B + A * y / z), def find_latest_timestamped_folder(logging_path=None): From 93b622341277c8da6e3c931b5c19b7c87933a0a6 Mon Sep 17 00:00:00 2001 From: NIXBNT <88088888+NIXBNT@users.noreply.github.com> Date: Thu, 2 May 2024 21:22:06 +1000 Subject: [PATCH 2/4] fix calculations for overlapping order modifier --- fastlane_bot/helpers/poolandtokens.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/fastlane_bot/helpers/poolandtokens.py b/fastlane_bot/helpers/poolandtokens.py index 4eac11a4c..b718d3404 100644 --- a/fastlane_bot/helpers/poolandtokens.py +++ b/fastlane_bot/helpers/poolandtokens.py @@ -403,6 +403,14 @@ def _carbon_to_cpc(self) -> ConstantProductCurve: allow to omit yint (in which case it is set to y, but this does not make a difference for the result) """ + def calculate_parameters(y: Decimal, pa: Decimal, pb: Decimal, pm: Decimal, n: Decimal): + H = pa.sqrt() ** n + L = pb.sqrt() ** n + M = pm.sqrt() ** n + A = H - L + B = L + z = y * (H - L) / (M - L) if M > L else y + return z # if idx == 0, use the first curve, otherwise use the second curve. change the numerical values to Decimal lst = [] @@ -523,14 +531,14 @@ def decimal_converter(idx): # modify the y_int based on the new geomean to the limit of y #TODO check that this math is correct typed_args0 = strategy_typed_args[0] - new_yint0 = typed_args0['y'] * (typed_args0['pa'] - typed_args0['pb']) / ((1/geomean_p_marg) - typed_args0['pb']) #this geomean is flipped because we flippend the pmarg from order 0 + new_yint0 = calculate_parameters(y=typed_args0['y'], pa=typed_args0['pa'], pb=typed_args0['pb'], pm=(1/geomean_p_marg), n=1) if new_yint0 < typed_args0['y']: new_yint0 = typed_args0['y'] - self.ConfigObj.logger.debug(f"[poolandtokens.py, _carbon_to_cpc] First order: typed_args0['yint'], new_yint0, , typed_args0['y']: {typed_args0['yint'], new_yint0, typed_args0['y']}") + self.ConfigObj.logger.debug(f"[poolandtokens.py, _carbon_to_cpc] First order: typed_args0['yint'], new_yint0, typed_args0['y']: {typed_args0['yint'], new_yint0, typed_args0['y']}") typed_args0['yint'] = new_yint0 typed_args1 = strategy_typed_args[1] - new_yint1 = typed_args1['y'] * (typed_args1['pa'] - typed_args1['pb']) / (geomean_p_marg - typed_args1['pb']) + new_yint1 = calculate_parameters(y=typed_args1['y'], pa=typed_args1['pa'], pb=typed_args1['pb'], pm=(geomean_p_marg), n=1) if new_yint1 < typed_args1['y']: new_yint1 = typed_args1['y'] self.ConfigObj.logger.debug(f"[poolandtokens.py, _carbon_to_cpc] Second order: typed_args1['yint'], new_yint1, typed_args1['y']: {typed_args1['yint'], new_yint1, typed_args1['y']} \n") From ff834abf7553a41d13dde3d2f59da5fde74b2e97 Mon Sep 17 00:00:00 2001 From: NIXBNT <88088888+NIXBNT@users.noreply.github.com> Date: Fri, 3 May 2024 13:26:18 +1000 Subject: [PATCH 3/4] cleaner method for checking overlapping strategies --- fastlane_bot/helpers/poolandtokens.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fastlane_bot/helpers/poolandtokens.py b/fastlane_bot/helpers/poolandtokens.py index b718d3404..1ae7c257e 100644 --- a/fastlane_bot/helpers/poolandtokens.py +++ b/fastlane_bot/helpers/poolandtokens.py @@ -411,6 +411,12 @@ def calculate_parameters(y: Decimal, pa: Decimal, pb: Decimal, pm: Decimal, n: D B = L z = y * (H - L) / (M - L) if M > L else y return z + + def check_overlap(pa0, pb0, pa1, pb1): + min0, max0 = sorted([pa0, pb0]) + min1, max1 = sorted([1 / pa1, 1 / pb1]) + prices_overlap = max(min0, min1) < min(max0, max1) + return prices_overlap # if idx == 0, use the first curve, otherwise use the second curve. change the numerical values to Decimal lst = [] @@ -513,8 +519,7 @@ def decimal_converter(idx): no_limit_orders = (strategy_typed_args[0]['is_limit_order'] == False) and (strategy_typed_args[1]['is_limit_order'] == False) # evaluate if the price boundaries pa/pb overlap at one end # TODO check logic and remove duplicate logic if necessary - prices_overlap = (strategy_typed_args[1]['pa']>(1/strategy_typed_args[0]['pa'])>strategy_typed_args[1]['pb']) # or (1/strategy_typed_args[0]['pa']<(strategy_typed_args[1]['pb'])) - + prices_overlap = check_overlap(strategy_typed_args[0]['pa'], strategy_typed_args[0]['pb'], strategy_typed_args[1]['pa'], strategy_typed_args[1]['pb']) # if (percent_component_met and no_limit_orders) and not prices_overlap: # print(percent_component_met, no_limit_orders, prices_overlap) # print(strategy_typed_args) From c52fe5cda474e70e0d44d296f2c7ca5fc2c039ea Mon Sep 17 00:00:00 2001 From: NIXBNT <88088888+NIXBNT@users.noreply.github.com> Date: Tue, 7 May 2024 09:57:33 +1000 Subject: [PATCH 4/4] correct handling of p_marg and cleanup --- fastlane_bot/utils.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/fastlane_bot/utils.py b/fastlane_bot/utils.py index 715f95bdc..042ed7a19 100644 --- a/fastlane_bot/utils.py +++ b/fastlane_bot/utils.py @@ -182,29 +182,12 @@ def p_start(self): @property def p_marg(self): + A = self.decodeFloat(int(self.A)) + B = self.decodeFloat(int(self.B)) if self.y == self.z: - # try: - new_method = self.decodeRate(self.decodeFloat(int(self.B)) + self.decodeFloat(int(self.A))) - # except: - # print(self.B, self.A) - # print(type(self.B), type(self.A)) - # print(self.decodeFloat(int(self.B)) + self.decodeFloat(int(self.A))) - # print(new_method, self.p_start) - assert new_method == self.p_start, f"{new_method}, {self.p_start} **************************************" - return self.p_start - elif self.y == 0: - return self.p_end - # return 0 + return self.decodeRate(B + A) else: - return self.decodeRate(self.decodeFloat(int(self.B)) + (self.decodeFloat(int(self.A)) * self.y/self.z)) - - # raise NotImplementedError("p_marg not implemented for non-full / empty orders") - # A = self.decodeFloat(self.A) - # B = self.decodeFloat(self.B) - # return self.decode(B + A * self.y / self.z) ** 2 - # # https://github.com/bancorprotocol/carbon-simulator/blob/beta/benchmark/core/trade/impl.py - # # 'marginalRate' : decodeRate(B + A if y == z else B + A * y / z), - + return self.decodeRate(B + A * self.y/self.z) def find_latest_timestamped_folder(logging_path=None): """