Skip to content
All library documents

Multi-Level Cross-Exchange Arbitrage Executor Allocation

Article Strategy library · Author: hummingbot

Summary

This controller coordinates cross-exchange market-making executors across a maker venue and a taker venue. Users configure separate buy and sell profitability targets with associated allocation weights. The controller divides its quote budget equally between the two sides, distributes each half among configured levels in proportion to their weights, and creates executors when a level has no active executor and the completed-executor imbalance remains within a limit.

For each level, it sets a target profitability plus lower and upper profitability bounds, and chooses which venue buys or sells according to maker side. It also initializes market rate sources and, for automated market makers, attempts to retrieve a gas token. The source is implementation detail rather than a strategy evaluation: it includes no trade history, profitability results, or validation. Execution depends on connector and market-data behavior; the imbalance rule counts completed executors with nonzero fills, so it is a coarse proxy for exposure rather than a direct inventory measure.

Key ideas

  • Buy and sell levels each pair a target profitability with an allocation weight.
  • The controller splits the quote budget evenly between buy and sell sides.
  • Each level's quote allocation is proportional to its weight within that side.
  • Executor creation is constrained by active levels and a completed-executor imbalance limit.
  • The controller configures maker and taker venues but provides no performance evidence.

Tags

Full text
# XEMMMultipleLevels


# XEMMMultipleLevels









## Source (Apache-2.0)

