mirror of
https://github.com/d0zingcat/deploy.git
synced 2026-05-14 07:26:43 +00:00
(feat) add grid strike
This commit is contained in:
216
bots/controllers/generic/grid_strike.py
Normal file
216
bots/controllers/generic/grid_strike.py
Normal file
@@ -0,0 +1,216 @@
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Optional, Set
|
||||
|
||||
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.models.executors_info import ExecutorInfo
|
||||
from hummingbot.strategy_v2.utils.distributions import Distributions
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
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_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))
|
||||
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")
|
||||
|
||||
def update_markets(self, markets: Dict[str, Set[str]]) -> Dict[str, Set[str]]:
|
||||
if self.connector_name not in markets:
|
||||
markets[self.connector_name] = set()
|
||||
markets[self.connector_name].add(self.trading_pair)
|
||||
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)
|
||||
self.config = config
|
||||
self._last_grid_levels_update = 0
|
||||
self.trading_rules = None
|
||||
|
||||
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
|
||||
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 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]:
|
||||
return [
|
||||
executor for executor in self.executors_info
|
||||
if executor.is_active and executor.is_trading == is_trading
|
||||
]
|
||||
|
||||
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()
|
||||
|
||||
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),
|
||||
})
|
||||
|
||||
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)]
|
||||
Reference in New Issue
Block a user