diff --git a/README.md b/README.md index ed30f245b..aab66cb5a 100644 --- a/README.md +++ b/README.md @@ -107,13 +107,10 @@ You can configure the Fastlane Arbitrage Bot using the options in the `@click.op - **Triangular**: This includes arbitrage trades between three liquidity pools that can create a triangular route, starting and ending in the same token. For example, USDC > ETH, ETH > LINK, LINK > USDC - **Multi**: These modes can trade through multiple Carbon orders as a single trade. - **arb_mode options**: - - **single**: Pairwise arbitrage between one Carbon curve and one other exchange curve. - - **multi** Pairwise arbitrage between **multiple** Carbon curves and one other exchange curve. - - **triangle**: Triangular arbitrage between one Carbon curve and two other exchange curves. - - **multi_triangle**: Triangular arbitrage between **multiple** Carbon curves and two other exchange curves. + - **multi_triangle**: Triangular arbitrage between multiple Carbon curves and two other exchange curves. + - **multi_triangle_complete**: Triangular arbitrage between multiple Carbon curves and two other exchange curves (experimental). - **b3_two_hop**: Triangular arbitrage - the same as bancor_v3 mode but more gas-efficient. - **multi_pairwise_pol**: Pairwise multi-mode that always routes through the Bancor protocol-owned liquidity contract. - - **multi_pairwise_bal**: Pairwise multi-mode that always routes through Balancer. - **multi_pairwise_all**: **(Default)** Pairwise multi-mode that searches all available exchanges for pairwise arbitrage. - **flashloan_tokens** (str): Tokens the bot can use for flash loans. Specify token addresses as a comma-separated string (e.g., 0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2). - **n_jobs** (int): The number of parallel jobs to run. The default, -1, will use all available cores for the process. @@ -145,7 +142,7 @@ You can configure the Fastlane Arbitrage Bot using the options in the `@click.op Specify options in the command line. For example: ```bash -poetry run python main.py --arb_mode=multi --polling_interval=12 --reorg_delay=10 --loglevel=INFO +poetry run python main.py --arb_mode=multi_pairwise_all --polling_interval=12 --reorg_delay=10 --loglevel=INFO ``` ## Troubleshooting @@ -176,7 +173,7 @@ poetry run python main.py --arb_mode=b3_two_hop --alchemy_max_block_fetch=200 -- #### Carbon-focused pairwise arbitrage ```commandline -poetry run python main.py --arb_mode=multi --alchemy_max_block_fetch=200 --loglevel=INFO --backdate_pools=False --polling_interval=0 --reorg_delay=0 --run_data_validator=False --default_min_profit_gas_token=0.01 --randomizer=2 --exchanges=bancor_v3,bancor_v2,carbon_v1,uniswap_v3,uniswap_v2,sushiswap_v2,balancer,pancakeswap_v2,pancakeswap_v3 --flashloan_tokens="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE,0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" +poetry run python main.py --arb_mode=multi_pairwise_all --alchemy_max_block_fetch=200 --loglevel=INFO --backdate_pools=False --polling_interval=0 --reorg_delay=0 --run_data_validator=False --default_min_profit_gas_token=0.01 --randomizer=2 --exchanges=bancor_v3,bancor_v2,carbon_v1,uniswap_v3,uniswap_v2,sushiswap_v2,balancer,pancakeswap_v2,pancakeswap_v3 --flashloan_tokens="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE,0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" ``` #### Unfocused pairwise arbitrage ```commandline diff --git a/fastlane_bot/bot.py b/fastlane_bot/bot.py index 31090b8c5..bf8ba3843 100644 --- a/fastlane_bot/bot.py +++ b/fastlane_bot/bot.py @@ -66,16 +66,13 @@ split_carbon_trades, maximize_last_trade_per_tkn ) -from fastlane_bot.tools.cpc import ConstantProductCurve as CPC, CPCContainer, T +from fastlane_bot.tools.cpc import ConstantProductCurve as CPC, CPCContainer from .config.constants import FLASHLOAN_FEE_MAP from .events.interface import QueryInterface -from .modes.pairwise_multi import FindArbitrageMultiPairwise from .modes.pairwise_multi_all import FindArbitrageMultiPairwiseAll from .modes.pairwise_multi_pol import FindArbitrageMultiPairwisePol -from .modes.pairwise_single import FindArbitrageSinglePairwise from .modes.triangle_multi import ArbitrageFinderTriangleMulti from .modes.triangle_multi_complete import ArbitrageFinderTriangleMultiComplete -from .modes.triangle_single import ArbitrageFinderTriangleSingle from .modes.triangle_bancor_v3_two_hop import ArbitrageFinderTriangleBancor3TwoHop from .utils import num_format @@ -103,9 +100,6 @@ class CarbonBot: SCALING_FACTOR = 0.999 ARB_FINDER = { - "single": FindArbitrageSinglePairwise, - "multi": FindArbitrageMultiPairwise, - "triangle": ArbitrageFinderTriangleSingle, "multi_triangle": ArbitrageFinderTriangleMulti, "b3_two_hop": ArbitrageFinderTriangleBancor3TwoHop, "multi_pairwise_pol": FindArbitrageMultiPairwisePol, @@ -554,9 +548,6 @@ def get_prices_simple(self, CCm, tkn0, tkn1): curve_prices += [(x.params['exchange'],x.descr,x.cid,1/x.p) for x in CCm.bytknx(tkn1).bytkny(tkn0)] return curve_prices - # Global constant for Carbon Forks ordering - CARBON_SORTING_ORDER = float('inf') - # Create a sort order mapping function def create_sort_order(self, sort_sequence): # Create a dictionary mapping from sort sequence to indices, except for Carbon Forks @@ -564,11 +555,7 @@ def create_sort_order(self, sort_sequence): # Define the sort key function separately def sort_key(self, item, sort_order): - # Check if the item is Carbon Forks - if item[0] in self.ConfigObj.CARBON_V1_FORKS: - return self.CARBON_SORTING_ORDER - # Otherwise, use the sort order from the dictionary, or a default high value - return sort_order.get(item[0], self.CARBON_SORTING_ORDER - 1) + return float('inf') if item[0] in self.ConfigObj.CARBON_V1_FORKS else sort_order.get(item[0], float('inf')) # Define the custom sort function def custom_sort(self, data, sort_sequence): diff --git a/fastlane_bot/modes/__init__.py b/fastlane_bot/modes/__init__.py index b5f2014df..e82ba65d6 100644 --- a/fastlane_bot/modes/__init__.py +++ b/fastlane_bot/modes/__init__.py @@ -24,13 +24,11 @@ - ``ArbitrageFinderBase`` (``base``): fundamental base class - ``ArbitrageFinderPairwiseBase`` (``base_pairwise``): base class for pairwise arbitrages - - ``FindArbitrageSinglePairwise`` (``pairwise_single``) - - ``FindArbitrageMultiPairwise`` (``pairwise_multi``) - ``FindArbitrageMultiPairwiseAll`` (``pairwise_multi_all``) - ``FindArbitrageMultiPairwisePol`` (``pairwise_multi_pol``) - ``ArbitrageFinderTriangleBase`` (``base_triangle``): base class for triangle arbitrages - - ``ArbitrageFinderTriangleSingle`` (``triangle_single``) - ``ArbitrageFinderTriangleMulti`` (``triangle_multi``) + - ``ArbitrageFinderTriangleMultiComplete`` (``triangle_multi_complete``) - ``ArbitrageFinderTriangleBancor3TwoHop`` (``triangle_bancor_v3_two_hop``) diff --git a/fastlane_bot/modes/base.py b/fastlane_bot/modes/base.py index 0f1cf1a4c..5384827f1 100644 --- a/fastlane_bot/modes/base.py +++ b/fastlane_bot/modes/base.py @@ -13,10 +13,6 @@ from _decimal import Decimal import pandas as pd -from fastlane_bot.tools.cpc import T -from fastlane_bot.utils import num_format - - class ArbitrageFinderBase: """ Base class for all arbitrage finder modes @@ -47,27 +43,10 @@ def __init__( self.base_exchange = "bancor_v3" if arb_mode == "bancor_v3" else "carbon_v1" @abc.abstractmethod - def find_arbitrage( - self, - candidates: List[Any] = None, - ops: Tuple = None, - best_profit: float = 0, - profit_src: float = 0, - ) -> Union[List, Tuple]: + def find_arbitrage() -> Union[List, Tuple]: """ See subclasses for details - Parameters - ---------- - candidates : List[Any], optional - List of candidates, by default None - ops : Tuple, optional - Tuple of operations, by default None - best_profit : float, optional - Best profit so far, by default 0 - profit_src : float, optional - Profit source, by default 0 - Returns ------- Union[List, Tuple] @@ -75,86 +54,66 @@ def find_arbitrage( """ pass - def _set_best_ops( - self, - best_profit: float, - ops: Tuple, - profit: float, - src_token: str, - trade_instructions: Any, - trade_instructions_df: pd.DataFrame, - trade_instructions_dic: Dict[str, Any], - ) -> Tuple[float, Tuple]: - """ - Set the best operations. - - Parameters: - - """ - self.ConfigObj.logger.debug("[modes.base._set_best_ops] *************") - self.ConfigObj.logger.debug( - f"[modes.base._set_best_ops] New best profit: {profit}" - ) - - # Update the best profit and source token - best_profit = profit - best_src_token = src_token - - # Update the best trade instructions - best_trade_instructions_df = trade_instructions_df - best_trade_instructions_dic = trade_instructions_dic - best_trade_instructions = trade_instructions - - self.ConfigObj.logger.debug( - f"[modes.base._set_best_ops] best_trade_instructions_df: {best_trade_instructions_df}" - ) - - # Update the optimal operations - ops = ( - best_profit, - best_trade_instructions_df, - best_trade_instructions_dic, - best_src_token, - best_trade_instructions, - ) - - self.ConfigObj.logger.debug("[modes.base.calculate_profit] *************") - - return best_profit, ops - def get_prices_simple(self, CCm, tkn0, tkn1): curve_prices = [(x.params['exchange'],x.descr,x.cid,x.p) for x in CCm.bytknx(tkn0).bytkny(tkn1)] curve_prices += [(x.params['exchange'],x.descr,x.cid,1/x.p) for x in CCm.bytknx(tkn1).bytkny(tkn0)] return curve_prices - # Global constant for 'carbon_v1' order - CARBON_SORTING_ORDER = float('inf') - # Create a sort order mapping function def create_sort_order(self, sort_sequence): - # Create a dictionary mapping from sort sequence to indices, except for 'carbon_v1' - return {key: index for index, key in enumerate(sort_sequence) if key != 'carbon_v1'} + # Create a dictionary mapping from sort sequence to indices, except for Carbon Forks + return {key: index for index, key in enumerate(sort_sequence) if key not in self.ConfigObj.CARBON_V1_FORKS} # Define the sort key function separately def sort_key(self, item, sort_order): - # Check if the item is 'carbon_v1' - if item[0] in self.ConfigObj.CARBON_V1_FORKS: - return self.CARBON_SORTING_ORDER - # Otherwise, use the sort order from the dictionary, or a default high value - return sort_order.get(item[0], self.CARBON_SORTING_ORDER - 1) + return float('inf') if item[0] in self.ConfigObj.CARBON_V1_FORKS else sort_order.get(item[0], float('inf')) # Define the custom sort function def custom_sort(self, data, sort_sequence): sort_order = self.create_sort_order(sort_sequence) return sorted(data, key=lambda item: self.sort_key(item, sort_order)) + def update_results( + self, + src_token: str, + r, + trade_instructions_dic, + trade_instructions_df, + trade_instructions, + candidates, + best_profit, + ops, + ): + # Calculate the profit + profit = self.calculate_profit(src_token, -r.result, self.CCm) + if str(profit) == "nan": + self.ConfigObj.logger.debug("profit is nan, skipping") + else: + # Handle candidates based on conditions + candidates += self.handle_candidates( + profit, + trade_instructions_df, + trade_instructions_dic, + src_token, + trade_instructions, + ) + # Find the best operations + best_profit, ops = self.find_best_operations( + best_profit, + ops, + profit, + trade_instructions_df, + trade_instructions_dic, + src_token, + trade_instructions, + ) + return best_profit, ops + def calculate_profit( self, src_token: str, profit_src: float, CCm: Any, - cids: List[str], - profit: int = 0, ) -> float: """ Calculate profit based on the source token. @@ -201,7 +160,6 @@ def get_netchange(trade_instructions_df: pd.DataFrame) -> List[float]: def handle_candidates( self, - best_profit: float, profit: float, trade_instructions_df: pd.DataFrame, trade_instructions_dic: Dict[str, Any], @@ -213,8 +171,6 @@ def handle_candidates( Parameters: ---------- - best_profit : float - Best profit profit : float Profit trade_instructions_df : pd.DataFrame @@ -290,29 +246,12 @@ def find_best_operations( condition_better_profit = profit > best_profit condition_zeros_one_token = max(netchange) < 1e-4 if condition_better_profit and condition_zeros_one_token: - return self._set_best_ops( - best_profit, - ops, + best_profit = profit + ops = ( profit, - src_token, - trade_instructions, trade_instructions_df, trade_instructions_dic, + src_token, + trade_instructions, ) return best_profit, ops - - def _check_limit_flashloan_tokens_for_bancor3(self): - """ - Limit the flashloan tokens for bancor v3. - """ - fltkns = self.CCm.byparams(exchange="bancor_v3").tknys() - if self.ConfigObj.LIMIT_BANCOR3_FLASHLOAN_TOKENS: - # Filter out tokens that are not in the existing flashloan_tokens list - self.flashloan_tokens = [ - tkn for tkn in fltkns if tkn in self.flashloan_tokens - ] - self.ConfigObj.logger.info( - f"[modes.base._check_limit_flashloan_tokens_for_bancor3] limiting flashloan_tokens to {self.flashloan_tokens}" - ) - else: - self.flashloan_tokens = fltkns diff --git a/fastlane_bot/modes/base_pairwise.py b/fastlane_bot/modes/base_pairwise.py index d5df65ef9..ae2d52d42 100644 --- a/fastlane_bot/modes/base_pairwise.py +++ b/fastlane_bot/modes/base_pairwise.py @@ -8,53 +8,55 @@ All rights reserved. Licensed under MIT. """ -import abc -import itertools -from typing import List, Tuple, Any, Union +from typing import List, Tuple, Union -from fastlane_bot.modes.base import ArbitrageFinderBase from fastlane_bot.tools.cpc import CPCContainer - +from fastlane_bot.tools.optimizer import PairOptimizer +from fastlane_bot.modes.base import ArbitrageFinderBase class ArbitrageFinderPairwiseBase(ArbitrageFinderBase): - """ - Base class for pairwise arbitrage finder modes - """ - - @abc.abstractmethod - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py - """ - pass - - @staticmethod - def get_combos( - CCm: CPCContainer, flashloan_tokens: List[str] - ) -> Tuple[List[Any], List[Any]]: - """ - Get combos for pairwise arbitrage - - Parameters - ---------- - CCm : CPCContainer - Container for all the curves - flashloan_tokens : list - List of flashloan tokens - - Returns - ------- - all_tokens : list - List of all tokens - - """ - all_tokens = CCm.tokens() - flashloan_tokens_intersect = all_tokens.intersection(set(flashloan_tokens)) - combos = [ - (tkn0, tkn1) - for tkn0, tkn1 in itertools.product(all_tokens, flashloan_tokens_intersect) - # tkn1 is always the token being flash loaned - # note that the pair is tkn0/tkn1, ie tkn1 is the quote token - if tkn0 != tkn1 - ] - return all_tokens, combos + def find_arbitrage(self) -> Union[List, Tuple]: + all_tokens, combos = self.get_combos(self.flashloan_tokens, self.CCm) + if self.result == self.AO_TOKENS: + return all_tokens, combos + + candidates = [] + best_profit = 0 + ops = None + + for tkn0, tkn1 in combos: + CC = self.CCm.bypairs(f"{tkn0}/{tkn1}") + if len(CC) < 2: + continue + + for curve_combo in self.get_curve_combos(CC): + src_token = tkn1 + if len(curve_combo) < 2: + continue + try: + CC_cc = CPCContainer(curve_combo) + O = PairOptimizer(CC_cc) + pstart = {tkn0: CC_cc.bypairs(f"{tkn0}/{tkn1}")[0].p} + r = O.optimize(src_token, params=dict(pstart=pstart)) + trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) + trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) + trade_instructions = r.trade_instructions() + except Exception as e: + self.ConfigObj.logger.debug(f"[base_pairwise] {e}") + continue + if trade_instructions_dic is None or len(trade_instructions_dic) < 2: + # Failed to converge + continue + # Update the results + best_profit, ops = self.update_results( + src_token, + r, + trade_instructions_dic, + trade_instructions_df, + trade_instructions, + candidates, + best_profit, + ops, + ) + + return candidates if self.result == self.AO_CANDIDATES else ops diff --git a/fastlane_bot/modes/base_triangle.py b/fastlane_bot/modes/base_triangle.py index 522283b36..5288a9c93 100644 --- a/fastlane_bot/modes/base_triangle.py +++ b/fastlane_bot/modes/base_triangle.py @@ -8,308 +8,58 @@ All rights reserved. Licensed under MIT. """ -import abc -import itertools -from typing import List, Any, Tuple, Union - -import pandas as pd +from typing import List, Tuple, Union +from fastlane_bot.tools.cpc import CPCContainer +from fastlane_bot.tools.optimizer import MargPOptimizer from fastlane_bot.modes.base import ArbitrageFinderBase -from fastlane_bot.tools.cpc import T - -def sort_pairs(pairs): - # Clean up the pairs alphabetically - return ["/".join(sorted(pair.split('/'))) for pair in pairs] - -def flatten_nested_items_in_list(nested_list): - # unpack nested items - flattened_list = [] - for items in nested_list: - flat_list = [] - for item in items: - if isinstance(item, list): - flat_list.extend(item) - else: - flat_list.append(item) - flattened_list.append(flat_list) - return flattened_list - -def get_triangle_groups(flt, x_y_pairs): - # Get groups of triangles that conform to (flt/x , x/y, y/flt) where x!=y - triangle_groups = [] - for pair in x_y_pairs: - x,y = pair.split('/') - triangle_groups += [("/".join(sorted([flt,x])), pair, "/".join(sorted([flt,y])))] - return triangle_groups - -def get_triangle_groups_stats(triangle_groups, all_relevant_pairs_info): - # Get the stats on the triangle group cohort for decision making - valid_carbon_triangles = [] - for triangle in triangle_groups: - path_len = 0 - has_carbon = False - for pair in triangle: - if all_relevant_pairs_info[pair]['all_counts'] > 0: - path_len += 1 - if all_relevant_pairs_info[pair]['carbon_counts'] > 0: - has_carbon = True - if path_len == 3 and has_carbon == True: - valid_carbon_triangles.append(triangle) - return valid_carbon_triangles class ArbitrageFinderTriangleBase(ArbitrageFinderBase): - """ - Base class for triangular arbitrage finder modes - """ - - @abc.abstractmethod - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py - """ - pass + def find_arbitrage(self) -> Union[List, Tuple]: + self.handle_exchange() + combos = self.get_combos(self.flashloan_tokens, self.CCm) - @staticmethod - def get_miniverse( - y_match_curves_not_carbon: List[Any], - base_exchange_curves: List[Any], - x_match_curves_not_carbon: List[Any], - flt: str, - arb_mode: str, - combos: List[Any], - ) -> List[Any]: - """ - Get miniverse for triangular arbitrage + candidates = [] + best_profit = 0 + ops = None - Parameters - ---------- - y_match_curves_not_carbon : list - List of curves that match the y token and are not on carbon - base_exchange_curves : list - List of curves on the base exchange - x_match_curves_not_carbon : list - List of curves that match the x token and are not on carbon - flt : str - Flashloan token - arb_mode : str - Arbitrage mode - combos : list - List of combos - - Returns - ------- - combos : list - List of combos - - """ - if arb_mode in ["single_triangle", "triangle"]: - miniverses = list( - itertools.product( - y_match_curves_not_carbon, - base_exchange_curves, - x_match_curves_not_carbon, - ) - ) - else: - external_curve_combos = list( - itertools.product(y_match_curves_not_carbon, x_match_curves_not_carbon) + for src_token, miniverse in combos: + try: + CC_cc = CPCContainer(miniverse) + O = MargPOptimizer(CC_cc) + pstart = build_pstart(self.CCm, CC_cc, src_token, self.ConfigObj) + r = O.optimize(src_token, params=dict(pstart=pstart)) + trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) + trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) + trade_instructions = r.trade_instructions() + except Exception as e: + self.ConfigObj.logger.debug(f"[base_triangle] {e}") + continue + if trade_instructions_dic is None or len(trade_instructions_dic) < 3: + # Failed to converge + continue + # Update the results + best_profit, ops = self.update_results( + src_token, + r, + trade_instructions_dic, + trade_instructions_df, + trade_instructions, + candidates, + best_profit, + ops, ) - miniverses = [ - base_exchange_curves + list(combo) for combo in external_curve_combos - ] - if miniverses: - combos += list(zip([flt] * len(miniverses), miniverses)) - return combos - - def get_combos( - self, flashloan_tokens: List[str], CCm: Any, arb_mode: str - ) -> Tuple[List[str], List[Any]]: - """ - Get combos for triangular arbitrage - - Parameters - ---------- - flashloan_tokens : list - List of flashloan tokens - CCm : object - CCm object - arb_mode : str - Arbitrage mode - - Returns - ------- - combos : list - List of combos - - """ - combos = [] - if arb_mode in ["b3_two_hop"]: - combos = [ - (tkn0, tkn1) - for tkn0, tkn1 in itertools.product(flashloan_tokens, flashloan_tokens) - # note that the pair is tkn0/tkn1, ie tkn1 is the quote token - if tkn0 != tkn1 - ] - else: - all_base_exchange_curves = CCm.byparams(exchange=self.base_exchange).curves - for flt in flashloan_tokens: # may wish to run this for one flt at a time - non_flt_base_exchange_curves = [ - x for x in all_base_exchange_curves if flt not in x.pair - ] - for non_flt_base_exchange_curve in non_flt_base_exchange_curves: - target_tkny = non_flt_base_exchange_curve.tkny - target_tknx = non_flt_base_exchange_curve.tknx - base_exchange_curves = ( - CCm.bypairs(f"{target_tknx}/{target_tkny}") - .byparams(exchange=self.base_exchange) - .curves - ) - if len(base_exchange_curves) == 0: - continue - - base_direction_pair = base_exchange_curves[0].pair - base_direction_one = [curve for curve in base_exchange_curves if curve.pair == base_direction_pair] - base_direction_two = [curve for curve in base_exchange_curves if curve.pair != base_direction_pair] - assert len(base_exchange_curves) == len(base_direction_one) + len(base_direction_two) - y_match_curves = CCm.bypairs( - set(CCm.filter_pairs(onein=target_tknx)) - & set(CCm.filter_pairs(onein=flt)) - ) - x_match_curves = CCm.bypairs( - set(CCm.filter_pairs(onein=target_tkny)) - & set(CCm.filter_pairs(onein=flt)) - ) - - y_match_curves_not_carbon = [ - x - for x in y_match_curves - if x.params.exchange != self.base_exchange - ] - if len(y_match_curves_not_carbon) == 0: - continue - x_match_curves_not_carbon = [ - x - for x in x_match_curves - if x.params.exchange != self.base_exchange - ] - if len(x_match_curves_not_carbon) == 0: - continue - if len(base_direction_one) > 0: - combos = self.get_miniverse( - y_match_curves_not_carbon, - base_direction_one, - x_match_curves_not_carbon, - flt, - arb_mode, - combos, - ) - if len(base_direction_two) > 0: - combos = self.get_miniverse( - y_match_curves_not_carbon, - base_direction_two, - x_match_curves_not_carbon, - flt, - arb_mode, - combos, - ) - return combos - - def get_all_relevant_pairs_info(self, CCm, all_relevant_pairs): - # Get pair info for the cohort to allow decision making at the triangle level - all_relevant_pairs_info = {} - for pair in all_relevant_pairs: - all_relevant_pairs_info[pair] = {} - pair_curves = CCm.bypair(pair) - carbon_curves = [] - non_carbon_curves = [] - for x in pair_curves: - if x.params.exchange in self.ConfigObj.CARBON_V1_FORKS: - carbon_curves += [x] - else: - non_carbon_curves += [x] - all_relevant_pairs_info[pair]['non_carbon_curves'] = non_carbon_curves - all_relevant_pairs_info[pair]['carbon_curves'] = carbon_curves - all_relevant_pairs_info[pair]['curves'] = non_carbon_curves + [carbon_curves] if len(carbon_curves) > 0 else non_carbon_curves # condense carbon curves into a single list - all_relevant_pairs_info[pair]['all_counts'] = len(pair_curves) - all_relevant_pairs_info[pair]['carbon_counts'] = len(carbon_curves) - return all_relevant_pairs_info - - def get_analysis_set_per_flt(self, flt, valid_triangles, all_relevant_pairs_info): - flt_triangle_analysis_set = [] - for triangle in valid_triangles: - multiverse = [all_relevant_pairs_info[pair]['curves'] for pair in triangle] - product_of_triangle = list(itertools.product(multiverse[0], multiverse[1], multiverse[2])) - triangles_to_run = flatten_nested_items_in_list(product_of_triangle) - flt_triangle_analysis_set += list(zip([flt] * len(triangles_to_run), triangles_to_run)) - - self.ConfigObj.logger.debug(f"[base_triangle.get_analysis_set_per_flt] Length of flt_triangle_analysis_set: {flt, len(flt_triangle_analysis_set)}") - return flt_triangle_analysis_set - - def get_comprehensive_triangles( - self, flashloan_tokens: List[str], CCm: Any - ) -> Tuple[List[str], List[Any]]: - """ - Get comprehensive combos for triangular arbitrage - - Parameters - ---------- - flashloan_tokens : list - List of flashloan tokens - CCm : object - CCm object - - Returns - ------- - combos : list - List of combos - - """ - combos = [] - for flt in flashloan_tokens: - - # Get the Carbon pairs - carbon_pairs = sort_pairs(set([x.pair for x in CCm.curves if x.params.exchange in self.ConfigObj.CARBON_V1_FORKS])) - - # Create a set of unique tokens, excluding 'flt' - x_tokens = {token for pair in carbon_pairs for token in pair.split('/') if token != flt} - - # Get relevant pairs containing the flashloan token - flt_x_pairs = sort_pairs([f"{x}/{flt}" for x in x_tokens]) - - # Generate all possible 2-item combinations from the unique tokens that arent the flashloan token - x_y_pairs = sort_pairs(["{}/{}".format(x, y) for x, y in itertools.combinations(x_tokens, 2)]) - - # 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)}") - - # Generate triangle groups - triangle_groups = get_triangle_groups(flt, x_y_pairs) - self.ConfigObj.logger.debug(f"len(triangle_groups) {len(triangle_groups)}") - # Get pair info for the cohort - all_relevant_pairs_info = self.get_all_relevant_pairs_info(CCm, all_relevant_pairs) - - # Generate valid triangles for the groups base on arb_mode - valid_triangles = get_triangle_groups_stats(triangle_groups, all_relevant_pairs_info) - - # Get [(flt,curves)] analysis set for the flt - flt_triangle_analysis_set = self.get_analysis_set_per_flt(flt, valid_triangles, all_relevant_pairs_info) - - # The entire analysis set for all flts - combos.extend(flt_triangle_analysis_set) - return combos + return candidates if self.result == self.AO_CANDIDATES else ops - def build_pstart(self, CCm, tkn0list, tkn1): - tkn0list = [x for x in tkn0list if x not in [tkn1]] - pstart = {} - for tkn0 in tkn0list: +def build_pstart(CCm, CC_cc, tkn1, cfg): + pstart = {tkn1: 1} + for tkn0 in [x for x in CC_cc.tokens() if x != tkn1]: + try: + pstart[tkn0] = CCm.bytknx(tkn0).bytkny(tkn1)[0].p + except: try: - pstart[tkn0] = CCm.bytknx(tkn0).bytkny(tkn1)[0].p - except: - try: - pstart[tkn0] = 1/CCm.bytknx(tkn1).bytkny(tkn0)[0].p - except Exception as e: - self.ConfigObj.logger.info(f"[pstart build] {tkn0}/{tkn1} price error {e}") - pstart[tkn1] = 1 - return pstart + pstart[tkn0] = 1/CCm.bytknx(tkn1).bytkny(tkn0)[0].p + except Exception as e: + cfg.logger.info(f"[pstart build] {tkn0}/{tkn1} price error {e}") + return pstart diff --git a/fastlane_bot/modes/pairwise_multi.py b/fastlane_bot/modes/pairwise_multi.py deleted file mode 100644 index 756345edf..000000000 --- a/fastlane_bot/modes/pairwise_multi.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Defines the Multi-pairwise arbitrage finder class - -[DOC-TODO-OPTIONAL-longer description in rst format] - ---- -(c) Copyright Bprotocol foundation 2023-24. -All rights reserved. -Licensed under MIT. -""" -from typing import List, Any, Tuple, Union, Hashable - -import pandas as pd - -from fastlane_bot.modes.base_pairwise import ArbitrageFinderPairwiseBase -from fastlane_bot.tools.cpc import CPCContainer -from fastlane_bot.tools.optimizer import MargPOptimizer, PairOptimizer - - -class FindArbitrageMultiPairwise(ArbitrageFinderPairwiseBase): - """ - Multi-pairwise arbitrage finder mode. - """ - - arb_mode = "multi_pairwise" - - def find_arbitrage( - self, - candidates: List[Any] = None, - ops: Tuple = None, - best_profit: float = 0, - profit_src: float = 0, - ) -> Union[List, Tuple]: - """ - see base.py - """ - if self.base_exchange != "carbon_v1": - raise ValueError("base_exchange must be carbon_v1 for `multi` mode") - - if candidates is None: - candidates = [] - - all_tokens, combos = self.get_combos(self.CCm, self.flashloan_tokens) - if self.result == self.AO_TOKENS: - return all_tokens, combos - - candidates = [] - self.ConfigObj.logger.debug( - f"\n ************ combos: {len(combos)} ************\n" - ) - for tkn0, tkn1 in combos: - r = None - CC = self.CCm.bypairs(f"{tkn0}/{tkn1}") - if len(CC) < 2: - continue - carbon_curves = [x for x in CC.curves if x.params.exchange in self.ConfigObj.CARBON_V1_FORKS] - not_carbon_curves = [ - x for x in CC.curves if x.params.exchange not in self.ConfigObj.CARBON_V1_FORKS - ] - curve_combos = [] - - if len(carbon_curves) > 0: - base_direction_pair = carbon_curves[0].pair - base_direction_one = [curve for curve in carbon_curves if curve.pair == base_direction_pair] - base_direction_two = [curve for curve in carbon_curves if curve.pair != base_direction_pair] - - if len(base_direction_one) > 0: - curve_combos += [[curve] + base_direction_one for curve in not_carbon_curves] - - if len(base_direction_two) > 0: - curve_combos += [[curve] + base_direction_two for curve in not_carbon_curves] - - - for curve_combo in curve_combos: - src_token = tkn1 - - if len(curve_combo) < 2: - continue - - try: - (O, profit_src, r, trade_instructions_df,) = self.run_main_flow( - curves=curve_combo, src_token=src_token, tkn0=tkn0, tkn1=tkn1 - ) - - trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) - trade_instructions = r.trade_instructions() - - except Exception: - continue - - if trade_instructions_dic is None: - continue - if len(trade_instructions_dic) < 2: - continue - - # Get the cids - cids = [ti["cid"] for ti in trade_instructions_dic] - - # Calculate the profit - profit = self.calculate_profit(src_token, profit_src, self.CCm, cids) - - if str(profit) == "nan": - self.ConfigObj.logger.debug("profit is nan, skipping") - continue - - # Handle candidates based on conditions - candidates += self.handle_candidates( - best_profit, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - # Find the best operations - best_profit, ops = self.find_best_operations( - best_profit, - ops, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - return candidates if self.result == self.AO_CANDIDATES else ops - - def get_wrong_direction_cids( - self, tkn0_into_carbon: bool, trade_instructions_df: pd.DataFrame - ) -> List[Hashable]: - """ - Get the cids of the wrong direction curves - - Parameters - ---------- - tkn0_into_carbon : bool - True if tkn0 is being converted into carbon, False otherwise - trade_instructions_df : pd.DataFrame - The trade instructions dataframe - - Returns - ------- - List[str] - The cids of the wrong direction curves - """ - return [ - idx - for idx, row in trade_instructions_df.iterrows() - if ( - (tkn0_into_carbon and row[0] < 0) - or (not tkn0_into_carbon and row.iloc[0] > 0) - ) - and ("-0" in idx or "-1" in idx) - ] - - @staticmethod - def run_main_flow( - curves: List[Any], src_token: str, tkn0: str, tkn1: str - ) -> Tuple[Any, float, Any, pd.DataFrame]: - """ - Run main flow to find arbitrage. - """ - CC_cc = CPCContainer(curves) - O = PairOptimizer(CC_cc) - pstart = { - tkn0: CC_cc.bypairs(f"{tkn0}/{tkn1}")[0].p - } # this intentionally selects the non_carbon curve - r = O.optimize(src_token, params=dict(pstart=pstart)) - profit_src = -r.result - trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) - return O, profit_src, r, trade_instructions_df - - def process_wrong_direction_pools( - self, curve_combo: List[Any], wrong_direction_cids: List[Hashable] - ) -> [str]: - """ - Process curves with wrong direction pools. - """ - new_curves = [ - curve for curve in curve_combo if curve.cid not in wrong_direction_cids - ] - return new_curves diff --git a/fastlane_bot/modes/pairwise_multi_all.py b/fastlane_bot/modes/pairwise_multi_all.py index 20cdbb46f..3fd68a017 100644 --- a/fastlane_bot/modes/pairwise_multi_all.py +++ b/fastlane_bot/modes/pairwise_multi_all.py @@ -8,179 +8,60 @@ All rights reserved. Licensed under MIT. """ -import itertools -from typing import List, Any, Tuple, Union, Hashable - -import pandas as pd +from typing import List, Any, Tuple +from itertools import product from fastlane_bot.modes.base_pairwise import ArbitrageFinderPairwiseBase -from fastlane_bot.tools.cpc import CPCContainer -from fastlane_bot.tools.optimizer import MargPOptimizer, PairOptimizer - class FindArbitrageMultiPairwiseAll(ArbitrageFinderPairwiseBase): - """ - Multi-pairwise arbitrage finder mode. - """ - arb_mode = "multi_pairwise_all" - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py + def get_combos(self, flashloan_tokens: List[str], CCm: Any) -> Tuple[List[str], List[Any]]: """ - - if candidates is None: - candidates = [] - - all_tokens, combos = self.get_combos(self.CCm, self.flashloan_tokens) - if self.result == self.AO_TOKENS: - return all_tokens, combos - #print(f"combos = {combos}") - - candidates = [] - self.ConfigObj.logger.debug( - f"\n ************ combos: {len(combos)} ************\n" - ) - - for tkn0, tkn1 in combos: - r = None - CC = self.CCm.bypairs(f"{tkn0}/{tkn1}") - if len(CC) < 2: - continue - carbon_curves = [x for x in CC.curves if x.params.exchange in self.ConfigObj.CARBON_V1_FORKS] - not_carbon_curves = [ - x for x in CC.curves if x.params.exchange not in self.ConfigObj.CARBON_V1_FORKS - ] - - curve_combos = [[_curve0] + [_curve1] for _curve0 in not_carbon_curves for _curve1 in not_carbon_curves if (_curve0 != _curve1)] - - if len(carbon_curves) > 0: - base_direction_pair = carbon_curves[0].pair - base_direction_one = [curve for curve in carbon_curves if curve.pair == base_direction_pair] - base_direction_two = [curve for curve in carbon_curves if curve.pair != base_direction_pair] - curve_combos = [] - - if len(base_direction_one) > 0: - curve_combos += [[curve] + base_direction_one for curve in not_carbon_curves] - - if len(base_direction_two) > 0: - curve_combos += [[curve] + base_direction_two for curve in not_carbon_curves] - - if len(carbon_curves) >= 2: - curve_combos += [carbon_curves] - - for curve_combo in curve_combos: - src_token = tkn1 - if len(curve_combo) < 2: - continue - try: - ( - O, - profit_src, - r, - trade_instructions_df, - ) = self.run_main_flow(curves=curve_combo, src_token=src_token, tkn0=tkn0, tkn1=tkn1) - except ValueError: - #Optimizer did not converge - continue - - - trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) - trade_instructions = r.trade_instructions() - if trade_instructions_dic is None: - continue - if len(trade_instructions_dic) < 2: - continue - # Get the cids - cids = [ti["cid"] for ti in trade_instructions_dic] - - # Calculate the profit - profit = self.calculate_profit(src_token, profit_src, self.CCm, cids) - if str(profit) == "nan": - self.ConfigObj.logger.debug("profit is nan, skipping") - continue - - # Handle candidates based on conditions - candidates += self.handle_candidates( - best_profit, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - # Find the best operations - best_profit, ops = self.find_best_operations( - best_profit, - ops, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - return candidates if self.result == self.AO_CANDIDATES else ops - - @staticmethod - def get_wrong_direction_cids( - tkn0_into_carbon: bool, trade_instructions_df: pd.DataFrame - ) -> List[Hashable]: - """ - Get the cids of the wrong direction curves + Get combos for pairwise arbitrage Parameters ---------- - tkn0_into_carbon : bool - True if tkn0 is being converted into carbon, False otherwise - trade_instructions_df : pd.DataFrame - The trade instructions dataframe + flashloan_tokens : list + List of flashloan tokens + CCm : object + CCm object Returns ------- - List[str] - The cids of the wrong direction curves + all_tokens : list + List of all tokens + """ - return [ - idx - for idx, row in trade_instructions_df.iterrows() - if ( - (tkn0_into_carbon and row.iloc[0] < 0) - or (not tkn0_into_carbon and row.iloc[0] > 0) - ) - and ("-0" in idx or "-1" in idx) + all_tokens = CCm.tokens() + flashloan_tokens_intersect = all_tokens.intersection(set(flashloan_tokens)) + combos = [ + (tkn0, tkn1) + for tkn0, tkn1 in product(all_tokens, flashloan_tokens_intersect) + # tkn1 is always the token being flash loaned + # note that the pair is tkn0/tkn1, ie tkn1 is the quote token + if tkn0 != tkn1 ] + return all_tokens, combos - @staticmethod - def run_main_flow( - curves: List[Any], src_token: str, tkn0: str, tkn1: str - ) -> Tuple[Any, float, Any, pd.DataFrame]: - """ - Run main flow to find arbitrage. - """ - CC_cc = CPCContainer(curves) - O = PairOptimizer(CC_cc) - pstart = { - tkn0: CC_cc.bypairs(f"{tkn0}/{tkn1}")[0].p - } # this intentionally selects the non_carbon curve + def get_curve_combos(self, CC: Any) -> List[Any]: + carbon_curves = [x for x in CC.curves if x.params.exchange in self.ConfigObj.CARBON_V1_FORKS] + not_carbon_curves = [x for x in CC.curves if x.params.exchange not in self.ConfigObj.CARBON_V1_FORKS] + curve_combos = [[_curve0] + [_curve1] for _curve0 in not_carbon_curves for _curve1 in not_carbon_curves if (_curve0 != _curve1)] - r = O.optimize(src_token, params=dict(pstart=pstart)) + if len(carbon_curves) > 0: + base_direction_pair = carbon_curves[0].pair + base_direction_one = [curve for curve in carbon_curves if curve.pair == base_direction_pair] + base_direction_two = [curve for curve in carbon_curves if curve.pair != base_direction_pair] + curve_combos = [] - profit_src = -r.result - trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) - return O, profit_src, r, trade_instructions_df + if len(base_direction_one) > 0: + curve_combos += [[curve] + base_direction_one for curve in not_carbon_curves] - @staticmethod - def process_wrong_direction_pools( - curve_combo: List[Any], wrong_direction_cids: List[Hashable] - ) -> [str]: - """ - Process curves with wrong direction pools. - """ - new_curves = [ - curve for curve in curve_combo if curve.cid not in wrong_direction_cids - ] - return new_curves + if len(base_direction_two) > 0: + curve_combos += [[curve] + base_direction_two for curve in not_carbon_curves] + + if len(carbon_curves) >= 2: + curve_combos += [carbon_curves] + return curve_combos diff --git a/fastlane_bot/modes/pairwise_multi_pol.py b/fastlane_bot/modes/pairwise_multi_pol.py index 36fc6f1cb..6ead55ce7 100644 --- a/fastlane_bot/modes/pairwise_multi_pol.py +++ b/fastlane_bot/modes/pairwise_multi_pol.py @@ -8,183 +8,24 @@ All rights reserved. Licensed under MIT. """ -from typing import List, Any, Tuple, Union, Hashable +from typing import List, Any, Tuple +from itertools import product -import pandas as pd -import itertools from fastlane_bot.modes.base_pairwise import ArbitrageFinderPairwiseBase -from fastlane_bot.tools.cpc import CPCContainer -from fastlane_bot.tools.optimizer import MargPOptimizer, PairOptimizer -from fastlane_bot.tools.cpc import T - class FindArbitrageMultiPairwisePol(ArbitrageFinderPairwiseBase): - """ - Multi-pairwise arbitrage finder mode for Bancor POL. - """ - arb_mode = "multi_pairwise_pol" - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py - """ - - all_tokens, combos = self.get_combos_pol(self.CCm, self.flashloan_tokens) - if self.result == self.AO_TOKENS: - return all_tokens, combos - - candidates = [] - self.ConfigObj.logger.debug( - f"\n ************ combos: {len(combos)} ************\n" - ) - - for tkn0, tkn1 in combos: - r = None - CC = self.CCm.bypairs(f"{tkn0}/{tkn1}") - if len(CC) < 2: - continue - pol_curves = [x for x in CC.curves if x.params.exchange == "bancor_pol"] - not_bancor_pol_curves = [ - x for x in CC.curves if x.params.exchange not in ["bancor_pol"] + self.ConfigObj.CARBON_V1_FORKS - ] - carbon_curves = [x for x in CC.curves if x.params.exchange in self.ConfigObj.CARBON_V1_FORKS] - curve_combos = [[curve] + pol_curves for curve in not_bancor_pol_curves] - - if len(carbon_curves) > 0: - base_direction_pair = carbon_curves[0].pair - base_direction_one = [curve for curve in carbon_curves if curve.pair == base_direction_pair] - base_direction_two = [curve for curve in carbon_curves if curve.pair != base_direction_pair] - - if len(base_direction_one) > 0: - curve_combos += [[curve] + base_direction_one for curve in pol_curves] - - if len(base_direction_two) > 0: - curve_combos += [[curve] + base_direction_two for curve in pol_curves] - - for curve_combo in curve_combos: - src_token = tkn1 - if len(curve_combo) < 2: - continue - - try: - ( - O, - profit_src, - r, - trade_instructions_df, - ) = self.run_main_flow(curves=curve_combo, src_token=src_token, tkn0=tkn0, tkn1=tkn1) - - trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) - trade_instructions = r.trade_instructions() - - except Exception: - continue - if trade_instructions_dic is None: - continue - if len(trade_instructions_dic) < 2: - continue - # Get the cids - cids = [ti["cid"] for ti in trade_instructions_dic] - - # Calculate the profit - profit = self.calculate_profit(src_token, profit_src, self.CCm, cids) - - if str(profit) == "nan": - self.ConfigObj.logger.debug("profit is nan, skipping") - continue - - # Handle candidates based on conditions - candidates += self.handle_candidates( - best_profit, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - # Find the best operations - best_profit, ops = self.find_best_operations( - best_profit, - ops, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - return candidates if self.result == self.AO_CANDIDATES else ops - - def get_wrong_direction_cids( - self, tkn0_into_carbon: bool, trade_instructions_df: pd.DataFrame - ) -> List[Hashable]: - """ - Get the cids of the wrong direction curves - - Parameters - ---------- - tkn0_into_carbon : bool - True if tkn0 is being converted into carbon, False otherwise - trade_instructions_df : pd.DataFrame - The trade instructions dataframe - - Returns - ------- - List[str] - The cids of the wrong direction curves - """ - return [ - idx - for idx, row in trade_instructions_df.iterrows() - if ( - (tkn0_into_carbon and row[0] < 0) - or (not tkn0_into_carbon and row[0] > 0) - ) - and ("-0" in idx or "-1" in idx) - ] - - @staticmethod - def run_main_flow( - curves: List[Any], src_token: str, tkn0: str, tkn1: str - ) -> Tuple[Any, float, Any, pd.DataFrame]: - """ - Run main flow to find arbitrage. - """ - CC_cc = CPCContainer(curves) - O = PairOptimizer(CC_cc) - pstart = { - tkn0: CC_cc.bypairs(f"{tkn0}/{tkn1}")[0].p - } # this intentionally selects the non_carbon curve - r = O.optimize(src_token, params=dict(pstart=pstart)) - profit_src = -r.result - trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) - return O, profit_src, r, trade_instructions_df - - def process_wrong_direction_pools( - self, curve_combo: List[Any], wrong_direction_cids: List[Hashable] - ) -> [str]: - """ - Process curves with wrong direction pools. - """ - new_curves = [ - curve for curve in curve_combo if curve.cid not in wrong_direction_cids - ] - return new_curves - - def get_combos_pol(self, - CCm: CPCContainer, flashloan_tokens: List[str] - ) -> Tuple[List[Any], List[Any]]: + def get_combos(self, flashloan_tokens: List[str], CCm: Any) -> Tuple[List[str], List[Any]]: """ Get combos for pairwise arbitrage specific to Bancor POL Parameters ---------- - CCm : CPCContainer - Container for all the curves flashloan_tokens : list List of flashloan tokens + CCm : object + CCm object Returns ------- @@ -194,13 +35,32 @@ def get_combos_pol(self, """ bancor_pol_tkns = CCm.byparams(exchange="bancor_pol").tokens() - bancor_pol_tkns = set([tkn for tkn in bancor_pol_tkns if tkn not in [T.ETH, T.WETH]]) + bancor_pol_tkns = set([tkn for tkn in bancor_pol_tkns if tkn != self.ConfigObj.WETH_ADDRESS]) combos = [ (tkn0, tkn1) - for tkn0, tkn1 in itertools.product(bancor_pol_tkns, [T.ETH, T.WETH]) + for tkn0, tkn1 in product(bancor_pol_tkns, [self.ConfigObj.WETH_ADDRESS]) # tkn1 is always the token being flash loaned # note that the pair is tkn0/tkn1, ie tkn1 is the quote token if tkn0 != tkn1 ] - return bancor_pol_tkns, combos \ No newline at end of file + return bancor_pol_tkns, combos + + def get_curve_combos(self, CC: Any) -> List[Any]: + pol_curves = [x for x in CC.curves if x.params.exchange == "bancor_pol"] + carbon_curves = [x for x in CC.curves if x.params.exchange in self.ConfigObj.CARBON_V1_FORKS] + not_carbon_curves = [x for x in CC.curves if x.params.exchange not in ["bancor_pol"] + self.ConfigObj.CARBON_V1_FORKS] + curve_combos = [[curve] + pol_curves for curve in not_carbon_curves] + + if len(carbon_curves) > 0: + base_direction_pair = carbon_curves[0].pair + base_direction_one = [curve for curve in carbon_curves if curve.pair == base_direction_pair] + base_direction_two = [curve for curve in carbon_curves if curve.pair != base_direction_pair] + + if len(base_direction_one) > 0: + curve_combos += [[curve] + base_direction_one for curve in pol_curves] + + if len(base_direction_two) > 0: + curve_combos += [[curve] + base_direction_two for curve in pol_curves] + + return curve_combos diff --git a/fastlane_bot/modes/pairwise_single.py b/fastlane_bot/modes/pairwise_single.py deleted file mode 100644 index d5128c6b3..000000000 --- a/fastlane_bot/modes/pairwise_single.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Defines the Single pairwise arbitrage finder class - -[DOC-TODO-OPTIONAL-longer description in rst format] - ---- -(c) Copyright Bprotocol foundation 2023-24. -All rights reserved. -Licensed under MIT. -""" -from typing import List, Any, Tuple, Union - -from tqdm.contrib import itertools - -from fastlane_bot.modes.base_pairwise import ArbitrageFinderPairwiseBase -from fastlane_bot.tools.cpc import CPCContainer -from fastlane_bot.tools.optimizer import MargPOptimizer, PairOptimizer - - -class FindArbitrageSinglePairwise(ArbitrageFinderPairwiseBase): - """ - Single pairwise arbitrage finder mode - """ - - arb_mode = "single_pairwise" - - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py - """ - - if candidates is None: - candidates = [] - - all_tokens, combos = self.get_combos(self.CCm, self.flashloan_tokens) - - if self.result == self.AO_TOKENS: - return all_tokens, combos - - for tkn0, tkn1 in combos: - r = None - CC = self.CCm.bypairs(f"{tkn0}/{tkn1}") - if len(CC) < 2: - continue - base_exchange_curves = [ - x for x in CC.curves if x.params.exchange == self.base_exchange - ] - not_base_exchange_curves = [ - x for x in CC.curves if x.params.exchange != self.base_exchange - ] - self.ConfigObj.logger.debug( - f"base_exchange: {self.base_exchange}, base_exchange_curves: {len(base_exchange_curves)}, not_base_exchange_curves: {len(not_base_exchange_curves)}" - ) - - curve_combos = list( - itertools.product(not_base_exchange_curves, base_exchange_curves) - ) - - if not curve_combos: - continue - - for curve_combo in curve_combos: - CC_cc = CPCContainer(curve_combo) - O = PairOptimizer(CC_cc) - src_token = tkn1 - try: - pstart = {tkn0: CC_cc.bypairs(f"{tkn0}/{tkn1}")[0].p} - r = O.optimize(src_token, params=dict(pstart=pstart)) - profit_src = -r.result - trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) - trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) - trade_instructions = r.trade_instructions() - except Exception as e: - print("[FindArbitrageSinglePairwise] Exception: ", e) - continue - if trade_instructions_dic is None: - continue - if len(trade_instructions_dic) < 2: - continue - # Get the candidate ids - cids = [ti["cid"] for ti in trade_instructions_dic] - - # Calculate the profit - profit = self.calculate_profit(src_token, profit_src, self.CCm, cids) - - if str(profit) == "nan": - self.ConfigObj.logger.debug("profit is nan, skipping") - continue - - # Handle candidates based on conditions - candidates += self.handle_candidates( - best_profit, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - # Find the best operations - best_profit, ops = self.find_best_operations( - best_profit, - ops, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - return candidates if self.result == self.AO_CANDIDATES else ops diff --git a/fastlane_bot/modes/triangle_bancor_v3_two_hop.py b/fastlane_bot/modes/triangle_bancor_v3_two_hop.py index cba6397b6..07b3e28f2 100644 --- a/fastlane_bot/modes/triangle_bancor_v3_two_hop.py +++ b/fastlane_bot/modes/triangle_bancor_v3_two_hop.py @@ -1,5 +1,5 @@ """ -Defines the Bancor V3 triangular arbitrage finder class +Defines the b3-two-hop-triangle arbitrage finder class [DOC-TODO-OPTIONAL-longer description in rst format] @@ -9,103 +9,90 @@ Licensed under MIT. """ import math -from typing import Union, List, Tuple, Any, Iterable +from typing import List, Tuple, Any +from itertools import product +from fastlane_bot.tools.cpc import CPCContainer, ConstantProductCurve from fastlane_bot.modes.base_triangle import ArbitrageFinderTriangleBase -from fastlane_bot.tools.cpc import CPCContainer, T, ConstantProductCurve -from fastlane_bot.tools.optimizer import MargPOptimizer - class ArbitrageFinderTriangleBancor3TwoHop(ArbitrageFinderTriangleBase): - """ - Bancor V3 triangular arbitrage finder mode - """ - arb_mode = "b3_two_hop" - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py - """ - if self.base_exchange != "bancor_v3": - self.ConfigObj.logger.warning( - f"base_exchange must be bancor_v3 for {self.arb_mode}, setting it to bancor_v3" - ) - self.base_exchange = "bancor_v3" - - self.ConfigObj.logger.info( - f"flashloan_tokens for arb_mode={self.arb_mode} will be overwritten. " - ) - - self._check_limit_flashloan_tokens_for_bancor3() - - if candidates is None: - candidates = [] - - # Get combinations of flashloan tokens - combos = self.get_combos( - self.flashloan_tokens, self.CCm, arb_mode=self.arb_mode - ) - - # Get the miniverse combinations - all_miniverses = self.get_miniverse_combos(combos) - - if len(all_miniverses) == 0: - return None - - # Check each source token and miniverse combination - for src_token, miniverse in all_miniverses: - r = None - - try: - # Run main flow with the new set of curves - ( - profit_src, - trade_instructions, - trade_instructions_df, - trade_instructions_dic, - ) = self.run_main_flow(miniverse, src_token) - - except Exception: + def handle_exchange(self): + assert self.base_exchange != "bancor_v3", "base_exchange for `b3_two_hop` mode must be `bancor_v3`" + fltkns = self.CCm.byparams(exchange="bancor_v3").tknys() + if self.ConfigObj.LIMIT_BANCOR3_FLASHLOAN_TOKENS: + # Filter out tokens that are not in the existing flashloan_tokens list + self.flashloan_tokens = [tkn for tkn in fltkns if tkn in self.flashloan_tokens] + self.ConfigObj.logger.info(f"limiting flashloan_tokens to {self.flashloan_tokens}") + else: + self.flashloan_tokens = fltkns + + def get_combos(self, flashloan_tokens: List[str], CCm: Any) -> Tuple[List[str], List[Any]]: + all_miniverses = [] + combos = [ + (tkn0, tkn1) + for tkn0, tkn1 in product(flashloan_tokens, flashloan_tokens) + # note that the pair is tkn0/tkn1, ie tkn1 is the quote token + if tkn0 != tkn1 + ] + for tkn0, tkn1 in combos: + external_curves = self.CCm.bypairs(f"{tkn0}/{tkn1}") + external_curves += self.CCm.bypairs(f"{tkn1}/{tkn0}") + external_curves = list(set(external_curves)) + carbon_curves = [ + curve + for curve in external_curves + if curve.params.get("exchange") in self.ConfigObj.CARBON_V1_FORKS + ] + external_curves = [ + curve + for curve in external_curves + if curve.params.get("exchange") not in self.ConfigObj.CARBON_V1_FORKS + ] + if not external_curves and not carbon_curves: continue - if trade_instructions_dic is None: + + bancor_v3_curve_0 = ( + self.CCm.bypairs(f"{self.ConfigObj.BNT_ADDRESS}/{tkn0}") + .byparams(exchange="bancor_v3") + .curves + ) + bancor_v3_curve_1 = ( + self.CCm.bypairs(f"{self.ConfigObj.BNT_ADDRESS}/{tkn1}") + .byparams(exchange="bancor_v3") + .curves + ) + if bancor_v3_curve_0 is None or bancor_v3_curve_1 is None: continue - if len(trade_instructions_dic) < 3: + if len(bancor_v3_curve_0) == 0 or len(bancor_v3_curve_1) == 0: continue - # Get the candidate ids - cids = [ti["cid"] for ti in trade_instructions_dic] - # Calculate the profit - profit = self.calculate_profit(src_token, profit_src, self.CCm, cids) + miniverses = [] + if len(external_curves) > 0: + for curve in external_curves: + miniverses += [bancor_v3_curve_0 + bancor_v3_curve_1 + [curve]] + if len(carbon_curves) > 0: - if str(profit) == "nan": - self.ConfigObj.logger.debug("profit is nan, skipping") - continue + if len(carbon_curves) > 0: + base_direction_pair = carbon_curves[0].pair + base_direction_one = [curve for curve in carbon_curves if curve.pair == base_direction_pair] + base_direction_two = [curve for curve in carbon_curves if curve.pair != base_direction_pair] - # Handle candidates based on conditions - candidates += self.handle_candidates( - best_profit, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) + if len(base_direction_one) > 0: + miniverses += [bancor_v3_curve_0 + bancor_v3_curve_1 + base_direction_one] - # Find the best operations - best_profit, ops = self.find_best_operations( - best_profit, - ops, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) + if len(base_direction_two) > 0: + miniverses += [bancor_v3_curve_0 + bancor_v3_curve_1 + base_direction_two] - return candidates if self.result == self.AO_CANDIDATES else ops + miniverses += [bancor_v3_curve_0 + bancor_v3_curve_1 + carbon_curves] + + if len(miniverses) > 0: + all_miniverses += list(zip([tkn1] * len(miniverses), miniverses)) + return all_miniverses - def get_tkn(self, pool: Any, tkn_num: int) -> str: + @staticmethod + def get_tkn(pool: Any, tkn_num: int) -> str: """ Gets the token ID from a pool object @@ -187,7 +174,8 @@ def get_optimal_arb_trade_amts(self, cids: List[str], flt: str) -> float: return self.max_arb_trade_in_constant_product(p0t0, p0t1, p1t0, p1t1, p2t0, p2t1, fee0=fee0, fee1=fee1, fee2=fee2) - def get_exact_input_with_carbon(self, p0t0: float, p0t1: float, p2t0: float, p2t1: float, carbon_pool: ConstantProductCurve) -> float: + @staticmethod + def get_exact_input_with_carbon(p0t0: float, p0t1: float, p2t0: float, p2t1: float, carbon_pool: ConstantProductCurve) -> float: """ Gets the optimal trade 0 amount for a triangular arb cycle with a single Carbon order in the middle @@ -209,28 +197,7 @@ def get_exact_input_with_carbon(self, p0t0: float, p0t1: float, p2t0: float, p2t B = carbon_pool.B C = (B * z + A * y) ** 2 D = B * A * z + A ** 2 * y - return self.max_arb_trade_in_cp_carbon_cp(p0t0, p0t1, p2t0, p2t1, C, D, z) - - @staticmethod - def max_arb_trade_in_cp_carbon_cp(p0t0: float, p0t1: float, p2t0: float, p2t1: float, C: float, D: float, z: float) -> float: - """ - Equation to solve optimal trade input for a constant product -> Carbon order -> constant product route. - Parameters - ---------- - p0t0: float - p0t1: float - p2t0: float - p2t1: float - C: float - D: float - z: float - Returns - ------- - float - - """ - trade_input = (z * (-p0t0 * p2t0 * z + math.sqrt(C * p0t0 * p2t0 * p0t1 * p2t1))) / (p0t1 * C + p0t1 * D * p2t0 + z ** 2 * p2t0) - return trade_input + return (z * (-p0t0 * p2t0 * z + math.sqrt(C * p0t0 * p2t0 * p0t1 * p2t1))) / (p0t1 * C + p0t1 * D * p2t0 + z ** 2 * p2t0) @staticmethod def max_arb_trade_in_constant_product(p0t0, p0t1, p1t0, p1t1, p2t0, p2t1, fee0, fee1, fee2): @@ -252,118 +219,4 @@ def max_arb_trade_in_constant_product(p0t0, p0t1, p1t0, p1t1, p2t0, p2t1, fee0, float """ - val = (-p1t0*p2t0*p0t0 + (p1t0*p2t0*p0t0*p1t1*p2t1*p0t1*(-fee1*fee2*fee0 + fee1*fee2 + fee1*fee0 - fee1 + fee2*fee0 - fee2 - fee0 + 1)) ** 0.5)/(p1t0*p2t0 - p2t0*p0t1*fee0 + p2t0*p0t1 + p1t1*p0t1*fee1*fee0 - p1t1*p0t1*fee1 - p1t1*p0t1*fee0 + p1t1*p0t1) - return val - - def run_main_flow(self, - miniverse: List, src_token: str - ) -> Tuple[float, Any, Any, Any]: - """ - Run the main flow of the arbitrage finder. - - Parameters - ---------- - miniverse : list - List of curves. - src_token : str - Source token. - - Returns - ------- - tuple - Tuple of profit, trade instructions, trade instructions dataframe and trade instructions dictionary. - - """ - - # Instantiate the container and optimizer objects - CC_cc = CPCContainer(miniverse) - O = MargPOptimizer(CC_cc) - pstart = self.build_pstart(CC_cc, CC_cc.tokens(), src_token) - # Perform the optimization - r = O.optimize(src_token, params=dict(pstart=pstart)) - - # Get the profit in the source token - profit_src = -r.result - - # Get trade instructions in different formats - trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) - trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) - trade_instructions = r.trade_instructions() - - return ( - profit_src, - trade_instructions, - trade_instructions_df, - trade_instructions_dic, - ) - - def get_miniverse_combos(self, combos: Iterable) -> List[Tuple[str, List]]: - """ - Get the miniverse combinations for a list of token pairs. - - Parameters - ---------- - combos : list - List of token pairs. - - Returns - ------- - list - List of miniverse combinations. - - """ - all_miniverses = [] - for tkn0, tkn1 in combos: - external_curves = self.CCm.bypairs(f"{tkn0}/{tkn1}") - external_curves += self.CCm.bypairs(f"{tkn1}/{tkn0}") - external_curves = list(set(external_curves)) - carbon_curves = [ - curve - for curve in external_curves - if curve.params.get("exchange") in self.ConfigObj.CARBON_V1_FORKS - ] - external_curves = [ - curve - for curve in external_curves - if curve.params.get("exchange") not in self.ConfigObj.CARBON_V1_FORKS - ] - if not external_curves and not carbon_curves: - continue - - bancor_v3_curve_0 = ( - self.CCm.bypairs(f"{T.BNT}/{tkn0}") - .byparams(exchange="bancor_v3") - .curves - ) - bancor_v3_curve_1 = ( - self.CCm.bypairs(f"{T.BNT}/{tkn1}") - .byparams(exchange="bancor_v3") - .curves - ) - if bancor_v3_curve_0 is None or bancor_v3_curve_1 is None: - continue - if len(bancor_v3_curve_0) == 0 or len(bancor_v3_curve_1) == 0: - continue - - miniverses = [] - if len(external_curves) > 0: - for curve in external_curves: - miniverses += [bancor_v3_curve_0 + bancor_v3_curve_1 + [curve]] - if len(carbon_curves) > 0: - - if len(carbon_curves) > 0: - base_direction_pair = carbon_curves[0].pair - base_direction_one = [curve for curve in carbon_curves if curve.pair == base_direction_pair] - base_direction_two = [curve for curve in carbon_curves if curve.pair != base_direction_pair] - - if len(base_direction_one) > 0: - miniverses += [bancor_v3_curve_0 + bancor_v3_curve_1 + base_direction_one] - - if len(base_direction_two) > 0: - miniverses += [bancor_v3_curve_0 + bancor_v3_curve_1 + base_direction_two] - - miniverses += [bancor_v3_curve_0 + bancor_v3_curve_1 + carbon_curves] - - if len(miniverses) > 0: - all_miniverses += list(zip([tkn1] * len(miniverses), miniverses)) - return all_miniverses + return (-p1t0*p2t0*p0t0 + (p1t0*p2t0*p0t0*p1t1*p2t1*p0t1*(-fee1*fee2*fee0 + fee1*fee2 + fee1*fee0 - fee1 + fee2*fee0 - fee2 - fee0 + 1)) ** 0.5)/(p1t0*p2t0 - p2t0*p0t1*fee0 + p2t0*p0t1 + p1t1*p0t1*fee1*fee0 - p1t1*p0t1*fee1 - p1t1*p0t1*fee0 + p1t1*p0t1) diff --git a/fastlane_bot/modes/triangle_multi.py b/fastlane_bot/modes/triangle_multi.py index e6d1f6672..bb478111c 100644 --- a/fastlane_bot/modes/triangle_multi.py +++ b/fastlane_bot/modes/triangle_multi.py @@ -1,5 +1,5 @@ """ -Defines the Triangular arbitrage finder class +Defines the multi-triangle arbitrage finder class [DOC-TODO-OPTIONAL-longer description in rst format] @@ -8,79 +8,124 @@ All rights reserved. Licensed under MIT. """ -from typing import List, Any, Tuple, Union +from typing import List, Any, Tuple +from itertools import product from fastlane_bot.modes.base_triangle import ArbitrageFinderTriangleBase -from fastlane_bot.tools.cpc import CPCContainer -from fastlane_bot.tools.optimizer import MargPOptimizer - class ArbitrageFinderTriangleMulti(ArbitrageFinderTriangleBase): - """ - Triangular arbitrage finder mode - """ - arb_mode = "multi_triangle" - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py - """ + def handle_exchange(self): + assert self.base_exchange in self.ConfigObj.CARBON_V1_FORKS, "base_exchange for `multi_triangle` mode must be a carbon_v1 fork" - if self.base_exchange != "carbon_v1": - raise ValueError("base_exchange must be carbon_v1 for `multi` mode") + def get_combos(self, flashloan_tokens: List[str], CCm: Any) -> Tuple[List[str], List[Any]]: + """ + Get combos for triangular arbitrage - if candidates is None: - candidates = [] + Parameters + ---------- + flashloan_tokens : list + List of flashloan tokens + CCm : object + CCm object - combos = self.get_combos(self.flashloan_tokens, self.CCm, arb_mode=self.arb_mode) + Returns + ------- + combos : list + List of combos - for src_token, miniverse in combos: - try: - CC_cc = CPCContainer(miniverse) - O = MargPOptimizer(CC_cc) - pstart = self.build_pstart(CC_cc, CC_cc.tokens(), src_token) - r = O.optimize(src_token, params=dict(pstart=pstart)) - trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) - if trade_instructions_dic is None or len(trade_instructions_dic) < 3: - # Failed to converge + """ + combos = [] + all_base_exchange_curves = CCm.byparams(exchange=self.base_exchange).curves + for flt in flashloan_tokens: # may wish to run this for one flt at a time + non_flt_base_exchange_curves = [ + x for x in all_base_exchange_curves if flt not in x.pair + ] + for non_flt_base_exchange_curve in non_flt_base_exchange_curves: + target_tkny = non_flt_base_exchange_curve.tkny + target_tknx = non_flt_base_exchange_curve.tknx + base_exchange_curves = ( + CCm.bypairs(f"{target_tknx}/{target_tkny}") + .byparams(exchange=self.base_exchange) + .curves + ) + if len(base_exchange_curves) == 0: continue - trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) - trade_instructions = r.trade_instructions() - except Exception as e: - self.ConfigObj.logger.info(f"[triangle multi] {e}") - continue - profit_src = -r.result - - # Get the cids - cids = [ti["cid"] for ti in trade_instructions_dic] - - # Calculate the profit - profit = self.calculate_profit(src_token, profit_src, self.CCm, cids) - if str(profit) == "nan": - self.ConfigObj.logger.debug("profit is nan, skipping") - continue - - # Handle candidates based on conditions - candidates += self.handle_candidates( - best_profit, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - # Find the best operations - best_profit, ops = self.find_best_operations( - best_profit, - ops, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) + base_direction_pair = base_exchange_curves[0].pair + base_direction_one = [curve for curve in base_exchange_curves if curve.pair == base_direction_pair] + base_direction_two = [curve for curve in base_exchange_curves if curve.pair != base_direction_pair] + + y_match_curves = CCm.bypairs( + set(CCm.filter_pairs(onein=target_tknx)) + & set(CCm.filter_pairs(onein=flt)) + ) + x_match_curves = CCm.bypairs( + set(CCm.filter_pairs(onein=target_tkny)) + & set(CCm.filter_pairs(onein=flt)) + ) + + y_match_curves_not_carbon = [ + x + for x in y_match_curves + if x.params.exchange != self.base_exchange + ] + if len(y_match_curves_not_carbon) == 0: + continue + x_match_curves_not_carbon = [ + x + for x in x_match_curves + if x.params.exchange != self.base_exchange + ] + if len(x_match_curves_not_carbon) == 0: + continue + if len(base_direction_one) > 0: + get_miniverse( + y_match_curves_not_carbon, + base_direction_one, + x_match_curves_not_carbon, + flt, + combos, + ) + if len(base_direction_two) > 0: + get_miniverse( + y_match_curves_not_carbon, + base_direction_two, + x_match_curves_not_carbon, + flt, + combos, + ) + return combos + +def get_miniverse( + y_match_curves_not_carbon: List[Any], + base_exchange_curves: List[Any], + x_match_curves_not_carbon: List[Any], + flt: str, + combos: List[Any], +): + """ + Get miniverse for triangular arbitrage + + Parameters + ---------- + y_match_curves_not_carbon : list + List of curves that match the y token and are not on carbon + base_exchange_curves : list + List of curves on the base exchange + x_match_curves_not_carbon : list + List of curves that match the x token and are not on carbon + flt : str + Flashloan token + combos : list + List of combos - return candidates if self.result == self.AO_CANDIDATES else ops + """ + external_curve_combos = list( + product(y_match_curves_not_carbon, x_match_curves_not_carbon) + ) + miniverses = [ + base_exchange_curves + list(combo) for combo in external_curve_combos + ] + combos += list(zip([flt] * len(miniverses), miniverses)) diff --git a/fastlane_bot/modes/triangle_multi_complete.py b/fastlane_bot/modes/triangle_multi_complete.py index d053ae0fc..6036a00b6 100644 --- a/fastlane_bot/modes/triangle_multi_complete.py +++ b/fastlane_bot/modes/triangle_multi_complete.py @@ -1,5 +1,5 @@ """ -Defines the Triangular arbitrage finder class +Defines the multi-triangle-complete arbitrage finder class [DOC-TODO-OPTIONAL-longer description in rst format] @@ -8,76 +8,135 @@ All rights reserved. Licensed under MIT. """ -from typing import List, Any, Tuple, Union +from typing import List, Any, Tuple +from itertools import product, combinations from fastlane_bot.modes.base_triangle import ArbitrageFinderTriangleBase -from fastlane_bot.tools.cpc import CPCContainer -from fastlane_bot.tools.optimizer import MargPOptimizer - class ArbitrageFinderTriangleMultiComplete(ArbitrageFinderTriangleBase): - """ - Triangular arbitrage finder mode - """ - arb_mode = "multi_triangle_complete" - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py + def handle_exchange(self): + pass + + def get_combos(self, flashloan_tokens: List[str], CCm: Any) -> Tuple[List[str], List[Any]]: """ + Get comprehensive combos for triangular arbitrage + + Parameters + ---------- + flashloan_tokens : list + List of flashloan tokens + CCm : object + CCm object + arb_mode : str + Arbitrage mode (unused) + + Returns + ------- + combos : list + List of combos - if candidates is None: - candidates = [] - - combos = self.get_comprehensive_triangles(self.flashloan_tokens, self.CCm) - - for src_token, miniverse in combos: - try: - CC_cc = CPCContainer(miniverse) - O = MargPOptimizer(CC_cc) - pstart = self.build_pstart(CC_cc, CC_cc.tokens(), src_token) - r = O.optimize(src_token, params=dict(pstart=pstart)) - trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) - if trade_instructions_dic is None or len(trade_instructions_dic) < 3: - # Failed to converge - continue - trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) - trade_instructions = r.trade_instructions() - - except Exception as e: - self.ConfigObj.logger.info(f"[triangle multi] {e}") - continue - profit_src = -r.result - - # Get the cids - cids = [ti["cid"] for ti in trade_instructions_dic] - - # Calculate the profit - profit = self.calculate_profit(src_token, profit_src, self.CCm, cids) - if str(profit) == "nan": - self.ConfigObj.logger.debug("profit is nan, skipping") - continue - - # Handle candidates based on conditions - candidates += self.handle_candidates( - best_profit, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - # Find the best operations - best_profit, ops = self.find_best_operations( - best_profit, - ops, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - return candidates if self.result == self.AO_CANDIDATES else ops + """ + combos = [] + for flt in flashloan_tokens: + + # Get the Carbon pairs + carbon_pairs = sort_pairs(set([x.pair for x in CCm.curves if x.params.exchange in self.ConfigObj.CARBON_V1_FORKS])) + + # Create a set of unique tokens, excluding 'flt' + x_tokens = {token for pair in carbon_pairs for token in pair.split('/') if token != flt} + + # Get relevant pairs containing the flashloan token + flt_x_pairs = sort_pairs([f"{x}/{flt}" for x in x_tokens]) + + # Generate all possible 2-item combinations from the unique tokens that arent the flashloan token + x_y_pairs = sort_pairs(["{}/{}".format(x, y) for x, y in combinations(x_tokens, 2)]) + + # 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)}") + + # Generate triangle groups + triangle_groups = get_triangle_groups(flt, x_y_pairs) + self.ConfigObj.logger.debug(f"len(triangle_groups) {len(triangle_groups)}") + + # Get pair info for the cohort + all_relevant_pairs_info = get_all_relevant_pairs_info(CCm, all_relevant_pairs, self.ConfigObj.CARBON_V1_FORKS) + + # Generate valid triangles for the groups base on arb_mode + valid_triangles = get_triangle_groups_stats(triangle_groups, all_relevant_pairs_info) + + # Get [(flt,curves)] analysis set for the flt + flt_triangle_analysis_set = get_analysis_set_per_flt(flt, valid_triangles, all_relevant_pairs_info) + + # The entire analysis set for all flts + combos.extend(flt_triangle_analysis_set) + return combos + +def sort_pairs(pairs): + # Clean up the pairs alphabetically + return ["/".join(sorted(pair.split('/'))) for pair in pairs] + +def flatten_nested_items_in_list(nested_list): + # unpack nested items + flattened_list = [] + for items in nested_list: + flat_list = [] + for item in items: + if isinstance(item, list): + flat_list.extend(item) + else: + flat_list.append(item) + flattened_list.append(flat_list) + return flattened_list + +def get_triangle_groups(flt, x_y_pairs): + # Get groups of triangles that conform to (flt/x , x/y, y/flt) where x!=y + triangle_groups = [] + for pair in x_y_pairs: + x,y = pair.split('/') + triangle_groups += [("/".join(sorted([flt,x])), pair, "/".join(sorted([flt,y])))] + return triangle_groups + +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: + all_relevant_pairs_info[pair] = {} + pair_curves = CCm.bypair(pair) + carbon_curves = [] + non_carbon_curves = [] + for x in pair_curves: + if x.params.exchange in carbon_v1_forks: + carbon_curves += [x] + else: + non_carbon_curves += [x] + all_relevant_pairs_info[pair]['curves'] = non_carbon_curves + [carbon_curves] if len(carbon_curves) > 0 else non_carbon_curves # condense carbon curves into a single list + all_relevant_pairs_info[pair]['all_counts'] = len(pair_curves) + all_relevant_pairs_info[pair]['carbon_counts'] = len(carbon_curves) + return all_relevant_pairs_info + +def get_triangle_groups_stats(triangle_groups, all_relevant_pairs_info): + # Get the stats on the triangle group cohort for decision making + valid_carbon_triangles = [] + for triangle in triangle_groups: + path_len = 0 + has_carbon = False + for pair in triangle: + if all_relevant_pairs_info[pair]['all_counts'] > 0: + path_len += 1 + if all_relevant_pairs_info[pair]['carbon_counts'] > 0: + has_carbon = True + if path_len == 3 and has_carbon == True: + valid_carbon_triangles.append(triangle) + return valid_carbon_triangles + +def get_analysis_set_per_flt(flt, valid_triangles, all_relevant_pairs_info): + flt_triangle_analysis_set = [] + for triangle in valid_triangles: + multiverse = [all_relevant_pairs_info[pair]['curves'] for pair in triangle] + product_of_triangle = list(product(multiverse[0], multiverse[1], multiverse[2])) + triangles_to_run = flatten_nested_items_in_list(product_of_triangle) + flt_triangle_analysis_set += list(zip([flt] * len(triangles_to_run), triangles_to_run)) + return flt_triangle_analysis_set diff --git a/fastlane_bot/modes/triangle_single.py b/fastlane_bot/modes/triangle_single.py deleted file mode 100644 index 3c3b3f825..000000000 --- a/fastlane_bot/modes/triangle_single.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Defines the Triangle single arbitrage finder class - -[DOC-TODO-OPTIONAL-longer description in rst format] - ---- -(c) Copyright Bprotocol foundation 2023-24. -All rights reserved. -Licensed under MIT. -""" -from typing import Union, List, Tuple, Any - -from fastlane_bot.modes.base_triangle import ArbitrageFinderTriangleBase -from fastlane_bot.tools.cpc import CPCContainer -from fastlane_bot.tools.optimizer import MargPOptimizer - - -class ArbitrageFinderTriangleSingle(ArbitrageFinderTriangleBase): - """ - Triangle single arbitrage finder mode - """ - - arb_mode = "single_triangle" - - def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_profit: float = 0, profit_src: float = 0) -> Union[List, Tuple]: - """ - see base.py - """ - - if candidates is None: - candidates = [] - - # Get combinations of flashloan tokens - combos = self.get_combos( - self.flashloan_tokens, self.CCm, arb_mode=self.arb_mode - ) - - # Check each source token and miniverse combination - for src_token, miniverse in combos: - r = None - - # Instantiate the container and optimizer objects - CC_cc = CPCContainer(miniverse) - O = MargPOptimizer(CC_cc) - - try: - # Perform the optimization - r = O.margp_optimizer(src_token) - - # Get the profit in the source token - profit_src = -r.result - - # Get trade instructions in different formats - trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR) - trade_instructions_dic = r.trade_instructions(O.TIF_DICTS) - trade_instructions = r.trade_instructions() - except Exception: - continue - - if trade_instructions_dic is None: - continue - if len(trade_instructions_dic) < 3: - continue - - # Get the candidate ids - cids = [ti["cid"] for ti in trade_instructions_dic] - - # Calculate the profit - profit = self.calculate_profit(src_token, profit_src, self.CCm, cids) - - if str(profit) == "nan": - self.ConfigObj.logger.debug("profit is nan, skipping") - continue - - # Handle candidates based on conditions - candidates += self.handle_candidates( - best_profit, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - # Find the best operations - best_profit, ops = self.find_best_operations( - best_profit, - ops, - profit, - trade_instructions_df, - trade_instructions_dic, - src_token, - trade_instructions, - ) - - return candidates if self.result == self.AO_CANDIDATES else ops diff --git a/fastlane_bot/pool_finder.py b/fastlane_bot/pool_finder.py index 15ff0584f..f9fb7ca99 100644 --- a/fastlane_bot/pool_finder.py +++ b/fastlane_bot/pool_finder.py @@ -69,7 +69,7 @@ def get_pools_for_unsupported_pairs(self, pools: List[Dict[str, Any]], arb_mode: if not carbon_pairs: return [], [], [] self.extract_univ3_fee_tiers(pools) # TODO: these should be configured per exchange - if arb_mode in ["triangle", "multi_triangle"]: + if arb_mode in ["multi_triangle", "multi_triangle_complete", "b3_two_hop"]: unsupported_pairs = PoolFinder._find_unsupported_triangles(self._flashloan_tokens, carbon_pairs=carbon_pairs, external_pairs=other_pairs) else: unsupported_pairs = PoolFinder._find_unsupported_pairs(self._flashloan_tokens, carbon_pairs=carbon_pairs, external_pairs=other_pairs) diff --git a/fastlane_bot/tests/test_039_TestMultiMode.py b/fastlane_bot/tests/test_039_TestMultiMode.py deleted file mode 100644 index b3c875d70..000000000 --- a/fastlane_bot/tests/test_039_TestMultiMode.py +++ /dev/null @@ -1,251 +0,0 @@ -# ------------------------------------------------------------ -# Auto generated test file `test_039_TestMultiMode.py` -# ------------------------------------------------------------ -# source file = NBTest_039_TestMultiMode.py -# test id = 039 -# test comment = TestMultiMode -# ------------------------------------------------------------ - - - -""" -This module contains the tests for the exchanges classes -""" -from fastlane_bot import Bot, Config -from fastlane_bot.bot import CarbonBot -from fastlane_bot.tools.cpc import ConstantProductCurve as CPC -from fastlane_bot.events.exchanges import UniswapV2, UniswapV3, CarbonV1, BancorV3 -from fastlane_bot.events.interface import QueryInterface -from fastlane_bot.events.managers.manager import Manager -from fastlane_bot.events.interface import QueryInterface -from joblib import Parallel, delayed -import math -import json -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(CPC)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(Bot)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(UniswapV2)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(UniswapV3)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(CarbonV1)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(BancorV3)) -from fastlane_bot.testing import * -#plt.style.use('seaborn-dark') -plt.rcParams['figure.figsize'] = [12,6] -from fastlane_bot import __VERSION__ -require("3.0", __VERSION__) - - - -C = cfg = Config.new(config=Config.CONFIG_MAINNET) -cfg.DEFAULT_MIN_PROFIT_GAS_TOKEN = 0.00001 -assert (C.NETWORK == C.NETWORK_MAINNET) -assert (C.PROVIDER == C.PROVIDER_ALCHEMY) -setup_bot = CarbonBot(ConfigObj=C) -pools = None -with open('fastlane_bot/tests/_data/latest_pool_data_testing.json') as f: - pools = json.load(f) -pools = [pool for pool in pools] -pools[0] -static_pools = pools -state = pools -exchanges = list({ex['exchange_name'] for ex in state}) -db = QueryInterface(state=state, ConfigObj=C, exchanges=exchanges) -setup_bot.db = db - -static_pool_data_filename = "static_pool_data" - -static_pool_data = pd.read_csv(f"fastlane_bot/data/{static_pool_data_filename}.csv", low_memory=False) - -uniswap_v2_event_mappings = pd.read_csv("fastlane_bot/data/uniswap_v2_event_mappings.csv", low_memory=False) - -tokens = pd.read_csv("fastlane_bot/data/tokens.csv", low_memory=False) - -exchanges = "carbon_v1,bancor_v3,uniswap_v3,uniswap_v2,sushiswap_v2" - -exchanges = exchanges.split(",") - - -alchemy_max_block_fetch = 20 -static_pool_data["cid"] = [ - cfg.w3.keccak(text=f"{row['descr']}").hex() - for index, row in static_pool_data.iterrows() - ] -static_pool_data = [ - row for index, row in static_pool_data.iterrows() - if row["exchange_name"] in exchanges -] - -static_pool_data = pd.DataFrame(static_pool_data) -static_pool_data['exchange_name'].unique() -mgr = Manager( - web3=cfg.w3, - w3_async=cfg.w3_async, - cfg=cfg, - pool_data=static_pool_data.to_dict(orient="records"), - SUPPORTED_EXCHANGES=exchanges, - alchemy_max_block_fetch=alchemy_max_block_fetch, - uniswap_v2_event_mappings=uniswap_v2_event_mappings, - tokens=tokens.to_dict(orient="records"), -) - -start_time = time.time() -Parallel(n_jobs=-1, backend="threading")( - delayed(mgr.add_pool_to_exchange)(row) for row in mgr.pool_data -) -cfg.logger.info(f"Time taken to add initial pools: {time.time() - start_time}") - -mgr.deduplicate_pool_data() -cids = [pool["cid"] for pool in mgr.pool_data] -assert len(cids) == len(set(cids)), "duplicate cid's exist in the pool data" -def init_bot(mgr: Manager) -> CarbonBot: - """ - Initializes the bot. - - Parameters - ---------- - mgr : Manager - The manager object. - - Returns - ------- - CarbonBot - The bot object. - """ - mgr.cfg.logger.info("Initializing the bot...") - bot = CarbonBot(ConfigObj=mgr.cfg) - bot.db = db - bot.db.mgr = mgr - assert isinstance( - bot.db, QueryInterface - ), "QueryInterface not initialized correctly" - return bot -bot = init_bot(mgr) -bot.db.remove_unmapped_uniswap_v2_pools() -bot.db.remove_zero_liquidity_pools() -bot.db.remove_unsupported_exchanges() -tokens = bot.db.get_tokens() -ADDRDEC = {t.address: (t.address, int(t.decimals)) for t in tokens if not math.isnan(t.decimals)} -flashloan_tokens = bot.RUN_FLASHLOAN_TOKENS -CCm = bot.get_curves() -pools = db.get_pool_data_with_tokens() - -arb_mode = "multi" - - -# ------------------------------------------------------------ -# Test 039 -# File test_039_TestMultiMode.py -# Segment Test_TAX_TOKENS -# ------------------------------------------------------------ -def test_test_tax_tokens(): -# ------------------------------------------------------------ - - assert any(token.address in cfg.TAX_TOKENS for token in tokens), f"[TestMultiMode], DB does not include any tax tokens" - assert len(CCm) == 516, f"[NBTest 039 TestMultiMode] Expected 516 curves, found {len(CCm)}" - - for curve in CCm: - for token in cfg.TAX_TOKENS: - assert token not in [curve.params['tknx_addr'], curve.params['tkny_addr']], f"[TestMultiMode], curve {curve} includes tax token {token}" - - -# ------------------------------------------------------------ -# Test 039 -# File test_039_TestMultiMode.py -# Segment Test_MIN_PROFIT -# ------------------------------------------------------------ -def test_test_min_profit(): -# ------------------------------------------------------------ - - assert(cfg.DEFAULT_MIN_PROFIT_GAS_TOKEN <= 0.0001), f"[TestMultiMode], default_min_profit_gas_token must be <= 0.02 for this Notebook to run, currently set to {cfg.DEFAULT_MIN_PROFIT_GAS_TOKEN}" - - -# ------------------------------------------------------------ -# Test 039 -# File test_039_TestMultiMode.py -# Segment Test_get_arb_finder -# ------------------------------------------------------------ -def test_test_get_arb_finder(): -# ------------------------------------------------------------ - - arb_finder = bot._get_arb_finder("multi") - assert arb_finder.__name__ == "FindArbitrageMultiPairwise", f"[TestMultiMode] Expected arb_finder class name name = FindArbitrageMultiPairwise, found {arb_finder.__name__}" - - -# ------------------------------------------------------------ -# Test 039 -# File test_039_TestMultiMode.py -# Segment Test_Combos_and_Tokens -# ------------------------------------------------------------ -def test_test_combos_and_tokens(): -# ------------------------------------------------------------ - - # + - assert len(CCm) == 516, f"[NBTest 039 TestMultiMode] Expected 516 curves, found {len(CCm)}" - arb_finder = bot._get_arb_finder("multi") - finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=arb_finder.AO_TOKENS, - ConfigObj=bot.ConfigObj, - ) - all_tokens, combos = finder.find_arbitrage() - - # subjected to the length of `TAX_TOKENS` - assert type(all_tokens) == set, f"[NBTest 039 TestMultiMode] all_tokens is wrong data type. Expected set, found: {type(all_tokens)}" - assert type(combos) == list, f"[NBTest 039 TestMultiMode] combos is wrong data type. Expected list, found: {type(combos)}" - assert len(all_tokens) >= 234, f"[NBTest 039 TestMultiMode] Using wrong dataset, expected at least 234 tokens, found {len(all_tokens)}" - assert len(combos) >= 1398, f"[NBTest 039 TestMultiMode] Using wrong dataset, expected at least 1398 combos, found {len(combos)}" - # - - - -# ------------------------------------------------------------ -# Test 039 -# File test_039_TestMultiMode.py -# Segment Test_Expected_Output -# ------------------------------------------------------------ -def test_test_expected_output(): -# ------------------------------------------------------------ - - # + - assert len(CCm) == 516, f"[NBTest 039 TestMultiMode] Expected 516 curves, found {len(CCm)}" - arb_finder = bot._get_arb_finder("multi") - finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=arb_finder.AO_CANDIDATES, - ConfigObj=bot.ConfigObj, - ) - - r = finder.find_arbitrage() - - multi_carbon_count = 0 - carbon_wrong_direction_count = 0 - for arb in r: - ( - best_profit, - best_trade_instructions_df, - best_trade_instructions_dic, - best_src_token, - best_trade_instructions, - ) = arb - if len(best_trade_instructions_dic) > 2: - multi_carbon_count += 1 - carbon_tkn_in = None - for trade in best_trade_instructions_dic: - if "-" in trade["cid"]: - if carbon_tkn_in is None: - carbon_tkn_in = trade["tknin"] - else: - if trade["tknin"] not in carbon_tkn_in: - carbon_wrong_direction_count += 1 - for ti in best_trade_instructions_dic: - for token in cfg.TAX_TOKENS: - assert token not in [ti['tknin'], ti['tknout']], f"[TestMultiMode], trade instruction {ti} includes tax token {token}" - - assert len(r) >= 27, f"[NBTest 039 TestMultiMode] Expected at least 27 arbs, found {len(r)}" - assert multi_carbon_count > 0, f"[NBTest 039 TestMultiMode] Not finding arbs with multiple Carbon curves." - assert carbon_wrong_direction_count == 0, f"[NBTest 039 TestMultiMode] Expected all Carbon curves to have the same tkn in and tkn out. Mixing is currently not supported." - # - - - \ No newline at end of file diff --git a/fastlane_bot/tests/test_040_TestSingleMode.py b/fastlane_bot/tests/test_040_TestSingleMode.py deleted file mode 100644 index c7a1ee890..000000000 --- a/fastlane_bot/tests/test_040_TestSingleMode.py +++ /dev/null @@ -1,197 +0,0 @@ -# ------------------------------------------------------------ -# Auto generated test file `test_040_TestSingleMode.py` -# ------------------------------------------------------------ -# source file = NBTest_040_TestSingleMode.py -# test id = 040 -# test comment = TestSingleMode -# ------------------------------------------------------------ - - - -""" -This module contains the tests for the exchanges classes -""" -from fastlane_bot import Bot, Config -from fastlane_bot.bot import CarbonBot -from fastlane_bot.tools.cpc import ConstantProductCurve as CPC -from fastlane_bot.events.exchanges import UniswapV2, UniswapV3, CarbonV1, BancorV3 -from fastlane_bot.events.interface import QueryInterface -from fastlane_bot.events.managers.manager import Manager -from fastlane_bot.events.interface import QueryInterface -from joblib import Parallel, delayed -import math -import json -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(CPC)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(Bot)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(UniswapV2)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(UniswapV3)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(CarbonV1)) -print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(BancorV3)) -from fastlane_bot.testing import * - -#plt.style.use('seaborn-dark') -plt.rcParams['figure.figsize'] = [12,6] -from fastlane_bot import __VERSION__ -require("3.0", __VERSION__) - - - -C = cfg = Config.new(config=Config.CONFIG_MAINNET) -cfg.DEFAULT_MIN_PROFIT_GAS_TOKEN = 0.00001 -assert (C.NETWORK == C.NETWORK_MAINNET) -assert (C.PROVIDER == C.PROVIDER_ALCHEMY) -setup_bot = CarbonBot(ConfigObj=C) -pools = None -with open('fastlane_bot/tests/_data/latest_pool_data_testing.json') as f: - pools = json.load(f) -pools = [pool for pool in pools] -pools[0] -static_pools = pools -state = pools -exchanges = list({ex['exchange_name'] for ex in state}) -db = QueryInterface(state=state, ConfigObj=C, exchanges=exchanges) -setup_bot.db = db - -static_pool_data_filename = "static_pool_data" - -static_pool_data = pd.read_csv(f"fastlane_bot/data/{static_pool_data_filename}.csv", low_memory=False) - -uniswap_v2_event_mappings = pd.read_csv("fastlane_bot/data/uniswap_v2_event_mappings.csv", low_memory=False) - -tokens = pd.read_csv("fastlane_bot/data/tokens.csv", low_memory=False) - -exchanges = "carbon_v1,bancor_v3,uniswap_v3,uniswap_v2,sushiswap_v2" - -exchanges = exchanges.split(",") - - -alchemy_max_block_fetch = 20 -static_pool_data["cid"] = [ - cfg.w3.keccak(text=f"{row['descr']}").hex() - for index, row in static_pool_data.iterrows() - ] -static_pool_data = [ - row for index, row in static_pool_data.iterrows() - if row["exchange_name"] in exchanges -] - -static_pool_data = pd.DataFrame(static_pool_data) -static_pool_data['exchange_name'].unique() -mgr = Manager( - web3=cfg.w3, - w3_async=cfg.w3_async, - cfg=cfg, - pool_data=static_pool_data.to_dict(orient="records"), - SUPPORTED_EXCHANGES=exchanges, - alchemy_max_block_fetch=alchemy_max_block_fetch, - uniswap_v2_event_mappings=uniswap_v2_event_mappings, - tokens=tokens.to_dict(orient="records"), -) - -start_time = time.time() -Parallel(n_jobs=-1, backend="threading")( - delayed(mgr.add_pool_to_exchange)(row) for row in mgr.pool_data -) -cfg.logger.info(f"Time taken to add initial pools: {time.time() - start_time}") - -mgr.deduplicate_pool_data() -cids = [pool["cid"] for pool in mgr.pool_data] -assert len(cids) == len(set(cids)), "duplicate cid's exist in the pool data" -def init_bot(mgr: Manager) -> CarbonBot: - """ - Initializes the bot. - - Parameters - ---------- - mgr : Manager - The manager object. - - Returns - ------- - CarbonBot - The bot object. - """ - mgr.cfg.logger.info("Initializing the bot...") - bot = CarbonBot(ConfigObj=mgr.cfg) - bot.db = db - bot.db.mgr = mgr - assert isinstance( - bot.db, QueryInterface - ), "QueryInterface not initialized correctly" - return bot -bot = init_bot(mgr) -bot.db.remove_unmapped_uniswap_v2_pools() -bot.db.remove_zero_liquidity_pools() -bot.db.remove_unsupported_exchanges() -tokens = bot.db.get_tokens() -ADDRDEC = {t.address: (t.address, int(t.decimals)) for t in tokens if not math.isnan(t.decimals)} -flashloan_tokens = bot.RUN_FLASHLOAN_TOKENS -CCm = bot.get_curves() -pools = db.get_pool_data_with_tokens() - -arb_mode = "single" - -assert(cfg.DEFAULT_MIN_PROFIT_GAS_TOKEN <= 0.0001), f"[TestSingleMode], default_min_profit_gas_token must be <= 0.02 for this Notebook to run, currently set to {cfg.DEFAULT_MIN_PROFIT_GAS_TOKEN}" - - - - - -# ------------------------------------------------------------ -# Test 040 -# File test_040_TestSingleMode.py -# Segment Test_arb_mode_class -# ------------------------------------------------------------ -def test_test_arb_mode_class(): -# ------------------------------------------------------------ - - arb_finder = bot._get_arb_finder("single") - assert arb_finder.__name__ == "FindArbitrageSinglePairwise", f"[TestSingleMode] Expected arb_finder class name name = FindArbitrageSinglePairwise, found {arb_finder.__name__}" - - -# ------------------------------------------------------------ -# Test 040 -# File test_040_TestSingleMode.py -# Segment Test_tokens_and_combos -# ------------------------------------------------------------ -def test_test_tokens_and_combos(): -# ------------------------------------------------------------ - - # + - arb_finder = bot._get_arb_finder("single") - finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=arb_finder.AO_TOKENS, - ConfigObj=bot.ConfigObj, - ) - all_tokens, combos = finder.find_arbitrage() - - assert type(all_tokens) == set, f"[TestSingleMode] all_tokens is wrong data type. Expected set, found: {type(all_tokens)}" - assert type(combos) == list, f"[TestSingleMode] combos is wrong data type. Expected list, found: {type(combos)}" - assert len(all_tokens) > 100, f"[TestSingleMode] Using wrong dataset, expected at least 100 tokens, found {len(all_tokens)}" - assert len(combos) > 1000, f"[TestSingleMode] Using wrong dataset, expected at least 100 combos, found {len(combos)}" - - arb_finder = bot._get_arb_finder("single") - finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=arb_finder.AO_CANDIDATES, - ConfigObj=bot.ConfigObj, - ) - r = finder.find_arbitrage() - - for arb in r: - ( - best_profit, - best_trade_instructions_df, - best_trade_instructions_dic, - best_src_token, - best_trade_instructions, - ) = arb - assert len(best_trade_instructions_dic) <= 2, "[TestSingleMode] Expected arbs without multiple Carbon curves" - - assert len(r) >= 20, f"[TestSingleMode] Expected at least 20 arbs, found {len(r)}" - # - diff --git a/fastlane_bot/tests/test_042_TestBancorV3ModeTwoHop.py b/fastlane_bot/tests/test_042_TestBancorV3ModeTwoHop.py index b94c8b0dc..66ef673ca 100644 --- a/fastlane_bot/tests/test_042_TestBancorV3ModeTwoHop.py +++ b/fastlane_bot/tests/test_042_TestBancorV3ModeTwoHop.py @@ -130,8 +130,6 @@ def init_bot(mgr: Manager) -> CarbonBot: CCm = bot.get_curves() pools = db.get_pool_data_with_tokens() -arb_mode = "b3_two_hop" - # ------------------------------------------------------------ # Test 042 @@ -366,32 +364,9 @@ def test_test_get_fee_safe(): # ------------------------------------------------------------ # Test 042 # File test_042_TestBancorV3ModeTwoHop.py -# Segment Test_combos -# ------------------------------------------------------------ -def test_test_combos(): -# ------------------------------------------------------------ - - arb_finder = bot._get_arb_finder("b3_two_hop") - finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=False, - ConfigObj=bot.ConfigObj, - ) - #test_2_pools = [ConstantProductCurve(k=2921921249910.464, x=2760126.9934445512, x_act=2760126.9934445512, y_act=1058618.410258, pair='BNT-FF1C/USDC-eB48', cid='0xc4771395e1389e2e3a12ec22efbb7aff5b1c04e5ce9c7596a82e9dc8fdec725b', fee=0.0, descr='bancor_v3 BNT-FF1C/USDC-eB48 0.000', constr='uv2', params={'exchange': 'bancor_v3', 'tknx_dec': 18, 'tkny_dec': 6, 'tknx_addr': '0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C', 'tkny_addr': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'blocklud': 17713739}), ConstantProductCurve(k=518129588.60853314, x=6351922.348885405, x_act=6351922.348885405, y_act=81.57051679, pair='BNT-FF1C/WBTC-C599', cid='0x3885d978c125e66686e3f678ab64d5b09e61f89bf6e87c9ff66e740fd06aeefa', fee=0.0, descr='bancor_v3 BNT-FF1C/WBTC-C599 0.000', constr='uv2', params={'exchange': 'bancor_v3', 'tknx_dec': 18, 'tkny_dec': 8, 'tknx_addr': '0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C', 'tkny_addr': '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 'blocklud': 17713739}), ConstantProductCurve(k=787603837541.6204, x=5107.692365701484, x_act=4.159867948255851, y_act=336571.44633978605, pair='WBTC-C599/USDC-eB48', cid='0x49ed97db2c080b7eac91dfaa7d51d5e8ac34c4dcfbcd3e8f2ed326a2a527b959', fee=0.003, descr='uniswap_v3 WBTC-C599/USDC-eB48 3000', constr='pkpp', params={'exchange': 'uniswap_v3', 'tknx_dec': 8, 'tkny_dec': 6, 'tknx_addr': '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 'tkny_addr': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'blocklud': 17713395, 'L': 887470.4713632})] - flt = {'MKR-79A2', 'TRAC-0A6F', 'MONA-412A', 'WBTC-C599', 'WOO-5D4B', 'MATIC-eBB0', 'BAT-87EF', 'UOS-5C8c', 'LRC-EafD', 'NMR-6671', 'DIP-cD83', 'TEMP-1aB9', 'ICHI-A881', 'USDC-eB48', 'ENS-9D72', 'vBNT-7f94', 'ANKR-EDD4', 'UNI-F984', 'REQ-938a', 'WETH-6Cc2', 'AAVE-DaE9', 'ENJ-3B9c', 'MANA-C942', 'wNXM-2bDE', 'QNT-4675', 'RLC-7375', 'CROWN-E0fa', 'CHZ-b4AF', 'USDT-1ec7', 'DAI-1d0F', 'RPL-A51f', 'HOT-26E2', 'LINK-86CA', 'wstETH-2Ca0'} - combos = finder.get_combos(flashloan_tokens=flt, CCm=CCm, arb_mode="b3_two_hop") - print(combos) - assert len(combos) >= 1122, "[test_bancor_v3_two_hop] Different data used for tests, expected 1122 combos" - - -# ------------------------------------------------------------ -# Test 042 -# File test_042_TestBancorV3ModeTwoHop.py -# Segment Test_get_miniverse_combos +# Segment Test_get_combos # ------------------------------------------------------------ -def test_test_get_miniverse_combos(): +def test_test_get_combos(): # ------------------------------------------------------------ arb_finder = bot._get_arb_finder("b3_two_hop") @@ -403,6 +378,5 @@ def test_test_get_miniverse_combos(): ConfigObj=bot.ConfigObj, ) flt = {"0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C","0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","0x514910771AF9Ca656af840dff83E8264EcF986CA"} - combos = finder.get_combos(flashloan_tokens=flt, CCm=CCm, arb_mode="b3_two_hop") - all_miniverses = finder.get_miniverse_combos(combos) - assert len(all_miniverses) >= 6, f"[test_bancor_v3_two_hop] Different data used for tests, expected 6 miniverses, found {len(all_miniverses)}" \ No newline at end of file + combos = finder.get_combos(flashloan_tokens=flt, CCm=CCm) + assert len(combos) >= 6, f"[test_bancor_v3_two_hop] Different data used for tests, expected 6 combos, found {len(combos)}" \ No newline at end of file diff --git a/fastlane_bot/tests/test_043_TestEmptyCarbonOrders.py b/fastlane_bot/tests/test_043_TestEmptyCarbonOrders.py index f49c68958..88ee7b1ad 100644 --- a/fastlane_bot/tests/test_043_TestEmptyCarbonOrders.py +++ b/fastlane_bot/tests/test_043_TestEmptyCarbonOrders.py @@ -138,8 +138,6 @@ def init_bot(mgr: Manager) -> CarbonBot: CCm = bot.get_curves() pools = db.get_pool_data_with_tokens() -arb_mode = "multi" - # ------------------------------------------------------------ # Test 043 @@ -150,7 +148,7 @@ def test_test_empty_carbon_orders_removed(): # ------------------------------------------------------------ # + - arb_finder = bot._get_arb_finder("multi") + arb_finder = bot._get_arb_finder("multi_pairwise_all") finder = arb_finder( flashloan_tokens=flashloan_tokens, CCm=CCm, diff --git a/fastlane_bot/tests/test_045_Validator.py b/fastlane_bot/tests/test_045_Validator.py index ca8732097..09767d52b 100644 --- a/fastlane_bot/tests/test_045_Validator.py +++ b/fastlane_bot/tests/test_045_Validator.py @@ -137,7 +137,7 @@ def init_bot(mgr: Manager) -> CarbonBot: # ------------------------------------------------------------ def test_test_validator(): # ------------------------------------------------------------ - for arb_mode in ["single", "multi", "multi_triangle"]: + for arb_mode in ["multi_triangle"]: arb_finder = bot._get_arb_finder(arb_mode) finder = arb_finder( flashloan_tokens=flashloan_tokens, diff --git a/fastlane_bot/tests/test_047_Randomizer.py b/fastlane_bot/tests/test_047_Randomizer.py index b2f250575..b3f0e832a 100644 --- a/fastlane_bot/tests/test_047_Randomizer.py +++ b/fastlane_bot/tests/test_047_Randomizer.py @@ -128,8 +128,6 @@ def init_bot(mgr: Manager) -> CarbonBot: CCm = bot.get_curves() pools = db.get_pool_data_with_tokens() -arb_mode = "multi" - # ------------------------------------------------------------ # Test 047 @@ -140,7 +138,7 @@ def test_test_randomizer(): # ------------------------------------------------------------ # + - arb_finder = bot._get_arb_finder("multi") + arb_finder = bot._get_arb_finder("multi_pairwise_all") finder = arb_finder( flashloan_tokens=flashloan_tokens, CCm=CCm, diff --git a/fastlane_bot/tests/test_048_RespectFlashloanTokensClickParam.py b/fastlane_bot/tests/test_048_RespectFlashloanTokensClickParam.py index 7169c7af0..a10be14f2 100644 --- a/fastlane_bot/tests/test_048_RespectFlashloanTokensClickParam.py +++ b/fastlane_bot/tests/test_048_RespectFlashloanTokensClickParam.py @@ -84,4 +84,4 @@ def run_command(arb_mode): def test_test_flashloan_tokens_is_respected(): # ------------------------------------------------------------ - run_command("multi") \ No newline at end of file + run_command("multi_pairwise_all") \ No newline at end of file diff --git a/fastlane_bot/tests/test_050_TestBancorV2.py b/fastlane_bot/tests/test_050_TestBancorV2.py index 45a036f8f..8dbf0b9f5 100644 --- a/fastlane_bot/tests/test_050_TestBancorV2.py +++ b/fastlane_bot/tests/test_050_TestBancorV2.py @@ -132,8 +132,6 @@ def init_bot(mgr: Manager) -> CarbonBot: CCm = bot.get_curves() pools = db.get_pool_data_with_tokens() -arb_mode = "multi" - # ------------------------------------------------------------ # Test 050 @@ -155,7 +153,7 @@ def test_test_min_profit(): def test_test_combos_and_tokens(): # ------------------------------------------------------------ - arb_finder = bot._get_arb_finder("multi") + arb_finder = bot._get_arb_finder("multi_pairwise_all") finder = arb_finder( flashloan_tokens=flashloan_tokens, CCm=CCm, @@ -307,9 +305,8 @@ def test_test_expected_output_bancorv2(): encoded_trade_instructions, deadline ) ] - b2pools = [pool['anchor'] for pool in mgr.pool_data if pool["exchange_name"] in "bancor_v2"] bancor_v2_converter_addresses = [pool["anchor"] for pool in state if pool["exchange_name"] in "bancor_v2"] - assert arb_finder.__name__ == "FindArbitrageMultiPairwiseAll", f"[NBTest_50_TestBancorV2] Expected arb_finder class name name = FindArbitrageMultiPairwise, found {arb_finder.__name__}" + assert arb_finder.__name__ == "FindArbitrageMultiPairwiseAll", f"[NBTest_50_TestBancorV2] Expected arb_finder class name = FindArbitrageMultiPairwiseAll, found {arb_finder.__name__}" assert len(r) > 30, f"[NBTest_50_TestBancorV2] Expected at least 30 arb opps, found {len(r)}" assert len(arb_with_bancor_v2) >= 3, f"[NBTest_50_TestBancorV2] Expected at least 3 arb opps with Bancor V2 pools, found {len(arb_with_bancor_v2)}" assert encoded_trade_instructions[0].amtin_wei == flashloan_amount, f"[NBTest_50_TestBancorV2] First trade in should match flashloan amount, {encoded_trade_instructions[0].amtin_wei} does not = {flashloan_amount}" diff --git a/fastlane_bot/tests/test_058_BalancerIntegration.py b/fastlane_bot/tests/test_058_BalancerIntegration.py index ab5fde773..9dbff5fd6 100644 --- a/fastlane_bot/tests/test_058_BalancerIntegration.py +++ b/fastlane_bot/tests/test_058_BalancerIntegration.py @@ -136,8 +136,6 @@ def init_bot(mgr: Manager) -> CarbonBot: CCm = bot.get_curves() pools = db.get_pool_data_with_tokens() -arb_mode = "multi_pairwise" - # ------------------------------------------------------------ # Test 058 diff --git a/fastlane_bot/tests/test_060_TestRoutehandlerCarbonPrecision.py b/fastlane_bot/tests/test_060_TestRoutehandlerCarbonPrecision.py index 33b0fd1c3..ed4f2c7e1 100644 --- a/fastlane_bot/tests/test_060_TestRoutehandlerCarbonPrecision.py +++ b/fastlane_bot/tests/test_060_TestRoutehandlerCarbonPrecision.py @@ -166,10 +166,8 @@ def init_bot(mgr: Manager) -> CarbonBot: def test_test_precision_using_all_tokens_in_carbon(): # ------------------------------------------------------------ - # + - arb_mode = "multi" - - arb_finder = bot._get_arb_finder(arb_mode) + # + + arb_finder = bot._get_arb_finder("multi_pairwise_all") finder = arb_finder( flashloan_tokens=flashloan_tokens, CCm=CCm, diff --git a/fastlane_bot/tests/test_061_TestWETHConversion.py b/fastlane_bot/tests/test_061_TestWETHConversion.py index aa96f81e2..3df06ab74 100644 --- a/fastlane_bot/tests/test_061_TestWETHConversion.py +++ b/fastlane_bot/tests/test_061_TestWETHConversion.py @@ -172,7 +172,7 @@ def init_bot(mgr: Manager) -> CarbonBot: # ## Test_Wrap_Unwrap_Gas_Token_In_Route_Struct # + -arb_mode = "multi" +arb_mode = "multi_pairwise_all" arb_finder = bot._get_arb_finder(arb_mode) finder = arb_finder( diff --git a/fastlane_bot/tests/test_063_TestBancorPOLMode.py b/fastlane_bot/tests/test_063_TestBancorPOLMode.py index 97ade2788..f68c7b83f 100644 --- a/fastlane_bot/tests/test_063_TestBancorPOLMode.py +++ b/fastlane_bot/tests/test_063_TestBancorPOLMode.py @@ -128,8 +128,6 @@ def init_bot(mgr: Manager) -> CarbonBot: CCm = bot.get_curves() pools = db.get_pool_data_with_tokens() -arb_mode = "multi_pairwise_pol" - # ------------------------------------------------------------ # Test 063 @@ -176,7 +174,7 @@ def test_test_combos_and_tokens(): assert type(combos) == list, f"[NBTest 063 TestMultiPairwisePOLMode] combos is wrong data type. Expected list, found: {type(combos)}" assert ('0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2') in combos or ('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', '0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C') in combos, f"[NBTest 063 TestMultiPairwisePOLMode] Expected BNT/WETH or WETH/BNT in combos" assert len(all_tokens) >= 73, f"[NBTest 063 TestMultiPairwisePOLMode] Using wrong dataset, expected at least 73 tokens, found {len(all_tokens)}" - assert len(combos) >= 146, f"[NBTest 063 TestMultiPairwisePOLMode] Using wrong dataset, expected at least 146 combos, found {len(combos)}" + assert len(combos) >= 73, f"[NBTest 063 TestMultiPairwisePOLMode] Using wrong dataset, expected at least 73 combos, found {len(combos)}" # ------------------------------------------------------------ @@ -220,6 +218,6 @@ def test_test_expected_output(): if trade["tknin"] not in carbon_tkn_in: carbon_wrong_direction_count += 1 - assert len(r) >= 36, f"[NBTest 063 TestMultiPairwisePOLMode] Expected at least 27 arbs, found {len(r)}" + assert len(r) >= 18, f"[NBTest 063 TestMultiPairwisePOLMode] Expected at least 18 arbs, found {len(r)}" assert multi_carbon_count > 0, f"[NBTest 063 TestMultiPairwisePOLMode] Not finding arbs with multiple Carbon curves." assert carbon_wrong_direction_count == 0, f"[NBTest 063 TestMultiPairwisePOLMode Mode] Expected all Carbon curves to have the same tkn in and tkn out. Mixing is currently not supported." \ No newline at end of file diff --git a/fastlane_bot/tests/test_064_TestMultiAllMode.py b/fastlane_bot/tests/test_064_TestMultiAllMode.py index 6d7ff45be..9a9c85776 100644 --- a/fastlane_bot/tests/test_064_TestMultiAllMode.py +++ b/fastlane_bot/tests/test_064_TestMultiAllMode.py @@ -128,8 +128,6 @@ def init_bot(mgr: Manager) -> CarbonBot: CCm = bot.get_curves() pools = db.get_pool_data_with_tokens() -arb_mode = "multi_pairwise_all" - # ------------------------------------------------------------ # Test 064 diff --git a/fastlane_bot/tests/test_901_TestMultiTriangleModeSlow.py b/fastlane_bot/tests/test_901_TestMultiTriangleModeSlow.py index a003d4fdf..7ad21b45b 100644 --- a/fastlane_bot/tests/test_901_TestMultiTriangleModeSlow.py +++ b/fastlane_bot/tests/test_901_TestMultiTriangleModeSlow.py @@ -129,8 +129,6 @@ def init_bot(mgr: Manager) -> CarbonBot: CCm = bot.get_curves() pools = db.get_pool_data_with_tokens() -arb_mode = "multi_triangle" - # ------------------------------------------------------------ # Test 901 @@ -145,7 +143,7 @@ def test_test_min_profit(): # ### Test_arb_mode_class arb_finder = bot._get_arb_finder("multi_triangle") - assert arb_finder.__name__ == "ArbitrageFinderTriangleMulti", f"[TestMultiTriangleMode] Expected arb_finder class name name = FindArbitrageMultiPairwise, found {arb_finder.__name__}" + assert arb_finder.__name__ == "ArbitrageFinderTriangleMulti", f"[TestMultiTriangleMode] Expected arb_finder class name = ArbitrageFinderTriangleMulti, found {arb_finder.__name__}" # ------------------------------------------------------------ @@ -164,7 +162,7 @@ def test_test_combos(): result=arb_finder.AO_TOKENS, ConfigObj=bot.ConfigObj, ) - combos = finder.get_combos(flashloan_tokens=flashloan_tokens, CCm=CCm, arb_mode="multi_triangle") + combos = finder.get_combos(flashloan_tokens=flashloan_tokens, CCm=CCm) assert len(combos) >= 1225, f"[TestMultiTriangleMode] Using wrong dataset, expected at least 1225 combos, found {len(combos)}" # + @@ -217,85 +215,3 @@ def test_test_combos(): assert multi_carbon_count > 0, f"[TestMultiTriangleMode] Not finding arbs with multiple Carbon curves." assert len(r) >= 58, f"[TestMultiTriangleMode] Expected at least 58 arbs, found {len(r)}" # - - - -# ------------------------------------------------------------ -# Test 901 -# File test_901_TestMultiTriangleModeSlow.py -# Segment Test Triangle Single -# ------------------------------------------------------------ -def test_test_triangle_single(): -# ------------------------------------------------------------ - - arb_finder = bot._get_arb_finder("triangle") - assert arb_finder.__name__ == "ArbitrageFinderTriangleSingle", f"[TestMultiTriangleMode] Expected arb_finder class name name = ArbitrageFinderTriangleSingle, found {arb_finder.__name__}" - - -# ------------------------------------------------------------ -# Test 901 -# File test_901_TestMultiTriangleModeSlow.py -# Segment Test_combos_triangle_single -# ------------------------------------------------------------ -def test_test_combos_triangle_single(): -# ------------------------------------------------------------ - - arb_finder = bot._get_arb_finder("triangle") - finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=arb_finder.AO_TOKENS, - ConfigObj=bot.ConfigObj, - ) - combos = finder.get_combos(flashloan_tokens=flashloan_tokens, CCm=CCm, arb_mode="multi_triangle") - assert len(combos) >= 1225, f"[TestMultiTriangleMode] Using wrong dataset, expected at least 1225 combos, found {len(combos)}" - - -# ------------------------------------------------------------ -# Test 901 -# File test_901_TestMultiTriangleModeSlow.py -# Segment Test_Find_Arbitrage_Single -# ------------------------------------------------------------ -def test_test_find_arbitrage_single(): -# ------------------------------------------------------------ - - # + - arb_finder = bot._get_arb_finder("triangle") - finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=arb_finder.AO_CANDIDATES, - ConfigObj=bot.ConfigObj, - ) - r = finder.find_arbitrage() - multi_carbon_count = 0 - for arb in r: - ( - best_profit, - best_trade_instructions_df, - best_trade_instructions_dic, - best_src_token, - best_trade_instructions, - ) = arb - if len(best_trade_instructions_dic) > 3: - multi_carbon_count += 1 - tkn_in = None - tkn_out = None - # Find the first Carbon Curve to establish tknin and tknout - for curve in best_trade_instructions_dic: - if "-0" in curve['cid'] or "-1" in curve['cid']: - tkn_in = curve["tknin"] - tknout = curve["tknout"] - break - for curve in best_trade_instructions_dic: - if "-0" in curve['cid'] or "-1" in curve['cid']: - if curve["tknin"] in [tkn_in, tkn_out] and curve["tknout"] in [tkn_in, tkn_out]: - assert curve["tknin"] in tkn_in, f"[TestMultiTriangleMode] Finding Carbon curves in opposite directions - not supported in this mode." - assert curve["tknout"] in tkn_out, f"[TestMultiTriangleMode] Finding Carbon curves in opposite directions - not supported in this mode." - - assert multi_carbon_count == 0, f"[TestMultiTriangleMode] Expected 0 arbs with multiple Carbon curves for Triangle Single mode, found {multi_carbon_count}." - assert len(r) >= 58, f"[TestMultiTriangleMode] Expected at least 58 arbs, found {len(r)}" - # - - - \ No newline at end of file diff --git a/fastlane_bot/tests/test_906_TargetTokens.py b/fastlane_bot/tests/test_906_TargetTokens.py index b2651b76c..86485a114 100644 --- a/fastlane_bot/tests/test_906_TargetTokens.py +++ b/fastlane_bot/tests/test_906_TargetTokens.py @@ -88,4 +88,4 @@ def run_command(mode): def test_test_flashloan_tokens_b3_two_hop(): # ------------------------------------------------------------ - run_command("single") \ No newline at end of file + run_command("b3_two_hop") \ No newline at end of file diff --git a/fastlane_bot/tests_on_hold/test_907_RuntimeParameters.py b/fastlane_bot/tests_on_hold/test_907_RuntimeParameters.py index f36d97192..6e0862e7d 100644 --- a/fastlane_bot/tests_on_hold/test_907_RuntimeParameters.py +++ b/fastlane_bot/tests_on_hold/test_907_RuntimeParameters.py @@ -57,9 +57,7 @@ def __init__(self): arb_mode_happy_path_options = [ - "single", - "multi", - "triangle", + "multi_pairwise_all", "multi_triangle", "b3_two_hop", ] diff --git a/main.py b/main.py index fcc722efe..1c09637d8 100644 --- a/main.py +++ b/main.py @@ -580,14 +580,11 @@ def run(mgr, args, tenderly_uri=None) -> None: default="multi_pairwise_all", help="See arb_mode in bot.py", choices=[ - "single", - "multi", - "triangle", "multi_triangle", + "multi_triangle_complete", "b3_two_hop", "multi_pairwise_pol", "multi_pairwise_all", - "multi_triangle_complete" ], ) parser.add_argument( diff --git a/scan_log_errors.py b/scan_log_errors.py deleted file mode 100644 index 5fd520ff8..000000000 --- a/scan_log_errors.py +++ /dev/null @@ -1,173 +0,0 @@ -import os -import re -import time -from datetime import datetime -from datetime import timedelta - -import click - - -def is_valid_timestamp_dir(name, time_format): - try: - datetime.strptime(name, time_format) - return True - except ValueError: - return False - - -def contains_only_one_specific_word(file_path, target_word, other_words): - with open(file_path, "r") as file: - file_contents = file.read() - - # Define a regex pattern with word boundaries for the target word - target_word_pattern = r"\b" + re.escape(target_word) + r"\b" - target_word_count = len(re.findall(target_word_pattern, file_contents)) - if target_word_count == 0: - return False - - # Check for other words - for word in other_words: - if word != target_word: - # Define a regex pattern with word boundaries for each other word - word_pattern = r"\b" + re.escape(word) + r"\b" - if re.search(word_pattern, file_contents): - return False - - return True - - -def find_latest_log_with_string( - logs_dir, search_string, non_search_patterns, time_format="%Y%m%d-%H%M%S" -): - latest_time = None - latest_folder = None - - for folder_name in os.listdir(logs_dir): - folder_path = os.path.join(logs_dir, folder_name) - if os.path.isdir(folder_path) and is_valid_timestamp_dir( - folder_name, time_format - ): - log_file_path = os.path.join(folder_path, "bot.log") - if os.path.exists(log_file_path): - is_match = contains_only_one_specific_word( - log_file_path, search_string, non_search_patterns - ) - if is_match: - folder_time = datetime.strptime(folder_name, time_format) - if not latest_time or folder_time > latest_time: - latest_time = folder_time - latest_folder = folder_path - - return latest_folder - - -def read_log_file(file_path): - with open(file_path, "r") as file: - return file.read() - - -def scan_logs_for_opportunity( - logs, search_pattern, max_minutes, latest_opportunity_timestamp=None -): - time_pattern = r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),\d{3}" - search_regex = re.compile(time_pattern + r".*" + search_pattern) - error_detected = True - - for line in logs.split("\n"): - match = search_regex.search(line) - if match: - last_timestamp = match.group(1) - if ( - latest_opportunity_timestamp - and datetime.strptime(last_timestamp, "%Y-%m-%d %H:%M:%S") - > latest_opportunity_timestamp - ): - error_detected = False - print(f"Opportunity found at {last_timestamp}") - break - - if error_detected: - raise ValueError( - f"Opportunity not found within {max_minutes} minutes interval." - ) - - -@click.command() -@click.option("--logs_directory", default="logs", type=str) -@click.option("--interval", default=30, type=int) -@click.option("--search_pattern", default="multi_pairwise_all", type=str) -@click.option("--max_minutes", default=10, type=int) -def main(logs_directory, interval, search_pattern, max_minutes): - - all_patterns = [ - "single", - "multi", - "triangle", - "multi_triangle", - "bancor_v3", - "b3_two_hop", - "multi_pairwise_pol", - "multi_pairwise_bal", - "multi_pairwise_all", - ] - - if search_pattern not in all_patterns: - print(f"Invalid search pattern. Valid patterns are: {all_patterns}") - raise ValueError("Invalid search pattern.") - - non_search_patterns = [ - f"arb_mode: {pattern}" for pattern in all_patterns if pattern != search_pattern - ] - - search_pattern = f"arb_mode: {search_pattern}" - - latest_folder = find_latest_log_with_string( - logs_directory, search_pattern, non_search_patterns - ) - - if latest_folder: - print(f"Latest folder containing '{search_pattern}': {latest_folder}") - else: - print("No folder found with the specified pattern.") - raise ValueError("No folder found with the specified pattern.") - - print("Starting continuous scan of log file.") - while True: - last_timestamp = datetime.now() - timedelta(minutes=max_minutes) - try: - logs = read_log_file(f"{latest_folder}/bot.log") - scan_logs_for_opportunity( - logs, r"Opportunity with profit:", max_minutes, last_timestamp - ) - print("Everything is OK...") - except ValueError as e: - next_latest_folder = find_latest_log_with_string( - logs_directory, search_pattern, non_search_patterns - ) - if next_latest_folder and next_latest_folder != latest_folder: - print( - f"Latest folder containing '{search_pattern}': {next_latest_folder}" - ) - latest_folder = next_latest_folder - continue - else: - print(e) - break - except Exception as ex: - next_latest_folder = find_latest_log_with_string( - logs_directory, search_pattern, non_search_patterns - ) - if next_latest_folder and next_latest_folder != latest_folder: - print( - f"Latest folder containing '{search_pattern}': {next_latest_folder}" - ) - latest_folder = next_latest_folder - continue - else: - print(f"An error occurred: {ex}") - break - time.sleep(interval) - - -if __name__ == "__main__": - main()