Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ run:
uvicorn main:app --reload

uninstall:
conda env remove -n backend-api
conda env remove -n backend-api -y

install:
if conda env list | grep -q '^backend-api '; then \
echo "Environment already exists."; \
else \
conda env create -f environment.yml; \
fi
conda activate backend-api
$(MAKE) install-pre-commit

install-pre-commit:
Expand Down
86 changes: 86 additions & 0 deletions bots/controllers/directional_trading/ai_livestream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from decimal import Decimal
from typing import List

import pandas_ta as ta # noqa: F401
from pydantic import Field

from hummingbot.core.data_type.common import TradeType
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.remote_iface.mqtt import ExternalTopicFactory
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
DirectionalTradingControllerBase,
DirectionalTradingControllerConfigBase,
)
from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig


class AILivestreamControllerConfig(DirectionalTradingControllerConfigBase):
controller_name: str = "ai_livestream"
candles_config: List[CandlesConfig] = []
long_threshold: float = Field(default=0.5, json_schema_extra={"is_updatable": True})
short_threshold: float = Field(default=0.5, json_schema_extra={"is_updatable": True})
topic: str = "hbot/predictions"


class AILivestreamController(DirectionalTradingControllerBase):
def __init__(self, config: AILivestreamControllerConfig, *args, **kwargs):
self.config = config
super().__init__(config, *args, **kwargs)
# Start ML signal listener
self._init_ml_signal_listener()

def _init_ml_signal_listener(self):
"""Initialize a listener for ML signals from the MQTT broker"""
try:
normalized_pair = self.config.trading_pair.replace("-", "_").lower()
topic = f"{self.config.topic}/{normalized_pair}/ML_SIGNALS"
self._ml_signal_listener = ExternalTopicFactory.create_async(
topic=topic,
callback=self._handle_ml_signal,
use_bot_prefix=False,
)
self.logger().info("ML signal listener initialized successfully")
except Exception as e:
self.logger().error(f"Failed to initialize ML signal listener: {str(e)}")
self._ml_signal_listener = None

def _handle_ml_signal(self, signal: dict, topic: str):
"""Handle incoming ML signal"""
# self.logger().info(f"Received ML signal: {signal}")
short, neutral, long = signal["probabilities"]
if short > self.config.short_threshold:
self.processed_data["signal"] = -1
elif long > self.config.long_threshold:
self.processed_data["signal"] = 1
else:
self.processed_data["signal"] = 0
self.processed_data["features"] = signal

async def update_processed_data(self):
pass

def get_executor_config(self, trade_type: TradeType, price: Decimal, amount: Decimal):
"""
Get the executor config based on the trade_type, price and amount. This method can be overridden by the
subclasses if required.
"""
return PositionExecutorConfig(
timestamp=self.market_data_provider.time(),
connector_name=self.config.connector_name,
trading_pair=self.config.trading_pair,
side=trade_type,
entry_price=price,
amount=amount,
triple_barrier_config=self.config.triple_barrier_config.new_instance_with_adjusted_volatility(
volatility_factor=self.processed_data["features"].get("target_pct", 0.01)),
leverage=self.config.leverage,
)

def to_format_status(self) -> List[str]:
lines = []
features = self.processed_data.get("features", {})
lines.append(f"Signal: {self.processed_data.get('signal', 'N/A')}")
lines.append(f"Timestamp: {features.get('timestamp', 'N/A')}")
lines.append(f"Probabilities: {features.get('probabilities', 'N/A')}")
lines.append(f"Target Pct: {features.get('target_pct', 'N/A')}")
return lines
61 changes: 29 additions & 32 deletions bots/controllers/directional_trading/bollinger_v1.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,53 @@
from typing import List

import pandas_ta as ta # noqa: F401
from hummingbot.client.config.config_data_types import ClientFieldData
from pydantic import Field, field_validator
from pydantic_core.core_schema import ValidationInfo

from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
DirectionalTradingControllerBase,
DirectionalTradingControllerConfigBase,
)
from pydantic import Field, validator


class BollingerV1ControllerConfig(DirectionalTradingControllerConfigBase):
controller_name = "bollinger_v1"
controller_name: str = "bollinger_v1"
candles_config: List[CandlesConfig] = []
candles_connector: str = Field(default=None)
candles_trading_pair: str = Field(default=None)
candles_connector: str = Field(
default=None,
json_schema_extra={
"prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
"prompt_on_new": True})
candles_trading_pair: str = Field(
default=None,
json_schema_extra={
"prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
"prompt_on_new": True})
interval: str = Field(
default="3m",
client_data=ClientFieldData(
prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
prompt_on_new=False))
json_schema_extra={
"prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
"prompt_on_new": True})
bb_length: int = Field(
default=100,
client_data=ClientFieldData(
prompt=lambda mi: "Enter the Bollinger Bands length: ",
prompt_on_new=True))
bb_std: float = Field(
default=2.0,
client_data=ClientFieldData(
prompt=lambda mi: "Enter the Bollinger Bands standard deviation: ",
prompt_on_new=False))
bb_long_threshold: float = Field(
default=0.0,
client_data=ClientFieldData(
prompt=lambda mi: "Enter the Bollinger Bands long threshold: ",
prompt_on_new=True))
bb_short_threshold: float = Field(
default=1.0,
client_data=ClientFieldData(
prompt=lambda mi: "Enter the Bollinger Bands short threshold: ",
prompt_on_new=True))
json_schema_extra={"prompt": "Enter the Bollinger Bands length: ", "prompt_on_new": True})
bb_std: float = Field(default=2.0)
bb_long_threshold: float = Field(default=0.0)
bb_short_threshold: float = Field(default=1.0)

@validator("candles_connector", pre=True, always=True)
def set_candles_connector(cls, v, values):
@field_validator("candles_connector", mode="before")
@classmethod
def set_candles_connector(cls, v, validation_info: ValidationInfo):
if v is None or v == "":
return values.get("connector_name")
return validation_info.data.get("connector_name")
return v

@validator("candles_trading_pair", pre=True, always=True)
def set_candles_trading_pair(cls, v, values):
@field_validator("candles_trading_pair", mode="before")
@classmethod
def set_candles_trading_pair(cls, v, validation_info: ValidationInfo):
if v is None or v == "":
return values.get("trading_pair")
return validation_info.data.get("trading_pair")
return v


Expand Down
Loading