Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hummingbot/hummingbot/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Controllers are the “brain” of V2 strategies. They analyze market data, generate trading signals, and create executor actions without directly managing orders. This separation enables:
  • Reusability: Use the same controller across multiple strategies
  • Testability: Backtest controllers independently
  • Composability: Combine multiple controllers in one strategy

Architecture

ControllerBase Class

All controllers inherit from ControllerBase, which extends RunnableBase.

Source Reference

See strategy_v2/controllers/controller_base.py:58 for the complete implementation.

Configuration

class ControllerConfigBase(BaseClientModel):
    id: str = Field(..., description="Unique identifier")
    controller_name: str
    controller_type: str = "generic"
    total_amount_quote: Decimal = Field(default=Decimal("100"))
    manual_kill_switch: bool = Field(default=False)
Key Fields:
  • id - Unique identifier for this controller instance
  • controller_name - Name matching the controller class module
  • total_amount_quote - Budget allocated to this controller
  • manual_kill_switch - Enable manual shutdown via config update

Controller Types

Hummingbot provides specialized base classes for common strategy patterns:

DirectionalTradingController

For trend-following and momentum strategies

MarketMakingController

For market-making and liquidity provision strategies

DirectionalTradingControllerBase

Optimized for strategies that take directional positions based on market signals. Location: strategy_v2/controllers/directional_trading_controller_base.py Features:
  • Position management for long/short trades
  • Trend detection utilities
  • Entry/exit signal generation

MarketMakingControllerBase

Designed for strategies that provide liquidity on both sides of the order book. Location: strategy_v2/controllers/market_making_controller_base.py Features:
  • Bid/ask spread management
  • Inventory balancing
  • Order refresh logic

Building a Controller

1

Create Config Class

Define parameters for your controller:
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
    DirectionalTradingControllerConfigBase
)

class MyControllerConfig(DirectionalTradingControllerConfigBase):
    controller_name: str = "my_controller"
    connector_name: str = Field("binance")
    trading_pair: str = Field("ETH-USDT")
    # Custom parameters
    signal_threshold: Decimal = Field(Decimal("0.02"))
2

Implement Controller Class

Create the controller logic:
from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
    DirectionalTradingControllerBase
)

class MyController(DirectionalTradingControllerBase):
    def __init__(self, config: MyControllerConfig, *args, **kwargs):
        super().__init__(config, *args, **kwargs)
        self.config = config
    
    async def update_processed_data(self):
        """Process market data and update internal state"""
        # Analyze candles, calculate indicators, etc.
        pass
    
    def determine_executor_actions(self) -> List[ExecutorAction]:
        """Generate trading actions based on signals"""
        actions = []
        # Generate buy/sell signals
        return actions
3

Add Candles Configuration

Define what market data your controller needs:
def __init__(self, config: MyControllerConfig, *args, **kwargs):
    super().__init__(config, *args, **kwargs)
    self.config = config

@property
def candles_configs(self) -> List[CandlesConfig]:
    return [
        CandlesConfig(
            connector=self.config.connector_name,
            trading_pair=self.config.trading_pair,
            interval="1m",
            max_records=500
        )
    ]

Real-World Example: DMan V3

A sophisticated directional trading controller using Bollinger Bands and DCA. Source: controllers/directional_trading/dman_v3.py:1

Configuration

class DManV3ControllerConfig(DirectionalTradingControllerConfigBase):
    controller_name: str = "dman_v3"
    
    # Candles configuration
    candles_connector: str = Field(default=None)
    candles_trading_pair: str = Field(default=None)
    interval: str = Field(default="3m")
    
    # Indicator parameters
    bb_length: int = Field(default=100)
    bb_std: float = Field(default=2.0)
    bb_long_threshold: float = Field(default=0.0)
    bb_short_threshold: float = Field(default=1.0)
    
    # Risk management
    trailing_stop: Optional[TrailingStop] = Field(default="0.015,0.005")
    dca_spreads: List[Decimal] = Field(default="0.001,0.018,0.15,0.25")
    dca_amounts_pct: List[Decimal] = Field(default=None)
    
    # Dynamic features
    dynamic_order_spread: bool = Field(default=None)
    dynamic_target: bool = Field(default=None)
