Skip to content

Commit

Permalink
Merge pull request #73 from hummingbot/feat/simplify_strategies
Browse files Browse the repository at this point in the history
Feat/simplify strategies
  • Loading branch information
cardosofede authored Oct 3, 2023
2 parents b5646cb + c6b7202 commit 7b5220d
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import time
from typing import Optional

import pandas as pd
Expand All @@ -9,18 +10,17 @@
from pydantic import Field


class BollingerConf(DirectionalTradingControllerConfigBase):
strategy_name = "bollinger"
bb_length: int = Field(default=100, ge=2, le=1000)
bb_std: float = Field(default=2.0, ge=0.5, le=4.0)
bb_long_threshold: float = Field(default=0.0, ge=-3.0, le=0.5)
bb_short_threshold: float = Field(default=1.0, ge=0.5, le=3.0)
std_span: Optional[int] = Field(default=100, ge=100, le=400)
class BollingerV1Conf(DirectionalTradingControllerConfigBase):
strategy_name = "bollinger_v1"
bb_length: int = Field(default=100, ge=100, le=400)
bb_std: float = Field(default=2.0, ge=2.0, le=3.0)
bb_long_threshold: float = Field(default=0.0, ge=-1.0, le=0.2)
bb_short_threshold: float = Field(default=1.0, ge=0.8, le=2.0)


class Bollinger(DirectionalTradingControllerBase):
class BollingerV1(DirectionalTradingControllerBase):

def __init__(self, config: BollingerConf):
def __init__(self, config: BollingerV1Conf):
super().__init__(config)
self.config = config

Expand Down Expand Up @@ -54,8 +54,7 @@ def get_processed_data(self) -> pd.DataFrame:
df["signal"] = 0
df.loc[long_condition, "signal"] = 1
df.loc[short_condition, "signal"] = -1

# Optional: Generate spread multiplier
if self.config.std_span:
df["target"] = df["close"].rolling(self.config.std_span).std() / df["close"]
return df

def extra_columns_to_show(self):
return [f"BBP_{self.config.bb_length}_{self.config.bb_std}"]
69 changes: 69 additions & 0 deletions quants_lab/controllers/macd_bb_v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import time
from typing import Optional

import pandas as pd
from pydantic import Field

from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor
from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel
from hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_controller_base import (
DirectionalTradingControllerBase,
DirectionalTradingControllerConfigBase,
)


class MACDBBV1Config(DirectionalTradingControllerConfigBase):
strategy_name: str = "macd_bb_v1"
bb_length: int = Field(default=24, ge=100, le=200)
bb_std: float = Field(default=2.0, ge=2.0, le=3.0)
bb_long_threshold: float = Field(default=0.0, ge=-1.0, le=0.2)
bb_short_threshold: float = Field(default=1.0, ge=0.8, le=2.0)
macd_fast: int = Field(default=21, ge=12, le=60)
macd_slow: int = Field(default=42, ge=26, le=200)
macd_signal: int = Field(default=9, ge=8, le=20)


class MACDBBV1(DirectionalTradingControllerBase):

def __init__(self, config: MACDBBV1Config):
super().__init__(config)
self.config = config

def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool:
"""
If an executor has an active position, should we close it based on a condition.
"""
return False

def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool:
"""
After finishing an order, the executor will be in cooldown for a certain amount of time.
This prevents the executor from creating a new order immediately after finishing one and execute a lot
of orders in a short period of time from the same side.
"""
if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time():
return True
return False

def get_processed_data(self) -> pd.DataFrame:
df = self.candles[0].candles_df

# Add indicators
df.ta.bbands(length=self.config.bb_length, std=self.config.bb_std, append=True)
df.ta.macd(fast=self.config.macd_fast, slow=self.config.macd_slow, signal=self.config.macd_signal, append=True)
bbp = df[f"BBP_{self.config.bb_length}_{self.config.bb_std}"]
macdh = df[f"MACDh_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"]
macd = df[f"MACD_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"]

