Skip to content
All library documents

D-Man Maker V2: Layered Maker Orders with Dollar-Cost Averaging

Article Strategy library · Author: hummingbot

Summary

D-Man Maker V2 configures a market-making controller that places maker-side dollar-cost-averaging orders at progressively wider price offsets. For buys, levels are set below the reference price; for sells, they are set above it. Configurable spreads and quote amounts determine the ladder, with each level’s amount normalized as a share of the total configured amounts.

The controller can stop and refresh active, non-trading executors after the general refresh interval, and can use a separate refresh interval for the top level. Its executor configuration also passes through time limits, stop loss, take profit, trailing stop, activation bounds, and leverage. The defaults provide example spread and amount ladders, but the document supplies no market rationale, backtest results, or evidence that these settings are profitable. The code is configuration and execution logic; outcomes will depend on market conditions and the surrounding controller and executor behavior.

Key ideas

  • The strategy creates maker DCA orders at configured price offsets around a reference price.
  • Buy levels are placed below the reference price, while sell levels are placed above it.
  • Configured DCA amounts are normalized into proportions of the total amount.
  • Executors can be refreshed using general and optional top-level timing rules.
  • Time limits, stop loss, take profit, trailing stop, activation bounds, and leverage are configurable inputs.

Tags

Full text
# DManMakerV2


# DManMakerV2









Configuration required to run the D-Man Maker V2 strategy.

## Source (Apache-2.0)

```python
from decimal import Decimal
from typing import List, Optional

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

from hummingbot.core.data_type.common import TradeType
from hummingbot.strategy_v2.controllers.market_making_controller_base import (
    MarketMakingControllerBase,
    MarketMakingControllerConfigBase,
)
from hummingbot.strategy_v2.executors.dca_executor.data_types import DCAExecutorConfig, DCAMode
from hummingbot.strategy_v2.models.executor_actions import ExecutorAction, StopExecutorAction


class DManMakerV2Config(MarketMakingControllerConfigBase):
    """
    Configuration required to run the D-Man Maker V2 strategy.
    """
    controller_name: str = "dman_maker_v2"

    # DCA configuration
    dca_spreads: List[Decimal] = Field(
        default="0.01,0.02,0.04,0.08",
        json_schema_extra={"prompt": "Enter a comma-separated list of spreads for each DCA level: ", "prompt_on_new": True})
    dca_amounts: List[Decimal] = Field(
        default="0.1,0.2,0.4,0.8",
        json_schema_extra={"prompt": "Enter a comma-separated list of amounts for each DCA level: ", "prompt_on_new": True})
    top_executor_refresh_time: Optional[float] = Field(default=None, json_schema_extra={"is_updatable": True})
    executor_activation_bounds: Optional[List[Decimal]] = Field(default=None, json_schema_extra={"is_updatable": True})

    @field_validator("executor_activation_bounds", mode="before")
    @classmethod
    def parse_activation_bounds(cls, v):
        if isinstance(v, list):
            return [Decimal(val) for val in v]
        elif isinstance(v, str):
            if v == "":
                return None
            return [Decimal(val) for val in v.split(",")]
        return v

    @field_validator('dca_spreads', mode="before")
    @classmethod
    def parse_dca_spreads(cls, v):
        if v is None:
            return []
        if isinstance(v, str):
            if v == "":
                return []
            return [float(x.strip()) for x in v.split(',')]
        return v

    @field_validator('dca_amounts', mode="before")
    @classmethod
    def parse_and_validate_dca_amounts(cls, v, validation_info):
        if v is None or v == "":
            return [1 for _ in validation_info.data['dca_spreads']]
        if isinstance(v, str):
            return [float(x.strip()) for x in v.split(',')]
        elif isinstance(v, list) and len(v) != len(validation_info.data['dca_spreads']):
            raise ValueError(
                f"The number of dca amounts must match the number of {validation_info.data['dca_spreads']}.")
        return v


class DManMakerV2(MarketMakingControllerBase):
    def __init__(self, config: DManMakerV2Config, *args, **kwargs):
        super().__init__(config, *args, **kwargs)
        self.config = config
        self.dca_amounts_pct = [Decimal(amount) / sum(self.config.dca_amounts) for amount in self.config.dca_amounts]
        self.spreads = self.config.dca_spreads

    def first_level_refresh_condition(self, executor):
        if self.config.top_executor_refresh_time is not None:
            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
        return False

    def order_level_refresh_condition(self, executor):
        return self.market_data_provider.time() - executor.timestamp > self.config.executor_refresh_time

    def executors_to_refresh(self) -> List[ExecutorAction]:
        executors_to_refresh = self.filter_executors(
            executors=self.executors_info,
            filter_func=lambda x: not x.is_trading and x.is_active and (self.order_level_refresh_condition(x) or self.first_level_refresh_condition(x)))
        return [StopExecutorAction(
            controller_id=self.config.id,
            executor_id=executor.id) for executor in executors_to_refresh]

    def get_executor_config(self, level_id: str, price: Decimal, amount: Decimal):
        trade_type = self.get_trade_type_from_level_id(level_id)
        if trade_type == TradeType.BUY:
            prices = [price * (1 - spread) for spread in self.spreads]
        else:
            prices = [price * (1 + spread) for spread in self.spreads]
        amounts = [amount * pct for pct in self.dca_amounts_pct]
        amounts_quote = [amount * price for amount, price in zip(amounts, prices)]
        return DCAExecutorConfig(
            timestamp=self.market_data_provider.time(),
            connector_name=self.config.connector_name,
            trading_pair=self.config.trading_pair,
            mode=DCAMode.MAKER,
            side=trade_type,
            prices=prices,
            amounts_quote=amounts_quote,
            level_id=level_id,
            time_limit=self.config.time_limit,
            stop_loss=self.config.stop_loss,
            take_profit=self.config.take_profit,
            trailing_stop=self.config.trailing_stop,
            activation_bounds=self.config.executor_activation_bounds,
            leverage=self.config.leverage,
        )

```

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.