(feat) update controllers

This commit is contained in:
cardosofede
2025-04-11 16:01:45 -03:00
parent e1d8d45339
commit 97c450e778
10 changed files with 396 additions and 689 deletions

View File

@@ -1,55 +1,56 @@
from decimal import Decimal
from typing import Dict, List, Optional, Set
from pydantic import BaseModel, Field
from pydantic import Field
from hummingbot.client.config.config_data_types import ClientFieldData
from hummingbot.core.data_type.common import OrderType, PositionMode, PriceType, TradeType
from hummingbot.core.data_type.trade_fee import TokenAmount
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers import ControllerBase, ControllerConfigBase
from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig, TripleBarrierConfig
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction, StopExecutorAction
from hummingbot.strategy_v2.executors.data_types import ConnectorPair
from hummingbot.strategy_v2.executors.grid_executor.data_types import GridExecutorConfig
from hummingbot.strategy_v2.executors.position_executor.data_types import TripleBarrierConfig
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction
from hummingbot.strategy_v2.models.executors_info import ExecutorInfo
from hummingbot.strategy_v2.utils.distributions import Distributions
class GridRange(BaseModel):
id: str
start_price: Decimal
end_price: Decimal
total_amount_pct: Decimal
side: TradeType = TradeType.BUY
open_order_type: OrderType = OrderType.LIMIT_MAKER
take_profit_order_type: OrderType = OrderType.LIMIT
active: bool = True
class GridStrikeConfig(ControllerConfigBase):
"""
Configuration required to run the GridStrike strategy for one connector and trading pair.
"""
controller_type: str = "generic"
controller_name: str = "grid_strike"
candles_config: List[CandlesConfig] = []
controller_type = "generic"
connector_name: str = "binance"
trading_pair: str = "BTC-USDT"
total_amount_quote: Decimal = Field(default=Decimal("1000"), client_data=ClientFieldData(is_updatable=True))
grid_ranges: List[GridRange] = Field(default=[GridRange(id="R0", start_price=Decimal("40000"),
end_price=Decimal("60000"),
total_amount_pct=Decimal("0.1"))],
client_data=ClientFieldData(is_updatable=True))
# Account configuration
leverage: int = 20
position_mode: PositionMode = PositionMode.HEDGE
leverage: int = 1
time_limit: Optional[int] = Field(default=60 * 60 * 24 * 2, client_data=ClientFieldData(is_updatable=True))
activation_bounds: Decimal = Field(default=Decimal("0.01"), client_data=ClientFieldData(is_updatable=True))
min_spread_between_orders: Optional[Decimal] = Field(default=None,
client_data=ClientFieldData(is_updatable=True))
min_order_amount: Optional[Decimal] = Field(default=Decimal("1"),
client_data=ClientFieldData(is_updatable=True))
max_open_orders: int = Field(default=5, client_data=ClientFieldData(is_updatable=True))
grid_range_update_interval: int = Field(default=60, client_data=ClientFieldData(is_updatable=True))
extra_balance_base_usd: Decimal = Decimal("10")
# Boundaries
connector_name: str = "binance_perpetual"
trading_pair: str = "WLD-USDT"
side: TradeType = TradeType.BUY
start_price: Decimal = Field(default=Decimal("0.58"), json_schema_extra={"is_updatable": True})
end_price: Decimal = Field(default=Decimal("0.95"), json_schema_extra={"is_updatable": True})
limit_price: Decimal = Field(default=Decimal("0.55"), json_schema_extra={"is_updatable": True})
# Profiling
total_amount_quote: Decimal = Field(default=Decimal("1000"), json_schema_extra={"is_updatable": True})
min_spread_between_orders: Optional[Decimal] = Field(default=Decimal("0.001"), json_schema_extra={"is_updatable": True})
min_order_amount_quote: Optional[Decimal] = Field(default=Decimal("5"), json_schema_extra={"is_updatable": True})
# Execution
max_open_orders: int = Field(default=2, json_schema_extra={"is_updatable": True})
max_orders_per_batch: Optional[int] = Field(default=1, json_schema_extra={"is_updatable": True})
order_frequency: int = Field(default=3, json_schema_extra={"is_updatable": True})
activation_bounds: Optional[Decimal] = Field(default=None, json_schema_extra={"is_updatable": True})
keep_position: bool = Field(default=False, json_schema_extra={"is_updatable": True})
# Risk Management
triple_barrier_config: TripleBarrierConfig = TripleBarrierConfig(
take_profit=Decimal("0.001"),
open_order_type=OrderType.LIMIT_MAKER,
take_profit_order_type=OrderType.LIMIT_MAKER,
)
def update_markets(self, markets: Dict[str, Set[str]]) -> Dict[str, Set[str]]:
if self.connector_name not in markets:
@@ -58,16 +59,6 @@ class GridStrikeConfig(ControllerConfigBase):
return markets
class GridLevel(BaseModel):
id: str
price: Decimal
amount: Decimal
step: Decimal
side: TradeType
open_order_type: OrderType
take_profit_order_type: OrderType
class GridStrike(ControllerBase):
def __init__(self, config: GridStrikeConfig, *args, **kwargs):
super().__init__(config, *args, **kwargs)
@@ -75,151 +66,134 @@ class GridStrike(ControllerBase):
self._last_grid_levels_update = 0
self.trading_rules = None
self.grid_levels = []
self.initialize_rate_sources()
def _calculate_grid_config(self):
self.trading_rules = self.market_data_provider.get_trading_rules(self.config.connector_name,
self.config.trading_pair)
grid_levels = []
if self.config.min_spread_between_orders:
spread_between_orders = self.config.min_spread_between_orders * self.get_mid_price()
step_proposed = max(self.trading_rules.min_price_increment, spread_between_orders)
else:
step_proposed = self.trading_rules.min_price_increment
amount_proposed = max(self.trading_rules.min_notional_size, self.config.min_order_amount) if \
self.config.min_order_amount else self.trading_rules.min_order_size
for grid_range in self.config.grid_ranges:
if grid_range.active:
total_amount = grid_range.total_amount_pct * self.config.total_amount_quote
theoretical_orders_by_step = (grid_range.end_price - grid_range.start_price) / step_proposed
theoretical_orders_by_amount = total_amount / amount_proposed
orders = int(min(theoretical_orders_by_step, theoretical_orders_by_amount))
prices = Distributions.linear(orders, float(grid_range.start_price), float(grid_range.end_price))
step = (grid_range.end_price - grid_range.start_price) / grid_range.end_price / orders
if orders == 0:
self.logger().warning(f"Grid range {grid_range.id} has no orders, change the parameters "
f"(min order amount, amount pct, min spread between orders or total amount)")
amount_quote = total_amount / orders
for i, price in enumerate(prices):
price_quantized = self.market_data_provider.quantize_order_price(
self.config.connector_name,
self.config.trading_pair, price)
amount_quantized = self.market_data_provider.quantize_order_amount(
self.config.connector_name,
self.config.trading_pair, amount_quote / self.get_mid_price())
# amount_quantized = amount_quote / self.get_mid_price()
grid_levels.append(GridLevel(id=f"{grid_range.id}_P{i}",
price=price_quantized,
amount=amount_quantized,
step=step, side=grid_range.side,
open_order_type=grid_range.open_order_type,
take_profit_order_type=grid_range.take_profit_order_type,
))
return grid_levels
def initialize_rate_sources(self):
self.market_data_provider.initialize_rate_sources([ConnectorPair(connector_name=self.config.connector_name,
trading_pair=self.config.trading_pair)])
def get_balance_requirements(self) -> List[TokenAmount]:
if "perpetual" in self.config.connector_name:
return []
base_currency = self.config.trading_pair.split("-")[0]
return [TokenAmount(base_currency, self.config.extra_balance_base_usd / self.get_mid_price())]
def get_mid_price(self) -> Decimal:
return self.market_data_provider.get_price_by_type(
self.config.connector_name,
self.config.trading_pair,
PriceType.MidPrice
)
def active_executors(self, is_trading: bool) -> List[ExecutorInfo]:
def active_executors(self) -> List[ExecutorInfo]:
return [
executor for executor in self.executors_info
if executor.is_active and executor.is_trading == is_trading
if executor.is_active
]
def is_inside_bounds(self, price: Decimal) -> bool:
return self.config.start_price <= price <= self.config.end_price
def determine_executor_actions(self) -> List[ExecutorAction]:
if self.market_data_provider.time() - self._last_grid_levels_update > 60:
self._last_grid_levels_update = self.market_data_provider.time()
self.grid_levels = self._calculate_grid_config()
return self.determine_create_executor_actions() + self.determine_stop_executor_actions()
mid_price = self.market_data_provider.get_price_by_type(
self.config.connector_name, self.config.trading_pair, PriceType.MidPrice)
if len(self.active_executors()) == 0 and self.is_inside_bounds(mid_price):
return [CreateExecutorAction(
controller_id=self.config.id,
executor_config=GridExecutorConfig(
timestamp=self.market_data_provider.time(),
connector_name=self.config.connector_name,
trading_pair=self.config.trading_pair,
start_price=self.config.start_price,
end_price=self.config.end_price,
leverage=self.config.leverage,
limit_price=self.config.limit_price,
side=self.config.side,
total_amount_quote=self.config.total_amount_quote,
min_spread_between_orders=self.config.min_spread_between_orders,
min_order_amount_quote=self.config.min_order_amount_quote,
max_open_orders=self.config.max_open_orders,
max_orders_per_batch=self.config.max_orders_per_batch,
order_frequency=self.config.order_frequency,
activation_bounds=self.config.activation_bounds,
triple_barrier_config=self.config.triple_barrier_config,
level_id=None,
keep_position=self.config.keep_position,
))]
return []
async def update_processed_data(self):
mid_price = self.get_mid_price()
self.processed_data.update({
"mid_price": mid_price,
"active_executors_order_placed": self.active_executors(is_trading=False),
"active_executors_order_trading": self.active_executors(is_trading=True),
"long_activation_bounds": mid_price * (1 - self.config.activation_bounds),
"short_activation_bounds": mid_price * (1 + self.config.activation_bounds),
})
pass
def determine_create_executor_actions(self) -> List[ExecutorAction]:
mid_price = self.processed_data["mid_price"]
long_activation_bounds = self.processed_data["long_activation_bounds"]
short_activation_bounds = self.processed_data["short_activation_bounds"]
levels_allowed = []
for level in self.grid_levels:
if (level.side == TradeType.BUY and level.price >= long_activation_bounds) or \
(level.side == TradeType.SELL and level.price <= short_activation_bounds):
levels_allowed.append(level)
active_executors = self.processed_data["active_executors_order_placed"] + \
self.processed_data["active_executors_order_trading"]
active_executors_level_id = [executor.custom_info["level_id"] for executor in active_executors]
levels_allowed = sorted([level for level in levels_allowed if level.id not in active_executors_level_id],
key=lambda level: abs(level.price - mid_price))
levels_allowed = levels_allowed[:self.config.max_open_orders]
create_actions = []
for level in levels_allowed:
if level.side == TradeType.BUY and level.price > mid_price:
entry_price = mid_price
take_profit = max(level.step * 2, ((level.price - mid_price) / mid_price) + level.step)
trailing_stop = None
# trailing_stop_ap = max(level.step * 2, ((mid_price - level.price) / mid_price) + level.step)
# trailing_stop = TrailingStop(activation_price=trailing_stop_ap, trailing_delta=level.step / 2)
elif level.side == TradeType.SELL and level.price < mid_price:
entry_price = mid_price
take_profit = max(level.step * 2, ((mid_price - level.price) / mid_price) + level.step)
# trailing_stop_ap = max(level.step * 2, ((mid_price - level.price) / mid_price) + level.step)
# trailing_stop = TrailingStop(activation_price=trailing_stop_ap, trailing_delta=level.step / 2)
trailing_stop = None
else:
entry_price = level.price
take_profit = level.step
trailing_stop = None
create_actions.append(CreateExecutorAction(controller_id=self.config.id,
executor_config=PositionExecutorConfig(
timestamp=self.market_data_provider.time(),
connector_name=self.config.connector_name,
trading_pair=self.config.trading_pair,
entry_price=entry_price,
amount=level.amount,
leverage=self.config.leverage,
side=level.side,
level_id=level.id,
activation_bounds=[self.config.activation_bounds,
self.config.activation_bounds],
triple_barrier_config=TripleBarrierConfig(
take_profit=take_profit,
time_limit=self.config.time_limit,
open_order_type=OrderType.LIMIT_MAKER,
take_profit_order_type=level.take_profit_order_type,
trailing_stop=trailing_stop,
))))
return create_actions
def determine_stop_executor_actions(self) -> List[ExecutorAction]:
long_activation_bounds = self.processed_data["long_activation_bounds"]
short_activation_bounds = self.processed_data["short_activation_bounds"]
active_executors_order_placed = self.processed_data["active_executors_order_placed"]
non_active_ranges = [grid_range.id for grid_range in self.config.grid_ranges if not grid_range.active]
active_executor_of_non_active_ranges = [executor.id for executor in self.executors_info if
executor.is_active and
executor.custom_info["level_id"].split("_")[0] in non_active_ranges]
long_executors_to_stop = [executor.id for executor in active_executors_order_placed if
executor.side == TradeType.BUY and
executor.config.entry_price <= long_activation_bounds]
short_executors_to_stop = [executor.id for executor in active_executors_order_placed if
executor.side == TradeType.SELL and
executor.config.entry_price >= short_activation_bounds]
executors_id_to_stop = set(
active_executor_of_non_active_ranges + long_executors_to_stop + short_executors_to_stop)
return [StopExecutorAction(controller_id=self.config.id, executor_id=executor) for executor in
list(executors_id_to_stop)]
def to_format_status(self) -> List[str]:
status = []
mid_price = self.market_data_provider.get_price_by_type(
self.config.connector_name, self.config.trading_pair, PriceType.MidPrice)
# Define standard box width for consistency
box_width = 114
# Top Grid Configuration box with simple borders
status.append("" + "" * box_width + "")
# First line: Grid Configuration and Mid Price
left_section = "Grid Configuration:"
padding = box_width - len(left_section) - 4 # -4 for the border characters and spacing
config_line1 = f"{left_section}{' ' * padding}"
padding2 = box_width - len(config_line1) + 1 # +1 for correct right border alignment
config_line1 += " " * padding2 + ""
status.append(config_line1)
# Second line: Configuration parameters
config_line2 = f"│ Start: {self.config.start_price:.4f} │ End: {self.config.end_price:.4f} │ Side: {self.config.side} │ Limit: {self.config.limit_price:.4f} │ Mid Price: {mid_price:.4f}"
padding = box_width - len(config_line2) + 1 # +1 for correct right border alignment
config_line2 += " " * padding + ""
status.append(config_line2)
# Third line: Max orders and Inside bounds
config_line3 = f"│ Max Orders: {self.config.max_open_orders} │ Inside bounds: {1 if self.is_inside_bounds(mid_price) else 0}"
padding = box_width - len(config_line3) + 1 # +1 for correct right border alignment
config_line3 += " " * padding + ""
status.append(config_line3)
status.append("" + "" * box_width + "")
for level in self.active_executors():
# Define column widths for perfect alignment
col_width = box_width // 3 # Dividing the total width by 3 for equal columns
total_width = box_width
# Grid Status header - use long line and running status
status_header = f"Grid Status: {level.id} (RunnableStatus.RUNNING)"
status_line = f"{status_header}" + "" * (total_width - len(status_header) - 2) + ""
status.append(status_line)
# Calculate exact column widths for perfect alignment
col1_end = col_width
# Column headers
header_line = "│ Level Distribution" + " " * (col1_end - 20) + ""
header_line += " Order Statistics" + " " * (col_width - 18) + ""
header_line += " Performance Metrics" + " " * (col_width - 21) + ""
status.append(header_line)
# Data for the three columns
level_dist_data = [
f"NOT_ACTIVE: {len(level.custom_info['levels_by_state'].get('NOT_ACTIVE', []))}",
f"OPEN_ORDER_PLACED: {len(level.custom_info['levels_by_state'].get('OPEN_ORDER_PLACED', []))}",
f"OPEN_ORDER_FILLED: {len(level.custom_info['levels_by_state'].get('OPEN_ORDER_FILLED', []))}",
f"CLOSE_ORDER_PLACED: {len(level.custom_info['levels_by_state'].get('CLOSE_ORDER_PLACED', []))}",
f"COMPLETE: {len(level.custom_info['levels_by_state'].get('COMPLETE', []))}"
]
order_stats_data = [
f"Total: {sum(len(level.custom_info[k]) for k in ['filled_orders', 'failed_orders', 'canceled_orders'])}",
f"Filled: {len(level.custom_info['filled_orders'])}",
f"Failed: {len(level.custom_info['failed_orders'])}",
f"Canceled: {len(level.custom_info['canceled_orders'])}"
]
perf_metrics_data = [
f"Buy Vol: {level.custom_info['realized_buy_size_quote']:.4f}",
f"Sell Vol: {level.custom_info['realized_sell_size_quote']:.4f}",
f"R. PnL: {level.custom_info['realized_pnl_quote']:.4f}",
f"R. Fees: {level.custom_info['realized_fees_quote']:.4f}",
f"P. PnL: {level.custom_info['position_pnl_quote']:.4f}",
f"Position: {level.custom_info['position_size_quote']:.4f}"
]
# Build rows with perfect alignment
max_rows = max(len(level_dist_data), len(order_stats_data), len(perf_metrics_data))
for i in range(max_rows):
col1 = level_dist_data[i] if i < len(level_dist_data) else ""
col2 = order_stats_data[i] if i < len(order_stats_data) else ""
col3 = perf_metrics_data[i] if i < len(perf_metrics_data) else ""
row = "" + col1
row += " " * (col1_end - len(col1) - 2) # -2 for the "│ " at the start
row += "" + col2
row += " " * (col_width - len(col2) - 2) # -2 for the "│ " before col2
row += "" + col3
row += " " * (col_width - len(col3) - 2) # -2 for the "│ " before col3
row += ""
status.append(row)
# Liquidity line with perfect alignment
status.append("" + "" * total_width + "")
liquidity_line = f"│ Open Liquidity: {level.custom_info['open_liquidity_placed']:.4f} │ Close Liquidity: {level.custom_info['close_liquidity_placed']:.4f}"
liquidity_line += " " * (total_width - len(liquidity_line) + 1) # +1 for correct right border alignment
liquidity_line += ""
status.append(liquidity_line)
status.append("" + "" * total_width + "")
return status