# Generate signal
long_condition = (bbp < self.config.bb_long_threshold) & (macdh > 0) & (macd < 0)
short_condition = (bbp > self.config.bb_short_threshold) & (macdh < 0) & (macd > 0)
df["signal"] = 0
df.loc[long_condition, "signal"] = 1
df.loc[short_condition, "signal"] = -1
return df

def extra_columns_to_show(self):
return [f"BBP_{self.config.bb_length}_{self.config.bb_std}",
f"MACDh_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}",
f"MACD_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"]
66 changes: 66 additions & 0 deletions quants_lab/controllers/trend_follower_v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import time
from typing import Optional

import pandas as pd
from pydantic import Field

from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor
from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel
from hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_controller_base import (
DirectionalTradingControllerBase,
DirectionalTradingControllerConfigBase,
)


class TrendFollowerV1Config(DirectionalTradingControllerConfigBase):
strategy_name: str = "trend_follower_v1"
sma_fast: int = Field(default=20, ge=10, le=150)
sma_slow: int = Field(default=100, ge=50, le=400)
bb_length: int = Field(default=100, ge=100, le=200)
bb_std: float = Field(default=2.0, ge=2.0, le=3.0)
bb_threshold: float = Field(default=0.2, ge=0.1, le=0.5)


class TrendFollowerV1(DirectionalTradingControllerBase):

def __init__(self, config: TrendFollowerV1Config):
super().__init__(config)
self.config = config

def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool:
# If an executor has an active position, should we close it based on a condition. This feature is not available
# for the backtesting yet
return False

def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool:
# After finishing an order, the executor will be in cooldown for a certain amount of time.
# This prevents the executor from creating a new order immediately after finishing one and execute a lot
# of orders in a short period of time from the same side.
if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time():
return True
return False

def get_processed_data(self) -> pd.DataFrame:
df = self.candles[0].candles_df
df.ta.sma(length=self.config.sma_fast, append=True)
df.ta.sma(length=self.config.sma_slow, append=True)
df.ta.bbands(length=self.config.bb_length, std=2.0, append=True)


# Generate long and short conditions
bbp = df[f"BBP_{self.config.bb_length}_2.0"]
inside_bounds_condition = (bbp < 0.5 + self.config.bb_threshold) & (bbp > 0.5 - self.config.bb_threshold)

long_cond = (df[f'SMA_{self.config.sma_fast}'] > df[f'SMA_{self.config.sma_slow}'])
short_cond = (df[f'SMA_{self.config.sma_fast}'] < df[f'SMA_{self.config.sma_slow}'])

# Choose side
df['signal'] = 0
df.loc[long_cond & inside_bounds_condition, 'signal'] = 1
df.loc[short_cond & inside_bounds_condition, 'signal'] = -1
return df

def extra_columns_to_show(self):
return [f"BBP_{self.config.bb_length}_{self.config.bb_std}",
f"SMA_{self.config.sma_fast}",
f"SMA_{self.config.sma_slow}"]
6 changes: 3 additions & 3 deletions utils/file_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def get_optuna_suggest_str(field_name: str, properties: Dict):
if field_name == "candles_config":
return f"""{field_name}=[
CandlesConfig(connector=exchange, trading_pair=trading_pair,
interval="3m", max_records=1000000) # Max number of candles for the real-time bot,
interval="1h", max_records=1000000)
]"""
if field_name == "strategy_name":
return f"{field_name}='{properties.get('default', '_')}'"
Expand Down Expand Up @@ -111,8 +111,8 @@ def strategy_optimization_template(strategy_info: dict):
def objective(trial):
try:
# General configuration for the backtesting
exchange = trial.suggest_categorical('exchange', ['binance_perpetual', ])
trading_pair = trial.suggest_categorical('trading_pair', ['BTC-USDT', ])
exchange = "binance_perpetual"
trading_pair = "BTC-USDT"
start = "2023-01-01"
end = "2023-08-01"
initial_portfolio_usd = 1000.0
Expand Down

0 comments on commit 7b5220d

Please sign in to comment.