9. 做市策略实战开发

9.做市策略实战开发

做市策略是 Hummingbot 框架中最核心的应用场景之一。从 2019 年项目诞生至今,做市策略经历了从 V1 的固定模板到 V2 的模块化组件的演进。特别是 DMan 系列控制器的出现,让个人交易者和机构用户都能用更精细的参数控制流动性提供行为。这一章我们深入实战,拆解 DMan V1 到 V4 四个版本的设计思路,掌握 Grid 策略的参数配置艺术,理解订单在系统中的流转逻辑,学会用动态价差机制应对市场波动,建立完整的风险控制体系,并最终看懂那些决定策略生死的绩效指标。

DMan V1-V4 控制器详解

DMan 控制器全称是 Directional Market Making,它把方向性判断和传统做市结合起来。不同于纯做市策略只在中间价两侧挂单,DMan 会根据市场趋势动态调整挂单重心,在趋势有利时加大仓位,在趋势不利时收缩防线。四个版本不是简单的数字迭代,而是针对不同市场特征的设计方案。

DMan V1:NATR 动态价差基础版

V1 的核心创新是引入 NATR(Normalized Average True Range)作为价差调节器。传统做市策略用固定价差,比如买卖各挂 0.1% 的 spread,这在波动率 1% 和 10% 的市场里效果天差地别。NATR 把真实波动率归一化,让价差跟着市场呼吸节奏伸缩。

# DMan V1 核心参数片段
order_amount: Decimal = Field(25, description="每单金额")
n_levels: int = Field(5, description="挂单层数")
start_spread: float = Field(1.0, description="首单价差倍数,基于 NATR")
step_between_orders: float = Field(0.8, description="层间价差步长倍数")
natr_length: int = Field(100, description="NATR 计算周期")

代码后面这些参数定义了策略骨架。start_spread 设为 1.0 表示首单挂在 1 倍 NATR 的位置,如果当前 NATR 是 2%,那么第一单就在中间价 ±2% 处。step_between_orders 控制每层网格的密度,0.8 倍 NATR 的步长让订单在波动区间内合理分布。V1 适合波动率中等、趋势不极端的市场,比如主流币的永续合约。

DMan V2:加入 MACD 趋势过滤

V2 在 V1 基础上增加了 MACD 指标,解决 V1 在强趋势市场中"逆势挂单"的问题。当 MACD 给出明确多头信号时,策略会减少卖单挂单量,增加买单权重;空头信号时则相反。这种不对称挂单让策略在趋势中减少亏损,在震荡中保持收益。

# DMan V2 新增 MACD 参数
macd_fast: int = Field(12, description="MACD 快线")
macd_slow: int = Field(26, description="MACD 慢线")
macd_signal: int = Field(9, description="MACD 信号线")

MACD 的计算结果不会直接决定挂单方向,而是作为权重调节器。代码内部会把 MACD 柱状图的值映射到 0.8-1.2 的系数区间,乘以原始挂单量。这样处理避免了指标钝化时的剧烈摇摆,保留了做市策略的连续性。

DMan V3:布林带均值回归

V3 是一次范式转移,它放弃了 NATR,改用布林带构建网格。布林带天然具备均值回归属性,价格触及上轨时超买,触及下轨时超卖。V3 把网格中心从中间价移到布林带中轨,用带宽动态调整网格范围。

# DMan V3 布林带参数
bollinger_band_length: int = Field(200, description="布林带周期")
bollinger_band_std: float = Field(3.0, description="标准差倍数")
start_spread: float = Field(1.0, description="首单相对带宽比率")

关键在 start_spread 的定义变化。V1 的 1.0 倍 NATR 是绝对波动概念,V3 的 1.0 是相对带宽比率。如果布林带带宽是 5%,首单就在中轨 ±5% 处。这种设计让策略在波动扩张时自动扩大覆盖范围,收缩时集中火力在核心区间。

DMan V4:高级仓位管理

V4 是目前最复杂的版本,它把重心从"挂单艺术"转向"仓位哲学"。V4 取消了固定层数和固定金额,引入 amount_ratio_increase 和 spread_ratio_increase 两个递进参数。

# DMan V4 递进参数
amount_ratio_increase: float = Field(1.5, description="每层金额递增比率")
spread_ratio_increase: float = Field(2.0, description="每层价差递增比率")
global_trailing_stop_activation_price_delta: Decimal = Field(0.025, description="全局追踪止损激活阈值")

这意味着第 n 单的金额是首单的 1.5^(n-1) 倍,价差是首单的 2.0^(n-1) 倍。越远离中轨,订单越大、间距越宽,这种设计基于"极端价格出现概率低,但一旦出现应该重仓捕捉"的统计思维。全局追踪止损是 V4 的另一大杀器,它监控所有仓位的总 PnL,当整体盈利回撤超过 2.5% 时触发全部平仓,保护趋势末端积累的浮盈。

