Skip to content

Commit

Permalink
Merge pull request #594 from bancorprotocol/convergence_overlap
Browse files Browse the repository at this point in the history
Convergence issues with overlapping strategies
  • Loading branch information
barakman authored May 7, 2024
2 parents 29ab08b + c52fe5c commit 9a640a8
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 10 deletions.
83 changes: 83 additions & 0 deletions fastlane_bot/helpers/poolandtokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,25 @@ 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

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 = []
errors = []
strategy_typed_args = []
for i in [0, 1]:

S = Decimal(self.A_1) if i == 0 else Decimal(self.A_0)
Expand Down Expand Up @@ -450,13 +465,19 @@ 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]
)
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 = {
Expand All @@ -466,7 +487,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
),
Expand All @@ -476,6 +499,66 @@ 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 = 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)

# 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 = 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']}")
typed_args0['yint'] = new_yint0

typed_args1 = strategy_typed_args[1]
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")
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(
Expand Down
19 changes: 9 additions & 10 deletions fastlane_bot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -178,17 +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:
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 self.decodeRate(B + A)
else:
return self.decodeRate(B + A * self.y/self.z)

def find_latest_timestamped_folder(logging_path=None):
"""
Expand Down

0 comments on commit 9a640a8

Please sign in to comment.