mirror of
https://github.com/d0zingcat/deploy.git
synced 2026-05-31 15:10:33 +00:00
(feat) add generic controllers
This commit is contained in:
0
bots/controllers/__init__.py
Normal file
0
bots/controllers/__init__.py
Normal file
0
bots/controllers/generic/__init__.py
Normal file
0
bots/controllers/generic/__init__.py
Normal file
196
bots/controllers/generic/spot_perp_arbitrage.py
Normal file
196
bots/controllers/generic/spot_perp_arbitrage.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import time
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Dict, List, Set
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from pydantic import Field, validator
|
||||||
|
|
||||||
|
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||||
|
from hummingbot.client.ui.interface_utils import format_df_for_printout
|
||||||
|
from hummingbot.core.data_type.common import PriceType, TradeType, PositionAction, OrderType
|
||||||
|
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.data_types import ConnectorPair
|
||||||
|
from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig, \
|
||||||
|
TripleBarrierConfig
|
||||||
|
from hummingbot.strategy_v2.executors.xemm_executor.data_types import XEMMExecutorConfig
|
||||||
|
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction, StopExecutorAction
|
||||||
|
|
||||||
|
|
||||||
|
class SpotPerpArbitrageConfig(ControllerConfigBase):
|
||||||
|
controller_name: str = "spot_perp_arbitrage"
|
||||||
|
candles_config: List[CandlesConfig] = []
|
||||||
|
spot_connector: str = Field(
|
||||||
|
default="binance",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the spot connector: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
spot_trading_pair: str = Field(
|
||||||
|
default="DOGE-USDT",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the spot trading pair: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
perp_connector: str = Field(
|
||||||
|
default="binance_perpetual",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the perp connector: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
perp_trading_pair: str = Field(
|
||||||
|
default="DOGE-USDT",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the perp trading pair: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
profitability: Decimal = Field(
|
||||||
|
default=0.002,
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the minimum profitability: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
position_size_quote: float = Field(
|
||||||
|
default=50,
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the position size in quote currency: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
|
||||||
|
def update_markets(self, markets: Dict[str, Set[str]]) -> Dict[str, Set[str]]:
|
||||||
|
if self.spot_connector not in markets:
|
||||||
|
markets[self.spot_connector] = set()
|
||||||
|
markets[self.spot_connector].add(self.spot_trading_pair)
|
||||||
|
if self.perp_connector not in markets:
|
||||||
|
markets[self.perp_connector] = set()
|
||||||
|
markets[self.perp_connector].add(self.perp_trading_pair)
|
||||||
|
return markets
|
||||||
|
|
||||||
|
|
||||||
|
class SpotPerpArbitrage(ControllerBase):
|
||||||
|
|
||||||
|
def __init__(self, config: SpotPerpArbitrageConfig, *args, **kwargs):
|
||||||
|
self.config = config
|
||||||
|
super().__init__(config, *args, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def spot_connector(self):
|
||||||
|
return self.market_data_provider.connectors[self.config.spot_connector]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def perp_connector(self):
|
||||||
|
return self.market_data_provider.connectors[self.config.perp_connector]
|
||||||
|
|
||||||
|
def get_current_profitability_after_fees(self):
|
||||||
|
"""
|
||||||
|
This methods compares the profitability of buying at market in the two exchanges. If the side is TradeType.BUY
|
||||||
|
means that the operation is long on connector 1 and short on connector 2.
|
||||||
|
"""
|
||||||
|
spot_trading_pair = self.config.spot_trading_pair
|
||||||
|
perp_trading_pair = self.config.perp_trading_pair
|
||||||
|
|
||||||
|
connector_spot_price = Decimal(self.market_data_provider.get_price_for_quote_volume(
|
||||||
|
connector_name=self.config.spot_connector,
|
||||||
|
trading_pair=spot_trading_pair,
|
||||||
|
quote_volume=self.config.position_size_quote,
|
||||||
|
is_buy=True,
|
||||||
|
).result_price)
|
||||||
|
connector_perp_price = Decimal(self.market_data_provider.get_price_for_quote_volume(
|
||||||
|
connector_name=self.config.spot_connector,
|
||||||
|
trading_pair=perp_trading_pair,
|
||||||
|
quote_volume=self.config.position_size_quote,
|
||||||
|
is_buy=False,
|
||||||
|
).result_price)
|
||||||
|
estimated_fees_spot_connector = self.spot_connector.get_fee(
|
||||||
|
base_currency=spot_trading_pair.split("-")[0],
|
||||||
|
quote_currency=spot_trading_pair.split("-")[1],
|
||||||
|
order_type=OrderType.MARKET,
|
||||||
|
order_side=TradeType.BUY,
|
||||||
|
amount=self.config.position_size_quote / float(connector_spot_price),
|
||||||
|
price=connector_spot_price,
|
||||||
|
is_maker=False,
|
||||||
|
).percent
|
||||||
|
estimated_fees_perp_connector = self.perp_connector.get_fee(
|
||||||
|
base_currency=perp_trading_pair.split("-")[0],
|
||||||
|
quote_currency=perp_trading_pair.split("-")[1],
|
||||||
|
order_type=OrderType.MARKET,
|
||||||
|
order_side=TradeType.BUY,
|
||||||
|
amount=self.config.position_size_quote / float(connector_perp_price),
|
||||||
|
price=connector_perp_price,
|
||||||
|
is_maker=False,
|
||||||
|
position_action=PositionAction.OPEN
|
||||||
|
).percent
|
||||||
|
|
||||||
|
estimated_trade_pnl_pct = (connector_perp_price - connector_spot_price) / connector_spot_price
|
||||||
|
return estimated_trade_pnl_pct - estimated_fees_spot_connector - estimated_fees_perp_connector
|
||||||
|
|
||||||
|
def is_active_arbitrage(self):
|
||||||
|
executors = self.filter_executors(
|
||||||
|
executors=self.executors_info,
|
||||||
|
filter_func=lambda e: e.is_active
|
||||||
|
)
|
||||||
|
return len(executors) > 0
|
||||||
|
|
||||||
|
def current_pnl_pct(self):
|
||||||
|
executors = self.filter_executors(
|
||||||
|
executors=self.executors_info,
|
||||||
|
filter_func=lambda e: e.is_active
|
||||||
|
)
|
||||||
|
filled_amount = sum(e.filled_amount_quote for e in executors)
|
||||||
|
return sum(e.net_pnl_quote for e in executors) / filled_amount if filled_amount > 0 else 0
|
||||||
|
|
||||||
|
async def update_processed_data(self):
|
||||||
|
self.processed_data = {
|
||||||
|
"profitability": self.get_current_profitability_after_fees(),
|
||||||
|
"active_arbitrage": self.is_active_arbitrage(),
|
||||||
|
"current_pnl": self.current_pnl_pct()
|
||||||
|
}
|
||||||
|
|
||||||
|
def determine_executor_actions(self) -> List[ExecutorAction]:
|
||||||
|
executor_actions = []
|
||||||
|
executor_actions.extend(self.create_new_arbitrage_actions())
|
||||||
|
executor_actions.extend(self.stop_arbitrage_actions())
|
||||||
|
return executor_actions
|
||||||
|
|
||||||
|
def create_new_arbitrage_actions(self):
|
||||||
|
create_actions = []
|
||||||
|
if not self.processed_data["active_arbitrage"] and self.processed_data["profitability"] > self.config.profitability:
|
||||||
|
mid_price = self.market_data_provider.get_price_by_type(self.config.spot_connector, self.config.spot_trading_pair, PriceType.MidPrice)
|
||||||
|
create_actions.append(CreateExecutorAction(
|
||||||
|
controller_id=self.config.id,
|
||||||
|
executor_config=PositionExecutorConfig(
|
||||||
|
timestamp=self.market_data_provider.time(),
|
||||||
|
connector_name=self.config.spot_connector,
|
||||||
|
trading_pair=self.config.spot_trading_pair,
|
||||||
|
side=TradeType.BUY,
|
||||||
|
amount=Decimal(self.config.position_size_quote) / mid_price,
|
||||||
|
triple_barrier_config=TripleBarrierConfig(open_order_type=OrderType.MARKET),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
create_actions.append(CreateExecutorAction(
|
||||||
|
controller_id=self.config.id,
|
||||||
|
executor_config=PositionExecutorConfig(
|
||||||
|
timestamp=self.market_data_provider.time(),
|
||||||
|
connector_name=self.config.perp_connector,
|
||||||
|
trading_pair=self.config.perp_trading_pair,
|
||||||
|
side=TradeType.SELL,
|
||||||
|
amount=Decimal(self.config.position_size_quote) / mid_price,
|
||||||
|
triple_barrier_config=TripleBarrierConfig(open_order_type=OrderType.MARKET),
|
||||||
|
))
|
||||||
|
)
|
||||||
|
return create_actions
|
||||||
|
|
||||||
|
def stop_arbitrage_actions(self):
|
||||||
|
stop_actions = []
|
||||||
|
if self.processed_data["current_pnl"] > 0.003:
|
||||||
|
executors = self.filter_executors(
|
||||||
|
executors=self.executors_info,
|
||||||
|
filter_func=lambda e: e.is_active
|
||||||
|
)
|
||||||
|
for executor in executors:
|
||||||
|
stop_actions.append(StopExecutorAction(controller_id=self.config.id, executor_id=executor.id))
|
||||||
|
|
||||||
|
def to_format_status(self) -> List[str]:
|
||||||
|
return [f"Current profitability: {self.processed_data['profitability']} | Min profitability: {self.config.profitability}",
|
||||||
|
f"Active arbitrage: {self.processed_data['active_arbitrage']}",
|
||||||
|
f"Current PnL: {self.processed_data['current_pnl']}"]
|
||||||
162
bots/controllers/generic/xemm_multiple_levels.py
Normal file
162
bots/controllers/generic/xemm_multiple_levels.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import time
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Dict, List, Set
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from pydantic import Field, validator
|
||||||
|
|
||||||
|
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||||
|
from hummingbot.client.ui.interface_utils import format_df_for_printout
|
||||||
|
from hummingbot.core.data_type.common import PriceType, TradeType
|
||||||
|
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.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"
|
||||||
|
candles_config: List[CandlesConfig] = []
|
||||||
|
maker_connector: str = Field(
|
||||||
|
default="kucoin",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the maker connector: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
maker_trading_pair: str = Field(
|
||||||
|
default="LBR-USDT",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the maker trading pair: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
taker_connector: str = Field(
|
||||||
|
default="okx",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the taker connector: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
taker_trading_pair: str = Field(
|
||||||
|
default="LBR-USDT",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "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",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "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",
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "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.002,
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the minimum profitability: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
max_profitability: Decimal = Field(
|
||||||
|
default=0.01,
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the maximum profitability: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
max_executors_imbalance: int = Field(
|
||||||
|
default=1,
|
||||||
|
client_data=ClientFieldData(
|
||||||
|
prompt=lambda e: "Enter the maximum executors imbalance: ",
|
||||||
|
prompt_on_new=True
|
||||||
|
))
|
||||||
|
|
||||||
|
@validator("buy_levels_targets_amount", "sell_levels_targets_amount", pre=True, always=True)
|
||||||
|
def validate_levels_targets_amount(cls, v, values):
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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:
|
||||||
|
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=amount / mid_price,
|
||||||
|
min_profitability=self.config.min_profitability,
|
||||||
|
target_profitability=target_profitability,
|
||||||
|
max_profitability=self.config.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:
|
||||||
|
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=amount / mid_price,
|
||||||
|
min_profitability=self.config.min_profitability,
|
||||||
|
target_profitability=target_profitability,
|
||||||
|
max_profitability=self.config.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", )]
|
||||||
Reference in New Issue
Block a user