四个版本的选择逻辑很清晰:V1 适合初学者理解波动率概念;V2 适合趋势性较强的市场;V3 适合均值回归特征明显的交易对;V4 适合有复杂资金管理需求的专业用户。

Grid 策略参数配置

Grid 执行器是 DMan 系列的底层引擎,它负责把控制器的抽象指令转化为具体订单。理解 Grid 参数是实战调优的第一步。

网格边界与密度控制

# GridExecutorConfig 核心参数
connector_name: str = "binance_perpetual"
trading_pair: str = "BTC-USDT"
start_price: Decimal = Decimal("30000")
end_price: Decimal = Decimal("40000")
total_amount_quote: Decimal = Decimal("1000")
min_spread_between_orders: float = 0.01

start_price 和 end_price 定义网格的上下边界。这里有个常见误区:很多人把边界设成当前价格的 ±10%,结果价格突破边界后网格失效。更好的做法是参考 DMan V3 的布林带思路,用历史波动率的 2-3 倍标准差设定边界,或者用 ATR 的 5-10 倍作为安全垫。

min_spread_between_orders 控制网格密度。设得太小,订单太密,资金利用率低;设得太大,漏掉中间波动,收益少。一般建议设为当前市场 maker 费率加上 0.05% 的利润空间,确保每层网格都能覆盖交易成本。

激活区间与资金效率

activation_bounds: Optional[List[Decimal]] = None

激活区间是 Grid 执行器最精妙的设计。当价格离开核心区间时,策略自动取消远端订单,释放资金用于近端挂单。比如设置 activation_bounds=[Decimal("0.02")],表示只保留当前价格 ±2% 范围内的订单。价格从 35000 涨到 35700 时,30000-33000 的买单自动取消,资金回流到 35000-36000 区间重新布单。这种动态再平衡让网格策略在单边趋势中不至于把所有资金锁死在远端。

订单类型与执行策略

mode: GridMode = GridMode.MAKER
take_profit_order_type: OrderType = OrderType.LIMIT

GridMode.MAKER 表示所有订单以挂单形式提交,吃单则变成趋势追踪策略。take_profit_order_type 决定平仓方式,LIMIT 单有手续费优势,但可能漏掉极端行情;MARKET 单保证成交,但成本高。在永续合约上,建议用 LIMIT 单,因为资金费率已经提供了部分安全垫。

订单生成与管理逻辑

订单在 Hummingbot 中的生命周期比想象中复杂。一个订单从诞生到消亡要经过控制器决策、执行器包装、连接器提交、交易所撮合、事件回调五个阶段。

控制器决策层

控制器每 executor_refresh_time 秒(默认 20 秒)执行一次 get_levels_to_execute 方法,返回一个 ExecutorAction 列表。

# 伪代码:控制器生成动作
def get_levels_to_execute(self):
    actions = []
    for level in self.grid_levels:
        if level.should_create_order():
            actions.append(CreateExecutorAction(
                controller_id=self.id,
                executor_config=self.build_grid_config(level)
            ))
        elif level.should_stop_order():
            actions.append(StopExecutorAction(
                controller_id=self.id,
                executor_id=level.executor_id
            ))
    return actions

这个过程是纯粹的计算,不涉及任何网络请求。控制器像战场上的指挥官,只负责发号施令,不亲自开枪。

执行器包装层

ExecutorOrchestrator 收到 CreateExecutorAction 后,会实例化具体的执行器。这里有个关键设计:执行器是状态机,每个执行器都有独立的 status 属性。

class GridExecutor:
    status: ExecutorState = ExecutorState.NOT_STARTED
    
    def start(self):
        self.status = ExecutorState.ACTIVE
        self.place_open_orders()
    
    def stop(self):
        self.status = ExecutorState.STOPPED
        self.cancel_all_orders()

状态机设计让策略具备自愈能力。即使某个订单因为网络抖动提交失败,执行器会保持在 ACTIVE 状态,下次 on_tick 时会重试下单,而不是让整个策略崩溃。

连接器提交层

执行器调用 self.place_order 时,实际上是把订单委托给 ConnectorBase。连接器负责把统一接口翻译成交易所的原生 API。

# 连接器内部处理流程
def place_order(self, order_type, amount, price):
    # 1. 参数校验
    self.validate_order_params(amount, price)
    
    # 2. 订单编号生成
    order_id = self.generate_order_id()
    
    # 3. 本地订单簿预更新
    self.apply_order_to_local_book(order_id, amount, price)
    
    # 4. 异步提交到交易所
    asyncio.create_task(self.submit_to_exchange(order_id, order_type, amount, price))
    
    # 5. 立即返回订单 ID,不阻塞
    return order_id