```python
import time
from decimal import Decimal
from typing import Dict, List, Optional, Set

import pandas as pd
from pydantic import Field, field_validator

from hummingbot.client.ui.interface_utils import format_df_for_printout
from hummingbot.core.data_type.common import PriceType, TradeType
from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient
from hummingbot.strategy_v2.controllers.controller_base import ControllerBase, ControllerConfigBase
from hummingbot.strategy_v2.executors.data_types import ConnectorPair
from hummingbot.strategy_v2.executors.xemm_executor.data_types import XEMMExecutorConfig
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction


class XEMMMultipleLevelsConfig(ControllerConfigBase):
    controller_name: str = "xemm_multiple_levels"
    maker_connector: str = Field(
        default="mexc",
        json_schema_extra={"prompt": "Enter the maker connector: ", "prompt_on_new": True})
    maker_trading_pair: str = Field(
        default="PEPE-USDT",
        json_schema_extra={"prompt": "Enter the maker trading pair: ", "prompt_on_new": True})
    taker_connector: str = Field(
        default="binance",
        json_schema_extra={"prompt": "Enter the taker connector: ", "prompt_on_new": True})
    taker_trading_pair: str = Field(
        default="PEPE-USDT",
        json_schema_extra={"prompt": "Enter the taker trading pair: ", "prompt_on_new": True})
    buy_levels_targets_amount: List[List[Decimal]] = Field(
        default="0.003,10-0.006,20-0.009,30",
        json_schema_extra={
            "prompt": "Enter the buy levels targets with the following structure: (target_profitability1,amount1-target_profitability2,amount2): ",
            "prompt_on_new": True})
    sell_levels_targets_amount: List[List[Decimal]] = Field(
        default="0.003,10-0.006,20-0.009,30",
        json_schema_extra={
            "prompt": "Enter the sell levels targets with the following structure: (target_profitability1,amount1-target_profitability2,amount2): ",
            "prompt_on_new": True})
    min_profitability: Decimal = Field(
        default=0.003,
        json_schema_extra={"prompt": "Enter the minimum profitability: ", "prompt_on_new": True})
    max_profitability: Decimal = Field(
        default=0.01,
        json_schema_extra={"prompt": "Enter the maximum profitability: ", "prompt_on_new": True})
    max_executors_imbalance: int = Field(
        default=1,
        json_schema_extra={"prompt": "Enter the maximum executors imbalance: ", "prompt_on_new": True})

    @field_validator("buy_levels_targets_amount", "sell_levels_targets_amount", mode="before")
    @classmethod
    def validate_levels_targets_amount(cls, v):
        if isinstance(v, str):
            v = [list(map(Decimal, x.split(","))) for x in v.split("-")]
        return v

    def update_markets(self, markets: Dict[str, Set[str]]) -> Dict[str, Set[str]]:
        if self.maker_connector not in markets:
            markets[self.maker_connector] = set()
        markets[self.maker_connector].add(self.maker_trading_pair)
        if self.taker_connector not in markets:
            markets[self.taker_connector] = set()
        markets[self.taker_connector].add(self.taker_trading_pair)
        return markets


class XEMMMultipleLevels(ControllerBase):

    def __init__(self, config: XEMMMultipleLevelsConfig, *args, **kwargs):
        self.config = config
        self.buy_levels_targets_amount = config.buy_levels_targets_amount
        self.sell_levels_targets_amount = config.sell_levels_targets_amount
        super().__init__(config, *args, **kwargs)
        self._gas_token_cache = {}
        self._initialize_gas_tokens()
        self.initialize_rate_sources()

    def initialize_rate_sources(self):
        rates_required = []
        for connector_pair in [
            ConnectorPair(connector_name=self.config.maker_connector, trading_pair=self.config.maker_trading_pair),
            ConnectorPair(connector_name=self.config.taker_connector, trading_pair=self.config.taker_trading_pair)
        ]:
            base, quote = connector_pair.trading_pair.split("-")

            # Add rate source for gas token if it's an AMM connector
            if connector_pair.is_amm_connector():
                gas_token = self.get_gas_token(connector_pair.connector_name)
                if gas_token and gas_token != base and gas_token != quote:
                    rates_required.append(ConnectorPair(connector_name=self.config.maker_connector,
                                                        trading_pair=f"{base}-{gas_token}"))

            # Add rate source for trading pairs
            rates_required.append(connector_pair)

        if len(rates_required) > 0:
            self.market_data_provider.initialize_rate_sources(rates_required)

    def _initialize_gas_tokens(self):
        """Initialize gas tokens for AMM connectors during controller initialization."""
        import asyncio

        async def fetch_gas_tokens():
            for connector_name in [self.config.maker_connector, self.config.taker_connector]:
                connector_pair = ConnectorPair(connector_name=connector_name, trading_pair="")
                if connector_pair.is_amm_connector():
                    if connector_name not in self._gas_token_cache:
                        try:
                            gateway_client = GatewayHttpClient.get_instance()

                            # Get chain and network for the connector
                            chain, network, error = await gateway_client.get_connector_chain_network(
                                connector_name
                            )

                            if error:
                                self.logger().warning(f"Failed to get chain info for {connector_name}: {error}")
                                continue

                            # Get native currency symbol
                            native_currency = await gateway_client.get_native_currency_symbol(chain, network)

                            if native_currency:
                                self._gas_token_cache[connector_name] = native_currency
                                self.logger().info(f"Gas token for {connector_name}: {native_currency}")
                            else:
                                self.logger().warning(f"Failed to get native currency for {connector_name}")
                        except Exception as e:
                            self.logger().error(f"Error getting gas token for {connector_name}: {e}")

        # Run the async function to fetch gas tokens
        loop = asyncio.get_event_loop()
        if loop.is_running():
            asyncio.create_task(fetch_gas_tokens())
        else:
            loop.run_until_complete(fetch_gas_tokens())

    def get_gas_token(self, connector_name: str) -> Optional[str]:
        """Get the cached gas token for a connector."""
        return self._gas_token_cache.get(connector_name)

    async def update_processed_data(self):
        pass

    def determine_executor_actions(self) -> List[ExecutorAction]:
        executor_actions = []
        mid_price = self.market_data_provider.get_price_by_type(self.config.maker_connector, self.config.maker_trading_pair, PriceType.MidPrice)
        active_buy_executors = self.filter_executors(
            executors=self.executors_info,
            filter_func=lambda e: not e.is_done and e.config.maker_side == TradeType.BUY
        )
        active_sell_executors = self.filter_executors(
            executors=self.executors_info,
            filter_func=lambda e: not e.is_done and e.config.maker_side == TradeType.SELL
        )
        stopped_buy_executors = self.filter_executors(
            executors=self.executors_info,
            filter_func=lambda e: e.is_done and e.config.maker_side == TradeType.BUY and e.filled_amount_quote != 0
        )
        stopped_sell_executors = self.filter_executors(
            executors=self.executors_info,
            filter_func=lambda e: e.is_done and e.config.maker_side == TradeType.SELL and e.filled_amount_quote != 0
        )
        imbalance = len(stopped_buy_executors) - len(stopped_sell_executors)

        # Calculate total amounts for proportional allocation
        total_buy_amount = sum(amount for _, amount in self.buy_levels_targets_amount)
        total_sell_amount = sum(amount for _, amount in self.sell_levels_targets_amount)

        # Allocate 50% of total_amount_quote to each side
        buy_side_quote = self.config.total_amount_quote * Decimal("0.5")
        sell_side_quote = self.config.total_amount_quote * Decimal("0.5")

        for target_profitability, amount in self.buy_levels_targets_amount:
            active_buy_executors_target = [e.config.target_profitability == target_profitability for e in active_buy_executors]

            if len(active_buy_executors_target) == 0 and imbalance < self.config.max_executors_imbalance:
                # Calculate proportional amount: (level_amount / total_side_amount) * (total_quote * 0.5)
                proportional_amount_quote = (amount / total_buy_amount) * buy_side_quote
                min_profitability = target_profitability - self.config.min_profitability
                max_profitability = target_profitability + self.config.max_profitability
                config = XEMMExecutorConfig(
                    controller_id=self.config.id,
                    timestamp=self.market_data_provider.time(),
                    buying_market=ConnectorPair(connector_name=self.config.maker_connector,
                                                trading_pair=self.config.maker_trading_pair),
                    selling_market=ConnectorPair(connector_name=self.config.taker_connector,
                                                 trading_pair=self.config.taker_trading_pair),
                    maker_side=TradeType.BUY,
                    order_amount=proportional_amount_quote / mid_price,
                    min_profitability=min_profitability,
                    target_profitability=target_profitability,
                    max_profitability=max_profitability
                )
                executor_actions.append(CreateExecutorAction(executor_config=config, controller_id=self.config.id))
        for target_profitability, amount in self.sell_levels_targets_amount:
            active_sell_executors_target = [e.config.target_profitability == target_profitability for e in active_sell_executors]
            if len(active_sell_executors_target) == 0 and imbalance > -self.config.max_executors_imbalance:
                # Calculate proportional amount: (level_amount / total_side_amount) * (total_quote * 0.5)
                proportional_amount_quote = (amount / total_sell_amount) * sell_side_quote
                min_profitability = target_profitability - self.config.min_profitability
                max_profitability = target_profitability + self.config.max_profitability
                config = XEMMExecutorConfig(
                    controller_id=self.config.id,
                    timestamp=time.time(),
                    buying_market=ConnectorPair(connector_name=self.config.taker_connector,
                                                trading_pair=self.config.taker_trading_pair),
                    selling_market=ConnectorPair(connector_name=self.config.maker_connector,
                                                 trading_pair=self.config.maker_trading_pair),
                    maker_side=TradeType.SELL,
                    order_amount=proportional_amount_quote / mid_price,
                    min_profitability=min_profitability,
                    target_profitability=target_profitability,
                    max_profitability=max_profitability
                )
                executor_actions.append(CreateExecutorAction(executor_config=config, controller_id=self.config.id))
        return executor_actions

    def to_format_status(self) -> List[str]:
        all_executors_custom_info = pd.DataFrame(e.custom_info for e in self.executors_info)
        return [format_df_for_printout(all_executors_custom_info, table_format="psql", )]

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.