class DManV3Controller(DirectionalTradingControllerBase):
    async def update_processed_data(self):
        """Calculate Bollinger Bands and generate signals"""
        df = self.market_data_provider.get_candles_df(
            connector_name=self.candles_connector,
            trading_pair=self.candles_trading_pair,
            interval=self.config.interval
        )
        
        # Add Bollinger Bands
        df.ta.bbands(
            length=self.config.bb_length,
            std=self.config.bb_std,
            append=True
        )
        
        # Calculate position in BB channel
        current_price = df["close"].iloc[-1]
        bb_lower = df[f"BBL_{self.config.bb_length}_{self.config.bb_std}"].iloc[-1]
        bb_upper = df[f"BBU_{self.config.bb_length}_{self.config.bb_std}"].iloc[-1]
        
        self.bb_percentage = (current_price - bb_lower) / (bb_upper - bb_lower)
    
    def determine_executor_actions(self) -> List[ExecutorAction]:
        """Generate DCA executor actions based on BB signals"""
        if self.bb_percentage <= self.config.bb_long_threshold:
            # Generate LONG signal with DCA levels
            return self.create_dca_executor_action(side=TradeType.BUY)
        elif self.bb_percentage >= self.config.bb_short_threshold:
            # Generate SHORT signal
            return self.create_dca_executor_action(side=TradeType.SELL)
        return []

Lifecycle Methods

Controllers inherit the RunnableBase lifecycle. Key methods to override:

async on_start()

Called once when the controller starts. Use for initialization:
async def on_start(self):
    await super().on_start()
    # Load historical data
    await self.load_historical_candles()
    # Initialize indicators
    self.initialize_indicators()

on_stop()

Called once when the controller stops. Use for cleanup:
def on_stop(self):
    super().on_stop()
    # Save state
    self.save_performance_metrics()
    # Log final statistics
    self.logger().info(f"Controller stopped. Final PnL: {self.net_pnl}")

async control_task()

Called every update_interval seconds. The main logic loop:
async def control_task(self):
    # 1. Update market data
    await self.update_processed_data()
    
    # 2. Generate trading actions
    actions = self.determine_executor_actions()
    
    # 3. Send actions to executor orchestrator
    if actions and self.actions_queue:
        await self.actions_queue.put(actions)

Working with Market Data

Accessing Candles

# Get candles as DataFrame
df = self.market_data_provider.get_candles_df(
    connector_name="binance",
    trading_pair="ETH-USDT",
    interval="1m",
    max_records=100
)

# Access OHLCV data
prices = df["close"]
volumes = df["volume"]
highs = df["high"]

Calculating Indicators

Hummingbot includes pandas_ta for technical analysis:
import pandas_ta as ta

# Add indicators to DataFrame
df.ta.sma(length=20, append=True)      # Simple Moving Average
df.ta.ema(length=50, append=True)      # Exponential Moving Average
df.ta.rsi(length=14, append=True)      # Relative Strength Index
df.ta.macd(append=True)                # MACD
df.ta.bbands(length=20, std=2, append=True)  # Bollinger Bands

# Access indicator values
sma_20 = df["SMA_20"].iloc[-1]  # Latest SMA value
rsi = df["RSI_14"].iloc[-1]     # Latest RSI value

Getting Current Price

# From candles
current_price = df["close"].iloc[-1]

# From order book (more accurate)
from hummingbot.core.data_type.common import PriceType

mid_price = self.market_data_provider.get_price_by_type(
    connector_name="binance",
    trading_pair="ETH-USDT",
    price_type=PriceType.MidPrice
)

Generating Executor Actions

Creating a Position Executor

from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction
from hummingbot.strategy_v2.executors.position_executor.data_types import (
    PositionExecutorConfig,
    TripleBarrierConfig
)

