Merge pull request #85 from hummingbot/feat/update_deploy

Feat/update deploy
This commit is contained in:
Ralph Comia
2025-04-15 08:43:42 +08:00
committed by GitHub
19 changed files with 758 additions and 985 deletions

View File

@@ -1,9 +1,9 @@
from typing import List from typing import List
import pandas_ta as ta # noqa: F401 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.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import ( from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
DirectionalTradingControllerBase, DirectionalTradingControllerBase,
@@ -12,56 +12,42 @@ from hummingbot.strategy_v2.controllers.directional_trading_controller_base impo
class BollingerV1ControllerConfig(DirectionalTradingControllerConfigBase): class BollingerV1ControllerConfig(DirectionalTradingControllerConfigBase):
controller_name = "bollinger_v1" controller_name: str = "bollinger_v1"
candles_config: List[CandlesConfig] = [] candles_config: List[CandlesConfig] = []
candles_connector: str = Field( candles_connector: str = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt_on_new=True, "prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
prompt=lambda mi: "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( candles_trading_pair: str = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt_on_new=True, "prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
prompt=lambda mi: "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( interval: str = Field(
default="3m", default="3m",
client_data=ClientFieldData( json_schema_extra={
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ", "prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
prompt_on_new=False)) "prompt_on_new": True})
bb_length: int = Field( bb_length: int = Field(
default=100, default=100,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the Bollinger Bands length: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the Bollinger Bands length: ", bb_std: float = Field(default=2.0)
prompt_on_new=True)) bb_long_threshold: float = Field(default=0.0)
bb_std: float = Field( bb_short_threshold: float = Field(default=1.0)
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))
@validator("candles_connector", pre=True, always=True) @field_validator("candles_connector", mode="before")
def set_candles_connector(cls, v, values): @classmethod
def set_candles_connector(cls, v, validation_info: ValidationInfo):
if v is None or v == "": if v is None or v == "":
return values.get("connector_name") return validation_info.data.get("connector_name")
return v return v
@validator("candles_trading_pair", pre=True, always=True) @field_validator("candles_trading_pair", mode="before")
def set_candles_trading_pair(cls, v, values): @classmethod
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
if v is None or v == "": if v is None or v == "":
return values.get("trading_pair") return validation_info.data.get("trading_pair")
return v return v

View File

@@ -3,9 +3,9 @@ from decimal import Decimal
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
import pandas_ta as ta # noqa: F401 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.core.data_type.common import TradeType
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import ( from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
@@ -21,75 +21,63 @@ class DManV3ControllerConfig(DirectionalTradingControllerConfigBase):
candles_config: List[CandlesConfig] = [] candles_config: List[CandlesConfig] = []
candles_connector: str = Field( candles_connector: str = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt_on_new=True, "prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
prompt=lambda mi: "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( candles_trading_pair: str = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt_on_new=True, "prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
prompt=lambda mi: "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( interval: str = Field(
default="30m", default="3m",
client_data=ClientFieldData( json_schema_extra={
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ", "prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
prompt_on_new=True)) "prompt_on_new": True})
bb_length: int = Field( bb_length: int = Field(
default=100, default=100,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the Bollinger Bands length: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the Bollinger Bands length: ", bb_std: float = Field(default=2.0)
prompt_on_new=True)) bb_long_threshold: float = Field(default=0.0)
bb_std: float = Field( bb_short_threshold: float = Field(default=1.0)
default=2.0, trailing_stop: Optional[TrailingStop] = Field(
client_data=ClientFieldData( default="0.015,0.005",
prompt=lambda mi: "Enter the Bollinger Bands standard deviation: ", json_schema_extra={
prompt_on_new=False)) "prompt": "Enter the trailing stop parameters (activation_price, trailing_delta) as a comma-separated list: ",
bb_long_threshold: float = Field( "prompt_on_new": True,
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))
dca_spreads: List[Decimal] = Field( dca_spreads: List[Decimal] = Field(
default="0.001,0.018,0.15,0.25", default="0.001,0.018,0.15,0.25",
client_data=ClientFieldData( json_schema_extra={
prompt=lambda mi: "Enter the spreads for each DCA level (comma-separated) if dynamic_spread=True this value " "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%)" "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%) ", "and the spread is 0.2, the distance of the order to the current price will be 0.02 (2%) ",
prompt_on_new=True)) "prompt_on_new": True},
)
dca_amounts_pct: List[Decimal] = Field( dca_amounts_pct: List[Decimal] = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt=lambda mi: "Enter the amounts for each DCA level (as a percentage of the total balance, " "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. ", "comma-separated). Don't worry about the final sum, it will be normalized. ",
prompt_on_new=True)) "prompt_on_new": True},
)
dynamic_order_spread: bool = Field( dynamic_order_spread: bool = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={"prompt": "Do you want to make the spread dynamic? (Yes/No) ", "prompt_on_new": True})
prompt=lambda mi: "Do you want to make the spread dynamic? (Yes/No) ",
prompt_on_new=True))
dynamic_target: bool = Field( dynamic_target: bool = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={"prompt": "Do you want to make the target dynamic? (Yes/No) ", "prompt_on_new": True})
prompt=lambda mi: "Do you want to make the target dynamic? (Yes/No) ",
prompt_on_new=True))
activation_bounds: Optional[List[Decimal]] = Field( activation_bounds: Optional[List[Decimal]] = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt=lambda mi: "Enter the activation bounds for the orders " "prompt": "Enter the activation bounds for the orders (e.g., 0.01 activates the next order when the price is closer than 1%): ",
"(e.g., 0.01 activates the next order when the price is closer than 1%): ", "prompt_on_new": True,
prompt_on_new=True)) }
)
@validator("activation_bounds", pre=True, always=True) @field_validator("activation_bounds", mode="before")
@classmethod
def parse_activation_bounds(cls, v): def parse_activation_bounds(cls, v):
if isinstance(v, str): if isinstance(v, str):
if v == "": if v == "":
@@ -99,15 +87,17 @@ class DManV3ControllerConfig(DirectionalTradingControllerConfigBase):
return [Decimal(val) for val in v] return [Decimal(val) for val in v]
return v return v
@validator('dca_spreads', pre=True, always=True) @field_validator('dca_spreads', mode="before")
@classmethod
def validate_spreads(cls, v): def validate_spreads(cls, v):
if isinstance(v, str): if isinstance(v, str):
return [Decimal(val) for val in v.split(",")] return [Decimal(val) for val in v.split(",")]
return v return v
@validator('dca_amounts_pct', pre=True, always=True) @field_validator('dca_amounts_pct', mode="before")
def validate_amounts(cls, v, values): @classmethod
spreads = values.get("dca_spreads") def validate_amounts(cls, v, validation_info: ValidationInfo):
spreads = validation_info.data.get("dca_spreads")
if isinstance(v, str): if isinstance(v, str):
if v == "": if v == "":
return [Decimal('1.0') / len(spreads) for _ in spreads] 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 [Decimal('1.0') / len(spreads) for _ in spreads]
return v 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]]: 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 amounts_pct = self.dca_amounts_pct
if amounts_pct is None: 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] 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): class DManV3Controller(DirectionalTradingControllerBase):
""" """
@@ -201,8 +193,12 @@ class DManV3Controller(DirectionalTradingControllerBase):
prices = [price * (1 + spread * spread_multiplier) for spread in spread] prices = [price * (1 + spread * spread_multiplier) for spread in spread]
if self.config.dynamic_target: if self.config.dynamic_target:
stop_loss = self.config.stop_loss * spread_multiplier stop_loss = self.config.stop_loss * spread_multiplier
trailing_stop = TrailingStop(activation_price=self.config.trailing_stop.activation_price * 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) trailing_delta=self.config.trailing_stop.trailing_delta * spread_multiplier)
else:
trailing_stop = None
else: else:
stop_loss = self.config.stop_loss stop_loss = self.config.stop_loss
trailing_stop = self.config.trailing_stop trailing_stop = self.config.trailing_stop

