Skip to content

Commit

Permalink
Merge branch 'triangle-mode-refactor' of https://github.com/bancorpro…
Browse files Browse the repository at this point in the history
…tocol/fastlane-bot into refactor-encode-decode-orders
  • Loading branch information
barak manos committed May 27, 2024
2 parents 3cb6a05 + 054fffe commit e667c92
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 64 deletions.
38 changes: 20 additions & 18 deletions fastlane_bot/modes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ def __init__(self, flashloan_tokens, CCm, ConfigObj):
self.flashloan_tokens = flashloan_tokens
self.CCm = CCm
self.ConfigObj = ConfigObj
self.sort_order = {
key: index for index, key in enumerate(
["bancor_v2", "bancor_v3"]
+ ConfigObj.UNI_V2_FORKS
+ ConfigObj.UNI_V3_FORKS
+ ConfigObj.CARBON_V1_FORKS
)
}

def find_combos(self) -> List[Any]:
return self.find_arbitrage()["combos"]
Expand All @@ -42,19 +50,17 @@ def find_arbitrage(self) -> Dict[List[Any], List[Any]]:
...

def get_profit(self, src_token: str, optimization, trade_instructions_df):
profit = self.calculate_profit(src_token, -optimization.result)
return profit if profit.is_finite() and profit > self.ConfigObj.DEFAULT_MIN_PROFIT_GAS_TOKEN and is_net_change_small(trade_instructions_df) else None
if is_net_change_small(trade_instructions_df):
profit = self.calculate_profit(src_token, -optimization.result)
if profit.is_finite() and profit > self.ConfigObj.DEFAULT_MIN_PROFIT_GAS_TOKEN:
return profit
return None

def calculate_profit(self, src_token: str, src_profit: float) -> Decimal:
"""
Calculate profit based on the source token.
"""
if src_token not in [self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS]:
sort_sequence = ['bancor_v2', 'bancor_v3'] + self.ConfigObj.UNI_V2_FORKS + self.ConfigObj.UNI_V3_FORKS
price_curves = get_prices_simple(self.CCm, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, src_token)
sorted_price_curves = custom_sort(price_curves, sort_sequence, self.ConfigObj.CARBON_V1_FORKS)
assert len(sorted_price_curves) > 0, f"Failed to get conversion rate for {src_token} and {self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS}"
return Decimal(str(src_profit)) / Decimal(str(sorted_price_curves[0][-1]))
items = get_items(self.CCm, self.sort_order, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, src_token)
assert len(items) > 0, f"Failed to get conversion rate for {src_token} and {self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS}"
return Decimal(str(src_profit)) / Decimal(str(items[0]["price"]))
return Decimal(str(src_profit))

def is_net_change_small(trade_instructions_df) -> bool:
Expand All @@ -63,11 +69,7 @@ def is_net_change_small(trade_instructions_df) -> bool:
except Exception:
return False

def get_prices_simple(CCm, dst_token, src_token):
curve_prices_1 = [(CC.params['exchange'], CC.descr, CC.cid, CC.p / 1) for CC in CCm.bytknx(dst_token).bytkny(src_token)]
curve_prices_2 = [(CC.params['exchange'], CC.descr, CC.cid, 1 / CC.p) for CC in CCm.bytknx(src_token).bytkny(dst_token)]
return curve_prices_1 + curve_prices_2

def custom_sort(data, sort_sequence, carbon_v1_forks):
sort_order = {key: index for index, key in enumerate(sort_sequence) if key not in carbon_v1_forks}
return sorted(data, key=lambda item: float('inf') if item[0] in carbon_v1_forks else sort_order.get(item[0], float('inf')))
def get_items(CCm, sort_order, dst_token, src_token):
list1 = [{"exchange": curve.params.exchange, "price": curve.p / 1} for curve in CCm.bytknx(dst_token).bytkny(src_token).curves]
list2 = [{"exchange": curve.params.exchange, "price": 1 / curve.p} for curve in CCm.bytknx(src_token).bytkny(dst_token).curves]
return sorted([item for item in list1 + list2 if item["exchange"] in sort_order], key=lambda item: sort_order[item["exchange"]])
20 changes: 7 additions & 13 deletions fastlane_bot/modes/base_pairwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ def find_arbitrage(self) -> Dict[List[Any], List[Any]]:
combos = self.get_combos()

for dst_token, src_token in combos:
CC = self.CCm.bypairs(f"{dst_token}/{src_token}")
if len(CC) < 2:
curves = self.CCm.bypairs(f"{dst_token}/{src_token}").curves
if len(curves) < 2:
continue