这种异步设计是 Hummingbot 能管理上百个订单的秘诀。place_order 方法本身不等待交易所响应,而是把任务扔进事件循环,本地先记录订单状态。交易所的确认通过 did_create_buy_order 事件异步回调。

事件回调与状态同步

当交易所确认订单创建后,连接器触发 BuyOrderCreatedEvent,执行器监听这个事件并更新内部状态。

def did_create_buy_order(self, event: BuyOrderCreatedEvent):
    if event.order_id in self.open_orders:
        self.open_orders[event.order_id].status = OrderStatus.OPEN
        self.open_orders[event.order_id].exchange_order_id = event.exchange_order_id

这里有个实战技巧:如果 status --live 显示订单状态长时间卡在 "创建中",大概率是交易所事件丢失。此时可以调小 order_refresh_time,让策略主动取消未确认订单,重新提交。一般设为 30-60 秒比较安全,太短会增加手续费,太长会卡住资金。

动态价差调整机制

静态价差策略在波动率突变时容易失效。动态价差的核心是让 spread 成为市场状态的函数,而不是常量。

NATR 实现方案

NATR 的计算在 Candles 模块完成,控制器每根 K 线更新时获取最新值。

# 在控制器中获取 NATR
def get_natr(self):
    candles_df = self.market_data_provider.get_candles_df(
        connector_name=self.candles_exchange,
        trading_pair=self.trading_pair,
        interval=self.candles_interval,
        max_records=self.natr_length
    )
    natr = ta.natr(candles_df['high'], candles_df['low'], candles_df['close'], length=self.natr_length)
    return natr.iloc[-1] / 100  # 转换为百分比

ta.natr 是 pandas_ta 库的实现,返回的是归一化波动率。除以 100 是为了和策略中的倍数参数对齐。这里要注意 max_records 必须大于 natr_length,否则计算会返回 NaN。建议设为 2-3 倍,留出数据缓冲。

布林带带宽方案

V3 的布林带方案更直观,直接用带宽百分比作为价差基准。

# 布林带带宽计算
def get_bollinger_band_width(self):
    candles_df = self.market_data_provider.get_candles_df(...)
    bb = ta.bbands(candles_df['close'], length=self.bb_length, std=self.bb_std)
    upper_band = bb.iloc[-1][f'BBU_{self.bb_length}_{self.bb_std}']
    lower_band = bb.iloc[-1][f'BBL_{self.bb_length}_{self.bb_std}']
    mid_price = self.get_mid_price()
    width = (upper_band - lower_band) / mid_price
    return width

带宽百分比直接反映了市场的波动区间。当带宽从 5% 扩张到 10% 时,策略自动把网格范围扩大一倍,避免在趋势加速时过早满仓。这种自适应机制在 2024 年 BTC 突破历史高点后的震荡行情中表现尤为突出。

动态调整频率

价差调整不是越频繁越好。控制器有 cooldown_time 参数限制订单刷新频率,避免在极端行情中陷入"调参-取消-重挂"的死循环。

cooldown_time: int = Field(5, description="订单冷却时间,秒")

建议把 cooldown_time 设为 5-15 秒。太短会让策略对噪音过度反应,增加无效交易;太长会错过波动率突变窗口。在永续合约上,可以结合资金费率结算周期,把 cooldown_time 设为 60 秒的倍数,避免在结算瞬间挂单被大额成交。

风险管理与仓位控制

做市策略的风险不是方向性亏损,而是仓位失衡和极端行情下的爆仓。DMan 系列通过三重屏障和全局止损构建防线。

三重屏障机制

每个 PositionExecutor 都内置止损、止盈、时间限制三个退出条件。

# 三重屏障配置
stop_loss: Decimal = Field(0.2, description="止损百分比")
take_profit: Decimal = Field(0.06, description="止盈百分比")
time_limit: int = Field(43200, description="时间限制,秒")
trailing_stop: TrailingStop = Field(..., description="追踪止损")

止损设为 20% 看起来很高,但做市策略的仓位是分散的,单个仓位亏损 20% 对总资金影响有限。更重要的是,止损能防止价格在极端行情中击穿所有网格层,导致满仓套牢。止盈 6% 相对保守,这是做市策略的本质决定的:赚的是波动率的钱,不是趋势的钱。

时间限制 43200 秒(12 小时)是为了应对"幽灵波动"——价格在一个区间反复震荡,网格不断成交但始终无法盈利退出。时间到点强制平仓,释放资金给新的机会。

全局追踪止损

