D-Man Maker V2: Layered Maker Orders with Dollar-Cost Averaging
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.