for curve_combo in self.get_curve_combos(CC):
if len(curve_combo) < 2:
for curve_combos in self.get_curve_combos(curves):
if len(curve_combos) < 2:
continue
try:
container = CPCContainer(curve_combo)
container = CPCContainer(curve_combos)
optimizer = PairOptimizer(container)
params = get_params(container, dst_token, src_token)
optimization = optimizer.optimize(src_token, params=params)
Expand All @@ -42,16 +42,10 @@ def find_arbitrage(self) -> Dict[List[Any], List[Any]]:

profit = self.get_profit(src_token, optimization, trade_instructions_df)
if profit is not None:
arb_opps.append(
{
"profit": profit,
"src_token": src_token,
'trade_instructions_dic': trade_instructions_dic,
}
)
arb_opps.append({"profit": profit, "src_token": src_token, "trade_instructions_dic": trade_instructions_dic})

return {"combos": combos, "arb_opps": sorted(arb_opps, key=lambda arb_opp: arb_opp["profit"], reverse=True)}

def get_params(container, dst_token, src_token):
pstart = {dst_token: container.bypairs(f"{dst_token}/{src_token}")[0].p}
pstart = {dst_token: container.bypairs(f"{dst_token}/{src_token}").curves[0].p}
return {"pstart": pstart}
20 changes: 7 additions & 13 deletions fastlane_bot/modes/base_triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,20 @@ def find_arbitrage(self) -> Dict[List[Any], List[Any]]:

profit = self.get_profit(src_token, optimization, trade_instructions_df)
if profit is not None:
arb_opps.append(
{
"profit": profit,
"src_token": src_token,
'trade_instructions_dic': trade_instructions_dic,
}
)
arb_opps.append({"profit": profit, "src_token": src_token, "trade_instructions_dic": trade_instructions_dic})

return {"combos": combos, "arb_opps": sorted(arb_opps, key=lambda arb_opp: arb_opp["profit"], reverse=True)}

def get_params(container, src_token):
pstart = {src_token: 1}
for dst_token in [token for token in container.tokens() if token != src_token]:
CC = container.bytknx(dst_token).bytkny(src_token)
if CC:
pstart[dst_token] = CC[0].p
curves = container.bytknx(dst_token).bytkny(src_token).curves
if len(curves) > 0:
pstart[dst_token] = curves[0].p
else:
CC = container.bytknx(src_token).bytkny(dst_token)
if CC:
pstart[dst_token] = 1 / CC[0].p
curves = container.bytknx(src_token).bytkny(dst_token).curves
if len(curves) > 0:
pstart[dst_token] = 1 / curves[0].p
else:
return None
return {"pstart": pstart}
6 changes: 3 additions & 3 deletions fastlane_bot/modes/pairwise_multi_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def get_combos(self) -> List[Any]:
flashloan_tokens_intersect = all_tokens.intersection(set(self.flashloan_tokens))
return [(tkn0, tkn1) for tkn0, tkn1 in product(all_tokens, flashloan_tokens_intersect) if tkn0 != tkn1]

def get_curve_combos(self, CC: Any) -> List[Any]:
carbon_curves = [curve for curve in CC.curves if curve.params.exchange in self.ConfigObj.CARBON_V1_FORKS]
other_curves = [curve for curve in CC.curves if curve.params.exchange not in self.ConfigObj.CARBON_V1_FORKS]
def get_curve_combos(self, curves: List[Any]) -> List[Any]:
carbon_curves = [curve for curve in curves if curve.params.exchange in self.ConfigObj.CARBON_V1_FORKS]
other_curves = [curve for curve in curves if curve.params.exchange not in self.ConfigObj.CARBON_V1_FORKS]

if len(carbon_curves) > 0:
curve_combos = []
Expand Down
8 changes: 4 additions & 4 deletions fastlane_bot/modes/pairwise_multi_pol.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def get_combos(self) -> List[Any]:
bancor_pol_tkns = set([tkn for tkn in bancor_pol_tkns if tkn != self.ConfigObj.WETH_ADDRESS])
return [(tkn0, tkn1) for tkn0, tkn1 in product(bancor_pol_tkns, [self.ConfigObj.WETH_ADDRESS]) if tkn0 != tkn1]

def get_curve_combos(self, CC: Any) -> List[Any]:
pol_curves = [curve for curve in CC.curves if curve.params.exchange == "bancor_pol"]
carbon_curves = [curve for curve in CC.curves if curve.params.exchange in self.ConfigObj.CARBON_V1_FORKS]
other_curves = [curve for curve in CC.curves if curve.params.exchange not in ["bancor_pol"] + self.ConfigObj.CARBON_V1_FORKS]
def get_curve_combos(self, curves: List[Any]) -> List[Any]:
pol_curves = [curve for curve in curves if curve.params.exchange == "bancor_pol"]
carbon_curves = [curve for curve in curves if curve.params.exchange in self.ConfigObj.CARBON_V1_FORKS]
other_curves = [curve for curve in curves if curve.params.exchange not in ["bancor_pol"] + self.ConfigObj.CARBON_V1_FORKS]
curve_combos = [[curve] + pol_curves for curve in other_curves]

