Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convergence issues with overlapping strategies #594

Merged
merged 4 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
sklbancor marked this conversation as resolved.
Show resolved Hide resolved

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)
sklbancor marked this conversation as resolved.
Show resolved Hide resolved
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
barakman marked this conversation as resolved.
Show resolved Hide resolved

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
Loading