From feed3f2c069fcdc460be34f7d9d90c692e05a435 Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:03:53 -0700 Subject: [PATCH 1/2] New Feature: Add target_tokens Setting to Narrow Search Space --- fastlane_bot/events/interface.py | 24 +- main.py | 36 ++ .../NBTest/NBTest_906_TargetTokens.ipynb | 314 ++++++++++++++++++ resources/NBTest/NBTest_906_TargetTokens.py | 101 ++++++ 4 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 resources/NBTest/NBTest_906_TargetTokens.ipynb create mode 100644 resources/NBTest/NBTest_906_TargetTokens.py diff --git a/fastlane_bot/events/interface.py b/fastlane_bot/events/interface.py index f4548a55b..15203b8b8 100644 --- a/fastlane_bot/events/interface.py +++ b/fastlane_bot/events/interface.py @@ -60,6 +60,26 @@ class QueryInterface: def cfg(self) -> Config: return self.ConfigObj + def filter_target_tokens(self, target_tokens: List[str]): + """ + Filter the pools to only include pools that are in the target pools list + + Parameters + ---------- + target_tokens: List[str] + The list of tokens to filter pools by. Pools must contain both tokens in the list to be included. + """ + initial_state = self.state.copy() + self.state = [ + pool + for pool in self.state + if pool["tkn0_key"] in target_tokens and pool["tkn1_key"] in target_tokens + ] + + self.cfg.logger.info( + f"Limiting pools by target_tokens. Removed {len(initial_state) - len(self.state)} non target-pools. {len(self.state)} pools remaining" + ) + def remove_unsupported_exchanges(self) -> None: initial_state = self.state.copy() self.state = [ @@ -479,8 +499,8 @@ def get_pool(self, **kwargs) -> Optional[PoolAndTokens]: ) pool.exchange_name except AttributeError: - if 'cid' in kwargs: - kwargs['cid'] = int(kwargs['cid']) + if "cid" in kwargs: + kwargs["cid"] = int(kwargs["cid"]) pool = next( ( pool diff --git a/main.py b/main.py index c7ed8b182..b5765ecdf 100644 --- a/main.py +++ b/main.py @@ -136,6 +136,12 @@ type=int, help="Set to the timeout in seconds. Set to None for no timeout.", ) +@click.option( + "--target_tokens", + default=None, + type=str, + help="A comma-separated string of tokens to target. Use None to target all tokens. Use `flashloan_tokens` to target only the flashloan tokens.", +) def main( cache_latest_only: bool, backdate_pools: bool, @@ -157,6 +163,7 @@ def main( limit_bancor3_flashloan_tokens: bool, default_min_profit_bnt: int, timeout: int, + target_tokens: str, ): """ The main entry point of the program. It sets up the configuration, initializes the web3 and Base objects, @@ -182,6 +189,7 @@ def main( limit_bancor3_flashloan_tokens (bool): Whether to limit the flashloan tokens to the ones supported by Bancor v3 or not. default_min_profit_bnt (int): The default minimum profit in BNT. timeout (int): The timeout in seconds. + target_tokens (str): A comma-separated string of tokens to target. Use None to target all tokens. Use `flashloan_tokens` to target only the flashloan tokens. """ start_time = time.time() @@ -221,6 +229,27 @@ def main( f"Flashloan tokens are set as: {flashloan_tokens}, {type(flashloan_tokens)}" ) + if target_tokens: + if target_tokens == "flashloan_tokens": + target_tokens = flashloan_tokens + else: + target_tokens = target_tokens.split(",") + target_tokens = [ + QueryInterface.cleanup_token_key(token) for token in target_tokens + ] + + # Ensure that the target tokens are a subset of the flashloan tokens + for token in flashloan_tokens: + if token not in target_tokens: + cfg.logger.warning( + f"Falshloan token {token} not in target tokens. Adding it to target tokens." + ) + target_tokens.append(token) + + cfg.logger.info( + f"Target tokens are set as: {target_tokens}, {type(target_tokens)}" + ) + # Set external exchanges exchanges = exchanges.split(",") cfg.logger.info(f"Running data fetching for exchanges: {exchanges}") @@ -304,6 +333,7 @@ def main( run_data_validator, randomizer, timeout, + target_tokens, ) @@ -322,6 +352,7 @@ def run( run_data_validator: bool, randomizer: int, timeout: int, + target_tokens: List[str] or None, ) -> None: """ The main function that drives the logic of the program. It uses helper functions to handle specific tasks. @@ -341,6 +372,7 @@ def run( run_data_validator (bool): Whether to run the data validator or not. randomizer (bool): Whether to randomize the polling interval or not. timeout (int): The timeout for the polling interval. + target_tokens (List[str]): List of tokens that the bot will target for arbitrage. """ def get_event_filters( @@ -662,6 +694,10 @@ def update_pools_from_contracts( bot.db.remove_zero_liquidity_pools() bot.db.remove_unsupported_exchanges() + # Filter the target tokens + if target_tokens: + bot.db.filter_target_tokens(target_tokens) + # Run the bot bot.run( polling_interval=polling_interval, diff --git a/resources/NBTest/NBTest_906_TargetTokens.ipynb b/resources/NBTest/NBTest_906_TargetTokens.ipynb new file mode 100644 index 000000000..290a360f7 --- /dev/null +++ b/resources/NBTest/NBTest_906_TargetTokens.ipynb @@ -0,0 +1,314 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 7, + "id": "initial_id", + "metadata": { + "ExecuteTime": { + "end_time": "2023-08-09T17:45:55.576598Z", + "start_time": "2023-08-09T17:45:55.569839Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ConstantProductCurve v2.14 (23/May/2023)\n", + "CarbonBot v3-b2.2 (20/June/2023)\n", + "UniswapV2 v0.0.1 (2023-07-03)\n", + "UniswapV3 v0.0.1 (2023-07-03)\n", + "SushiswapV2 v0.0.1 (2023-07-03)\n", + "CarbonV1 v0.0.1 (2023-07-03)\n", + "BancorV3 v0.0.1 (2023-07-03)\n", + "Version = 3-b2.2 [requirements >= 3.0 is met]\n" + ] + } + ], + "source": [ + "# coding=utf-8\n", + "\"\"\"\n", + "This module contains the tests which ensure the target_tokens parameter is respected.\n", + "\"\"\"\n", + "from fastlane_bot import Bot\n", + "from fastlane_bot.tools.cpc import ConstantProductCurve as CPC\n", + "from fastlane_bot.events.exchanges import UniswapV2, UniswapV3, SushiswapV2, CarbonV1, BancorV3\n", + "import subprocess, os, sys\n", + "import pytest\n", + "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(CPC))\n", + "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(Bot))\n", + "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(UniswapV2))\n", + "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(UniswapV3))\n", + "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(SushiswapV2))\n", + "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(CarbonV1))\n", + "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(BancorV3))\n", + "from fastlane_bot.testing import *\n", + "plt.rcParams['figure.figsize'] = [12,6]\n", + "from fastlane_bot import __VERSION__\n", + "require(\"3.0\", __VERSION__)" + ] + }, + { + "cell_type": "markdown", + "id": "dfafc69730cbead3", + "metadata": {}, + "source": [ + "# Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1640a40ee6ae871c", + "metadata": { + "ExecuteTime": { + "end_time": "2023-08-09T17:45:55.588072Z", + "start_time": "2023-08-09T17:45:55.575696Z" + } + }, + "outputs": [], + "source": [ + "from fastlane_bot.tools.cpc import T\n", + "\n", + "\n", + "def find_main_py():\n", + " # Start at the directory of the current script\n", + " cwd = os.path.abspath(os.path.join(os.getcwd()))\n", + " \n", + " with open(\"log.txt\", \"w\") as f:\n", + " f.write(f\"Searching for main.py in {cwd}\")\n", + " \n", + " print(f\"Searching for main.py in {cwd}\")\n", + " while True:\n", + " # Check if main.py exists in the current directory\n", + " if \"main.py\" in os.listdir(cwd):\n", + " return cwd # Found the directory containing main.py\n", + " else:\n", + " # If not, go up one directory\n", + " new_cwd = os.path.dirname(cwd)\n", + "\n", + " # If we're already at the root directory, stop searching\n", + " if new_cwd == cwd:\n", + " raise FileNotFoundError(\"Could not find main.py in any parent directory\")\n", + "\n", + " cwd = new_cwd\n", + " \n", + " \n", + "def run_command(mode):\n", + " \n", + " # Find the correct path to main.py\n", + " main_script_path = find_main_py()\n", + " print(f\"Found main.py in {main_script_path}\")\n", + " main_script_path = main_script_path + \"/main.py\"\n", + "\n", + " # Run the command\n", + " cmd = [\n", + " \"python\",\n", + " main_script_path,\n", + " f\"--arb_mode={mode}\",\n", + " \"--use_cached_events=True\",\n", + " \"--logging_path=fastlane_bot/data/\",\n", + " \"--timeout=45\",\n", + " f\"--target_tokens={T.WETH},{T.DAI}\"\n", + " ]\n", + " subprocess.Popen(cmd)\n", + " \n", + " # Wait for the expected log line to appear\n", + " expected_log_line = \"Limiting pools by target_tokens. Removed \"\n", + " found = False\n", + " result = subprocess.run(cmd, text=True, capture_output=True, check=True, timeout=120)\n", + "\n", + " # Check if the expected log line is in the output\n", + " if expected_log_line in result.stderr:\n", + " found = True\n", + "\n", + " if not found:\n", + " pytest.fail(\"Expected log line was not found within 1 minute\") # If we reach this point, the test has failed" + ] + }, + { + "cell_type": "markdown", + "id": "614f482aec4be2f8", + "metadata": {}, + "source": [ + "## Test Flashloan Tokens b3_two_hop" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c6e198d0eeba3183", + "metadata": { + "ExecuteTime": { + "end_time": "2023-08-09T17:46:55.475498Z", + "start_time": "2023-08-09T17:45:55.578507Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Searching for main.py in /Users/mikewcasale/Documents/GitHub/fastlane-bot/resources/NBTest\n", + "Found main.py in /Users/mikewcasale/Documents/GitHub/fastlane-bot\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Python-dotenv could not parse statement starting at line 3\n", + "Python-dotenv could not parse statement starting at line 4\n", + "Python-dotenv could not parse statement starting at line 5\n", + "Python-dotenv could not parse statement starting at line 6\n", + "Python-dotenv could not parse statement starting at line 3\n", + "Python-dotenv could not parse statement starting at line 4\n", + "Python-dotenv could not parse statement starting at line 5\n", + "Python-dotenv could not parse statement starting at line 6\n", + "Python-dotenv could not parse statement starting at line 3\n", + "Python-dotenv could not parse statement starting at line 4\n", + "Python-dotenv could not parse statement starting at line 5\n", + "Python-dotenv could not parse statement starting at line 6\n", + "Python-dotenv could not parse statement starting at line 3\n", + "Python-dotenv could not parse statement starting at line 4\n", + "Python-dotenv could not parse statement starting at line 5\n", + "Python-dotenv could not parse statement starting at line 6\n", + "Python-dotenv could not parse statement starting at line 3\n", + "Python-dotenv could not parse statement starting at line 4\n", + "Python-dotenv could not parse statement starting at line 5\n", + "Python-dotenv could not parse statement starting at line 6\n", + "Python-dotenv could not parse statement starting at line 3\n", + "Python-dotenv could not parse statement starting at line 4\n", + "Python-dotenv could not parse statement starting at line 5\n", + "Python-dotenv could not parse statement starting at line 6\n", + "Python-dotenv could not parse statement starting at line 3\n", + "Python-dotenv could not parse statement starting at line 4\n", + "Python-dotenv could not parse statement starting at line 5\n", + "Python-dotenv could not parse statement starting at line 6\n", + "2023-08-09 10:45:59,352 [fastlane:INFO] - Using mainnet config\n", + "2023-08-09 10:45:59,352 [fastlane:INFO] - Flashloan tokens are set as: ['WETH-6Cc2', 'DAI-1d0F', 'USDC-eB48', 'USDT-1ec7', 'WBTC-C599', 'BNT-FF1C', 'ETH-EEeE'], \n", + "2023-08-09 10:45:59,352 [fastlane:WARNING] - Falshloan token USDC-eB48 not in target tokens. Adding it to target tokens.\n", + "2023-08-09 10:45:59,352 [fastlane:WARNING] - Falshloan token USDT-1ec7 not in target tokens. Adding it to target tokens.\n", + "2023-08-09 10:45:59,352 [fastlane:WARNING] - Falshloan token WBTC-C599 not in target tokens. Adding it to target tokens.\n", + "2023-08-09 10:45:59,352 [fastlane:WARNING] - Falshloan token BNT-FF1C not in target tokens. Adding it to target tokens.\n", + "2023-08-09 10:45:59,352 [fastlane:WARNING] - Falshloan token ETH-EEeE not in target tokens. Adding it to target tokens.\n", + "2023-08-09 10:45:59,352 [fastlane:INFO] - Target tokens are set as: ['WETH-6Cc2', 'DAI-1d0F', 'USDC-eB48', 'USDT-1ec7', 'WBTC-C599', 'BNT-FF1C', 'ETH-EEeE'], \n", + "2023-08-09 10:45:59,352 [fastlane:INFO] - Running data fetching for exchanges: ['carbon_v1', 'bancor_v3', 'uniswap_v3', 'uniswap_v2', 'sushiswap_v2']\n", + "2023-08-09 10:45:59,631 [fastlane:INFO] - Time taken to add initial pools: 0.08833503723144531\n", + "2023-08-09 10:45:59,840 [fastlane:INFO] - Fetching events from 17876915 to 17878915... 0\n", + "2023-08-09 10:45:59,840 [fastlane:INFO] - Using cached events\n", + "2023-08-09 10:45:59,841 [fastlane:INFO] - Found 25 new events\n", + "2023-08-09 10:46:02,626 [fastlane:INFO] - Updating carbon pools w/ multicall...\n", + "2023-08-09 10:46:03,654 [fastlane:INFO] - Fetched 281 carbon strategies in 1.0274429321289062 seconds\n", + "2023-08-09 10:46:09,610 [fastlane:INFO] - Updated 281 carbon strategies info in 5.956198215484619 seconds\n", + "2023-08-09 10:46:27,709 [fastlane:INFO] - Initializing the bot...\n", + "2023-08-09 10:46:27,818 [fastlane:INFO] - State has changed...\n", + "2023-08-09 10:46:27,825 [fastlane:INFO] - Removed 122 unmapped uniswap_v2/sushi pools. 5078 uniswap_v2/sushi pools remaining\n", + "2023-08-09 10:46:27,825 [fastlane:INFO] - Unmapped uniswap_v2/sushi pools:\n", + "2023-08-09 10:46:28,146 [fastlane:INFO] - uniswap_v3: 0\n", + "2023-08-09 10:46:28,146 [fastlane:INFO] - uniswap_v2: 122\n", + "2023-08-09 10:46:28,146 [fastlane:INFO] - sushiswap_v2: 0\n", + "2023-08-09 10:46:40,251 [fastlane:INFO] - Fetching events from 17878916 to 17878919... 17878915\n", + "2023-08-09 10:46:40,251 [fastlane:INFO] - Using cached events\n", + "2023-08-09 10:46:40,251 [fastlane:INFO] - Found 25 new events\n", + "2023-08-09 10:46:42,744 [fastlane:INFO] - Initializing the bot...\n", + "2023-08-09 10:46:42,873 [fastlane:INFO] - Removed 122 unmapped uniswap_v2/sushi pools. 5078 uniswap_v2/sushi pools remaining\n", + "2023-08-09 10:46:42,873 [fastlane:INFO] - Unmapped uniswap_v2/sushi pools:\n", + "2023-08-09 10:46:43,219 [fastlane:INFO] - uniswap_v3: 0\n", + "2023-08-09 10:46:43,219 [fastlane:INFO] - uniswap_v2: 122\n", + "2023-08-09 10:46:43,219 [fastlane:INFO] - sushiswap_v2: 0\n", + "2023-08-09 10:46:43,221 [fastlane:INFO] - uniswap_v3: 11\n", + "2023-08-09 10:46:43,221 [fastlane:INFO] - sushiswap_v2: 2\n", + "2023-08-09 10:46:43,221 [fastlane:INFO] - uniswap_v2: 13\n", + "2023-08-09 10:46:43,221 [fastlane:INFO] - bancor_v2: 0\n", + "2023-08-09 10:46:43,221 [fastlane:INFO] - bancor_v3: 33\n", + "2023-08-09 10:46:43,221 [fastlane:INFO] - carbon_v1: 281\n", + "2023-08-09 10:46:43,238 [fastlane:INFO] - uniswap_v3_zero_liquidity_pools: 1483\n", + "2023-08-09 10:46:43,238 [fastlane:INFO] - sushiswap_v2_zero_liquidity_pools: 87\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - uniswap_v2_zero_liquidity_pools: 3130\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - bancor_v2_zero_liquidity_pools: 0\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - bancor_v3_zero_liquidity_pools: 38\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - carbon_v1_zero_liquidity_pools: 0\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - Removed 0 unsupported exchanges. 340 pools remaining\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - Pools remaining per exchange:\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - carbon_v1: 281\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - bancor_v3: 33\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - uniswap_v3: 11\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - uniswap_v2: 13\n", + "2023-08-09 10:46:43,239 [fastlane:INFO] - sushiswap_v2: 2\n", + "2023-08-09 10:46:43,240 [fastlane:INFO] - Limiting pools by target_tokens. Removed 194 non target-pools. 146 pools remaining\n", + "0it [00:00, ?it/s]\n", + "0it [00:00, ?it/s]\n", + "100%|██████████| 7/7 [00:00<00:00, 638263.65it/s]\n", + "100%|██████████| 5/5 [00:00<00:00, 262144.00it/s]\n", + "0it [00:00, ?it/s]\n", + "100%|██████████| 7/7 [00:00<00:00, 458752.00it/s]\n", + "0it [00:00, ?it/s]\n", + "100%|██████████| 69/69 [00:00<00:00, 2097152.00it/s]\n", + "100%|██████████| 26/26 [00:00<00:00, 1028791.55it/s]\n", + "0it [00:00, ?it/s]\n", + "0it [00:00, ?it/s]\n", + "0it [00:00, ?it/s]\n", + "0it [00:00, ?it/s]\n", + "100%|██████████| 5/5 [00:00<00:00, 388361.48it/s]\n", + "100%|██████████| 69/69 [00:00<00:00, 1982239.56it/s]\n", + "0it [00:00, ?it/s]\n", + "0it [00:00, ?it/s]\n", + "100%|██████████| 26/26 [00:00<00:00, 1380403.85it/s]\n", + "/Users/mikewcasale/Documents/GitHub/fastlane-bot/fastlane_bot/tools/optimizer/margpoptimizer.py:217: RuntimeWarning: divide by zero encountered in scalar divide\n", + " price = get(p, tokens_ix.get(tknb)) / get(p, tokens_ix.get(tknq))\n", + "/Users/mikewcasale/Documents/GitHub/fastlane-bot/fastlane_bot/tools/optimizer/margpoptimizer.py:61: RuntimeWarning: invalid value encountered in subtract\n", + " jac[:, j] = (func(x_plus, quiet=True) - y) / Dxj\n", + "0it [00:00, ?it/s]\n", + "0it [00:00, ?it/s]\n", + "2023-08-09 10:46:43,646 [fastlane:INFO] - No eligible arb opportunities.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[FindArbitrageSinglePairwise] Exception: float division by zero\n", + "[FindArbitrageSinglePairwise] Exception: float division by zero\n", + "[FindArbitrageSinglePairwise] Exception: bad operand type for unary -: 'NoneType'\n", + "[FindArbitrageSinglePairwise] Exception: float division by zero\n", + "[FindArbitrageSinglePairwise] Exception: float division by zero\n", + "[FindArbitrageSinglePairwise] Exception: bad operand type for unary -: 'NoneType'\n", + "[FindArbitrageSinglePairwise] Exception: float division by zero\n", + "[FindArbitrageSinglePairwise] Exception: bad operand type for unary -: 'NoneType'\n", + "[FindArbitrageSinglePairwise] Exception: float division by zero\n", + "[FindArbitrageSinglePairwise] Exception: bad operand type for unary -: 'NoneType'\n", + "[FindArbitrageSinglePairwise] Exception: float division by zero\n", + "[FindArbitrageSinglePairwise] Exception: float division by zero\n", + "[FindArbitrageSinglePairwise] Exception: bad operand type for unary -: 'NoneType'\n" + ] + } + ], + "source": [ + "run_command(\"single\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.17" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/resources/NBTest/NBTest_906_TargetTokens.py b/resources/NBTest/NBTest_906_TargetTokens.py new file mode 100644 index 000000000..4a18adc45 --- /dev/null +++ b/resources/NBTest/NBTest_906_TargetTokens.py @@ -0,0 +1,101 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.7 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# coding=utf-8 +""" +This module contains the tests which ensure the target_tokens parameter is respected. +""" +from fastlane_bot import Bot +from fastlane_bot.tools.cpc import ConstantProductCurve as CPC +from fastlane_bot.events.exchanges import UniswapV2, UniswapV3, SushiswapV2, CarbonV1, BancorV3 +import subprocess, os, sys +import pytest +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(SushiswapV2)) +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.rcParams['figure.figsize'] = [12,6] +from fastlane_bot import __VERSION__ +require("3.0", __VERSION__) + +# # Setup + +# + +from fastlane_bot.tools.cpc import T + + +def find_main_py(): + # Start at the directory of the current script + cwd = os.path.abspath(os.path.join(os.getcwd())) + + with open("log.txt", "w") as f: + f.write(f"Searching for main.py in {cwd}") + + print(f"Searching for main.py in {cwd}") + while True: + # Check if main.py exists in the current directory + if "main.py" in os.listdir(cwd): + return cwd # Found the directory containing main.py + else: + # If not, go up one directory + new_cwd = os.path.dirname(cwd) + + # If we're already at the root directory, stop searching + if new_cwd == cwd: + raise FileNotFoundError("Could not find main.py in any parent directory") + + cwd = new_cwd + + +def run_command(mode): + + # Find the correct path to main.py + main_script_path = find_main_py() + print(f"Found main.py in {main_script_path}") + main_script_path = main_script_path + "/main.py" + + # Run the command + cmd = [ + "python", + main_script_path, + f"--arb_mode={mode}", + "--use_cached_events=True", + "--logging_path=fastlane_bot/data/", + "--timeout=45", + f"--target_tokens={T.WETH},{T.DAI}" + ] + subprocess.Popen(cmd) + + # Wait for the expected log line to appear + expected_log_line = "Limiting pools by target_tokens. Removed " + found = False + result = subprocess.run(cmd, text=True, capture_output=True, check=True, timeout=120) + + # Check if the expected log line is in the output + if expected_log_line in result.stderr: + found = True + + if not found: + pytest.fail("Expected log line was not found within 1 minute") # If we reach this point, the test has failed + + +# - + +# ## Test Flashloan Tokens b3_two_hop + +run_command("single") From 5585ebe469a1670d78a02a3835ee98f2dd5990ca Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:05:10 -0700 Subject: [PATCH 2/2] Removes flaky tests - should be added back later. --- .../NBTest/NBTest_902_ValidatorSlow.ipynb | 1769 ----------------- resources/NBTest/NBTest_902_ValidatorSlow.py | 291 --- 2 files changed, 2060 deletions(-) delete mode 100644 resources/NBTest/NBTest_902_ValidatorSlow.ipynb delete mode 100644 resources/NBTest/NBTest_902_ValidatorSlow.py diff --git a/resources/NBTest/NBTest_902_ValidatorSlow.ipynb b/resources/NBTest/NBTest_902_ValidatorSlow.ipynb deleted file mode 100644 index 46d2b023d..000000000 --- a/resources/NBTest/NBTest_902_ValidatorSlow.ipynb +++ /dev/null @@ -1,1769 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 9, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ConstantProductCurve v2.14 (23/May/2023)\n", - "CarbonBot v3-b2.2 (20/June/2023)\n", - "UniswapV2 v0.0.1 (2023-07-03)\n", - "UniswapV3 v0.0.1 (2023-07-03)\n", - "SushiswapV2 v0.0.1 (2023-07-03)\n", - "CarbonV1 v0.0.1 (2023-07-03)\n", - "BancorV3 v0.0.1 (2023-07-03)\n", - "Version = 3-b2.2 [requirements >= 3.0 is met]\n" - ] - } - ], - "source": [ - "# coding=utf-8\n", - "\"\"\"\n", - "This module contains the tests for the exchanges classes\n", - "\"\"\"\n", - "from fastlane_bot import Bot, Config\n", - "from fastlane_bot.bot import CarbonBot\n", - "from fastlane_bot.tools.cpc import ConstantProductCurve\n", - "from fastlane_bot.tools.cpc import ConstantProductCurve as CPC\n", - "from fastlane_bot.events.exchanges import UniswapV2, UniswapV3, SushiswapV2, CarbonV1, BancorV3\n", - "from fastlane_bot.events.interface import QueryInterface\n", - "from fastlane_bot.helpers.poolandtokens import PoolAndTokens\n", - "from fastlane_bot.helpers import TradeInstruction, TxReceiptHandler, TxRouteHandler, TxSubmitHandler, TxHelpers, TxHelper\n", - "from fastlane_bot.events.managers.manager import Manager\n", - "from fastlane_bot.events.interface import QueryInterface\n", - "from joblib import Parallel, delayed\n", - "import pytest\n", - "import math\n", - "import json\n", - "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(CPC))\n", - "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(Bot))\n", - "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(UniswapV2))\n", - "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(UniswapV3))\n", - "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(SushiswapV2))\n", - "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(CarbonV1))\n", - "print(\"{0.__name__} v{0.__VERSION__} ({0.__DATE__})\".format(BancorV3))\n", - "from fastlane_bot.testing import *\n", - "from fastlane_bot.modes import triangle_single_bancor3\n", - "#plt.style.use('seaborn-dark')\n", - "plt.rcParams['figure.figsize'] = [12,6]\n", - "from fastlane_bot import __VERSION__\n", - "require(\"3.0\", __VERSION__)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2023-08-06T12:29:53.196201Z", - "start_time": "2023-08-06T12:29:53.124133Z" - } - }, - "id": "56224b770915d14" - }, - { - "cell_type": "markdown", - "id": "4c4e2283", - "metadata": {}, - "source": [ - "# Multi Mode [NB039]" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "a51e5ec2", - "metadata": { - "ExecuteTime": { - "end_time": "2023-08-06T12:29:54.497241Z", - "start_time": "2023-08-06T12:29:53.143Z" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2023-08-06 05:29:54,070 [fastlane:INFO] - Time taken to add initial pools: 0.06647491455078125\n", - "2023-08-06 05:29:54,070 [fastlane:INFO] - Time taken to add initial pools: 0.06647491455078125\n", - "2023-08-06 05:29:54,073 [fastlane:INFO] - Initializing the bot...\n", - "2023-08-06 05:29:54,073 [fastlane:INFO] - Initializing the bot...\n", - "2023-08-06 05:29:54,191 [fastlane:INFO] - Removed 3242 unmapped uniswap_v2/sushi pools. 1897 uniswap_v2/sushi pools remaining\n", - "2023-08-06 05:29:54,191 [fastlane:INFO] - Removed 3242 unmapped uniswap_v2/sushi pools. 1897 uniswap_v2/sushi pools remaining\n", - "2023-08-06 05:29:54,192 [fastlane:INFO] - Unmapped uniswap_v2/sushi pools:\n", - "2023-08-06 05:29:54,192 [fastlane:INFO] - Unmapped uniswap_v2/sushi pools:\n", - "2023-08-06 05:29:54,379 [fastlane:INFO] - uniswap_v3: 0\n", - "2023-08-06 05:29:54,379 [fastlane:INFO] - uniswap_v3: 0\n", - "2023-08-06 05:29:54,380 [fastlane:INFO] - uniswap_v2: 3242\n", - "2023-08-06 05:29:54,380 [fastlane:INFO] - uniswap_v2: 3242\n", - "2023-08-06 05:29:54,380 [fastlane:INFO] - sushiswap_v2: 0\n", - "2023-08-06 05:29:54,380 [fastlane:INFO] - sushiswap_v2: 0\n", - "2023-08-06 05:29:54,382 [fastlane:INFO] - uniswap_v3: 636\n", - "2023-08-06 05:29:54,382 [fastlane:INFO] - uniswap_v3: 636\n", - "2023-08-06 05:29:54,382 [fastlane:INFO] - sushiswap_v2: 78\n", - "2023-08-06 05:29:54,382 [fastlane:INFO] - sushiswap_v2: 78\n", - "2023-08-06 05:29:54,383 [fastlane:INFO] - uniswap_v2: 0\n", - "2023-08-06 05:29:54,383 [fastlane:INFO] - uniswap_v2: 0\n", - "2023-08-06 05:29:54,383 [fastlane:INFO] - bancor_v2: 0\n", - "2023-08-06 05:29:54,383 [fastlane:INFO] - bancor_v2: 0\n", - "2023-08-06 05:29:54,383 [fastlane:INFO] - bancor_v3: 34\n", - "2023-08-06 05:29:54,383 [fastlane:INFO] - bancor_v3: 34\n", - "2023-08-06 05:29:54,384 [fastlane:INFO] - carbon_v1: 220\n", - "2023-08-06 05:29:54,384 [fastlane:INFO] - carbon_v1: 220\n", - "2023-08-06 05:29:54,415 [fastlane:INFO] - uniswap_v3_zero_liquidity_pools: 858\n", - "2023-08-06 05:29:54,415 [fastlane:INFO] - uniswap_v3_zero_liquidity_pools: 858\n", - "2023-08-06 05:29:54,415 [fastlane:INFO] - sushiswap_v2_zero_liquidity_pools: 34\n", - "2023-08-06 05:29:54,415 [fastlane:INFO] - sushiswap_v2_zero_liquidity_pools: 34\n", - "2023-08-06 05:29:54,416 [fastlane:INFO] - uniswap_v2_zero_liquidity_pools: 0\n", - "2023-08-06 05:29:54,416 [fastlane:INFO] - uniswap_v2_zero_liquidity_pools: 0\n", - "2023-08-06 05:29:54,416 [fastlane:INFO] - bancor_v2_zero_liquidity_pools: 0\n", - "2023-08-06 05:29:54,416 [fastlane:INFO] - bancor_v2_zero_liquidity_pools: 0\n", - "2023-08-06 05:29:54,417 [fastlane:INFO] - bancor_v3_zero_liquidity_pools: 37\n", - "2023-08-06 05:29:54,417 [fastlane:INFO] - bancor_v3_zero_liquidity_pools: 37\n", - "2023-08-06 05:29:54,417 [fastlane:INFO] - carbon_v1_zero_liquidity_pools: 0\n", - "2023-08-06 05:29:54,417 [fastlane:INFO] - carbon_v1_zero_liquidity_pools: 0\n", - "2023-08-06 05:29:54,418 [fastlane:INFO] - Removed 0 unsupported exchanges. 968 pools remaining\n", - "2023-08-06 05:29:54,418 [fastlane:INFO] - Removed 0 unsupported exchanges. 968 pools remaining\n", - "2023-08-06 05:29:54,419 [fastlane:INFO] - Pools remaining per exchange:\n", - "2023-08-06 05:29:54,419 [fastlane:INFO] - Pools remaining per exchange:\n", - "2023-08-06 05:29:54,419 [fastlane:INFO] - carbon_v1: 220\n", - "2023-08-06 05:29:54,419 [fastlane:INFO] - carbon_v1: 220\n", - "2023-08-06 05:29:54,419 [fastlane:INFO] - sushiswap_v2: 78\n", - "2023-08-06 05:29:54,419 [fastlane:INFO] - sushiswap_v2: 78\n", - "2023-08-06 05:29:54,420 [fastlane:INFO] - uniswap_v2: 0\n", - "2023-08-06 05:29:54,420 [fastlane:INFO] - uniswap_v2: 0\n", - "2023-08-06 05:29:54,420 [fastlane:INFO] - bancor_v3: 34\n", - "2023-08-06 05:29:54,420 [fastlane:INFO] - bancor_v3: 34\n", - "2023-08-06 05:29:54,420 [fastlane:INFO] - uniswap_v3: 636\n", - "2023-08-06 05:29:54,420 [fastlane:INFO] - uniswap_v3: 636\n" - ] - } - ], - "source": [ - "C = cfg = Config.new(config=Config.CONFIG_MAINNET)\n", - "C.DEFAULT_MIN_PROFIT_BNT = 0.02\n", - "C.DEFAULT_MIN_PROFIT = 0.02\n", - "cfg.DEFAULT_MIN_PROFIT_BNT = 0.02\n", - "cfg.DEFAULT_MIN_PROFIT = 0.02\n", - "assert (C.NETWORK == C.NETWORK_MAINNET)\n", - "assert (C.PROVIDER == C.PROVIDER_ALCHEMY)\n", - "setup_bot = CarbonBot(ConfigObj=C)\n", - "pools = None\n", - "with open('fastlane_bot/data/tests/latest_pool_data_testing.json') as f:\n", - " pools = json.load(f)\n", - "pools = [pool for pool in pools]\n", - "pools[0]\n", - "static_pools = pools\n", - "state = pools\n", - "exchanges = list({ex['exchange_name'] for ex in state})\n", - "db = QueryInterface(state=state, ConfigObj=C, exchanges=exchanges)\n", - "setup_bot.db = db\n", - "\n", - "static_pool_data_filename = \"static_pool_data\"\n", - "\n", - "static_pool_data = pd.read_csv(f\"fastlane_bot/data/{static_pool_data_filename}.csv\", low_memory=False)\n", - " \n", - "uniswap_v2_event_mappings = pd.read_csv(\"fastlane_bot/data/uniswap_v2_event_mappings.csv\", low_memory=False)\n", - " \n", - "tokens = pd.read_csv(\"fastlane_bot/data/tokens.csv\", low_memory=False)\n", - " \n", - "exchanges = \"carbon_v1,bancor_v3,uniswap_v3,uniswap_v2,sushiswap_v2\"\n", - "\n", - "exchanges = exchanges.split(\",\")\n", - "\n", - "\n", - "alchemy_max_block_fetch = 20\n", - "static_pool_data[\"cid\"] = [\n", - " cfg.w3.keccak(text=f\"{row['descr']}\").hex()\n", - " for index, row in static_pool_data.iterrows()\n", - " ]\n", - "# Filter out pools that are not in the supported exchanges\n", - "static_pool_data = [\n", - " row for index, row in static_pool_data.iterrows()\n", - " if row[\"exchange_name\"] in exchanges\n", - "]\n", - "\n", - "static_pool_data = pd.DataFrame(static_pool_data)\n", - "static_pool_data['exchange_name'].unique()\n", - "# Initialize data fetch manager\n", - "mgr = Manager(\n", - " web3=cfg.w3,\n", - " cfg=cfg,\n", - " pool_data=static_pool_data.to_dict(orient=\"records\"),\n", - " SUPPORTED_EXCHANGES=exchanges,\n", - " alchemy_max_block_fetch=alchemy_max_block_fetch,\n", - " uniswap_v2_event_mappings=uniswap_v2_event_mappings,\n", - " tokens=tokens.to_dict(orient=\"records\"),\n", - ")\n", - "\n", - "# Add initial pools for each row in the static_pool_data\n", - "start_time = time.time()\n", - "Parallel(n_jobs=-1, backend=\"threading\")(\n", - " delayed(mgr.add_pool_to_exchange)(row) for row in mgr.pool_data\n", - ")\n", - "cfg.logger.info(f\"Time taken to add initial pools: {time.time() - start_time}\")\n", - "\n", - "# check if any duplicate cid's exist in the pool data\n", - "mgr.deduplicate_pool_data()\n", - "cids = [pool[\"cid\"] for pool in mgr.pool_data]\n", - "assert len(cids) == len(set(cids)), \"duplicate cid's exist in the pool data\"\n", - "def init_bot(mgr: Manager) -> CarbonBot:\n", - " \"\"\"\n", - " Initializes the bot.\n", - "\n", - " Parameters\n", - " ----------\n", - " mgr : Manager\n", - " The manager object.\n", - "\n", - " Returns\n", - " -------\n", - " CarbonBot\n", - " The bot object.\n", - " \"\"\"\n", - " mgr.cfg.logger.info(\"Initializing the bot...\")\n", - " bot = CarbonBot(ConfigObj=mgr.cfg)\n", - " bot.db = db\n", - " bot.db.mgr = mgr\n", - " assert isinstance(\n", - " bot.db, QueryInterface\n", - " ), \"QueryInterface not initialized correctly\"\n", - " return bot\n", - "bot = init_bot(mgr)\n", - "# add data cleanup steps from main.py\n", - "bot.db.handle_token_key_cleanup()\n", - "bot.db.remove_unmapped_uniswap_v2_pools()\n", - "bot.db.remove_zero_liquidity_pools()\n", - "bot.db.remove_unsupported_exchanges()\n", - "tokens = bot.db.get_tokens()\n", - "ADDRDEC = {t.key: (t.address, int(t.decimals)) for t in tokens if not math.isnan(t.decimals)}\n", - "flashloan_tokens = bot.setup_flashloan_tokens(None)\n", - "CCm = bot.setup_CCm(None)\n", - "pools = db.get_pool_data_with_tokens()\n", - "\n", - "arb_mode = \"multi\"" - ] - }, - { - "cell_type": "markdown", - "id": "a70cf16f", - "metadata": {}, - "source": [ - "## Test_MIN_PROFIT" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "c8f41237", - "metadata": { - "ExecuteTime": { - "end_time": "2023-08-06T12:29:54.497413Z", - "start_time": "2023-08-06T12:29:54.494079Z" - } - }, - "outputs": [], - "source": [ - "assert(cfg.DEFAULT_MIN_PROFIT_BNT <= 0.02), f\"[TestMultiMode], DEFAULT_MIN_PROFIT_BNT must be <= 0.02 for this Notebook to run, currently set to {cfg.DEFAULT_MIN_PROFIT_BNT}\"\n", - "assert(C.DEFAULT_MIN_PROFIT_BNT <= 0.02), f\"[TestMultiMode], DEFAULT_MIN_PROFIT_BNT must be <= 0.02 for this Notebook to run, currently set to {cfg.DEFAULT_MIN_PROFIT_BNT}\"" - ] - }, - { - "cell_type": "markdown", - "id": "eddfdea7", - "metadata": {}, - "source": [ - "## Test_validator_in_out" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "aaaf7423", - "metadata": { - "ExecuteTime": { - "end_time": "2023-08-06T12:29:54.500387Z", - "start_time": "2023-08-06T12:29:54.496715Z" - } - }, - "outputs": [], - "source": [ - "arb_finder = bot._get_arb_finder(\"multi\")\n", - "assert arb_finder.__name__ == \"FindArbitrageMultiPairwise\", f\"[TestMultiMode] Expected arb_finder class name name = FindArbitrageMultiPairwise, found {arb_finder.__name__}\"" - ] - }, - { - "cell_type": "markdown", - "id": "96ef3bf0", - "metadata": {}, - "source": [ - "## Test_validator_multi" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "db7ec9ec", - "metadata": { - "ExecuteTime": { - "end_time": "2023-08-06T12:29:54.788652Z", - "start_time": "2023-08-06T12:29:54.508442Z" - } - }, - "outputs": [], - "source": [ - "arb_finder = bot._get_arb_finder(\"multi\")\n", - "finder = arb_finder(\n", - " flashloan_tokens=flashloan_tokens,\n", - " CCm=CCm,\n", - " mode=\"bothin\",\n", - " result=bot.AO_CANDIDATES,\n", - " ConfigObj=bot.ConfigObj,\n", - " )\n", - "r = finder.find_arbitrage()\n", - "\n", - "arb_opp = r[0]\n", - "\n", - "validated = bot.validate_optimizer_trades(arb_opp=arb_opp, arb_mode=\"multi\", arb_finder=finder)\n", - "\n", - "\n", - "\n", - "assert arb_opp == validated" - ] - }, - { - "cell_type": "markdown", - "id": "5b195f52", - "metadata": {}, - "source": [ - "## Test_validator_single" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "78d0dbc1", - "metadata": { - "ExecuteTime": { - "end_time": "2023-08-06T12:29:58.990699Z", - "start_time": "2023-08-06T12:29:54.793412Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": "0it [00:00, ?it/s]", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 0, - "model_id": "56850cdfceff41aab83fda57bbd21b77" - } - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": " 0%| | 0/2 [00:00 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) -# add data cleanup steps from main.py -bot.db.handle_token_key_cleanup() -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.key: (t.address, int(t.decimals)) for t in tokens if not math.isnan(t.decimals) -} -flashloan_tokens = bot.setup_flashloan_tokens(None) -CCm = bot.setup_CCm(None) -pools = db.get_pool_data_with_tokens() - -arb_mode = "multi" -# - - -# ## Test_MIN_PROFIT - -assert ( - cfg.DEFAULT_MIN_PROFIT_BNT <= 0.02 -), f"[TestMultiMode], DEFAULT_MIN_PROFIT_BNT must be <= 0.02 for this Notebook to run, currently set to {cfg.DEFAULT_MIN_PROFIT_BNT}" -assert ( - C.DEFAULT_MIN_PROFIT_BNT <= 0.02 -), f"[TestMultiMode], DEFAULT_MIN_PROFIT_BNT must be <= 0.02 for this Notebook to run, currently set to {cfg.DEFAULT_MIN_PROFIT_BNT}" - -# ## Test_validator_in_out - -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_validator_multi - -# + -arb_finder = bot._get_arb_finder("multi") -finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=bot.AO_CANDIDATES, - ConfigObj=bot.ConfigObj, -) -r = finder.find_arbitrage() - -arb_opp = r[0] - -validated = bot.validate_optimizer_trades( - arb_opp=arb_opp, arb_mode="multi", arb_finder=finder -) - - -assert arb_opp == validated -# - - -# ## Test_validator_single - -# + -arb_mode = "single" -arb_finder = bot._get_arb_finder(arb_mode) -finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=bot.AO_CANDIDATES, - ConfigObj=bot.ConfigObj, -) -r = finder.find_arbitrage() - -arb_opp = r[0] - -validated = bot.validate_optimizer_trades( - arb_opp=arb_opp, arb_mode=arb_mode, arb_finder=finder -) - - -assert arb_opp == validated -# - - -# ## Test_validator_bancor_v3 - -# + -arb_mode = "bancor_v3" - -arb_finder = bot._get_arb_finder(arb_mode) -finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=bot.AO_CANDIDATES, - ConfigObj=bot.ConfigObj, -) -r = finder.find_arbitrage() - -arb_opp = r[0] - -validated = bot.validate_optimizer_trades( - arb_opp=arb_opp, arb_mode=arb_mode, arb_finder=finder -) - - -assert arb_opp == validated -# - - -# ## Test_validator_multi_triangle - -# + -arb_mode = "multi_triangle" -arb_finder = bot._get_arb_finder(arb_mode) -finder = arb_finder( - flashloan_tokens=flashloan_tokens, - CCm=CCm, - mode="bothin", - result=bot.AO_CANDIDATES, - ConfigObj=bot.ConfigObj, -) -r = finder.find_arbitrage() - -arb_opp = r[0] - -validated = bot.validate_optimizer_trades( - arb_opp=arb_opp, arb_mode=arb_mode, arb_finder=finder -) - - -assert arb_opp == validated