if len(carbon_curves) > 0:
Expand Down
17 changes: 7 additions & 10 deletions fastlane_bot/modes/triangle_bancor_v3_two_hop.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,24 @@ class ArbitrageFinderTriangleBancor3TwoHop(ArbitrageFinderTriangleBase):
def get_combos(self) -> List[Any]:
miniverse_combos = []

fltkns = self.CCm.byparams(exchange="bancor_v3").tknys()
CC = self.CCm.byparams(exchange="bancor_v3")
if self.ConfigObj.LIMIT_BANCOR3_FLASHLOAN_TOKENS:
# Filter out tokens that are not in the existing flashloan_tokens list
flashloan_tokens = [tkn for tkn in fltkns if tkn in self.flashloan_tokens]
flashloan_tokens = [tkn for tkn in CC.tknys() if tkn in self.flashloan_tokens]
self.ConfigObj.logger.info(f"limiting flashloan_tokens to {self.flashloan_tokens}")
else:
flashloan_tokens = fltkns
flashloan_tokens = CC.tknys()

for tkn0, tkn1 in [(tkn0, tkn1) for tkn0, tkn1 in product(flashloan_tokens, flashloan_tokens) if tkn0 != tkn1]:
bancor_curve_0 = self.CCm.bypairs(f"{self.ConfigObj.BNT_ADDRESS}/{tkn0}").byparams(exchange="bancor_v3").curves
if len(bancor_curve_0) == 0:
continue
bancor_curves_dict = {tkn: CC.bypairs(f"{self.ConfigObj.BNT_ADDRESS}/{tkn}").curves for tkn in flashloan_tokens}

bancor_curve_1 = self.CCm.bypairs(f"{self.ConfigObj.BNT_ADDRESS}/{tkn1}").byparams(exchange="bancor_v3").curves
if len(bancor_curve_1) == 0:
for tkn0, tkn1 in [(tkn0, tkn1) for tkn0, tkn1 in product(flashloan_tokens, flashloan_tokens) if tkn0 != tkn1]:
if len(bancor_curves_dict[tkn0]) == 0 or len(bancor_curves_dict[tkn1]) == 0:
continue

all_curves = list(set(self.CCm.bypairs(f"{tkn0}/{tkn1}")) | set(self.CCm.bypairs(f"{tkn1}/{tkn0}")))
carbon_curves = [curve for curve in all_curves if curve.params.exchange in self.ConfigObj.CARBON_V1_FORKS]
other_curves = [curve for curve in all_curves if curve.params.exchange not in self.ConfigObj.CARBON_V1_FORKS]
bancor_curves = bancor_curve_0 + bancor_curve_1
bancor_curves = bancor_curves_dict[tkn0] + bancor_curves_dict[tkn1]

base_dir_one = [curve for curve in carbon_curves if curve.pair == carbon_curves[0].pair]
base_dir_two = [curve for curve in carbon_curves if curve.pair != carbon_curves[0].pair]
Expand Down
5 changes: 3 additions & 2 deletions fastlane_bot/modes/triangle_multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ def get_combos(self) -> List[Any]:
if len(carbon_curves) == 0:
continue

y_match_curves = self.CCm.bypairs(set(self.CCm.filter_pairs(onein=tknx)) & set(self.CCm.filter_pairs(onein=flt)))
x_match_curves = self.CCm.bypairs(set(self.CCm.filter_pairs(onein=tkny)) & set(self.CCm.filter_pairs(onein=flt)))
filtered_curves = {tkn : set(self.CCm.filter_pairs(onein=tkn)) for tkn in [tknx, tkny, flt]}
y_match_curves = self.CCm.bypairs(filtered_curves[tknx] & filtered_curves[flt]).curves
x_match_curves = self.CCm.bypairs(filtered_curves[tkny] & filtered_curves[flt]).curves

y_match_other_curves = [curve for curve in y_match_curves if curve.params.exchange != "carbon_v1"]
if len(y_match_other_curves) == 0:
Expand Down
2 changes: 1 addition & 1 deletion fastlane_bot/modes/triangle_multi_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def get_all_relevant_pairs_info(CCm, all_relevant_pairs, carbon_v1_forks):
# Get pair info for the cohort to allow decision making at the triangle level
all_relevant_pairs_info = {}
for pair in all_relevant_pairs:
pair_curves = CCm.bypair(pair)
pair_curves = CCm.bypair(pair).curves
carbon_curves = [curve for curve in pair_curves if curve.params.exchange in carbon_v1_forks]
other_curves = [curve for curve in pair_curves if curve.params.exchange not in carbon_v1_forks]
all_relevant_pairs_info[pair] = {
Expand Down

0 comments on commit e667c92

Please sign in to comment.