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

Triangle mode refactor with pstart #676

Open
wants to merge 4 commits into
base: triangle-mode-refactor
Choose a base branch
from
Open
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
10 changes: 5 additions & 5 deletions fastlane_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,9 @@ def calculate_profit(
if fl_token not in [self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS]:
price_curves = get_prices_simple(CCm, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, fl_token)
sorted_price_curves = custom_sort(price_curves, sort_sequence, self.ConfigObj.CARBON_V1_FORKS)
self.ConfigObj.logger.debug(f"[bot.calculate_profit sort_sequence] {sort_sequence}")
self.ConfigObj.logger.debug(f"[bot.calculate_profit price_curves] {price_curves}")
self.ConfigObj.logger.debug(f"[bot.calculate_profit sorted_price_curves] {sorted_price_curves}")
# self.ConfigObj.logger.debug(f"[bot.calculate_profit sort_sequence] {sort_sequence}")
# self.ConfigObj.logger.debug(f"[bot.calculate_profit price_curves] {price_curves}")
# self.ConfigObj.logger.debug(f"[bot.calculate_profit sorted_price_curves] {sorted_price_curves}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entire code section replaced with calling ArbitrageFinderBase.calculate_profit.
Need to pull from the base branch.

if len(sorted_price_curves)>0:
fltkn_gastkn_conversion_rate = sorted_price_curves[0][-1]
flashloan_fee_amt_gastkn = Decimal(str(flashloan_fee_amt_fl_token)) / Decimal(str(fltkn_gastkn_conversion_rate))
Expand All @@ -366,8 +366,8 @@ def calculate_profit(
try:
price_curves_usd = get_prices_simple(CCm, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, self.ConfigObj.STABLECOIN_ADDRESS)
sorted_price_curves_usd = custom_sort(price_curves_usd, sort_sequence, self.ConfigObj.CARBON_V1_FORKS)
self.ConfigObj.logger.debug(f"[bot.calculate_profit price_curves_usd] {price_curves_usd}")
self.ConfigObj.logger.debug(f"[bot.calculate_profit sorted_price_curves_usd] {sorted_price_curves_usd}")
# self.ConfigObj.logger.debug(f"[bot.calculate_profit price_curves_usd] {price_curves_usd}")
# self.ConfigObj.logger.debug(f"[bot.calculate_profit sorted_price_curves_usd] {sorted_price_curves_usd}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entire code section replaced with calling ArbitrageFinderBase.calculate_profit.
Need to pull from the base branch.

usd_gastkn_conversion_rate = Decimal(str(sorted_price_curves_usd[0][-1]))
except Exception:
usd_gastkn_conversion_rate = Decimal("NaN")
Expand Down
4 changes: 1 addition & 3 deletions fastlane_bot/config/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,7 @@ def __post_init__(self):
df=self.network_df, fork_name=S.UNISWAP_V3
)
self.SOLIDLY_FEE_MAPPING = get_fee_map(df=self.network_df, fork_name=S.SOLIDLY_V2)
self.UNI_V2_FORKS = [key for key in self.UNI_V2_ROUTER_MAPPING.keys()] + [
"uniswap_v2"
]
self.UNI_V2_FORKS = [key for key in self.UNI_V2_ROUTER_MAPPING.keys()]
self.UNI_V3_FORKS = [key for key in self.UNI_V3_ROUTER_MAPPING.keys()]

self.SOLIDLY_V2_ROUTER_MAPPING = get_fork_map(
Expand Down
1 change: 0 additions & 1 deletion fastlane_bot/events/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,6 @@ def init_bot(mgr: Any) -> CarbonBot:
mgr=mgr,
ConfigObj=mgr.cfg,
state=mgr.pool_data,
uniswap_v2_event_mappings=mgr.uniswap_v2_event_mappings,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may yield a functional (behavioral) change outside the scope of this PR.
Do you know the exact implications of this change?
If not, then we should leave it out of this PR (and probably open an issue on this).

exchanges=mgr.exchanges,
)
bot = CarbonBot(ConfigObj=mgr.cfg)
Expand Down
8 changes: 4 additions & 4 deletions fastlane_bot/modes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ def calculate_profit(self, src_token: str, src_profit: float, CCm: Any) -> Decim
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', 'uniswap_v2', 'uniswap_v3']
sort_sequence = ['bancor_v2','bancor_v3'] + self.ConfigObj.UNI_V2_FORKS + self.ConfigObj.UNI_V3_FORKS
price_curves = get_prices_simple(CCm, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, src_token)
sorted_price_curves = custom_sort(price_curves, sort_sequence, self.ConfigObj.CARBON_V1_FORKS)
self.ConfigObj.logger.debug(f"[modes.base.calculate_profit sort_sequence] {sort_sequence}")
self.ConfigObj.logger.debug(f"[modes.base.calculate_profit price_curves] {price_curves}")
self.ConfigObj.logger.debug(f"[modes.base.calculate_profit sorted_price_curves] {sorted_price_curves}")
# self.ConfigObj.logger.debug(f"[modes.base.calculate_profit sort_sequence] {sort_sequence}")
# self.ConfigObj.logger.debug(f"[modes.base.calculate_profit price_curves] {price_curves}")
# self.ConfigObj.logger.debug(f"[modes.base.calculate_profit sorted_price_curves] {sorted_price_curves}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entire function section revised.
Need to pull from the base branch.

assert len(sorted_price_curves) > 0, f"[modes.base.calculate_profit] 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]))
return Decimal(str(src_profit))
Expand Down
33 changes: 27 additions & 6 deletions fastlane_bot/modes/base_triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
All rights reserved.
Licensed under MIT.
"""
import random
from typing import Any, List, Dict
from fastlane_bot.tools.cpc import CPCContainer
from fastlane_bot.tools.optimizer import MargPOptimizer
Expand All @@ -22,7 +23,7 @@ def find_arbitrage(self) -> Dict[List[Any], List[Any]]:
try:
container = CPCContainer(miniverse)
optimizer = MargPOptimizer(container)
params = get_params(self.CCm, container.tokens(), src_token)
params = get_params(self, container, container.tokens(), src_token)
optimization = optimizer.optimize(src_token, params=params)
trade_instructions_dic = optimization.trade_instructions(optimizer.TIF_DICTS)
trade_instructions_df = optimization.trade_instructions(optimizer.TIF_DFAGGR)
Expand All @@ -45,16 +46,36 @@ def find_arbitrage(self) -> Dict[List[Any], List[Any]]:

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

def get_params(CCm, dst_tokens, src_token):
def get_params(self, CCm, dst_tokens, src_token):
# For a triangle, the pstart of each dst_token is derived from its rate vs the src_token.
# Since Carbon orders can contain diverse prices independent of external market prices, and
# we require that the pstart be on the Carbon curve to get successful optimizer runs,
# then for Carbon orders only we must randomize the pstart from the list of available Carbon curves.
# Random selection chosen as opposed to iterating over all possible combinations.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why (and since when) do we "require that the pstart be on the Carbon curve to get successful optimizer runs"?

The code below actually opts for a pstart which is NOT on a Crabon curve, as it literally sorts the list of curves such that all Carbon curves are at the end of that list, and it then picks the price of the first curve on that list.


What exactly does choosing a random carbon strategy to get the price from achieve here "as opposed to iterating over all possible combinations"?

First of all, It's not like we were iterating these curves prior to this change.
We simply chose the first curve on the list (CC[0]), and then used its price for pstart.

Second, even if for some reason we need a random Carbon curve here, the first curve is just as random as any other curve on that list, since you have no knowledge of how this list was generated to begin with.

Third, adding randomness in a system requires a really (REALLY) good reason, as it turns the behavior of that system inconsistent, unexpected, non-deterministic, hard to test and hard to reproduce any reported issues.

For example, reducing the probability of collisions between different bot operators is a good reason for choosing a random arbitrage-opportunity (at the top layer). Such reason does not appear to be viable in the case at hand.


# ASSUMPTIONS: There must be a complete triangle arb available i.e. src_token->dst_token1->dst_token2->src_token
sort_sequence = ['bancor_v2','bancor_v3'] + self.ConfigObj.UNI_V2_FORKS + self.ConfigObj.UNI_V3_FORKS
pstart = {src_token: 1}
for dst_token in [token for token in dst_tokens if token != src_token]:
CC = CCm.bytknx(dst_token).bytkny(src_token)
curves = list(CCm.bytknx(dst_token).bytkny(src_token))
CC = CPCContainer(custom_sort(curves, sort_sequence, self.ConfigObj.CARBON_V1_FORKS))
if CC:
pstart[dst_token] = CC[0].p
if CC[0].params['exchange'] in self.ConfigObj.CARBON_V1_FORKS: #only carbon curve options left
pstart[dst_token] = random.choice(CC).p
else:
pstart[dst_token] = CC[0].p
else:
CC = CCm.bytknx(src_token).bytkny(dst_token)
curves = list(CCm.bytknx(src_token).bytkny(dst_token))
CC = CPCContainer(custom_sort(curves, sort_sequence, self.ConfigObj.CARBON_V1_FORKS))
if CC:
pstart[dst_token] = 1 / CC[0].p
if CC[0].params['exchange'] in self.ConfigObj.CARBON_V1_FORKS: #only carbon curve options left
pstart[dst_token] = 1/(random.choice(CC).p)
else:
pstart[dst_token] = 1 / CC[0].p
else:
return None
return {"pstart": pstart}

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.params['exchange'] in carbon_v1_forks else sort_order.get(item.params['exchange'], float('inf')))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code for sorting the list of curves by order of exchange-significance (B2, B3, U2, U3, Carbon) is already implemented in the base class (please pull from the base branch first because it has been slightly modified since you posted this PR).

But even more importantly - you are using it here only for the triangle modes.
If it does indeed the improve the outcome, then it should most likely be implemented for all the other modes as well (probably in the base class, where this code is already implemented anyway).

And as explained in a previous comment - the randomization of Carbon curve prices seems completely out of place. A new list of curves is generated on every iteration of the bot, and the first Carbon curve is as random as any other Carbon curve.

10 changes: 7 additions & 3 deletions fastlane_bot/modes/triangle_multi_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
class ArbitrageFinderTriangleMultiComplete(ArbitrageFinderTriangleBase):
def get_combos(self) -> List[Any]:
combos = []
flashloan_tokens = [x.replace(self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS) for x in self.flashloan_tokens]
flashloan_tokens = list(set(flashloan_tokens))

for flt in self.flashloan_tokens:
for flt in flashloan_tokens:
# Get the Carbon pairs
carbon_pairs = sort_pairs(set([curve.pair for curve in self.CCm.curves if curve.params.exchange in self.ConfigObj.CARBON_V1_FORKS]))

# Create a set of unique tokens excluding the flashloan token
x_tokens = {token for pair in carbon_pairs for token in pair.split("/") if token != flt}
x_tokens = list(set([x.replace(self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS) for x in x_tokens]))

# Get relevant pairs containing the flashloan token
flt_x_pairs = sort_pairs([f"{x_token}/{flt}" for x_token in x_tokens])
Expand All @@ -32,11 +35,11 @@ def get_combos(self) -> List[Any]:

# Note the relevant pairs
all_relevant_pairs = flt_x_pairs + x_y_pairs
self.ConfigObj.logger.debug(f"len(all_relevant_pairs) {len(all_relevant_pairs)}")
self.ConfigObj.logger.debug(f"[triangle_multi_complete.get_combos] all_relevant_pairs: {len(all_relevant_pairs)}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to go wild, especially in the case of debug loggings.
This convention generates a lot more occurrences of every function name in the code, making it harder to track down when searching for them in actual code (i.e., not within quoted strings).


# Generate triangle groups
triangle_groups = get_triangle_groups(flt, x_y_pairs)
self.ConfigObj.logger.debug(f"len(triangle_groups) {len(triangle_groups)}")
self.ConfigObj.logger.debug(f"[triangle_multi_complete.get_combos] triangle_groups: {len(triangle_groups)}")

# Get pair info for the cohort
all_relevant_pairs_info = get_all_relevant_pairs_info(self.CCm, all_relevant_pairs, self.ConfigObj.CARBON_V1_FORKS)
Expand All @@ -46,6 +49,7 @@ def get_combos(self) -> List[Any]:

# Get [(flt,curves)] analysis set for the flashloan token
flt_triangle_analysis_set = get_analysis_set_per_flt(flt, valid_triangles, all_relevant_pairs_info)
self.ConfigObj.logger.debug(f"[triangle_multi_complete.get_combos] flt_triangle_analysis_set {flt, len(flt_triangle_analysis_set)}")

# The entire analysis set for all flashloan tokens
combos.extend(flt_triangle_analysis_set)
Expand Down
Loading