mirror of
https://github.com/d0zingcat/deploy.git
synced 2026-05-13 15:09:33 +00:00
Merge pull request #85 from hummingbot/feat/update_deploy
Feat/update deploy
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):
|
||||
|
||||
@@ -24,8 +24,6 @@ kill_switch_mode: {}
|
||||
# What to auto-fill in the prompt after each import command (start/config)
|
||||
autofill_import: disabled
|
||||
|
||||
telegram_mode: {}
|
||||
|
||||
# MQTT Bridge configuration.
|
||||
mqtt_bridge:
|
||||
mqtt_host: localhost
|
||||
@@ -59,8 +57,6 @@ previous_strategy: some-strategy.yml
|
||||
db_mode:
|
||||
db_engine: sqlite
|
||||
|
||||
pmm_script_mode: {}
|
||||
|
||||
# Balance Limit Configurations
|
||||
# e.g. Setting USDT and BTC limits on Binance.
|
||||
# balance_asset_limit:
|
||||
|
||||
@@ -3,8 +3,6 @@ import time
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Optional, Set
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from hummingbot.client.hummingbot_application import HummingbotApplication
|
||||
from hummingbot.connector.connector_base import ConnectorBase
|
||||
from hummingbot.core.clock import Clock
|
||||
@@ -17,13 +15,12 @@ from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction,
|
||||
|
||||
|
||||
class GenericV2StrategyWithCashOutConfig(StrategyV2ConfigBase):
|
||||
script_file_name: str = Field(default_factory=lambda: os.path.basename(__file__))
|
||||
script_file_name: str = os.path.basename(__file__)
|
||||
candles_config: List[CandlesConfig] = []
|
||||
markets: Dict[str, Set[str]] = {}
|
||||
time_to_cash_out: Optional[int] = None
|
||||
max_global_drawdown: Optional[float] = None
|
||||
max_controller_drawdown: Optional[float] = None
|
||||
performance_report_interval: int = 1
|
||||
rebalance_interval: Optional[int] = None
|
||||
extra_inventory: Optional[float] = 0.02
|
||||
min_amount_to_rebalance_usd: Decimal = Decimal("8")
|
||||
@@ -41,6 +38,8 @@ class GenericV2StrategyWithCashOut(StrategyV2Base):
|
||||
specific controller and wait until the active executors finalize their execution. The rest of the executors will
|
||||
wait until the main strategy stops them.
|
||||
"""
|
||||
performance_report_interval: int = 1
|
||||
|
||||
def __init__(self, connectors: Dict[str, ConnectorBase], config: GenericV2StrategyWithCashOutConfig):
|
||||
super().__init__(connectors, config)
|
||||
self.config = config
|
||||
@@ -50,7 +49,6 @@ class GenericV2StrategyWithCashOut(StrategyV2Base):
|
||||
self.max_global_pnl = Decimal("0")
|
||||
self.drawdown_exited_controllers = []
|
||||
self.closed_executors_buffer: int = 30
|
||||
self.performance_report_interval: int = self.config.performance_report_interval
|
||||
self.rebalance_interval: int = self.config.rebalance_interval
|
||||
self._last_performance_report_timestamp = 0
|
||||
self._last_rebalance_check_timestamp = 0
|
||||
@@ -91,7 +89,7 @@ class GenericV2StrategyWithCashOut(StrategyV2Base):
|
||||
if self.rebalance_interval and self._last_rebalance_check_timestamp + self.rebalance_interval <= self.current_timestamp:
|
||||
balance_required = {}
|
||||
for controller_id, controller in self.controllers.items():
|
||||
connector_name = controller.config.dict().get("connector_name")
|
||||
connector_name = controller.config.model_dump().get("connector_name")
|
||||
if connector_name and "perpetual" in connector_name:
|
||||
continue
|
||||
if connector_name not in balance_required:
|
||||
@@ -124,21 +122,21 @@ class GenericV2StrategyWithCashOut(StrategyV2Base):
|
||||
order_type = OrderType.MARKET
|
||||
if base_balance_diff > 0:
|
||||
if trading_rules_condition:
|
||||
self.logger().debug(f"Rebalance: Selling {amount_with_safe_margin} {token} to {self.config.asset_to_rebalance}. Balance: {balance} | Executors unmatched balance {unmatched_amount / mid_price}")
|
||||
self.logger().info(f"Rebalance: Selling {amount_with_safe_margin} {token} to {self.config.asset_to_rebalance}. Balance: {balance} | Executors unmatched balance {unmatched_amount / mid_price}")
|
||||
connector.sell(
|
||||
trading_pair=trading_pair,
|
||||
amount=abs_balance_diff,
|
||||
order_type=order_type,
|
||||
price=mid_price)
|
||||
else:
|
||||
self.logger().debug("Skipping rebalance due a low amount to sell that may cause future imbalance")
|
||||
self.logger().info("Skipping rebalance due a low amount to sell that may cause future imbalance")
|
||||
else:
|
||||
if not trading_rules_condition:
|
||||
amount = max([self.config.min_amount_to_rebalance_usd / mid_price, trading_rule.min_order_size, trading_rule.min_notional_size / mid_price])
|
||||
self.logger().debug(f"Rebalance: Buying for a higher value to avoid future imbalance {amount} {token} to {self.config.asset_to_rebalance}. Balance: {balance} | Executors unmatched balance {unmatched_amount}")
|
||||
self.logger().info(f"Rebalance: Buying for a higher value to avoid future imbalance {amount} {token} to {self.config.asset_to_rebalance}. Balance: {balance} | Executors unmatched balance {unmatched_amount}")
|
||||
else:
|
||||
amount = abs_balance_diff
|
||||
self.logger().debug(f"Rebalance: Buying {amount} {token} to {self.config.asset_to_rebalance}. Balance: {balance} | Executors unmatched balance {unmatched_amount}")
|
||||
self.logger().info(f"Rebalance: Buying {amount} {token} to {self.config.asset_to_rebalance}. Balance: {balance} | Executors unmatched balance {unmatched_amount}")
|
||||
connector.buy(
|
||||
trading_pair=trading_pair,
|
||||
amount=amount,
|
||||
@@ -154,6 +152,8 @@ class GenericV2StrategyWithCashOut(StrategyV2Base):
|
||||
|
||||
def check_max_controller_drawdown(self):
|
||||
for controller_id, controller in self.controllers.items():
|
||||
if controller.status != RunnableStatus.RUNNING:
|
||||
continue
|
||||
controller_pnl = self.performance_reports[controller_id]["global_pnl_quote"]
|
||||
last_max_pnl = self.max_pnl_by_controller[controller_id]
|
||||
if controller_pnl > last_max_pnl:
|
||||
|
||||
@@ -19,7 +19,7 @@ def user_inputs():
|
||||
config = {
|
||||
"controller_name": "dman_maker_v2",
|
||||
"controller_type": "market_making",
|
||||
"manual_kill_switch": None,
|
||||
"manual_kill_switch": False,
|
||||
"candles_config": [],
|
||||
"connector_name": connector_name,
|
||||
"trading_pair": trading_pair,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Grid Strike Configuration Tool
|
||||
# Grid Strike Grid Component Configuration Tool
|
||||
|
||||
Welcome to the Grid Strike Configuration Tool! This tool allows you to create, modify, visualize, and save configurations for the Grid Strike trading strategy. Here's how you can make the most out of it.
|
||||
Welcome to the Grid Strike Grid Component Configuration Tool! This tool allows you to create, modify, visualize, and save configurations for the Grid Strike Grid Component trading strategy, which is a simplified version of the Grid Strike strategy focused on a single grid.
|
||||
|
||||
## Features
|
||||
|
||||
- **Start from Default Configurations**: Begin with a default configuration or use the values from an existing configuration.
|
||||
- **Dynamic Price Range Defaults**: Automatically sets grid ranges based on current market conditions.
|
||||
- **Visual Grid Configuration**: See your grid ranges directly on the price chart.
|
||||
- **Multiple Grid Ranges**: Configure up to 5 different grid ranges with different sides (BUY/SELL).
|
||||
- **Simple Grid Configuration**: Configure a single grid with start, end, and limit prices.
|
||||
- **Dynamic Price Range Defaults**: Automatically sets price ranges based on current market conditions.
|
||||
- **Visual Grid Configuration**: See your grid settings directly on the price chart.
|
||||
- **Triple Barrier Risk Management**: Configure take profit, stop loss, and time limit parameters.
|
||||
- **Save and Deploy**: Once satisfied, save the configuration to deploy it later.
|
||||
|
||||
## How to Use
|
||||
@@ -15,9 +15,10 @@ Welcome to the Grid Strike Configuration Tool! This tool allows you to create, m
|
||||
### 1. Basic Configuration
|
||||
|
||||
Start by configuring the basic parameters:
|
||||
- **ID Prefix**: Prefix for the strategy ID (default: "grid_").
|
||||
- **Trading Pair**: Choose the cryptocurrency trading pair (e.g., "BTC-FDUSD").
|
||||
- **Connector Name**: Select the trading platform or exchange (e.g., "binance").
|
||||
- **Trading Pair**: Choose the cryptocurrency trading pair (e.g., "BTC-USDT").
|
||||
- **Leverage**: Set the leverage ratio (use 1 for spot trading).
|
||||
- **Leverage**: Set the leverage ratio for margin/futures trading.
|
||||
|
||||
### 2. Chart Configuration
|
||||
|
||||
@@ -26,69 +27,111 @@ Configure how you want to visualize the market data:
|
||||
- **Interval**: Choose the timeframe for the candlesticks (1m to 1d).
|
||||
- **Days to Display**: Select how many days of historical data to show.
|
||||
|
||||
### 3. Grid Ranges
|
||||
### 3. Grid Configuration
|
||||
|
||||
Configure up to 5 grid ranges with different parameters:
|
||||
- **Number of Grid Ranges**: Select how many ranges you want to configure (1-5).
|
||||
- **Side**: Choose BUY or SELL for each range.
|
||||
- **Start Price**: The price where the range begins.
|
||||
- **End Price**: The price where the range ends.
|
||||
- **Amount %**: Percentage of total amount allocated to this range.
|
||||
|
||||
### 4. Advanced Configuration
|
||||
|
||||
Fine-tune your strategy with advanced parameters:
|
||||
- **Position Mode**: Choose between HEDGE or ONE-WAY.
|
||||
- **Time Limit**: Maximum duration for orders (in hours).
|
||||
- **Activation Bounds**: Price deviation to trigger updates.
|
||||
Configure your grid parameters:
|
||||
- **Side**: Choose BUY or SELL for the grid.
|
||||
- **Start Price**: The price where the grid begins.
|
||||
- **End Price**: The price where the grid ends.
|
||||
- **Limit Price**: A price limit that will stop the strategy.
|
||||
- **Min Spread Between Orders**: Minimum price difference between orders.
|
||||
- **Min Order Amount**: Minimum size for individual orders.
|
||||
- **Max Open Orders**: Maximum number of active orders per range.
|
||||
- **Grid Range Update Interval**: How often to update grid ranges (in seconds).
|
||||
- **Min Order Amount (Quote)**: Minimum size for individual orders.
|
||||
- **Maximum Open Orders**: Maximum number of active orders in the grid.
|
||||
|
||||
## Understanding Grid Strike Strategy
|
||||
### 4. Order Configuration
|
||||
|
||||
The Grid Strike strategy creates a grid of orders within specified price ranges. Here's how it works:
|
||||
Fine-tune your order placement:
|
||||
- **Max Orders Per Batch**: Maximum number of orders to place at once.
|
||||
- **Order Frequency**: Time between order placements in seconds.
|
||||
- **Activation Bounds**: Price deviation to trigger updates.
|
||||
|
||||
### Grid Range Mechanics
|
||||
- Each grid range defines a price zone where the strategy will place orders
|
||||
- BUY ranges place buy orders from higher to lower prices
|
||||
- SELL ranges place sell orders from lower to higher prices
|
||||
- Multiple ranges can work simultaneously with different configurations
|
||||
### 5. Triple Barrier Configuration
|
||||
|
||||
Set up risk management parameters:
|
||||
- **Open Order Type**: The type of order to open positions (e.g., MARKET, LIMIT).
|
||||
- **Take Profit**: Price movement percentage for take profit.
|
||||
- **Stop Loss**: Price movement percentage for stop loss.
|
||||
- **Time Limit**: Time limit for orders in hours.
|
||||
- **Order Type Settings**: Configure order types for each barrier.
|
||||
|
||||
### 6. Advanced Configuration
|
||||
|
||||
Additional settings:
|
||||
- **Position Mode**: Choose between HEDGE or ONE-WAY.
|
||||
- **Strategy Time Limit**: Maximum duration for the entire strategy in hours.
|
||||
- **Manual Kill Switch**: Option to enable manual kill switch.
|
||||
|
||||
## Understanding Grid Strike Grid Component
|
||||
|
||||
The Grid Strike Grid Component strategy creates a single grid of orders within a specified price range. Here's how it works:
|
||||
|
||||
### Grid Mechanics
|
||||
- The strategy places orders uniformly between the start and end prices
|
||||
- BUY grids place buy orders from start (higher) to end (lower) prices
|
||||
- SELL grids place sell orders from start (lower) to end (higher) prices
|
||||
- The limit price serves as an additional safety boundary
|
||||
|
||||
### Order Placement
|
||||
- Orders are placed within each range based on the min spread between orders
|
||||
- The amount per order is calculated based on the range's allocation percentage
|
||||
- Orders are placed within the grid based on the min spread between orders
|
||||
- The amount per order is calculated based on the total amount specified
|
||||
- Orders are automatically adjusted when price moves beyond activation bounds
|
||||
|
||||
### Visual Indicators
|
||||
- Green lines represent BUY ranges
|
||||
- Red lines represent SELL ranges
|
||||
- Different dash patterns distinguish multiple ranges of the same side
|
||||
- Horizontal lines show the start and end prices of each range
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Range Placement**
|
||||
- Place BUY ranges below current price
|
||||
- Place SELL ranges above current price
|
||||
- Avoid overlapping ranges of the same side
|
||||
|
||||
2. **Amount Allocation**
|
||||
- Distribute amounts across ranges based on your risk preference
|
||||
- Ensure total allocation across all ranges doesn't exceed 100%
|
||||
- Consider larger allocations for ranges closer to current price
|
||||
|
||||
3. **Spread Configuration**
|
||||
- Set min spread based on the asset's volatility
|
||||
- Larger spreads mean fewer, more profitable orders
|
||||
- Smaller spreads mean more frequent, less profitable orders
|
||||
|
||||
4. **Risk Management**
|
||||
- Use appropriate leverage (1 for spot)
|
||||
- Set reasonable time limits for orders
|
||||
- Monitor and adjust activation bounds based on market conditions
|
||||
- Green lines represent the start and end prices
|
||||
- Red line represents the limit price
|
||||
- Candlestick chart shows the market price action
|
||||
|
||||
## Example Configuration
|
||||
|
||||
Here's a sample configuration for a BTC-USDT grid:
|
||||
Here's a sample configuration for a BTC-FDUSD grid:
|
||||
|
||||
```yaml
|
||||
id: grid_btcfdusd
|
||||
controller_name: grid_strike
|
||||
controller_type: generic
|
||||
total_amount_quote: 200
|
||||
manual_kill_switch: null
|
||||
candles_config: []
|
||||
leverage: 75
|
||||
position_mode: HEDGE
|
||||
connector_name: binance
|
||||
trading_pair: BTC-FDUSD
|
||||
side: 1
|
||||
start_price: 84000
|
||||
end_price: 84300
|
||||
limit_price: 83700
|
||||
min_spread_between_orders: 0.0001
|
||||
min_order_amount_quote: 5
|
||||
max_open_orders: 40
|
||||
max_orders_per_batch: 1
|
||||
order_frequency: 2
|
||||
activation_bounds: 0.01
|
||||
triple_barrier_config:
|
||||
open_order_type: 3
|
||||
stop_loss: null
|
||||
stop_loss_order_type: 1
|
||||
take_profit: 0.0001
|
||||
take_profit_order_type: 3
|
||||
time_limit: 21600
|
||||
time_limit_order_type: 1
|
||||
time_limit: 172800
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Grid Placement**
|
||||
- For BUY grids, set start price above end price
|
||||
- For SELL grids, set end price above start price
|
||||
- Set limit price as a safety boundary where you want to stop the strategy
|
||||
|
||||
2. **Amount Management**
|
||||
- Set total amount based on your risk tolerance
|
||||
- Configure min order amount to ensure meaningful trade sizes
|
||||
|
||||
3. **Grid Density**
|
||||
- Adjust min spread between orders based on the asset's volatility
|
||||
- Set max open orders to control grid density
|
||||
|
||||
4. **Risk Management**
|
||||
- Use triple barrier parameters to manage risk for individual positions
|
||||
- Set appropriate time limits for both positions and the overall strategy
|
||||
@@ -13,45 +13,46 @@ from frontend.visualization.candles import get_candlestick_trace
|
||||
from frontend.visualization.utils import add_traces_to_fig
|
||||
|
||||
|
||||
def get_grid_range_traces(grid_ranges):
|
||||
"""Generate horizontal line traces for grid ranges with different colors."""
|
||||
dash_styles = ['solid', 'dash', 'dot', 'dashdot', 'longdash'] # 5 different styles
|
||||
def get_grid_trace(start_price, end_price, limit_price):
|
||||
"""Generate horizontal line traces for the grid with different colors."""
|
||||
traces = []
|
||||
buy_count = 0
|
||||
sell_count = 0
|
||||
for i, grid_range in enumerate(grid_ranges):
|
||||
# Set color based on trade type
|
||||
if grid_range["side"] == TradeType.BUY:
|
||||
color = 'rgba(0, 255, 0, 1)' # Bright green for buy
|
||||
dash_style = dash_styles[buy_count % len(dash_styles)]
|
||||
buy_count += 1
|
||||
else:
|
||||
color = 'rgba(255, 0, 0, 1)' # Bright red for sell
|
||||
dash_style = dash_styles[sell_count % len(dash_styles)]
|
||||
sell_count += 1
|
||||
# Start price line
|
||||
|
||||
# Start price line
|
||||
traces.append(go.Scatter(
|
||||
x=[], # Will be set to full range when plotting
|
||||
y=[float(start_price), float(start_price)],
|
||||
mode='lines',
|
||||
line=dict(color='rgba(0, 255, 0, 1)', width=1.5, dash='solid'),
|
||||
name=f'Start Price: {float(start_price):,.2f}',
|
||||
hoverinfo='name'
|
||||
))
|
||||
|
||||
# End price line
|
||||
traces.append(go.Scatter(
|
||||
x=[], # Will be set to full range when plotting
|
||||
y=[float(end_price), float(end_price)],
|
||||
mode='lines',
|
||||
line=dict(color='rgba(0, 255, 0, 1)', width=1.5, dash='dot'),
|
||||
name=f'End Price: {float(end_price):,.2f}',
|
||||
hoverinfo='name'
|
||||
))
|
||||
|
||||
# Limit price line (if provided)
|
||||
if limit_price:
|
||||
traces.append(go.Scatter(
|
||||
x=[], # Will be set to full range when plotting
|
||||
y=[float(grid_range["start_price"]), float(grid_range["start_price"])],
|
||||
y=[float(limit_price), float(limit_price)],
|
||||
mode='lines',
|
||||
line=dict(color=color, width=1.5, dash=dash_style),
|
||||
name=f'Range {i} Start: {float(grid_range["start_price"]):,.2f} ({grid_range["side"].name})',
|
||||
hoverinfo='name'
|
||||
))
|
||||
# End price line
|
||||
traces.append(go.Scatter(
|
||||
x=[], # Will be set to full range when plotting
|
||||
y=[float(grid_range["end_price"]), float(grid_range["end_price"])],
|
||||
mode='lines',
|
||||
line=dict(color=color, width=1.5, dash=dash_style),
|
||||
name=f'Range {i} End: {float(grid_range["end_price"]):,.2f} ({grid_range["side"].name})',
|
||||
line=dict(color='rgba(255, 0, 0, 1)', width=1.5, dash='dashdot'),
|
||||
name=f'Limit Price: {float(limit_price):,.2f}',
|
||||
hoverinfo='name'
|
||||
))
|
||||
|
||||
return traces
|
||||
|
||||
|
||||
# Initialize the Streamlit page
|
||||
initialize_st_page(title="Grid Strike", icon="📊", initial_sidebar_state="expanded")
|
||||
initialize_st_page(title="Grid Strike Grid Component", icon="📊", initial_sidebar_state="expanded")
|
||||
backend_api_client = get_backend_api_client()
|
||||
|
||||
get_default_config_loader("grid_strike")
|
||||
@@ -70,28 +71,34 @@ candles = get_candles(
|
||||
# Create a subplot with just 1 row for price action
|
||||
fig = make_subplots(
|
||||
rows=1, cols=1,
|
||||
subplot_titles=(f'Grid Strike - {inputs["trading_pair"]} ({inputs["interval"]})',),
|
||||
subplot_titles=(f'Grid Strike Grid Component - {inputs["trading_pair"]} ({inputs["interval"]})',),
|
||||
)
|
||||
|
||||
# Add basic candlestick chart
|
||||
candlestick_trace = get_candlestick_trace(candles)
|
||||
add_traces_to_fig(fig, [candlestick_trace], row=1, col=1)
|
||||
|
||||
# Add grid range visualization
|
||||
grid_traces = get_grid_range_traces(inputs["grid_ranges"])
|
||||
# Add grid visualization
|
||||
grid_traces = get_grid_trace(
|
||||
inputs["start_price"],
|
||||
inputs["end_price"],
|
||||
inputs["limit_price"]
|
||||
)
|
||||
|
||||
for trace in grid_traces:
|
||||
# Set the x-axis range for all grid traces
|
||||
trace.x = [candles.index[0], candles.index[-1]]
|
||||
fig.add_trace(trace, row=1, col=1)
|
||||
|
||||
# Update y-axis to make sure all grid ranges and candles are visible
|
||||
# Update y-axis to make sure all grid points and candles are visible
|
||||
all_prices = []
|
||||
# Add candle prices
|
||||
all_prices.extend(candles['high'].tolist())
|
||||
all_prices.extend(candles['low'].tolist())
|
||||
# Add grid range prices
|
||||
for grid_range in inputs["grid_ranges"]:
|
||||
all_prices.extend([float(grid_range["start_price"]), float(grid_range["end_price"])])
|
||||
# Add grid prices
|
||||
all_prices.extend([float(inputs["start_price"]), float(inputs["end_price"])])
|
||||
if inputs["limit_price"]:
|
||||
all_prices.append(float(inputs["limit_price"]))
|
||||
|
||||
y_min, y_max = min(all_prices), max(all_prices)
|
||||
padding = (y_max - y_min) * 0.1 # Add 10% padding
|
||||
@@ -128,21 +135,27 @@ st.plotly_chart(fig, use_container_width=True)
|
||||
def prepare_config_for_save(config):
|
||||
"""Prepare config for JSON serialization."""
|
||||
prepared_config = config.copy()
|
||||
grid_ranges = []
|
||||
for grid_range in prepared_config["grid_ranges"]:
|
||||
grid_range = grid_range.copy()
|
||||
grid_range["side"] = grid_range["side"].value
|
||||
grid_range["open_order_type"] = grid_range["open_order_type"].value
|
||||
grid_range["take_profit_order_type"] = grid_range["take_profit_order_type"].value
|
||||
grid_ranges.append(grid_range)
|
||||
prepared_config["grid_ranges"] = grid_ranges
|
||||
|
||||
# Convert position mode enum to value
|
||||
prepared_config["position_mode"] = prepared_config["position_mode"].value
|
||||
|
||||
# Convert side to value
|
||||
prepared_config["side"] = prepared_config["side"].value
|
||||
|
||||
# Convert triple barrier order types to values
|
||||
if "triple_barrier_config" in prepared_config and prepared_config["triple_barrier_config"]:
|
||||
for key in ["open_order_type", "stop_loss_order_type", "take_profit_order_type", "time_limit_order_type"]:
|
||||
if key in prepared_config["triple_barrier_config"] and prepared_config["triple_barrier_config"][key] is not None:
|
||||
prepared_config["triple_barrier_config"][key] = prepared_config["triple_barrier_config"][key].value
|
||||
|
||||
# Remove chart-specific fields
|
||||
del prepared_config["candles_connector"]
|
||||
del prepared_config["interval"]
|
||||
del prepared_config["days_to_visualize"]
|
||||
|
||||
return prepared_config
|
||||
|
||||
|
||||
# Replace the render_save_config line with:
|
||||
# Render save config component
|
||||
render_save_config(st.session_state["default_config"]["id"],
|
||||
prepare_config_for_save(st.session_state["default_config"]))
|
||||
prepare_config_for_save(st.session_state["default_config"]))
|
||||
@@ -1,6 +1,3 @@
|
||||
from decimal import Decimal
|
||||
|
||||
import plotly.graph_objs as go
|
||||
import streamlit as st
|
||||
from hummingbot.core.data_type.common import OrderType, PositionMode, TradeType
|
||||
|
||||
@@ -16,65 +13,177 @@ def get_price_range_defaults(connector_name: str, trading_pair: str, interval: s
|
||||
interval=interval,
|
||||
days=days
|
||||
)
|
||||
current_price = float(candles['close'].iloc[-1])
|
||||
min_price = float(candles['low'].quantile(0.05))
|
||||
max_price = float(candles['high'].quantile(0.95))
|
||||
return round(min_price, 2), round(max_price, 2)
|
||||
return round(min_price, 2), round(current_price, 2), round(max_price, 2)
|
||||
except Exception as e:
|
||||
st.warning(f"Could not fetch price data: {str(e)}. Using default values.")
|
||||
return 40000.0, 60000.0 # Fallback defaults
|
||||
|
||||
|
||||
def get_grid_range_traces(grid_ranges):
|
||||
"""Generate horizontal line traces for grid ranges with different colors."""
|
||||
dash_styles = ['solid', 'dash', 'dot', 'dashdot', 'longdash'] # 5 different styles
|
||||
traces = []
|
||||
buy_count = 0
|
||||
sell_count = 0
|
||||
for i, grid_range in enumerate(grid_ranges):
|
||||
# Set color based on trade type
|
||||
if grid_range["side"] == TradeType.BUY:
|
||||
color = 'rgba(0, 255, 0, 1)' # Bright green for buy
|
||||
dash_style = dash_styles[buy_count % len(dash_styles)]
|
||||
buy_count += 1
|
||||
else:
|
||||
color = 'rgba(255, 0, 0, 1)' # Bright red for sell
|
||||
dash_style = dash_styles[sell_count % len(dash_styles)]
|
||||
sell_count += 1
|
||||
# Start price line
|
||||
traces.append(go.Scatter(
|
||||
x=[], # Will be set to full range when plotting
|
||||
y=[float(grid_range["start_price"]), float(grid_range["start_price"])],
|
||||
mode='lines',
|
||||
line=dict(color=color, width=1.5, dash=dash_style),
|
||||
name=f'Range {i} Start: {float(grid_range["start_price"]):,.2f} ({grid_range["side"].name})',
|
||||
hoverinfo='name'
|
||||
))
|
||||
# End price line
|
||||
traces.append(go.Scatter(
|
||||
x=[], # Will be set to full range when plotting
|
||||
y=[float(grid_range["end_price"]), float(grid_range["end_price"])],
|
||||
mode='lines',
|
||||
line=dict(color=color, width=1.5, dash=dash_style),
|
||||
name=f'Range {i} End: {float(grid_range["end_price"]):,.2f} ({grid_range["side"].name})',
|
||||
hoverinfo='name'
|
||||
))
|
||||
return traces
|
||||
return 40000.0, 42000.0, 44000.0 # Fallback defaults
|
||||
|
||||
|
||||
def user_inputs():
|
||||
# Split the page into two columns for the expanders
|
||||
left_col, right_col = st.columns(2)
|
||||
with left_col:
|
||||
# Basic trading parameters
|
||||
with st.expander("Basic Configuration", expanded=True):
|
||||
# Combined Basic, Amount, and Grid Configuration
|
||||
with st.expander("Grid Configuration", expanded=True):
|
||||
# Basic parameters
|
||||
c1, c2 = st.columns(2)
|
||||
with c1:
|
||||
connector_name = st.text_input("Connector Name", value="binance_perpetual")
|
||||
# Side selection
|
||||
side = st.selectbox(
|
||||
"Side",
|
||||
options=["BUY", "SELL"],
|
||||
index=0,
|
||||
help="Trading direction for the grid"
|
||||
)
|
||||
leverage = st.number_input("Leverage", min_value=1, value=20)
|
||||
with c2:
|
||||
trading_pair = st.text_input("Trading Pair", value="WLD-USDT")
|
||||
# Amount parameter
|
||||
total_amount_quote = st.number_input(
|
||||
"Total Amount (Quote)",
|
||||
min_value=0.0,
|
||||
value=200.0,
|
||||
help="Total amount in quote currency to use for trading"
|
||||
)
|
||||
position_mode = st.selectbox(
|
||||
"Position Mode",
|
||||
options=["HEDGE", "ONEWAY"],
|
||||
index=0
|
||||
)
|
||||
# Grid price parameters
|
||||
with c1:
|
||||
# Get default price ranges based on current market data
|
||||
min_price, current_price, max_price = get_price_range_defaults(
|
||||
connector_name,
|
||||
trading_pair,
|
||||
"1h", # Default interval for price range calculation
|
||||
30 # Default days for price range calculation
|
||||
)
|
||||
if side == "BUY":
|
||||
start_price = min(min_price, current_price)
|
||||
end_price = max(current_price, max_price)
|
||||
limit_price = start_price * 0.95
|
||||
else:
|
||||
start_price = max(max_price, current_price)
|
||||
end_price = min(current_price, min_price)
|
||||
limit_price = start_price * 1.05
|
||||
# Price configuration with meaningful defaults
|
||||
start_price = st.number_input(
|
||||
"Start Price",
|
||||
value=start_price,
|
||||
format="%.2f",
|
||||
help="Grid start price"
|
||||
)
|
||||
|
||||
end_price = st.number_input(
|
||||
"End Price",
|
||||
value=end_price,
|
||||
format="%.2f",
|
||||
help="Grid end price"
|
||||
)
|
||||
|
||||
limit_price = st.number_input(
|
||||
"Limit Price",
|
||||
value=limit_price,
|
||||
format="%.2f",
|
||||
help="Price limit to stop the strategy"
|
||||
)
|
||||
|
||||
with c2:
|
||||
# Grid spacing configuration
|
||||
min_spread = st.number_input(
|
||||
"Min Spread Between Orders",
|
||||
min_value=0.0000,
|
||||
value=0.0001,
|
||||
format="%.4f",
|
||||
help="Minimum price difference between orders",
|
||||
step=0.0001
|
||||
)
|
||||
|
||||
min_order_amount = st.number_input(
|
||||
"Min Order Amount (Quote)",
|
||||
min_value=1.0,
|
||||
value=6.0,
|
||||
help="Minimum amount for each order in quote currency"
|
||||
)
|
||||
|
||||
max_open_orders = st.number_input(
|
||||
"Maximum Open Orders",
|
||||
min_value=1,
|
||||
value=3,
|
||||
help="Maximum number of active orders in the grid"
|
||||
)
|
||||
|
||||
with right_col:
|
||||
# Order configuration
|
||||
with st.expander("Order Configuration", expanded=True):
|
||||
c1, c2, c3 = st.columns(3)
|
||||
with c1:
|
||||
connector_name = st.text_input("Connector Name", value="binance")
|
||||
max_orders_per_batch = st.number_input(
|
||||
"Max Orders Per Batch",
|
||||
min_value=1,
|
||||
value=1,
|
||||
help="Maximum number of orders to place at once"
|
||||
)
|
||||
with c2:
|
||||
trading_pair = st.text_input("Trading Pair", value="BTC-USDT")
|
||||
order_frequency = st.number_input(
|
||||
"Order Frequency (s)",
|
||||
min_value=1,
|
||||
value=2,
|
||||
help="Time between order placements in seconds"
|
||||
)
|
||||
with c3:
|
||||
leverage = st.number_input("Leverage", min_value=1, value=20)
|
||||
# Visualization Configuration
|
||||
activation_bounds = st.number_input(
|
||||
"Activation Bounds",
|
||||
min_value=0.0,
|
||||
value=0.01,
|
||||
format="%.4f",
|
||||
help="Price deviation to trigger updates"
|
||||
)
|
||||
|
||||
# Triple barrier configuration
|
||||
with st.expander("Triple Barrier Configuration", expanded=True):
|
||||
c1, c2 = st.columns(2)
|
||||
with c1:
|
||||
# Order types
|
||||
open_order_type_options = ["LIMIT", "LIMIT_MAKER", "MARKET"]
|
||||
open_order_type = st.selectbox(
|
||||
"Open Order Type",
|
||||
options=open_order_type_options,
|
||||
index=1, # Default to MARKET
|
||||
key="open_order_type"
|
||||
)
|
||||
|
||||
take_profit_order_type_options = ["LIMIT", "LIMIT_MAKER", "MARKET"]
|
||||
take_profit_order_type = st.selectbox(
|
||||
"Take Profit Order Type",
|
||||
options=take_profit_order_type_options,
|
||||
index=1, # Default to MARKET
|
||||
key="tp_order_type"
|
||||
)
|
||||
|
||||
with c2:
|
||||
# Barrier values
|
||||
take_profit = st.number_input(
|
||||
"Take Profit",
|
||||
min_value=0.0,
|
||||
value=0.0001,
|
||||
format="%.4f",
|
||||
help="Price movement percentage for take profit"
|
||||
)
|
||||
|
||||
stop_loss = st.number_input(
|
||||
"Stop Loss",
|
||||
min_value=0.0,
|
||||
value=0.1,
|
||||
format="%.4f",
|
||||
help="Price movement percentage for stop loss (0 for none)"
|
||||
)
|
||||
# Chart configuration
|
||||
with st.expander("Chart Configuration", expanded=True):
|
||||
c1, c2, c3 = st.columns(3)
|
||||
with c1:
|
||||
@@ -87,7 +196,7 @@ def user_inputs():
|
||||
interval = st.selectbox(
|
||||
"Interval",
|
||||
options=["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "1d"],
|
||||
index=10, # Default to 30m
|
||||
index=4, # Default to 1h
|
||||
help="Candlestick interval"
|
||||
)
|
||||
with c3:
|
||||
@@ -95,122 +204,23 @@ def user_inputs():
|
||||
"Days to Display",
|
||||
min_value=1,
|
||||
max_value=365,
|
||||
value=180,
|
||||
value=30,
|
||||
help="Number of days of historical data to display"
|
||||
)
|
||||
|
||||
# Get default price ranges based on current market data
|
||||
default_min, default_max = get_price_range_defaults(
|
||||
candles_connector,
|
||||
trading_pair,
|
||||
interval,
|
||||
days_to_visualize
|
||||
)
|
||||
# Grid Ranges Configuration
|
||||
with st.expander("Grid Ranges", expanded=True):
|
||||
grid_ranges = []
|
||||
num_ranges = st.number_input("Number of Grid Ranges", min_value=1, max_value=5, value=1)
|
||||
for i in range(num_ranges):
|
||||
st.markdown(f"#### Range {i}")
|
||||
# Price configuration
|
||||
c1, c2, c3, c4 = st.columns(4)
|
||||
with c1:
|
||||
# Set default start price based on side
|
||||
side = st.selectbox(
|
||||
f"Side {i}",
|
||||
options=[TradeType.BUY.name, TradeType.SELL.name],
|
||||
key=f"side_{i}"
|
||||
)
|
||||
with c2:
|
||||
# Set default start price based on side
|
||||
start_price = st.number_input(
|
||||
f"Start Price {i}",
|
||||
value=default_min,
|
||||
key=f"start_price_{i}"
|
||||
)
|
||||
with c3:
|
||||
# Set default end price based on side
|
||||
end_price = st.number_input(
|
||||
f"End Price {i}",
|
||||
value=default_max,
|
||||
key=f"end_price_{i}"
|
||||
)
|
||||
with c4:
|
||||
total_amount_pct = st.number_input(
|
||||
f"Amount % {i}",
|
||||
min_value=0.0,
|
||||
max_value=100.0,
|
||||
value=50.0,
|
||||
key=f"amount_pct_{i}"
|
||||
)
|
||||
st.markdown("---")
|
||||
grid_ranges.append({
|
||||
"id": f"R{i}",
|
||||
"start_price": Decimal(str(start_price)),
|
||||
"end_price": Decimal(str(end_price)),
|
||||
"total_amount_pct": Decimal(str(total_amount_pct/100)),
|
||||
"side": TradeType[side],
|
||||
"open_order_type": OrderType.LIMIT_MAKER,
|
||||
"take_profit_order_type": OrderType.LIMIT
|
||||
})
|
||||
with right_col:
|
||||
# Amount configuration
|
||||
with st.expander("Amount Configuration", expanded=True):
|
||||
total_amount_quote = st.number_input(
|
||||
"Total Amount (Quote)",
|
||||
min_value=0.0,
|
||||
value=1000.0,
|
||||
help="Total amount in quote currency to use for trading"
|
||||
)
|
||||
min_order_amount = st.number_input(
|
||||
"Minimum Order Amount",
|
||||
min_value=1.0,
|
||||
value=10.0,
|
||||
help="Minimum amount for each order"
|
||||
)
|
||||
# Advanced Configuration
|
||||
with st.expander("Advanced Configuration", expanded=True):
|
||||
position_mode = st.selectbox(
|
||||
"Position Mode",
|
||||
options=["HEDGE", "ONEWAY"],
|
||||
index=0
|
||||
)
|
||||
c1, c2 = st.columns(2)
|
||||
with c1:
|
||||
time_limit = st.number_input(
|
||||
"Time Limit (hours)",
|
||||
min_value=1,
|
||||
value=48,
|
||||
help="Strategy time limit in hours"
|
||||
)
|
||||
min_spread = st.number_input(
|
||||
"Min Spread Between Orders",
|
||||
min_value=0.0000,
|
||||
value=0.0001,
|
||||
format="%.4f", # Show 3 decimal places
|
||||
help="Minimum price difference between orders",
|
||||
step=0.0001
|
||||
)
|
||||
activation_bounds = st.number_input(
|
||||
"Activation Bounds",
|
||||
min_value=0.0,
|
||||
value=0.01,
|
||||
format="%.4f",
|
||||
help="Price deviation to trigger updates"
|
||||
)
|
||||
with c2:
|
||||
max_open_orders = st.number_input(
|
||||
"Maximum Open Orders",
|
||||
min_value=1,
|
||||
value=5,
|
||||
help="Maximum number of open orders"
|
||||
)
|
||||
grid_update_interval = st.number_input(
|
||||
"Grid Update Interval (s)",
|
||||
min_value=1,
|
||||
value=60,
|
||||
help="How often to update grid ranges"
|
||||
)
|
||||
|
||||
|
||||
# Convert stop_loss to None if it's zero
|
||||
stop_loss_value = stop_loss if stop_loss > 0 else None
|
||||
|
||||
# Prepare triple barrier config
|
||||
triple_barrier_config = {
|
||||
"open_order_type": OrderType[open_order_type],
|
||||
"stop_loss": stop_loss_value,
|
||||
"take_profit": take_profit,
|
||||
"take_profit_order_type": OrderType[take_profit_order_type],
|
||||
"time_limit": None,
|
||||
}
|
||||
|
||||
return {
|
||||
"controller_name": "grid_strike",
|
||||
@@ -221,14 +231,18 @@ def user_inputs():
|
||||
"interval": interval,
|
||||
"days_to_visualize": days_to_visualize,
|
||||
"leverage": leverage,
|
||||
"total_amount_quote": Decimal(str(total_amount_quote)),
|
||||
"grid_ranges": grid_ranges,
|
||||
"side": TradeType[side],
|
||||
"start_price": start_price,
|
||||
"end_price": end_price,
|
||||
"limit_price": limit_price,
|
||||
"position_mode": PositionMode[position_mode],
|
||||
"time_limit": time_limit * 60 * 60,
|
||||
"activation_bounds": Decimal(str(activation_bounds)),
|
||||
"min_spread_between_orders": Decimal(str(min_spread)) if min_spread > 0 else None,
|
||||
"min_order_amount": Decimal(str(min_order_amount)),
|
||||
"total_amount_quote": total_amount_quote,
|
||||
"min_spread_between_orders": min_spread,
|
||||
"min_order_amount_quote": min_order_amount,
|
||||
"max_open_orders": max_open_orders,
|
||||
"grid_range_update_interval": grid_update_interval,
|
||||
"extra_balance_base_usd": Decimal("10")
|
||||
}
|
||||
"max_orders_per_batch": max_orders_per_batch,
|
||||
"order_frequency": order_frequency,
|
||||
"activation_bounds": activation_bounds,
|
||||
"triple_barrier_config": triple_barrier_config,
|
||||
"candles_config": []
|
||||
}
|
||||
@@ -190,7 +190,7 @@ config = {
|
||||
"id": id,
|
||||
"controller_name": "bollinger_v1",
|
||||
"controller_type": "directional_trading",
|
||||
"manual_kill_switch": None,
|
||||
"manual_kill_switch": False,
|
||||
"candles_config": [],
|
||||
"connector_name": connector_name,
|
||||
"trading_pair": trading_pair,
|
||||
|
||||
@@ -28,7 +28,7 @@ def user_inputs():
|
||||
config = {
|
||||
"controller_name": "pmm_dynamic",
|
||||
"controller_type": "market_making",
|
||||
"manual_kill_switch": None,
|
||||
"manual_kill_switch": False,
|
||||
"candles_config": [],
|
||||
"connector_name": connector_name,
|
||||
"trading_pair": trading_pair,
|
||||
|
||||
@@ -13,7 +13,7 @@ def user_inputs():
|
||||
config = {
|
||||
"controller_name": "pmm_simple",
|
||||
"controller_type": "market_making",
|
||||
"manual_kill_switch": None,
|
||||
"manual_kill_switch": False,
|
||||
"candles_config": [],
|
||||
"connector_name": connector_name,
|
||||
"trading_pair": trading_pair,
|
||||
|
||||
Reference in New Issue
Block a user