View File

@@ -1,196 +0,0 @@
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']}"]

View File

@@ -3,9 +3,8 @@ from decimal import Decimal
from typing import Dict, List, Set
import pandas as pd
from pydantic import Field, validator
from pydantic import Field, 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
@@ -19,62 +18,40 @@ 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
))
default="mexc",
json_schema_extra={"prompt": "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
))
default="PEPE-USDT",
json_schema_extra={"prompt": "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
))
default="binance",
json_schema_extra={"prompt": "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
))
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",
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
))
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",
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
))
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.002,
client_data=ClientFieldData(
prompt=lambda e: "Enter the minimum profitability: ",
prompt_on_new=True
))
default=0.003,
json_schema_extra={"prompt": "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
))
json_schema_extra={"prompt": "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
))
json_schema_extra={"prompt": "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):
@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
@@ -124,6 +101,8 @@ class XEMMMultipleLevels(ControllerBase):
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:
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(),
@@ -133,14 +112,16 @@ class XEMMMultipleLevels(ControllerBase):
trading_pair=self.config.taker_trading_pair),
maker_side=TradeType.BUY,
order_amount=amount / mid_price,
min_profitability=self.config.min_profitability,
min_profitability=min_profitability,
target_profitability=target_profitability,
max_profitability=self.config.max_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:
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(),
@@ -150,9 +131,9 @@ class XEMMMultipleLevels(ControllerBase):
trading_pair=self.config.maker_trading_pair),
maker_side=TradeType.SELL,
order_amount=amount / mid_price,
min_profitability=self.config.min_profitability,
min_profitability=min_profitability,
target_profitability=target_profitability,
max_profitability=self.config.max_profitability
max_profitability=max_profitability
)
executor_actions.append(CreateExecutorAction(executor_config=config, controller_id=self.config.id))
return executor_actions