View File

@@ -1,9 +1,9 @@
from typing import List from typing import List
import pandas_ta as ta # noqa: F401 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.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import ( from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
DirectionalTradingControllerBase, DirectionalTradingControllerBase,
@@ -12,71 +12,51 @@ from hummingbot.strategy_v2.controllers.directional_trading_controller_base impo
class MACDBBV1ControllerConfig(DirectionalTradingControllerConfigBase): class MACDBBV1ControllerConfig(DirectionalTradingControllerConfigBase):
controller_name = "macd_bb_v1" controller_name: str = "macd_bb_v1"
candles_config: List[CandlesConfig] = [] candles_config: List[CandlesConfig] = []
candles_connector: str = Field( candles_connector: str = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt_on_new=True, "prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
prompt=lambda mi: "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( candles_trading_pair: str = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt_on_new=True, "prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
prompt=lambda mi: "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( interval: str = Field(
default="3m", default="3m",
client_data=ClientFieldData( json_schema_extra={
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ", "prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
prompt_on_new=False)) "prompt_on_new": True})
bb_length: int = Field( bb_length: int = Field(
default=100, default=100,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the Bollinger Bands length: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the Bollinger Bands length: ", bb_std: float = Field(default=2.0)
prompt_on_new=True)) bb_long_threshold: float = Field(default=0.0)
bb_std: float = Field( bb_short_threshold: float = Field(default=1.0)
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))
macd_fast: int = Field( macd_fast: int = Field(
default=21, default=21,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the MACD fast period: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the MACD fast period: ",
prompt_on_new=True))
macd_slow: int = Field( macd_slow: int = Field(
default=42, default=42,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the MACD slow period: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the MACD slow period: ",
prompt_on_new=True))
macd_signal: int = Field( macd_signal: int = Field(
default=9, default=9,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the MACD signal period: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the MACD signal period: ",
prompt_on_new=True))
@validator("candles_connector", pre=True, always=True) @field_validator("candles_connector", mode="before")
def set_candles_connector(cls, v, values): @classmethod
def set_candles_connector(cls, v, validation_info: ValidationInfo):
if v is None or v == "": if v is None or v == "":
return values.get("connector_name") return validation_info.data.get("connector_name")
return v return v
@validator("candles_trading_pair", pre=True, always=True) @field_validator("candles_trading_pair", mode="before")
def set_candles_trading_pair(cls, v, values): @classmethod
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
if v is None or v == "": if v is None or v == "":
return values.get("trading_pair") return validation_info.data.get("trading_pair")
return v return v
@@ -84,7 +64,7 @@ class MACDBBV1Controller(DirectionalTradingControllerBase):
def __init__(self, config: MACDBBV1ControllerConfig, *args, **kwargs): def __init__(self, config: MACDBBV1ControllerConfig, *args, **kwargs):
self.config = config 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: if len(self.config.candles_config) == 0:
self.config.candles_config = [CandlesConfig( self.config.candles_config = [CandlesConfig(
connector=config.candles_connector, connector=config.candles_connector,

View File

@@ -1,9 +1,9 @@
from typing import List, Optional from typing import List
import pandas_ta as ta # noqa: F401 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.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import ( from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
DirectionalTradingControllerBase, DirectionalTradingControllerBase,
@@ -14,23 +14,41 @@ from hummingbot.strategy_v2.controllers.directional_trading_controller_base impo
class SuperTrendConfig(DirectionalTradingControllerConfigBase): class SuperTrendConfig(DirectionalTradingControllerConfigBase):
controller_name: str = "supertrend_v1" controller_name: str = "supertrend_v1"
candles_config: List[CandlesConfig] = [] 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_connector: str = Field(
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: ", )) default=None,
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={
length: int = Field(default=20, client_data=ClientFieldData(prompt=lambda mi: "Enter the supertrend length: ", prompt_on_new=True)) "prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
multiplier: float = Field(default=4.0, client_data=ClientFieldData(prompt=lambda mi: "Enter the supertrend multiplier: ", prompt_on_new=True)) "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_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) @field_validator("candles_connector", mode="before")
def set_candles_connector(cls, v, values): @classmethod
def set_candles_connector(cls, v, validation_info: ValidationInfo):
if v is None or v == "": if v is None or v == "":
return values.get("connector_name") return validation_info.data.get("connector_name")
return v return v
@validator("candles_trading_pair", pre=True, always=True) @field_validator("candles_trading_pair", mode="before")
def set_candles_trading_pair(cls, v, values): @classmethod
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
if v is None or v == "": if v is None or v == "":
return values.get("trading_pair") return validation_info.data.get("trading_pair")
return v return v

View File

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

View File

@@ -1,196 +0,0 @@
import time
from decimal import Decimal
from typing import Dict, List, Set
import pandas as pd
from pydantic import Field, validator
from hummingbot.client.config.config_data_types import ClientFieldData
from hummingbot.client.ui.interface_utils import format_df_for_printout
from hummingbot.core.data_type.common import PriceType, TradeType, PositionAction, OrderType
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.controller_base import ControllerBase, ControllerConfigBase
from hummingbot.strategy_v2.executors.data_types import ConnectorPair
from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig, \
TripleBarrierConfig
from hummingbot.strategy_v2.executors.xemm_executor.data_types import XEMMExecutorConfig
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction, StopExecutorAction
class SpotPerpArbitrageConfig(ControllerConfigBase):
controller_name: str = "spot_perp_arbitrage"
candles_config: List[CandlesConfig] = []
spot_connector: str = Field(
default="binance",
client_data=ClientFieldData(
prompt=lambda e: "Enter the spot connector: ",
prompt_on_new=True
))
spot_trading_pair: str = Field(
default="DOGE-USDT",
client_data=ClientFieldData(
prompt=lambda e: "Enter the spot trading pair: ",
prompt_on_new=True
))
perp_connector: str = Field(
default="binance_perpetual",
client_data=ClientFieldData(
prompt=lambda e: "Enter the perp connector: ",
prompt_on_new=True
))
perp_trading_pair: str = Field(
default="DOGE-USDT",
client_data=ClientFieldData(
prompt=lambda e: "Enter the perp trading pair: ",
prompt_on_new=True
))
profitability: Decimal = Field(
default=0.002,
client_data=ClientFieldData(
prompt=lambda e: "Enter the minimum profitability: ",
prompt_on_new=True
))
position_size_quote: float = Field(
default=50,
client_data=ClientFieldData(
prompt=lambda e: "Enter the position size in quote currency: ",
prompt_on_new=True
))
def update_markets(self, markets: Dict[str, Set[str]]) -> Dict[str, Set[str]]:
if self.spot_connector not in markets:
markets[self.spot_connector] = set()
markets[self.spot_connector].add(self.spot_trading_pair)
if self.perp_connector not in markets:
markets[self.perp_connector] = set()
markets[self.perp_connector].add(self.perp_trading_pair)
return markets
class SpotPerpArbitrage(ControllerBase):
def __init__(self, config: SpotPerpArbitrageConfig, *args, **kwargs):
self.config = config
super().__init__(config, *args, **kwargs)
@property
def spot_connector(self):
return self.market_data_provider.connectors[self.config.spot_connector]
@property
def perp_connector(self):
return self.market_data_provider.connectors[self.config.perp_connector]
def get_current_profitability_after_fees(self):
"""
This methods compares the profitability of buying at market in the two exchanges. If the side is TradeType.BUY
means that the operation is long on connector 1 and short on connector 2.
"""
spot_trading_pair = self.config.spot_trading_pair
perp_trading_pair = self.config.perp_trading_pair
connector_spot_price = Decimal(self.market_data_provider.get_price_for_quote_volume(
connector_name=self.config.spot_connector,
trading_pair=spot_trading_pair,
quote_volume=self.config.position_size_quote,
is_buy=True,
).result_price)
connector_perp_price = Decimal(self.market_data_provider.get_price_for_quote_volume(
connector_name=self.config.spot_connector,
trading_pair=perp_trading_pair,
quote_volume=self.config.position_size_quote,
is_buy=False,
).result_price)
estimated_fees_spot_connector = self.spot_connector.get_fee(
base_currency=spot_trading_pair.split("-")[0],
quote_currency=spot_trading_pair.split("-")[1],
order_type=OrderType.MARKET,
order_side=TradeType.BUY,
amount=self.config.position_size_quote / float(connector_spot_price),
price=connector_spot_price,
is_maker=False,
).percent
estimated_fees_perp_connector = self.perp_connector.get_fee(
base_currency=perp_trading_pair.split("-")[0],
quote_currency=perp_trading_pair.split("-")[1],
order_type=OrderType.MARKET,
order_side=TradeType.BUY,
amount=self.config.position_size_quote / float(connector_perp_price),
price=connector_perp_price,
is_maker=False,
position_action=PositionAction.OPEN
).percent
estimated_trade_pnl_pct = (connector_perp_price - connector_spot_price) / connector_spot_price
return estimated_trade_pnl_pct - estimated_fees_spot_connector - estimated_fees_perp_connector
def is_active_arbitrage(self):
executors = self.filter_executors(
executors=self.executors_info,
filter_func=lambda e: e.is_active
)
return len(executors) > 0
def current_pnl_pct(self):
executors = self.filter_executors(
executors=self.executors_info,
filter_func=lambda e: e.is_active
)
filled_amount = sum(e.filled_amount_quote for e in executors)
return sum(e.net_pnl_quote for e in executors) / filled_amount if filled_amount > 0 else 0
async def update_processed_data(self):
self.processed_data = {
"profitability": self.get_current_profitability_after_fees(),
"active_arbitrage": self.is_active_arbitrage(),
"current_pnl": self.current_pnl_pct()
}
def determine_executor_actions(self) -> List[ExecutorAction]:
executor_actions = []
executor_actions.extend(self.create_new_arbitrage_actions())
executor_actions.extend(self.stop_arbitrage_actions())
return executor_actions
def create_new_arbitrage_actions(self):
create_actions = []
if not self.processed_data["active_arbitrage"] and self.processed_data["profitability"] > self.config.profitability:
mid_price = self.market_data_provider.get_price_by_type(self.config.spot_connector, self.config.spot_trading_pair, PriceType.MidPrice)
create_actions.append(CreateExecutorAction(
controller_id=self.config.id,
executor_config=PositionExecutorConfig(
timestamp=self.market_data_provider.time(),
connector_name=self.config.spot_connector,
trading_pair=self.config.spot_trading_pair,
side=TradeType.BUY,
amount=Decimal(self.config.position_size_quote) / mid_price,
triple_barrier_config=TripleBarrierConfig(open_order_type=OrderType.MARKET),
)
))
create_actions.append(CreateExecutorAction(
controller_id=self.config.id,
executor_config=PositionExecutorConfig(
timestamp=self.market_data_provider.time(),
connector_name=self.config.perp_connector,
trading_pair=self.config.perp_trading_pair,
side=TradeType.SELL,
amount=Decimal(self.config.position_size_quote) / mid_price,
triple_barrier_config=TripleBarrierConfig(open_order_type=OrderType.MARKET),
))
)
return create_actions
def stop_arbitrage_actions(self):
stop_actions = []
if self.processed_data["current_pnl"] > 0.003:
executors = self.filter_executors(
executors=self.executors_info,
filter_func=lambda e: e.is_active
)
for executor in executors:
stop_actions.append(StopExecutorAction(controller_id=self.config.id, executor_id=executor.id))
def to_format_status(self) -> List[str]:
return [f"Current profitability: {self.processed_data['profitability']} | Min profitability: {self.config.profitability}",
f"Active arbitrage: {self.processed_data['active_arbitrage']}",
f"Current PnL: {self.processed_data['current_pnl']}"]

View File

@@ -3,9 +3,8 @@ from decimal import Decimal
from typing import Dict, List, Set from typing import Dict, List, Set
import pandas as pd 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.client.ui.interface_utils import format_df_for_printout
from hummingbot.core.data_type.common import PriceType, TradeType from hummingbot.core.data_type.common import PriceType, TradeType
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
@@ -19,62 +18,40 @@ class XEMMMultipleLevelsConfig(ControllerConfigBase):
controller_name: str = "xemm_multiple_levels" controller_name: str = "xemm_multiple_levels"
candles_config: List[CandlesConfig] = [] candles_config: List[CandlesConfig] = []
maker_connector: str = Field( maker_connector: str = Field(
default="kucoin", default="mexc",
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the maker connector: ", "prompt_on_new": True})
prompt=lambda e: "Enter the maker connector: ",
prompt_on_new=True
))
maker_trading_pair: str = Field( maker_trading_pair: str = Field(
default="LBR-USDT", default="PEPE-USDT",
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the maker trading pair: ", "prompt_on_new": True})
prompt=lambda e: "Enter the maker trading pair: ",
prompt_on_new=True
))
taker_connector: str = Field( taker_connector: str = Field(
default="okx", default="binance",
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the taker connector: ", "prompt_on_new": True})
prompt=lambda e: "Enter the taker connector: ",
prompt_on_new=True
))
taker_trading_pair: str = Field( taker_trading_pair: str = Field(
default="LBR-USDT", default="PEPE-USDT",
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the taker trading pair: ", "prompt_on_new": True})
prompt=lambda e: "Enter the taker trading pair: ",
prompt_on_new=True
))
buy_levels_targets_amount: List[List[Decimal]] = Field( buy_levels_targets_amount: List[List[Decimal]] = Field(
default="0.003,10-0.006,20-0.009,30", default="0.003,10-0.006,20-0.009,30",
client_data=ClientFieldData( json_schema_extra={
prompt=lambda e: "Enter the buy levels targets with the following structure: (target_profitability1,amount1-target_profitability2,amount2): ", "prompt": "Enter the buy levels targets with the following structure: (target_profitability1,amount1-target_profitability2,amount2): ",
prompt_on_new=True "prompt_on_new": True})
))
sell_levels_targets_amount: List[List[Decimal]] = Field( sell_levels_targets_amount: List[List[Decimal]] = Field(
default="0.003,10-0.006,20-0.009,30", default="0.003,10-0.006,20-0.009,30",
client_data=ClientFieldData( json_schema_extra={
prompt=lambda e: "Enter the sell levels targets with the following structure: (target_profitability1,amount1-target_profitability2,amount2): ", "prompt": "Enter the sell levels targets with the following structure: (target_profitability1,amount1-target_profitability2,amount2): ",
prompt_on_new=True "prompt_on_new": True})
))
min_profitability: Decimal = Field( min_profitability: Decimal = Field(
default=0.002, default=0.003,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the minimum profitability: ", "prompt_on_new": True})
prompt=lambda e: "Enter the minimum profitability: ",
prompt_on_new=True
))
max_profitability: Decimal = Field( max_profitability: Decimal = Field(
default=0.01, default=0.01,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the maximum profitability: ", "prompt_on_new": True})
prompt=lambda e: "Enter the maximum profitability: ",
prompt_on_new=True
))
max_executors_imbalance: int = Field( max_executors_imbalance: int = Field(
default=1, default=1,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the maximum executors imbalance: ", "prompt_on_new": True})
prompt=lambda e: "Enter the maximum executors imbalance: ",
prompt_on_new=True
))
@validator("buy_levels_targets_amount", "sell_levels_targets_amount", pre=True, always=True) @field_validator("buy_levels_targets_amount", "sell_levels_targets_amount", mode="before")
def validate_levels_targets_amount(cls, v, values): @classmethod
def validate_levels_targets_amount(cls, v):
if isinstance(v, str): if isinstance(v, str):
v = [list(map(Decimal, x.split(","))) for x in v.split("-")] v = [list(map(Decimal, x.split(","))) for x in v.split("-")]
return v 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] 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: 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( config = XEMMExecutorConfig(
controller_id=self.config.id, controller_id=self.config.id,
timestamp=self.market_data_provider.time(), timestamp=self.market_data_provider.time(),
@@ -133,14 +112,16 @@ class XEMMMultipleLevels(ControllerBase):
trading_pair=self.config.taker_trading_pair), trading_pair=self.config.taker_trading_pair),
maker_side=TradeType.BUY, maker_side=TradeType.BUY,
order_amount=amount / mid_price, order_amount=amount / mid_price,
min_profitability=self.config.min_profitability, min_profitability=min_profitability,
target_profitability=target_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)) executor_actions.append(CreateExecutorAction(executor_config=config, controller_id=self.config.id))
for target_profitability, amount in self.sell_levels_targets_amount: 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] 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: 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( config = XEMMExecutorConfig(
controller_id=self.config.id, controller_id=self.config.id,
timestamp=time.time(), timestamp=time.time(),
@@ -150,9 +131,9 @@ class XEMMMultipleLevels(ControllerBase):
trading_pair=self.config.maker_trading_pair), trading_pair=self.config.maker_trading_pair),
maker_side=TradeType.SELL, maker_side=TradeType.SELL,
order_amount=amount / mid_price, order_amount=amount / mid_price,
min_profitability=self.config.min_profitability, min_profitability=min_profitability,
target_profitability=target_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)) executor_actions.append(CreateExecutorAction(executor_config=config, controller_id=self.config.id))
return executor_actions return executor_actions

