mirror of
https://github.com/d0zingcat/deploy.git
synced 2026-05-14 15:09:44 +00:00
149 lines
8.1 KiB
Python
149 lines
8.1 KiB
Python
from decimal import Decimal
|
|
from typing import List
|
|
|
|
import pandas as pd
|
|
|
|
from hummingbot.client.ui.interface_utils import format_df_for_printout
|
|
from hummingbot.core.data_type.common import MarketDict
|
|
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
|
from hummingbot.strategy_v2.controllers.controller_base import ControllerBase, ControllerConfigBase
|
|
from hummingbot.strategy_v2.executors.arbitrage_executor.data_types import ArbitrageExecutorConfig
|
|
from hummingbot.strategy_v2.executors.data_types import ConnectorPair
|
|
from hummingbot.strategy_v2.models.base import RunnableStatus
|
|
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction
|
|
|
|
|
|
class ArbitrageControllerConfig(ControllerConfigBase):
|
|
controller_name: str = "arbitrage_controller"
|
|
candles_config: List[CandlesConfig] = []
|
|
exchange_pair_1: ConnectorPair = ConnectorPair(connector_name="binance", trading_pair="PENGU-USDT")
|
|
exchange_pair_2: ConnectorPair = ConnectorPair(connector_name="solana_jupiter_mainnet-beta", trading_pair="PENGU-USDC")
|
|
min_profitability: Decimal = Decimal("0.01")
|
|
delay_between_executors: int = 10 # in seconds
|
|
max_executors_imbalance: int = 1
|
|
rate_connector: str = "binance"
|
|
quote_conversion_asset: str = "USDT"
|
|
|
|
def update_markets(self, markets: MarketDict) -> MarketDict:
|
|
return [markets.add_or_update(cp.connector_name, cp.trading_pair) for cp in [self.exchange_pair_1, self.exchange_pair_2]][-1]
|
|
|
|
|
|
class ArbitrageController(ControllerBase):
|
|
gas_token_by_network = {
|
|
"ethereum": "ETH",
|
|
"solana": "SOL",
|
|
"binance-smart-chain": "BNB",
|
|
"polygon": "POL",
|
|
"avalanche": "AVAX",
|
|
"dexalot": "AVAX"
|
|
}
|
|
|
|
def __init__(self, config: ArbitrageControllerConfig, *args, **kwargs):
|
|
self.config = config
|
|
super().__init__(config, *args, **kwargs)
|
|
self._imbalance = 0
|
|
self._last_buy_closed_timestamp = 0
|
|
self._last_sell_closed_timestamp = 0
|
|
self._len_active_buy_arbitrages = 0
|
|
self._len_active_sell_arbitrages = 0
|
|
self.base_asset = self.config.exchange_pair_1.trading_pair.split("-")[0]
|
|
self.initialize_rate_sources()
|
|
|
|
def initialize_rate_sources(self):
|
|
rates_required = []
|
|
for connector_pair in [self.config.exchange_pair_1, self.config.exchange_pair_2]:
|
|
base, quote = connector_pair.trading_pair.split("-")
|
|
# Add rate source for gas token
|
|
if connector_pair.is_amm_connector():
|
|
gas_token = self.get_gas_token(connector_pair.connector_name)
|
|
if gas_token != quote:
|
|
rates_required.append(ConnectorPair(connector_name=self.config.rate_connector,
|
|
trading_pair=f"{gas_token}-{quote}"))
|
|
|
|
# Add rate source for quote conversion asset
|
|
if quote != self.config.quote_conversion_asset:
|
|
rates_required.append(ConnectorPair(connector_name=self.config.rate_connector,
|
|
trading_pair=f"{quote}-{self.config.quote_conversion_asset}"))
|
|
|
|
# Add rate source for trading pairs
|
|
rates_required.append(ConnectorPair(connector_name=connector_pair.connector_name,
|
|
trading_pair=connector_pair.trading_pair))
|
|
if len(rates_required) > 0:
|
|
self.market_data_provider.initialize_rate_sources(rates_required)
|
|
|
|
def get_gas_token(self, connector_name: str) -> str:
|
|
_, chain, _ = connector_name.split("_")
|
|
return self.gas_token_by_network[chain]
|
|
|
|
async def update_processed_data(self):
|
|
pass
|
|
|
|
def determine_executor_actions(self) -> List[ExecutorAction]:
|
|
self.update_arbitrage_stats()
|
|
executor_actions = []
|
|
current_time = self.market_data_provider.time()
|
|
if (abs(self._imbalance) >= self.config.max_executors_imbalance or
|
|
self._last_buy_closed_timestamp + self.config.delay_between_executors > current_time or
|
|
self._last_sell_closed_timestamp + self.config.delay_between_executors > current_time):
|
|
return executor_actions
|
|
if self._len_active_buy_arbitrages == 0:
|
|
executor_actions.append(self.create_arbitrage_executor_action(self.config.exchange_pair_1,
|
|
self.config.exchange_pair_2))
|
|
if self._len_active_sell_arbitrages == 0:
|
|
executor_actions.append(self.create_arbitrage_executor_action(self.config.exchange_pair_2,
|
|
self.config.exchange_pair_1))
|
|
return executor_actions
|
|
|
|
def create_arbitrage_executor_action(self, buying_exchange_pair: ConnectorPair,
|
|
selling_exchange_pair: ConnectorPair):
|
|
try:
|
|
if buying_exchange_pair.is_amm_connector():
|
|
gas_token = self.get_gas_token(buying_exchange_pair.connector_name)
|
|
pair = buying_exchange_pair.trading_pair.split("-")[0] + "-" + gas_token
|
|
gas_conversion_price = self.market_data_provider.get_rate(pair)
|
|
elif selling_exchange_pair.is_amm_connector():
|
|
gas_token = self.get_gas_token(selling_exchange_pair.connector_name)
|
|
pair = selling_exchange_pair.trading_pair.split("-")[0] + "-" + gas_token
|
|
gas_conversion_price = self.market_data_provider.get_rate(pair)
|
|
else:
|
|
gas_conversion_price = None
|
|
rate = self.market_data_provider.get_rate(self.base_asset + "-" + self.config.quote_conversion_asset)
|
|
amount_quantized = self.market_data_provider.quantize_order_amount(
|
|
buying_exchange_pair.connector_name, buying_exchange_pair.trading_pair,
|
|
self.config.total_amount_quote / rate)
|
|
arbitrage_config = ArbitrageExecutorConfig(
|
|
timestamp=self.market_data_provider.time(),
|
|
buying_market=buying_exchange_pair,
|
|
selling_market=selling_exchange_pair,
|
|
order_amount=amount_quantized,
|
|
min_profitability=self.config.min_profitability,
|
|
gas_conversion_price=gas_conversion_price,
|
|
)
|
|
return CreateExecutorAction(
|
|
executor_config=arbitrage_config,
|
|
controller_id=self.config.id)
|
|
except Exception as e:
|
|
self.logger().error(
|
|
f"Error creating executor to buy on {buying_exchange_pair.connector_name} and sell on {selling_exchange_pair.connector_name}, {e}")
|
|
|
|
def update_arbitrage_stats(self):
|
|
closed_executors = [e for e in self.executors_info if e.status == RunnableStatus.TERMINATED]
|
|
active_executors = [e for e in self.executors_info if e.status != RunnableStatus.TERMINATED]
|
|
buy_arbitrages = [arbitrage for arbitrage in closed_executors if
|
|
arbitrage.config.buying_market == self.config.exchange_pair_1]
|
|
sell_arbitrages = [arbitrage for arbitrage in closed_executors if
|
|
arbitrage.config.buying_market == self.config.exchange_pair_2]
|
|
self._imbalance = len(buy_arbitrages) - len(sell_arbitrages)
|
|
self._last_buy_closed_timestamp = max([arbitrage.close_timestamp for arbitrage in buy_arbitrages]) if len(
|
|
buy_arbitrages) > 0 else 0
|
|
self._last_sell_closed_timestamp = max([arbitrage.close_timestamp for arbitrage in sell_arbitrages]) if len(
|
|
sell_arbitrages) > 0 else 0
|
|
self._len_active_buy_arbitrages = len([arbitrage for arbitrage in active_executors if
|
|
arbitrage.config.buying_market == self.config.exchange_pair_1])
|
|
self._len_active_sell_arbitrages = len([arbitrage for arbitrage in active_executors if
|
|
arbitrage.config.buying_market == self.config.exchange_pair_2])
|
|
|
|
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", )]
|