mirror of
https://github.com/d0zingcat/deploy.git
synced 2026-05-13 23:16:53 +00:00
(feat) update controllers
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
from typing import List
|
||||
|
||||
import pandas_ta as ta # noqa: F401
|
||||
from pydantic import Field, validator
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
||||
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
|
||||
DirectionalTradingControllerBase,
|
||||
@@ -12,56 +12,42 @@ from hummingbot.strategy_v2.controllers.directional_trading_controller_base impo
|
||||
|
||||
|
||||
class BollingerV1ControllerConfig(DirectionalTradingControllerConfigBase):
|
||||
controller_name = "bollinger_v1"
|
||||
controller_name: str = "bollinger_v1"
|
||||
candles_config: List[CandlesConfig] = []
|
||||
candles_connector: str = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", )
|
||||
)
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
candles_trading_pair: str = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", )
|
||||
)
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
interval: str = Field(
|
||||
default="3m",
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
|
||||
prompt_on_new=False))
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
|
||||
"prompt_on_new": True})
|
||||
bb_length: int = Field(
|
||||
default=100,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands length: ",
|
||||
prompt_on_new=True))
|
||||
bb_std: float = Field(
|
||||
default=2.0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands standard deviation: ",
|
||||
prompt_on_new=False))
|
||||
bb_long_threshold: float = Field(
|
||||
default=0.0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands long threshold: ",
|
||||
prompt_on_new=True))
|
||||
bb_short_threshold: float = Field(
|
||||
default=1.0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands short threshold: ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Enter the Bollinger Bands length: ", "prompt_on_new": True})
|
||||
bb_std: float = Field(default=2.0)
|
||||
bb_long_threshold: float = Field(default=0.0)
|
||||
bb_short_threshold: float = Field(default=1.0)
|
||||
|
||||
@validator("candles_connector", pre=True, always=True)
|
||||
def set_candles_connector(cls, v, values):
|
||||
@field_validator("candles_connector", mode="before")
|
||||
@classmethod
|
||||
def set_candles_connector(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return values.get("connector_name")
|
||||
return validation_info.data.get("connector_name")
|
||||
return v
|
||||
|
||||
@validator("candles_trading_pair", pre=True, always=True)
|
||||
def set_candles_trading_pair(cls, v, values):
|
||||
@field_validator("candles_trading_pair", mode="before")
|
||||
@classmethod
|
||||
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return values.get("trading_pair")
|
||||
return validation_info.data.get("trading_pair")
|
||||
return v
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ from decimal import Decimal
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import pandas_ta as ta # noqa: F401
|
||||
from pydantic import Field, validator
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||
from hummingbot.core.data_type.common import TradeType
|
||||
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
||||
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
|
||||
@@ -21,75 +21,63 @@ class DManV3ControllerConfig(DirectionalTradingControllerConfigBase):
|
||||
candles_config: List[CandlesConfig] = []
|
||||
candles_connector: str = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",)
|
||||
)
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
candles_trading_pair: str = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",)
|
||||
)
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
interval: str = Field(
|
||||
default="30m",
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
|
||||
prompt_on_new=True))
|
||||
default="3m",
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
|
||||
"prompt_on_new": True})
|
||||
bb_length: int = Field(
|
||||
default=100,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands length: ",
|
||||
prompt_on_new=True))
|
||||
bb_std: float = Field(
|
||||
default=2.0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands standard deviation: ",
|
||||
prompt_on_new=False))
|
||||
bb_long_threshold: float = Field(
|
||||
default=0.0,
|
||||
client_data=ClientFieldData(
|
||||
is_updatable=True,
|
||||
prompt=lambda mi: "Enter the Bollinger Bands long threshold: ",
|
||||
prompt_on_new=True))
|
||||
bb_short_threshold: float = Field(
|
||||
default=1.0,
|
||||
client_data=ClientFieldData(
|
||||
is_updatable=True,
|
||||
prompt=lambda mi: "Enter the Bollinger Bands short threshold: ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Enter the Bollinger Bands length: ", "prompt_on_new": True})
|
||||
bb_std: float = Field(default=2.0)
|
||||
bb_long_threshold: float = Field(default=0.0)
|
||||
bb_short_threshold: float = Field(default=1.0)
|
||||
trailing_stop: Optional[TrailingStop] = Field(
|
||||
default="0.015,0.005",
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the trailing stop parameters (activation_price, trailing_delta) as a comma-separated list: ",
|
||||
"prompt_on_new": True,
|
||||
}
|
||||
)
|
||||
dca_spreads: List[Decimal] = Field(
|
||||
default="0.001,0.018,0.15,0.25",
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the spreads for each DCA level (comma-separated) if dynamic_spread=True this value "
|
||||
"will multiply the Bollinger Bands width, e.g. if the Bollinger Bands width is 0.1 (10%)"
|
||||
"and the spread is 0.2, the distance of the order to the current price will be 0.02 (2%) ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the spreads for each DCA level (comma-separated) if dynamic_spread=True this value "
|
||||
"will multiply the Bollinger Bands width, e.g. if the Bollinger Bands width is 0.1 (10%)"
|
||||
"and the spread is 0.2, the distance of the order to the current price will be 0.02 (2%) ",
|
||||
"prompt_on_new": True},
|
||||
)
|
||||
dca_amounts_pct: List[Decimal] = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the amounts for each DCA level (as a percentage of the total balance, "
|
||||
"comma-separated). Don't worry about the final sum, it will be normalized. ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the amounts for each DCA level (as a percentage of the total balance, "
|
||||
"comma-separated). Don't worry about the final sum, it will be normalized. ",
|
||||
"prompt_on_new": True},
|
||||
)
|
||||
dynamic_order_spread: bool = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Do you want to make the spread dynamic? (Yes/No) ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Do you want to make the spread dynamic? (Yes/No) ", "prompt_on_new": True})
|
||||
dynamic_target: bool = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Do you want to make the target dynamic? (Yes/No) ",
|
||||
prompt_on_new=True))
|
||||
|
||||
json_schema_extra={"prompt": "Do you want to make the target dynamic? (Yes/No) ", "prompt_on_new": True})
|
||||
activation_bounds: Optional[List[Decimal]] = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the activation bounds for the orders "
|
||||
"(e.g., 0.01 activates the next order when the price is closer than 1%): ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the activation bounds for the orders (e.g., 0.01 activates the next order when the price is closer than 1%): ",
|
||||
"prompt_on_new": True,
|
||||
}
|
||||
)
|
||||
|
||||
@validator("activation_bounds", pre=True, always=True)
|
||||
@field_validator("activation_bounds", mode="before")
|
||||
@classmethod
|
||||
def parse_activation_bounds(cls, v):
|
||||
if isinstance(v, str):
|
||||
if v == "":
|
||||
@@ -99,15 +87,17 @@ class DManV3ControllerConfig(DirectionalTradingControllerConfigBase):
|
||||
return [Decimal(val) for val in v]
|
||||
return v
|
||||
|
||||
@validator('dca_spreads', pre=True, always=True)
|
||||
@field_validator('dca_spreads', mode="before")
|
||||
@classmethod
|
||||
def validate_spreads(cls, v):
|
||||
if isinstance(v, str):
|
||||
return [Decimal(val) for val in v.split(",")]
|
||||
return v
|
||||
|
||||
@validator('dca_amounts_pct', pre=True, always=True)
|
||||
def validate_amounts(cls, v, values):
|
||||
spreads = values.get("dca_spreads")
|
||||
@field_validator('dca_amounts_pct', mode="before")
|
||||
@classmethod
|
||||
def validate_amounts(cls, v, validation_info: ValidationInfo):
|
||||
spreads = validation_info.data.get("dca_spreads")
|
||||
if isinstance(v, str):
|
||||
if v == "":
|
||||
return [Decimal('1.0') / len(spreads) for _ in spreads]
|
||||
@@ -119,6 +109,20 @@ class DManV3ControllerConfig(DirectionalTradingControllerConfigBase):
|
||||
return [Decimal('1.0') / len(spreads) for _ in spreads]
|
||||
return v
|
||||
|
||||
@field_validator("candles_connector", mode="before")
|
||||
@classmethod
|
||||
def set_candles_connector(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return validation_info.data.get("connector_name")
|
||||
return v
|
||||
|
||||
@field_validator("candles_trading_pair", mode="before")
|
||||
@classmethod
|
||||
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return validation_info.data.get("trading_pair")
|
||||
return v
|
||||
|
||||
def get_spreads_and_amounts_in_quote(self, trade_type: TradeType, total_amount_quote: Decimal) -> Tuple[List[Decimal], List[Decimal]]:
|
||||
amounts_pct = self.dca_amounts_pct
|
||||
if amounts_pct is None:
|
||||
@@ -133,18 +137,6 @@ class DManV3ControllerConfig(DirectionalTradingControllerConfigBase):
|
||||
|
||||
return self.dca_spreads, [amt_pct * total_amount_quote for amt_pct in normalized_amounts_pct]
|
||||
|
||||
@validator("candles_connector", pre=True, always=True)
|
||||
def set_candles_connector(cls, v, values):
|
||||
if v is None or v == "":
|
||||
return values.get("connector_name")
|
||||
return v
|
||||
|
||||
@validator("candles_trading_pair", pre=True, always=True)
|
||||
def set_candles_trading_pair(cls, v, values):
|
||||
if v is None or v == "":
|
||||
return values.get("trading_pair")
|
||||
return v
|
||||
|
||||
|
||||
class DManV3Controller(DirectionalTradingControllerBase):
|
||||
"""
|
||||
@@ -201,8 +193,12 @@ class DManV3Controller(DirectionalTradingControllerBase):
|
||||
prices = [price * (1 + spread * spread_multiplier) for spread in spread]
|
||||
if self.config.dynamic_target:
|
||||
stop_loss = self.config.stop_loss * spread_multiplier
|
||||
trailing_stop = TrailingStop(activation_price=self.config.trailing_stop.activation_price * spread_multiplier,
|
||||
trailing_delta=self.config.trailing_stop.trailing_delta * spread_multiplier)
|
||||
if self.config.trailing_stop:
|
||||
trailing_stop = TrailingStop(
|
||||
activation_price=self.config.trailing_stop.activation_price * spread_multiplier,
|
||||
trailing_delta=self.config.trailing_stop.trailing_delta * spread_multiplier)
|
||||
else:
|
||||
trailing_stop = None
|
||||
else:
|
||||
stop_loss = self.config.stop_loss
|
||||
trailing_stop = self.config.trailing_stop
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import List
|
||||
|
||||
import pandas_ta as ta # noqa: F401
|
||||
from pydantic import Field, validator
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
||||
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
|
||||
DirectionalTradingControllerBase,
|
||||
@@ -12,71 +12,51 @@ from hummingbot.strategy_v2.controllers.directional_trading_controller_base impo
|
||||
|
||||
|
||||
class MACDBBV1ControllerConfig(DirectionalTradingControllerConfigBase):
|
||||
controller_name = "macd_bb_v1"
|
||||
controller_name: str = "macd_bb_v1"
|
||||
candles_config: List[CandlesConfig] = []
|
||||
candles_connector: str = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", )
|
||||
)
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
candles_trading_pair: str = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", )
|
||||
)
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
interval: str = Field(
|
||||
default="3m",
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
|
||||
prompt_on_new=False))
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
|
||||
"prompt_on_new": True})
|
||||
bb_length: int = Field(
|
||||
default=100,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands length: ",
|
||||
prompt_on_new=True))
|
||||
bb_std: float = Field(
|
||||
default=2.0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands standard deviation: ",
|
||||
prompt_on_new=False))
|
||||
bb_long_threshold: float = Field(
|
||||
default=0.0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands long threshold: ",
|
||||
prompt_on_new=True))
|
||||
bb_short_threshold: float = Field(
|
||||
default=1.0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the Bollinger Bands short threshold: ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Enter the Bollinger Bands length: ", "prompt_on_new": True})
|
||||
bb_std: float = Field(default=2.0)
|
||||
bb_long_threshold: float = Field(default=0.0)
|
||||
bb_short_threshold: float = Field(default=1.0)
|
||||
macd_fast: int = Field(
|
||||
default=21,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the MACD fast period: ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Enter the MACD fast period: ", "prompt_on_new": True})
|
||||
macd_slow: int = Field(
|
||||
default=42,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the MACD slow period: ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Enter the MACD slow period: ", "prompt_on_new": True})
|
||||
macd_signal: int = Field(
|
||||
default=9,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the MACD signal period: ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Enter the MACD signal period: ", "prompt_on_new": True})
|
||||
|
||||
@validator("candles_connector", pre=True, always=True)
|
||||
def set_candles_connector(cls, v, values):
|
||||
@field_validator("candles_connector", mode="before")
|
||||
@classmethod
|
||||
def set_candles_connector(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return values.get("connector_name")
|
||||
return validation_info.data.get("connector_name")
|
||||
return v
|
||||
|
||||
@validator("candles_trading_pair", pre=True, always=True)
|
||||
def set_candles_trading_pair(cls, v, values):
|
||||
@field_validator("candles_trading_pair", mode="before")
|
||||
@classmethod
|
||||
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return values.get("trading_pair")
|
||||
return validation_info.data.get("trading_pair")
|
||||
return v
|
||||
|
||||
|
||||
@@ -84,7 +64,7 @@ class MACDBBV1Controller(DirectionalTradingControllerBase):
|
||||
|
||||
def __init__(self, config: MACDBBV1ControllerConfig, *args, **kwargs):
|
||||
self.config = config
|
||||
self.max_records = max(config.macd_slow, config.macd_fast, config.macd_signal, config.bb_length) + 200
|
||||
self.max_records = max(config.macd_slow, config.macd_fast, config.macd_signal, config.bb_length) + 20
|
||||
if len(self.config.candles_config) == 0:
|
||||
self.config.candles_config = [CandlesConfig(
|
||||
connector=config.candles_connector,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
import pandas_ta as ta # noqa: F401
|
||||
from pydantic import Field, validator
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
||||
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
|
||||
DirectionalTradingControllerBase,
|
||||
@@ -14,23 +14,41 @@ from hummingbot.strategy_v2.controllers.directional_trading_controller_base impo
|
||||
class SuperTrendConfig(DirectionalTradingControllerConfigBase):
|
||||
controller_name: str = "supertrend_v1"
|
||||
candles_config: List[CandlesConfig] = []
|
||||
candles_connector: Optional[str] = Field(default=None, client_data=ClientFieldData(prompt_on_new=True, prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", ))
|
||||
candles_trading_pair: Optional[str] = Field(default=None, client_data=ClientFieldData(prompt_on_new=True, prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", ))
|
||||
interval: str = Field(default="3m", client_data=ClientFieldData(prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ", prompt_on_new=False))
|
||||
length: int = Field(default=20, client_data=ClientFieldData(prompt=lambda mi: "Enter the supertrend length: ", prompt_on_new=True))
|
||||
multiplier: float = Field(default=4.0, client_data=ClientFieldData(prompt=lambda mi: "Enter the supertrend multiplier: ", prompt_on_new=True))
|
||||
percentage_threshold: float = Field(default=0.01, client_data=ClientFieldData(prompt=lambda mi: "Enter the percentage threshold: ", prompt_on_new=True))
|
||||
candles_connector: str = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
candles_trading_pair: str = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
interval: str = Field(
|
||||
default="3m",
|
||||
json_schema_extra={"prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ", "prompt_on_new": True})
|
||||
length: int = Field(
|
||||
default=20,
|
||||
json_schema_extra={"prompt": "Enter the supertrend length: ", "prompt_on_new": True})
|
||||
multiplier: float = Field(
|
||||
default=4.0,
|
||||
json_schema_extra={"prompt": "Enter the supertrend multiplier: ", "prompt_on_new": True})
|
||||
percentage_threshold: float = Field(
|
||||
default=0.01,
|
||||
json_schema_extra={"prompt": "Enter the percentage threshold: ", "prompt_on_new": True})
|
||||
|
||||
@validator("candles_connector", pre=True, always=True)
|
||||
def set_candles_connector(cls, v, values):
|
||||
@field_validator("candles_connector", mode="before")
|
||||
@classmethod
|
||||
def set_candles_connector(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return values.get("connector_name")
|
||||
return validation_info.data.get("connector_name")
|
||||
return v
|
||||
|
||||
@validator("candles_trading_pair", pre=True, always=True)
|
||||
def set_candles_trading_pair(cls, v, values):
|
||||
@field_validator("candles_trading_pair", mode="before")
|
||||
@classmethod
|
||||
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return values.get("trading_pair")
|
||||
return validation_info.data.get("trading_pair")
|
||||
return v
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']}"]
|
||||
@@ -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
|
||||
|
||||
@@ -2,9 +2,8 @@ from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
|
||||
import pandas_ta as ta # noqa: F401
|
||||
from pydantic import Field, validator
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||
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 (
|
||||
@@ -25,38 +24,15 @@ class DManMakerV2Config(MarketMakingControllerConfigBase):
|
||||
# DCA configuration
|
||||
dca_spreads: List[Decimal] = Field(
|
||||
default="0.01,0.02,0.04,0.08",
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter a comma-separated list of spreads for each DCA level: "))
|
||||
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",
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter a comma-separated list of amounts for each DCA level: "))
|
||||
time_limit: int = Field(
|
||||
default=60 * 60 * 24 * 7, gt=0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the time limit for each DCA level: ",
|
||||
prompt_on_new=False))
|
||||
stop_loss: Decimal = Field(
|
||||
default=Decimal("0.03"), gt=0,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the stop loss (as a decimal, e.g., 0.03 for 3%): ",
|
||||
prompt_on_new=True))
|
||||
top_executor_refresh_time: Optional[float] = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
is_updatable=True,
|
||||
prompt_on_new=False))
|
||||
executor_activation_bounds: Optional[List[Decimal]] = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
is_updatable=True,
|
||||
prompt=lambda mi: "Enter the activation bounds for the orders "
|
||||
"(e.g., 0.01 activates the next order when the price is closer than 1%): ",
|
||||
prompt_on_new=False))
|
||||
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})
|
||||
|
||||
@validator("executor_activation_bounds", pre=True, always=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]
|
||||
@@ -66,8 +42,9 @@ class DManMakerV2Config(MarketMakingControllerConfigBase):
|
||||
return [Decimal(val) for val in v.split(",")]
|
||||
return v
|
||||
|
||||
@validator('dca_spreads', pre=True, always=True)
|
||||
def parse_spreads(cls, v):
|
||||
@field_validator('dca_spreads', mode="before")
|
||||
@classmethod
|
||||
def parse_dca_spreads(cls, v):
|
||||
if v is None:
|
||||
return []
|
||||
if isinstance(v, str):
|
||||
@@ -76,15 +53,16 @@ class DManMakerV2Config(MarketMakingControllerConfigBase):
|
||||
return [float(x.strip()) for x in v.split(',')]
|
||||
return v
|
||||
|
||||
@validator('dca_amounts', pre=True, always=True)
|
||||
def parse_and_validate_amounts(cls, v, values, field):
|
||||
@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 values[values['dca_spreads']]]
|
||||
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(values['dca_spreads']):
|
||||
elif isinstance(v, list) and len(v) != len(validation_info.data['dca_spreads']):
|
||||
raise ValueError(
|
||||
f"The number of {field.name} must match the number of {values['dca_spreads']}.")
|
||||
f"The number of dca amounts must match the number of {validation_info.data['dca_spreads']}.")
|
||||
return v
|
||||
|
||||
|
||||
@@ -98,11 +76,11 @@ class DManMakerV2(MarketMakingControllerBase):
|
||||
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 * 1000
|
||||
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 * 1000
|
||||
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(
|
||||
|
||||
@@ -2,9 +2,9 @@ from decimal import Decimal
|
||||
from typing import List
|
||||
|
||||
import pandas_ta as ta # noqa: F401
|
||||
from pydantic import Field, validator
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
||||
from hummingbot.strategy_v2.controllers.market_making_controller_base import (
|
||||
MarketMakingControllerBase,
|
||||
@@ -14,69 +14,60 @@ from hummingbot.strategy_v2.executors.position_executor.data_types import Positi
|
||||
|
||||
|
||||
class PMMDynamicControllerConfig(MarketMakingControllerConfigBase):
|
||||
controller_name = "pmm_dynamic"
|
||||
controller_name: str = "pmm_dynamic"
|
||||
candles_config: List[CandlesConfig] = []
|
||||
buy_spreads: List[float] = Field(
|
||||
default="1,2,4",
|
||||
client_data=ClientFieldData(
|
||||
is_updatable=True,
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter a comma-separated list of buy spreads (e.g., '0.01, 0.02'):"))
|
||||
json_schema_extra={
|
||||
"prompt": "Enter a comma-separated list of buy spreads measured in units of volatility(e.g., '1, 2'): ",
|
||||
"prompt_on_new": True, "is_updatable": True}
|
||||
)
|
||||
sell_spreads: List[float] = Field(
|
||||
default="1,2,4",
|
||||
client_data=ClientFieldData(
|
||||
is_updatable=True,
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter a comma-separated list of sell spreads (e.g., '0.01, 0.02'):"))
|
||||
json_schema_extra={
|
||||
"prompt": "Enter a comma-separated list of sell spreads measured in units of volatility(e.g., '1, 2'): ",
|
||||
"prompt_on_new": True, "is_updatable": True}
|
||||
)
|
||||
candles_connector: str = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", )
|
||||
)
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
candles_trading_pair: str = Field(
|
||||
default=None,
|
||||
client_data=ClientFieldData(
|
||||
prompt_on_new=True,
|
||||
prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", )
|
||||
)
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
|
||||
"prompt_on_new": True})
|
||||
interval: str = Field(
|
||||
default="3m",
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
|
||||
prompt_on_new=False))
|
||||
|
||||
json_schema_extra={
|
||||
"prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
|
||||
"prompt_on_new": True})
|
||||
macd_fast: int = Field(
|
||||
default=12,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the MACD fast length: ",
|
||||
prompt_on_new=True))
|
||||
default=21,
|
||||
json_schema_extra={"prompt": "Enter the MACD fast period: ", "prompt_on_new": True})
|
||||
macd_slow: int = Field(
|
||||
default=26,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the MACD slow length: ",
|
||||
prompt_on_new=True))
|
||||
default=42,
|
||||
json_schema_extra={"prompt": "Enter the MACD slow period: ", "prompt_on_new": True})
|
||||
macd_signal: int = Field(
|
||||
default=9,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the MACD signal length: ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Enter the MACD signal period: ", "prompt_on_new": True})
|
||||
natr_length: int = Field(
|
||||
default=14,
|
||||
client_data=ClientFieldData(
|
||||
prompt=lambda mi: "Enter the NATR length: ",
|
||||
prompt_on_new=True))
|
||||
json_schema_extra={"prompt": "Enter the NATR length: ", "prompt_on_new": True})
|
||||
|
||||
@validator("candles_connector", pre=True, always=True)
|
||||
def set_candles_connector(cls, v, values):
|
||||
@field_validator("candles_connector", mode="before")
|
||||
@classmethod
|
||||
def set_candles_connector(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return values.get("connector_name")
|
||||
return validation_info.data.get("connector_name")
|
||||
return v
|
||||
|
||||
@validator("candles_trading_pair", pre=True, always=True)
|
||||
def set_candles_trading_pair(cls, v, values):
|
||||
@field_validator("candles_trading_pair", mode="before")
|
||||
@classmethod
|
||||
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
return values.get("trading_pair")
|
||||
return validation_info.data.get("trading_pair")
|
||||
return v
|
||||
|
||||
|
||||
@@ -87,7 +78,7 @@ class PMMDynamicController(MarketMakingControllerBase):
|
||||
"""
|
||||
def __init__(self, config: PMMDynamicControllerConfig, *args, **kwargs):
|
||||
self.config = config
|
||||
self.max_records = max(config.macd_slow, config.macd_fast, config.macd_signal, config.natr_length) + 200
|
||||
self.max_records = max(config.macd_slow, config.macd_fast, config.macd_signal, config.natr_length) + 100
|
||||
if len(self.config.candles_config) == 0:
|
||||
self.config.candles_config = [CandlesConfig(
|
||||
connector=config.candles_connector,
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import List
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from hummingbot.client.config.config_data_types import ClientFieldData
|
||||
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
||||
from hummingbot.strategy_v2.controllers.market_making_controller_base import (
|
||||
MarketMakingControllerBase,
|
||||
@@ -13,9 +12,9 @@ from hummingbot.strategy_v2.executors.position_executor.data_types import Positi
|
||||
|
||||
|
||||
class PMMSimpleConfig(MarketMakingControllerConfigBase):
|
||||
controller_name = "pmm_simple"
|
||||
controller_name: str = "pmm_simple"
|
||||
# As this controller is a simple version of the PMM, we are not using the candles feed
|
||||
candles_config: List[CandlesConfig] = Field(default=[], client_data=ClientFieldData(prompt_on_new=False))
|
||||
candles_config: List[CandlesConfig] = Field(default=[])
|
||||
|
||||
|
||||
class PMMSimpleController(MarketMakingControllerBase):
|
||||
|
||||
Reference in New Issue
Block a user