MACD and Bollinger Band Position Signals for Directional Trading
Summary
This controller creates directional signals by combining Bollinger Band position with MACD level and histogram direction. It computes Bollinger Bands and MACD from a configurable candle interval, then emits a long signal when the band-position value is below its long threshold, the MACD histogram is positive, and MACD itself is negative. A short signal requires band position above its short threshold, a negative histogram, and positive MACD. Otherwise, it records no directional signal. The defaults use a three-minute interval, 100-period bands with two standard deviations, and MACD periods of 21, 42, and 9.
The code describes signal generation and candle-data configuration, not a complete trading system: it does not specify entries, exits, position sizing, or execution behavior. No backtest results or asset-specific evidence are provided. The defaults and threshold values are configurable, so their suitability and signal frequency depend on the market and data interval; evaluation would need to include out-of-sample testing and trading costs.
Key ideas
- The controller combines Bollinger Band position with MACD and histogram signs.
- Long signals require low band position, positive MACD histogram, and negative MACD.
- Short signals require high band position, negative histogram, and positive MACD.
- The controller records a signal and candle features but does not define execution or exits.
- No backtest evidence is provided, so thresholds and defaults need empirical evaluation.
Tags
Full text
# MACDBBV1Controller
# MACDBBV1Controller
## Source (Apache-2.0)
```python
from typing import List
import pandas_ta as ta # noqa: F401
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,
)
class MACDBBV1ControllerConfig(DirectionalTradingControllerConfigBase):
controller_name: str = "macd_bb_v1"
candles_connector: str = Field(
default=None,
json_schema_extra={
"prompt": "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",
"prompt_on_new": True})
candles_trading_pair: str = Field(
default=None,
json_schema_extra={
"prompt": "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",
"prompt_on_new": True})
interval: str = Field(
default="3m",
json_schema_extra={
"prompt": "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ",
"prompt_on_new": True})
bb_length: int = Field(
default=100,
json_schema_extra={"prompt": "Enter the Bollinger Bands length: ", "prompt_on_new": True})
bb_std: float = Field(default=2.0)
bb_long_threshold: float = Field(default=0.0)
bb_short_threshold: float = Field(default=1.0)
macd_fast: int = Field(
default=21,
json_schema_extra={"prompt": "Enter the MACD fast period: ", "prompt_on_new": True})
macd_slow: int = Field(
default=42,
json_schema_extra={"prompt": "Enter the MACD slow period: ", "prompt_on_new": True})
macd_signal: int = Field(
default=9,
json_schema_extra={"prompt": "Enter the MACD signal period: ", "prompt_on_new": True})
@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
class MACDBBV1Controller(DirectionalTradingControllerBase):
def __init__(self, config: MACDBBV1ControllerConfig, *args, **kwargs):
self.config = config
self.max_records = max(config.macd_slow, config.macd_fast, config.macd_signal, config.bb_length) + 20
super().__init__(config, *args, **kwargs)
async def update_processed_data(self):
df = self.market_data_provider.get_candles_df(connector_name=self.config.candles_connector,
trading_pair=self.config.candles_trading_pair,
interval=self.config.interval,
max_records=self.max_records)
# Add indicators
df.ta.bbands(length=self.config.bb_length, lower_std=self.config.bb_std, upper_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}_{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
# Update processed data
self.processed_data["signal"] = df["signal"].iloc[-1]
self.processed_data["features"] = df
def get_candles_config(self) -> List[CandlesConfig]:
return [CandlesConfig(
connector=self.config.candles_connector,
trading_pair=self.config.candles_trading_pair,
interval=self.config.interval,
max_records=self.max_records
)]
```Shown in full with attribution under the source's licence. Licence: Apache-2.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.