def determine_executor_actions(self) -> List[ExecutorAction]:
    actions = []
    
    if self.should_enter_long():
        config = PositionExecutorConfig(
            timestamp=self.market_data_provider.time(),
            controller_id=self.config.id,
            connector_name=self.config.connector_name,
            trading_pair=self.config.trading_pair,
            side=TradeType.BUY,
            amount=Decimal("0.1"),
            triple_barrier_config=TripleBarrierConfig(
                stop_loss=Decimal("0.02"),      # 2% stop loss
                take_profit=Decimal("0.05"),    # 5% take profit
                time_limit=60 * 60              # 1 hour time limit
            )
        )
        
        actions.append(CreateExecutorAction(
            controller_id=self.config.id,
            executor_config=config
        ))
    
    return actions

Creating a DCA Executor

from hummingbot.strategy_v2.executors.dca_executor.data_types import (
    DCAExecutorConfig,
    DCAMode
)

def create_dca_action(self, side: TradeType) -> CreateExecutorAction:
    config = DCAExecutorConfig(
        timestamp=self.market_data_provider.time(),
        controller_id=self.config.id,
        connector_name=self.config.connector_name,
        trading_pair=self.config.trading_pair,
        side=side,
        amounts_quote=[Decimal("10"), Decimal("20"), Decimal("40")],
        spreads=[Decimal("0.001"), Decimal("0.002"), Decimal("0.003")],
        mode=DCAMode.MAKER,
        take_profit=Decimal("0.03"),
        stop_loss=Decimal("0.02"),
    )
    
    return CreateExecutorAction(
        controller_id=self.config.id,
        executor_config=config
    )

Performance Monitoring

Controllers can track their own performance:
class MyController(DirectionalTradingControllerBase):
    def get_custom_info(self) -> Dict:
        """Return custom metrics for monitoring"""
        return {
            "signal_strength": float(self.current_signal),
            "active_positions": len(self.active_executors),
            "bb_percentage": float(self.bb_percentage),
            "win_rate": self.calculate_win_rate(),
        }
Access from strategy:
controller = self.controllers["controller_id"]
custom_info = controller.get_custom_info()
print(f"Signal strength: {custom_info['signal_strength']}")

Configuration Management

Loading from YAML

Controllers are typically configured via YAML files:
controller_type: directional_trading
controller_name: dman_v3
connector_name: binance
trading_pair: ETH-USDT
total_amount_quote: 100
bb_length: 100
bb_std: 2.0
interval: 3m
Load in strategy config:
class MyStrategyConfig(StrategyV2ConfigBase):
    controllers_config: List[str] = ["my_controller.yml"]

Dynamic Updates

Controllers support runtime configuration updates:
# Update via manual_kill_switch
controller.config.manual_kill_switch = True  # Will stop on next tick

# Update other parameters (if marked is_updatable)
controller.config.total_amount_quote = Decimal("200")

Best Practices

Store state in executors, not controllers. Controllers should generate actions based on current market data, not maintain complex internal state.
Request only the data you need:
CandlesConfig(
    connector="binance",
    trading_pair="ETH-USDT",
    interval="1m",
    max_records=100  # Don't request more than needed
)
Always check for sufficient data before generating signals:
if len(df) < self.config.bb_length:
    self.logger().warning("Insufficient data for BB calculation")
    return []
Make debugging easier with clear logging:
self.logger().info(
    f"LONG signal generated: BB%={self.bb_percentage:.2%}, "
    f"Threshold={self.config.bb_long_threshold}"
)

Testing Controllers

Controllers can be tested independently using the backtesting engine:
from hummingbot.strategy_v2.backtesting.backtesting_engine_base import BacktestingEngineBase

engine = BacktestingEngineBase()
config = MyControllerConfig(
    id="test_1",
    connector_name="binance",
    trading_pair="ETH-USDT",
    # ... parameters
)

results = await engine.run_backtesting(
    controller_config=config,
    start=start_timestamp,
    end=end_timestamp,
    backtesting_resolution="1m"
)
See Backtesting for more details.

Next Steps

Executors

Learn how executors handle order execution

Backtesting

Test your controllers with historical data

Strategy V2

Understand the complete V2 framework

Custom Scripts

Simpler alternative for basic strategies