View File

@@ -2,9 +2,8 @@ from decimal import Decimal
from typing import List, Optional from typing import List, Optional
import pandas_ta as ta # noqa: F401 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.core.data_type.common import TradeType
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.market_making_controller_base import ( from hummingbot.strategy_v2.controllers.market_making_controller_base import (
@@ -25,38 +24,15 @@ class DManMakerV2Config(MarketMakingControllerConfigBase):
# DCA configuration # DCA configuration
dca_spreads: List[Decimal] = Field( dca_spreads: List[Decimal] = Field(
default="0.01,0.02,0.04,0.08", default="0.01,0.02,0.04,0.08",
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter a comma-separated list of spreads for each DCA level: ", "prompt_on_new": True})
prompt_on_new=True,
prompt=lambda mi: "Enter a comma-separated list of spreads for each DCA level: "))
dca_amounts: List[Decimal] = Field( dca_amounts: List[Decimal] = Field(
default="0.1,0.2,0.4,0.8", default="0.1,0.2,0.4,0.8",
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter a comma-separated list of amounts for each DCA level: ", "prompt_on_new": True})
prompt_on_new=True, top_executor_refresh_time: Optional[float] = Field(default=None, json_schema_extra={"is_updatable": True})
prompt=lambda mi: "Enter a comma-separated list of amounts for each DCA level: ")) executor_activation_bounds: Optional[List[Decimal]] = Field(default=None, json_schema_extra={"is_updatable": True})
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))
@validator("executor_activation_bounds", pre=True, always=True) @field_validator("executor_activation_bounds", mode="before")
@classmethod
def parse_activation_bounds(cls, v): def parse_activation_bounds(cls, v):
if isinstance(v, list): if isinstance(v, list):
return [Decimal(val) for val in v] return [Decimal(val) for val in v]
@@ -66,8 +42,9 @@ class DManMakerV2Config(MarketMakingControllerConfigBase):
return [Decimal(val) for val in v.split(",")] return [Decimal(val) for val in v.split(",")]
return v return v
@validator('dca_spreads', pre=True, always=True) @field_validator('dca_spreads', mode="before")
def parse_spreads(cls, v): @classmethod
def parse_dca_spreads(cls, v):
if v is None: if v is None:
return [] return []
if isinstance(v, str): if isinstance(v, str):
@@ -76,15 +53,16 @@ class DManMakerV2Config(MarketMakingControllerConfigBase):
return [float(x.strip()) for x in v.split(',')] return [float(x.strip()) for x in v.split(',')]
return v return v
@validator('dca_amounts', pre=True, always=True) @field_validator('dca_amounts', mode="before")
def parse_and_validate_amounts(cls, v, values, field): @classmethod
def parse_and_validate_dca_amounts(cls, v, validation_info):
if v is None or v == "": 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): if isinstance(v, str):
return [float(x.strip()) for x in v.split(',')] 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( 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 return v
@@ -98,11 +76,11 @@ class DManMakerV2(MarketMakingControllerBase):
def first_level_refresh_condition(self, executor): def first_level_refresh_condition(self, executor):
if self.config.top_executor_refresh_time is not None: if self.config.top_executor_refresh_time is not None:
if self.get_level_from_level_id(executor.custom_info["level_id"]) == 0: 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 return False
def order_level_refresh_condition(self, executor): 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]: def executors_to_refresh(self) -> List[ExecutorAction]:
executors_to_refresh = self.filter_executors( executors_to_refresh = self.filter_executors(

View File

@@ -2,9 +2,9 @@ from decimal import Decimal
from typing import List from typing import List
import pandas_ta as ta # noqa: F401 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.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.market_making_controller_base import ( from hummingbot.strategy_v2.controllers.market_making_controller_base import (
MarketMakingControllerBase, MarketMakingControllerBase,
@@ -14,69 +14,60 @@ from hummingbot.strategy_v2.executors.position_executor.data_types import Positi
class PMMDynamicControllerConfig(MarketMakingControllerConfigBase): class PMMDynamicControllerConfig(MarketMakingControllerConfigBase):
controller_name = "pmm_dynamic" controller_name: str = "pmm_dynamic"
candles_config: List[CandlesConfig] = [] candles_config: List[CandlesConfig] = []
buy_spreads: List[float] = Field( buy_spreads: List[float] = Field(
default="1,2,4", default="1,2,4",
client_data=ClientFieldData( json_schema_extra={
is_updatable=True, "prompt": "Enter a comma-separated list of buy spreads measured in units of volatility(e.g., '1, 2'): ",
prompt_on_new=True, "prompt_on_new": True, "is_updatable": True}
prompt=lambda mi: "Enter a comma-separated list of buy spreads (e.g., '0.01, 0.02'):")) )
sell_spreads: List[float] = Field( sell_spreads: List[float] = Field(
default="1,2,4", default="1,2,4",
client_data=ClientFieldData( json_schema_extra={
is_updatable=True, "prompt": "Enter a comma-separated list of sell spreads measured in units of volatility(e.g., '1, 2'): ",
prompt_on_new=True, "prompt_on_new": True, "is_updatable": True}
prompt=lambda mi: "Enter a comma-separated list of sell spreads (e.g., '0.01, 0.02'):")) )
candles_connector: str = Field( candles_connector: str = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt_on_new=True, "prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
prompt=lambda mi: "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( candles_trading_pair: str = Field(
default=None, default=None,
client_data=ClientFieldData( json_schema_extra={
prompt_on_new=True, "prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
prompt=lambda mi: "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( interval: str = Field(
default="3m", default="3m",
client_data=ClientFieldData( json_schema_extra={
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ", "prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
prompt_on_new=False)) "prompt_on_new": True})
macd_fast: int = Field( macd_fast: int = Field(
default=12, default=21,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the MACD fast period: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the MACD fast length: ",
prompt_on_new=True))
macd_slow: int = Field( macd_slow: int = Field(
default=26, default=42,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the MACD slow period: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the MACD slow length: ",
prompt_on_new=True))
macd_signal: int = Field( macd_signal: int = Field(
default=9, default=9,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the MACD signal period: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the MACD signal length: ",
prompt_on_new=True))
natr_length: int = Field( natr_length: int = Field(
default=14, default=14,
client_data=ClientFieldData( json_schema_extra={"prompt": "Enter the NATR length: ", "prompt_on_new": True})
prompt=lambda mi: "Enter the NATR length: ",
prompt_on_new=True))
@validator("candles_connector", pre=True, always=True) @field_validator("candles_connector", mode="before")
def set_candles_connector(cls, v, values): @classmethod
def set_candles_connector(cls, v, validation_info: ValidationInfo):
if v is None or v == "": if v is None or v == "":
return values.get("connector_name") return validation_info.data.get("connector_name")
return v return v
@validator("candles_trading_pair", pre=True, always=True) @field_validator("candles_trading_pair", mode="before")
def set_candles_trading_pair(cls, v, values): @classmethod
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
if v is None or v == "": if v is None or v == "":
return values.get("trading_pair") return validation_info.data.get("trading_pair")
return v return v
@@ -87,7 +78,7 @@ class PMMDynamicController(MarketMakingControllerBase):
""" """
def __init__(self, config: PMMDynamicControllerConfig, *args, **kwargs): def __init__(self, config: PMMDynamicControllerConfig, *args, **kwargs):
self.config = config 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: if len(self.config.candles_config) == 0:
self.config.candles_config = [CandlesConfig( self.config.candles_config = [CandlesConfig(
connector=config.candles_connector, connector=config.candles_connector,

View File

@@ -3,7 +3,6 @@ from typing import List
from pydantic import Field 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.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.market_making_controller_base import ( from hummingbot.strategy_v2.controllers.market_making_controller_base import (
MarketMakingControllerBase, MarketMakingControllerBase,
@@ -13,9 +12,9 @@ from hummingbot.strategy_v2.executors.position_executor.data_types import Positi
class PMMSimpleConfig(MarketMakingControllerConfigBase): 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 # 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): class PMMSimpleController(MarketMakingControllerBase):

View File

@@ -24,8 +24,6 @@ kill_switch_mode: {}
# What to auto-fill in the prompt after each import command (start/config) # What to auto-fill in the prompt after each import command (start/config)
autofill_import: disabled autofill_import: disabled
telegram_mode: {}
# MQTT Bridge configuration. # MQTT Bridge configuration.
mqtt_bridge: mqtt_bridge:
mqtt_host: localhost mqtt_host: localhost
@@ -59,8 +57,6 @@ previous_strategy: some-strategy.yml
db_mode: db_mode:
db_engine: sqlite db_engine: sqlite
pmm_script_mode: {}
# Balance Limit Configurations # Balance Limit Configurations
# e.g. Setting USDT and BTC limits on Binance. # e.g. Setting USDT and BTC limits on Binance.
# balance_asset_limit: # balance_asset_limit:

View File

@@ -3,8 +3,6 @@ import time
from decimal import Decimal from decimal import Decimal
from typing import Dict, List, Optional, Set from typing import Dict, List, Optional, Set
from pydantic import Field
from hummingbot.client.hummingbot_application import HummingbotApplication from hummingbot.client.hummingbot_application import HummingbotApplication
from hummingbot.connector.connector_base import ConnectorBase from hummingbot.connector.connector_base import ConnectorBase
from hummingbot.core.clock import Clock from hummingbot.core.clock import Clock
@@ -17,13 +15,12 @@ from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction,
class GenericV2StrategyWithCashOutConfig(StrategyV2ConfigBase): 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] = [] candles_config: List[CandlesConfig] = []
markets: Dict[str, Set[str]] = {} markets: Dict[str, Set[str]] = {}
time_to_cash_out: Optional[int] = None time_to_cash_out: Optional[int] = None
max_global_drawdown: Optional[float] = None max_global_drawdown: Optional[float] = None
max_controller_drawdown: Optional[float] = None max_controller_drawdown: Optional[float] = None
performance_report_interval: int = 1
rebalance_interval: Optional[int] = None rebalance_interval: Optional[int] = None
extra_inventory: Optional[float] = 0.02 extra_inventory: Optional[float] = 0.02
min_amount_to_rebalance_usd: Decimal = Decimal("8") 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 specific controller and wait until the active executors finalize their execution. The rest of the executors will
wait until the main strategy stops them. wait until the main strategy stops them.
""" """
performance_report_interval: int = 1
def __init__(self, connectors: Dict[str, ConnectorBase], config: GenericV2StrategyWithCashOutConfig): def __init__(self, connectors: Dict[str, ConnectorBase], config: GenericV2StrategyWithCashOutConfig):
super().__init__(connectors, config) super().__init__(connectors, config)
self.config = config self.config = config
@@ -50,7 +49,6 @@ class GenericV2StrategyWithCashOut(StrategyV2Base):
self.max_global_pnl = Decimal("0") self.max_global_pnl = Decimal("0")
self.drawdown_exited_controllers = [] self.drawdown_exited_controllers = []
self.closed_executors_buffer: int = 30 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.rebalance_interval: int = self.config.rebalance_interval
self._last_performance_report_timestamp = 0 self._last_performance_report_timestamp = 0
self._last_rebalance_check_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: if self.rebalance_interval and self._last_rebalance_check_timestamp + self.rebalance_interval <= self.current_timestamp:
balance_required = {} balance_required = {}
for controller_id, controller in self.controllers.items(): 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: if connector_name and "perpetual" in connector_name:
continue continue
if connector_name not in balance_required: if connector_name not in balance_required:
@@ -124,21 +122,21 @@ class GenericV2StrategyWithCashOut(StrategyV2Base):
order_type = OrderType.MARKET order_type = OrderType.MARKET
if base_balance_diff > 0: if base_balance_diff > 0:
if trading_rules_condition: 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( connector.sell(
trading_pair=trading_pair, trading_pair=trading_pair,
amount=abs_balance_diff, amount=abs_balance_diff,
order_type=order_type, order_type=order_type,
price=mid_price) price=mid_price)
else: 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: else:
if not trading_rules_condition: 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]) 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: else:
amount = abs_balance_diff 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( connector.buy(
trading_pair=trading_pair, trading_pair=trading_pair,
amount=amount, amount=amount,
@@ -154,6 +152,8 @@ class GenericV2StrategyWithCashOut(StrategyV2Base):
def check_max_controller_drawdown(self): def check_max_controller_drawdown(self):
for controller_id, controller in self.controllers.items(): for controller_id, controller in self.controllers.items():
if controller.status != RunnableStatus.RUNNING:
continue
controller_pnl = self.performance_reports[controller_id]["global_pnl_quote"] controller_pnl = self.performance_reports[controller_id]["global_pnl_quote"]
last_max_pnl = self.max_pnl_by_controller[controller_id] last_max_pnl = self.max_pnl_by_controller[controller_id]
if controller_pnl > last_max_pnl: if controller_pnl > last_max_pnl:

View File

@@ -19,7 +19,7 @@ def user_inputs():
config = { config = {
"controller_name": "dman_maker_v2", "controller_name": "dman_maker_v2",
"controller_type": "market_making", "controller_type": "market_making",
"manual_kill_switch": None, "manual_kill_switch": False,
"candles_config": [], "candles_config": [],
"connector_name": connector_name, "connector_name": connector_name,
"trading_pair": trading_pair, "trading_pair": trading_pair,

View File

@@ -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 ## Features
- **Start from Default Configurations**: Begin with a default configuration or use the values from an existing configuration. - **Simple Grid Configuration**: Configure a single grid with start, end, and limit prices.
- **Dynamic Price Range Defaults**: Automatically sets grid ranges based on current market conditions. - **Dynamic Price Range Defaults**: Automatically sets price ranges based on current market conditions.
- **Visual Grid Configuration**: See your grid ranges directly on the price chart. - **Visual Grid Configuration**: See your grid settings directly on the price chart.
- **Multiple Grid Ranges**: Configure up to 5 different grid ranges with different sides (BUY/SELL). - **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. - **Save and Deploy**: Once satisfied, save the configuration to deploy it later.
## How to Use ## How to Use
@@ -15,9 +15,10 @@ Welcome to the Grid Strike Configuration Tool! This tool allows you to create, m
### 1. Basic Configuration ### 1. Basic Configuration
Start by configuring the basic parameters: 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"). - **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 for margin/futures trading.
- **Leverage**: Set the leverage ratio (use 1 for spot trading).
### 2. Chart Configuration ### 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). - **Interval**: Choose the timeframe for the candlesticks (1m to 1d).
- **Days to Display**: Select how many days of historical data to show. - **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: Configure your grid parameters:
- **Number of Grid Ranges**: Select how many ranges you want to configure (1-5). - **Side**: Choose BUY or SELL for the grid.
- **Side**: Choose BUY or SELL for each range. - **Start Price**: The price where the grid begins.
- **Start Price**: The price where the range begins. - **End Price**: The price where the grid ends.
- **End Price**: The price where the range ends. - **Limit Price**: A price limit that will stop the strategy.
- **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.
- **Min Spread Between Orders**: Minimum price difference between orders. - **Min Spread Between Orders**: Minimum price difference between orders.
- **Min Order Amount**: Minimum size for individual orders. - **Min Order Amount (Quote)**: Minimum size for individual orders.
- **Max Open Orders**: Maximum number of active orders per range. - **Maximum Open Orders**: Maximum number of active orders in the grid.
- **Grid Range Update Interval**: How often to update grid ranges (in seconds).
## 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 ### 5. Triple Barrier Configuration
- Each grid range defines a price zone where the strategy will place orders
- BUY ranges place buy orders from higher to lower prices Set up risk management parameters:
- SELL ranges place sell orders from lower to higher prices - **Open Order Type**: The type of order to open positions (e.g., MARKET, LIMIT).
- Multiple ranges can work simultaneously with different configurations - **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 ### Order Placement
- Orders are placed within each range based on the min spread between orders - Orders are placed within the grid based on the min spread between orders
- The amount per order is calculated based on the range's allocation percentage - The amount per order is calculated based on the total amount specified
- Orders are automatically adjusted when price moves beyond activation bounds - Orders are automatically adjusted when price moves beyond activation bounds
### Visual Indicators ### Visual Indicators
- Green lines represent BUY ranges - Green lines represent the start and end prices
- Red lines represent SELL ranges - Red line represents the limit price
- Different dash patterns distinguish multiple ranges of the same side - Candlestick chart shows the market price action
- 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
## Example Configuration ## 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

View File

@@ -13,45 +13,46 @@ from frontend.visualization.candles import get_candlestick_trace
from frontend.visualization.utils import add_traces_to_fig from frontend.visualization.utils import add_traces_to_fig
def get_grid_range_traces(grid_ranges): def get_grid_trace(start_price, end_price, limit_price):
"""Generate horizontal line traces for grid ranges with different colors.""" """Generate horizontal line traces for the grid with different colors."""
dash_styles = ['solid', 'dash', 'dot', 'dashdot', 'longdash'] # 5 different styles
traces = [] 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( traces.append(go.Scatter(
x=[], # Will be set to full range when plotting x=[], # Will be set to full range when plotting
y=[float(grid_range["start_price"]), float(grid_range["start_price"])], y=[float(start_price), float(start_price)],
mode='lines', mode='lines',
line=dict(color=color, width=1.5, dash=dash_style), line=dict(color='rgba(0, 255, 0, 1)', width=1.5, dash='solid'),
name=f'Range {i} Start: {float(grid_range["start_price"]):,.2f} ({grid_range["side"].name})', name=f'Start Price: {float(start_price):,.2f}',
hoverinfo='name' hoverinfo='name'
)) ))
# End price line # End price line
traces.append(go.Scatter( traces.append(go.Scatter(
x=[], # Will be set to full range when plotting x=[], # Will be set to full range when plotting
y=[float(grid_range["end_price"]), float(grid_range["end_price"])], y=[float(end_price), float(end_price)],
mode='lines', mode='lines',
line=dict(color=color, width=1.5, dash=dash_style), line=dict(color='rgba(0, 255, 0, 1)', width=1.5, dash='dot'),
name=f'Range {i} End: {float(grid_range["end_price"]):,.2f} ({grid_range["side"].name})', name=f'End Price: {float(end_price):,.2f}',
hoverinfo='name' 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(limit_price), float(limit_price)],
mode='lines',
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 return traces
# Initialize the Streamlit page # 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() backend_api_client = get_backend_api_client()
get_default_config_loader("grid_strike") get_default_config_loader("grid_strike")
@@ -70,28 +71,34 @@ candles = get_candles(
# Create a subplot with just 1 row for price action # Create a subplot with just 1 row for price action
fig = make_subplots( fig = make_subplots(
rows=1, cols=1, 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 # Add basic candlestick chart
candlestick_trace = get_candlestick_trace(candles) candlestick_trace = get_candlestick_trace(candles)
add_traces_to_fig(fig, [candlestick_trace], row=1, col=1) add_traces_to_fig(fig, [candlestick_trace], row=1, col=1)
# Add grid range visualization # Add grid visualization
grid_traces = get_grid_range_traces(inputs["grid_ranges"]) grid_traces = get_grid_trace(
inputs["start_price"],
inputs["end_price"],
inputs["limit_price"]
)
for trace in grid_traces: for trace in grid_traces:
# Set the x-axis range for all grid traces # Set the x-axis range for all grid traces
trace.x = [candles.index[0], candles.index[-1]] trace.x = [candles.index[0], candles.index[-1]]
fig.add_trace(trace, row=1, col=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 = [] all_prices = []
# Add candle prices # Add candle prices
all_prices.extend(candles['high'].tolist()) all_prices.extend(candles['high'].tolist())
all_prices.extend(candles['low'].tolist()) all_prices.extend(candles['low'].tolist())
# Add grid range prices # Add grid prices
for grid_range in inputs["grid_ranges"]: all_prices.extend([float(inputs["start_price"]), float(inputs["end_price"])])
all_prices.extend([float(grid_range["start_price"]), float(grid_range["end_price"])]) if inputs["limit_price"]:
all_prices.append(float(inputs["limit_price"]))
y_min, y_max = min(all_prices), max(all_prices) y_min, y_max = min(all_prices), max(all_prices)
padding = (y_max - y_min) * 0.1 # Add 10% padding 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): def prepare_config_for_save(config):
"""Prepare config for JSON serialization.""" """Prepare config for JSON serialization."""
prepared_config = config.copy() prepared_config = config.copy()
grid_ranges = []
for grid_range in prepared_config["grid_ranges"]: # Convert position mode enum to value
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
prepared_config["position_mode"] = prepared_config["position_mode"].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["candles_connector"]
del prepared_config["interval"] del prepared_config["interval"]
del prepared_config["days_to_visualize"] del prepared_config["days_to_visualize"]
return prepared_config return prepared_config
# Replace the render_save_config line with: # Render save config component
render_save_config(st.session_state["default_config"]["id"], 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"]))

View File

@@ -1,6 +1,3 @@
from decimal import Decimal
import plotly.graph_objs as go
import streamlit as st import streamlit as st
from hummingbot.core.data_type.common import OrderType, PositionMode, TradeType 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, interval=interval,
days=days days=days
) )
current_price = float(candles['close'].iloc[-1])
min_price = float(candles['low'].quantile(0.05)) min_price = float(candles['low'].quantile(0.05))
max_price = float(candles['high'].quantile(0.95)) 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: except Exception as e:
st.warning(f"Could not fetch price data: {str(e)}. Using default values.") st.warning(f"Could not fetch price data: {str(e)}. Using default values.")
return 40000.0, 60000.0 # Fallback defaults return 40000.0, 42000.0, 44000.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
def user_inputs(): def user_inputs():
# Split the page into two columns for the expanders # Split the page into two columns for the expanders
left_col, right_col = st.columns(2) left_col, right_col = st.columns(2)
with left_col: with left_col:
# Basic trading parameters # Combined Basic, Amount, and Grid Configuration
with st.expander("Basic Configuration", expanded=True): 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) c1, c2, c3 = st.columns(3)
with c1: 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: 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: with c3:
leverage = st.number_input("Leverage", min_value=1, value=20) activation_bounds = st.number_input(
# Visualization Configuration "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): with st.expander("Chart Configuration", expanded=True):
c1, c2, c3 = st.columns(3) c1, c2, c3 = st.columns(3)
with c1: with c1:
@@ -87,7 +196,7 @@ def user_inputs():
interval = st.selectbox( interval = st.selectbox(
"Interval", "Interval",
options=["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "1d"], 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" help="Candlestick interval"
) )
with c3: with c3:
@@ -95,122 +204,23 @@ def user_inputs():
"Days to Display", "Days to Display",
min_value=1, min_value=1,
max_value=365, max_value=365,
value=180, value=30,
help="Number of days of historical data to display" 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, # Convert stop_loss to None if it's zero
trading_pair, stop_loss_value = stop_loss if stop_loss > 0 else None
interval,
days_to_visualize # Prepare triple barrier config
) triple_barrier_config = {
# Grid Ranges Configuration "open_order_type": OrderType[open_order_type],
with st.expander("Grid Ranges", expanded=True): "stop_loss": stop_loss_value,
grid_ranges = [] "take_profit": take_profit,
num_ranges = st.number_input("Number of Grid Ranges", min_value=1, max_value=5, value=1) "take_profit_order_type": OrderType[take_profit_order_type],
for i in range(num_ranges): "time_limit": None,
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"
)
return { return {
"controller_name": "grid_strike", "controller_name": "grid_strike",
@@ -221,14 +231,18 @@ def user_inputs():
"interval": interval, "interval": interval,
"days_to_visualize": days_to_visualize, "days_to_visualize": days_to_visualize,
"leverage": leverage, "leverage": leverage,
"total_amount_quote": Decimal(str(total_amount_quote)), "side": TradeType[side],
"grid_ranges": grid_ranges, "start_price": start_price,
"end_price": end_price,
"limit_price": limit_price,
"position_mode": PositionMode[position_mode], "position_mode": PositionMode[position_mode],
"time_limit": time_limit * 60 * 60, "total_amount_quote": total_amount_quote,
"activation_bounds": Decimal(str(activation_bounds)), "min_spread_between_orders": min_spread,
"min_spread_between_orders": Decimal(str(min_spread)) if min_spread > 0 else None, "min_order_amount_quote": min_order_amount,
"min_order_amount": Decimal(str(min_order_amount)),
"max_open_orders": max_open_orders, "max_open_orders": max_open_orders,
"grid_range_update_interval": grid_update_interval, "max_orders_per_batch": max_orders_per_batch,
"extra_balance_base_usd": Decimal("10") "order_frequency": order_frequency,
"activation_bounds": activation_bounds,
"triple_barrier_config": triple_barrier_config,
"candles_config": []
} }

View File

@@ -190,7 +190,7 @@ config = {
"id": id, "id": id,
"controller_name": "bollinger_v1", "controller_name": "bollinger_v1",
"controller_type": "directional_trading", "controller_type": "directional_trading",
"manual_kill_switch": None, "manual_kill_switch": False,
"candles_config": [], "candles_config": [],
"connector_name": connector_name, "connector_name": connector_name,
"trading_pair": trading_pair, "trading_pair": trading_pair,

View File

@@ -28,7 +28,7 @@ def user_inputs():
config = { config = {
"controller_name": "pmm_dynamic", "controller_name": "pmm_dynamic",
"controller_type": "market_making", "controller_type": "market_making",
"manual_kill_switch": None, "manual_kill_switch": False,
"candles_config": [], "candles_config": [],
"connector_name": connector_name, "connector_name": connector_name,
"trading_pair": trading_pair, "trading_pair": trading_pair,

View File

@@ -13,7 +13,7 @@ def user_inputs():
config = { config = {
"controller_name": "pmm_simple", "controller_name": "pmm_simple",
"controller_type": "market_making", "controller_type": "market_making",
"manual_kill_switch": None, "manual_kill_switch": False,
"candles_config": [], "candles_config": [],
"connector_name": connector_name, "connector_name": connector_name,
"trading_pair": trading_pair, "trading_pair": trading_pair,