mirror of
https://github.com/d0zingcat/deploy.git
synced 2026-05-13 15:09:33 +00:00
117 lines
5.1 KiB
Python
117 lines
5.1 KiB
Python
from decimal import Decimal
|
|
from typing import List, Optional
|
|
|
|
import pandas_ta as ta # noqa: F401
|
|
from pydantic import Field, field_validator
|
|
|
|
from hummingbot.core.data_type.common import TradeType
|
|
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
|
from hummingbot.strategy_v2.controllers.market_making_controller_base import (
|
|
MarketMakingControllerBase,
|
|
MarketMakingControllerConfigBase,
|
|
)
|
|
from hummingbot.strategy_v2.executors.dca_executor.data_types import DCAExecutorConfig, DCAMode
|
|
from hummingbot.strategy_v2.models.executor_actions import ExecutorAction, StopExecutorAction
|
|
|
|
|
|
class DManMakerV2Config(MarketMakingControllerConfigBase):
|
|
"""
|
|
Configuration required to run the D-Man Maker V2 strategy.
|
|
"""
|
|
controller_name: str = "dman_maker_v2"
|
|
candles_config: List[CandlesConfig] = []
|
|
|
|
# DCA configuration
|
|
dca_spreads: List[Decimal] = Field(
|
|
default="0.01,0.02,0.04,0.08",
|
|
json_schema_extra={"prompt": "Enter a comma-separated list of spreads for each DCA level: ", "prompt_on_new": True})
|
|
dca_amounts: List[Decimal] = Field(
|
|
default="0.1,0.2,0.4,0.8",
|
|
json_schema_extra={"prompt": "Enter a comma-separated list of amounts for each DCA level: ", "prompt_on_new": True})
|
|
top_executor_refresh_time: Optional[float] = Field(default=None, json_schema_extra={"is_updatable": True})
|
|
executor_activation_bounds: Optional[List[Decimal]] = Field(default=None, json_schema_extra={"is_updatable": True})
|
|
|
|
@field_validator("executor_activation_bounds", mode="before")
|
|
@classmethod
|
|
def parse_activation_bounds(cls, v):
|
|
if isinstance(v, list):
|
|
return [Decimal(val) for val in v]
|
|
elif isinstance(v, str):
|
|
if v == "":
|
|
return None
|
|
return [Decimal(val) for val in v.split(",")]
|
|
return v
|
|
|
|
@field_validator('dca_spreads', mode="before")
|
|
@classmethod
|
|
def parse_dca_spreads(cls, v):
|
|
if v is None:
|
|
return []
|
|
if isinstance(v, str):
|
|
if v == "":
|
|
return []
|
|
return [float(x.strip()) for x in v.split(',')]
|
|
return v
|
|
|
|
@field_validator('dca_amounts', mode="before")
|
|
@classmethod
|
|
def parse_and_validate_dca_amounts(cls, v, validation_info):
|
|
if v is None or v == "":
|
|
return [1 for _ in validation_info.data['dca_spreads']]
|
|
if isinstance(v, str):
|
|
return [float(x.strip()) for x in v.split(',')]
|
|
elif isinstance(v, list) and len(v) != len(validation_info.data['dca_spreads']):
|
|
raise ValueError(
|
|
f"The number of dca amounts must match the number of {validation_info.data['dca_spreads']}.")
|
|
return v
|
|
|
|
|
|
class DManMakerV2(MarketMakingControllerBase):
|
|
def __init__(self, config: DManMakerV2Config, *args, **kwargs):
|
|
super().__init__(config, *args, **kwargs)
|
|
self.config = config
|
|
self.dca_amounts_pct = [Decimal(amount) / sum(self.config.dca_amounts) for amount in self.config.dca_amounts]
|
|
self.spreads = self.config.dca_spreads
|
|
|
|
def first_level_refresh_condition(self, executor):
|
|
if self.config.top_executor_refresh_time is not None:
|
|
if self.get_level_from_level_id(executor.custom_info["level_id"]) == 0:
|
|
return self.market_data_provider.time() - executor.timestamp > self.config.top_executor_refresh_time
|
|
return False
|
|
|
|
def order_level_refresh_condition(self, executor):
|
|
return self.market_data_provider.time() - executor.timestamp > self.config.executor_refresh_time
|
|
|
|
def executors_to_refresh(self) -> List[ExecutorAction]:
|
|
executors_to_refresh = self.filter_executors(
|
|
executors=self.executors_info,
|
|
filter_func=lambda x: not x.is_trading and x.is_active and (self.order_level_refresh_condition(x) or self.first_level_refresh_condition(x)))
|
|
return [StopExecutorAction(
|
|
controller_id=self.config.id,
|
|
executor_id=executor.id) for executor in executors_to_refresh]
|
|
|
|
def get_executor_config(self, level_id: str, price: Decimal, amount: Decimal):
|
|
trade_type = self.get_trade_type_from_level_id(level_id)
|
|
if trade_type == TradeType.BUY:
|
|
prices = [price * (1 - spread) for spread in self.spreads]
|
|
else:
|
|
prices = [price * (1 + spread) for spread in self.spreads]
|
|
amounts = [amount * pct for pct in self.dca_amounts_pct]
|
|
amounts_quote = [amount * price for amount, price in zip(amounts, prices)]
|
|
return DCAExecutorConfig(
|
|
timestamp=self.market_data_provider.time(),
|
|
connector_name=self.config.connector_name,
|
|
trading_pair=self.config.trading_pair,
|
|
mode=DCAMode.MAKER,
|
|
side=trade_type,
|
|
prices=prices,
|
|
amounts_quote=amounts_quote,
|
|
level_id=level_id,
|
|
time_limit=self.config.time_limit,
|
|
stop_loss=self.config.stop_loss,
|
|
take_profit=self.config.take_profit,
|
|
trailing_stop=self.config.trailing_stop,
|
|
activation_bounds=self.config.executor_activation_bounds,
|
|
leverage=self.config.leverage,
|
|
)
|