Skip to content

Commit

Permalink
Refactor triangle-mode
Browse files Browse the repository at this point in the history
Minor

Change `assert_supported` exception type from `ValueError` to `AssertionError`

Remove unused return-value

Remove unused code

Refactor single-trianglre mode as well

Remove unneeded assertion

Cosmetic

Fix single-triangle mode

Fix single-triangle mode

Refactor the b3-two-hop-triangle mode as well

Remove the importing of `T`

Remove unused code

Remove single-triangle mode, and fix a bunch of problems

Cleanup `calculate_profit` related code

Fix test 042

Fix  test 042

Remove unused code

Some more general cleanup

Cleanup

Remove unused code

Fix test 063

Cleanup

Cleanup

Fix tests

Cleanup

Cleanup

Fix test 042

Remove single_paiwise and multi_paiwise arb modes (major change)

Fix test 045

Fix test 045

Refactor the pairwise arb-mode classes

Ongoing refactor

Refactor triangle arb-mode classes

Cleanup

Remove file `scan_log_errors.py` from the repository

Cleanup

Remove unused code

Code reuse

Fix functions `create_sort_order` and `sort_key`
  • Loading branch information
platonfloria committed May 18, 2024
1 parent f1e0e06 commit 80664b2
Show file tree
Hide file tree
Showing 33 changed files with 537 additions and 2,307 deletions.
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
17 changes: 2 additions & 15 deletions fastlane_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -554,21 +548,14 @@ 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
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 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):
Expand Down
4 changes: 1 addition & 3 deletions fastlane_bot/modes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``)
Expand Down
149 changes: 44 additions & 105 deletions fastlane_bot/modes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -47,114 +43,77 @@ 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]
If self.result == self.AO_CANDIDATES, it returns a list of candidates.
"""
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.
Expand Down Expand Up @@ -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],
Expand All @@ -213,8 +171,6 @@ def handle_candidates(
Parameters:
----------
best_profit : float
Best profit
profit : float
Profit
trade_instructions_df : pd.DataFrame
Expand Down Expand Up @@ -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
Loading

0 comments on commit 80664b2

Please sign in to comment.