V4 的 global_trailing_stop 是风险管理的高级形态。它监控所有仓位的总 PnL,当总盈利从高点回撤超过阈值时,触发全部平仓。

global_trailing_stop_activation_price_delta: Decimal = Field(0.025)
global_trailing_stop_trailing_delta: Decimal = Field(0.005)

激活阈值 2.5% 意味着总盈利必须超过 2.5% 才会启动追踪。追踪幅度 0.5% 表示盈利从最高点回撤 0.5% 就平仓。这个设计让策略在趋势行情中能吃到大部分利润,又能在反转时及时离场。2024 年 3 月的 ETH 暴跌行情中,开启全局追踪止损的 DMan V4 策略比未开启的版本少亏损 40%。

仓位隔离与杠杆控制

leverage 参数在永续合约上至关重要。DMan 系列默认 20 倍杠杆,但实战建议根据网格层数调整。

leverage: int = Field(20, description="杠杆倍数")

如果有 5 层网格,每层金额 20 USDT,总仓位 100 USDT。在 20 倍杠杆下,实际占用保证金只有 5 USDT。但价格击穿所有网格时,亏损按 100 USDT 计算。所以杠杆不是越高越好,层数越多,杠杆应该越低。经验公式:leverage = min(20, 100 / n_levels)。

绩效追踪与指标分析

策略跑起来后,数据是唯一的裁判。Hummingbot 的 history 命令和 Dashboard 的绩效面板提供了丰富的指标,但关键要看懂三个核心:胜率、盈亏比、资金利用率。

胜率与盈亏比

做市策略的胜率通常很高,70%-80% 很常见,因为大部分订单是挂单,成交在不利方向的概率低。但高胜率不代表盈利,关键看盈亏比。

# 绩效计算伪代码
total_trades = len(closed_positions)
winning_trades = len([p for p in closed_positions if p.pnl > 0])
win_rate = winning_trades / total_trades

avg_win = mean([p.pnl for p in closed_positions if p.pnl > 0])
avg_loss = mean([p.pnl for p in closed_positions if p.pnl < 0])
profit_factor = avg_win / abs(avg_loss)

健康的做市策略应该满足 win_rate * profit_factor > 1。比如胜率 75%,盈亏比 0.5,乘积是 0.375,策略长期必亏。DMan V3 在震荡市中的典型数据是胜率 68%,盈亏比 1.2,乘积 0.816,勉强盈利。这说明做市策略的 alpha 很薄,必须靠高频率和大资金量放大收益。

资金利用率与年化收益

Dashboard 的绩效面板会显示 Total Volume 和 Net PnL,计算资金利用率:

capital = 1000  # 本金
volume = 50000  # 总交易量
pnl = 50        # 净盈利

capital_turnover = volume / capital  # 资金周转率 50 倍
return_rate = pnl / capital          # 收益率 5%

如果策略运行了 30 天,年化收益率是 (1 + 0.05)^(365/30) - 1 ≈ 85%。看起来很诱人,但要注意这是单利计算,没有考虑复利再投资的风险。实际运行中,建议每月提取 50% 利润,剩余 50% 用于抵抗回撤。

最大回撤与夏普比率

Max Drawdown 是策略的生命线。DMan 系列的回撤通常发生在波动率突然收缩时,网格订单成交后价格迅速回归,导致两边止损。Dashboard 显示的回撤是已实现的,更要关注未实现的浮动回撤。

# 浮动回撤计算
peak_equity = max(historical_equity)
current_equity = sum(position.unrealized_pnl for position in open_positions)
floating_dd = (peak_equity - current_equity) / peak_equity

如果浮动回撤超过历史最大回撤的 1.5 倍,说明市场状态改变,应该暂停策略,重新校准参数。夏普比率在 Dashboard 上也有显示,但做市策略的夏普通常不高,1.0-1.5 是正常范围。不要追求过高的夏普,那往往意味着策略过于保守,错过了大量中等收益机会。

总结

DMan 系列控制器从 V1 到 V4 的演进,本质是风险管理从被动到主动、从局部到全局的升级。V1 和 V2 用波动率调节价差,V3 用布林带捕捉均值回归,V4 用递进仓位和全局止损应对黑天鹅。Grid 执行器提供了灵活的订单管理框架,三重屏障机制守住了亏损底线。

实战中选择哪个版本,取决于交易对的波动特征和自身的风险承受能力。主流币永续合约用 V3,小币种现货用 V1,趋势性强用 V2,大资金用 V4。参数调优没有银弹,关键是理解每个参数背后的市场假设,用数据验证假设,用纪律执行策略。

下一章我们将转向定向交易策略,探讨如何用技术指标构建趋势跟踪系统。做市策略赚的是波动率的钱,定向策略赚的是方向的钱,两者结合才能在市场里活得长久。