NautilusTrader量化交易终极指南-第27章第4节-商业场景:加密货币做市机器人

一句话导读:做市(Market Making)是加密货币里最抓人的赛道之一——我不赌涨跌,而是在买卖两边同时挂单,赚"接单方的手续费 + 价差",靠日积月累的微利取胜。但做市的核心难点不在挂单,而在库存管理:怎么在行情单边时把越积越多的单边库存消掉。这一节给你一个能跑的做市策略骨架。

本文导航

  • 做市到底是干什么的
  • 双边挂单:报价引擎怎么定买卖价
  • 库存风险:做市真正的敌人
  • 库存管理:怎么在单边行情里活下来
  • 订单簿视角:挂在哪、怎么撤
  • 完整可运行做市策略 + 输出
  • 架构图与小结
  • 下节预告

做市到底是干什么的

做市商(Market Maker)为市场提供双边报价:同时对某个币挂买价和卖价,承诺在价格到达时成交。赚的是买卖价差(spread)和交易所给的做市返佣/手续费优惠

打个比方:你不赌 BTC 涨还是跌,你只是"摆摊"——左边贴着当前买价挂买单,右边贴着卖价挂卖单,等两边都成交一次,中间那个价差就是你的利润。一天成交几千次,薄利多销,累积成大利润。

但为什么不是人人赚?因为库存风险:市场不会总让你"双边都成交",当行情单边下跌,你的买单不断成交(你不断囤币)、卖单却不成交,你手上越积越多正在贬值的币——这就是做市的核心风险。

双边挂单:报价引擎怎么定买卖价

一个最简单的做市报价逻辑:以当前市场中间价(mid)为基准,上下各留一个半价差 spread/2,分别挂买价 mid - spread/2 和卖价 mid + spread/2

mid  = (best_bid + best_ask) / 2
buy_price  = mid - spread / 2   # 挂单: 低于中间价买
sell_price = mid + spread / 2   # 挂单: 高于中间价卖

多项选择对做市商很关键:

  • 价差宽度:越大单边利润越厚,但成交概率越低、库存占用越久。要权衡。
  • 挂单量:每边挂多少量,受你的资金、交易所最小下单量、库存目标约束。
  • 撤单重挂:行情一动,原有挂单就"过时"了,需要撤掉重挂到新的中间价两侧,维持你报出去的价格仍贴得住市场。

做市本质是"永远贴住市场、又保持两边承诺"的持续循环。

库存风险:做市真正的敌人

我强调过,做市最大的风险不是价差没赚到,而是库存(inventory)单边堆积

场景还原:BTC 突然快速下跌。你的买单在下跌途中不断被吃(你囤币),你的卖买单却因为价格太高不成交。结果你持有大量正在贬值的 BTC,账户风险敞口越来越大——这跟你"不赌方向"的初衷完全相悖。

所以做市策略里,库存管理是生死线,优先级高于"把价差做满"。

库存管理:怎么在单边行情里活下来

库存管理的目标是把净头寸拉回接近 0(中性),别让自己变成"方向仓"。常见手段:

  1. 非对称报价:当库存偏多(持多头仓),让卖价更贴近市场(卖的更积极),同时让买价离市场更远(不那么想买)——用报价本身倾斜来推动去库存。
  2. 调宽/调窄价差:库存方向风险大时,把风险方向那一侧的价差拉大,降低成交概率。
  3. 对冲:在现货/合约/另一币对上反向对冲敞口(进阶,本节能理解思路即可)。
  4. 库存阈值 + 清仓:净敞口超过设定上限(如名义的 ±10%),触发"只出不进"或专门清仓模式,把库存压回界内。

我给一个可感的量化方式:维护 inventory_target(目标净头寸,一般 = 0),当实际净头寸 > +max_inv 时,把卖价往下压(更易成交)、把买价往上抬(更难买);反之亦然。这样报价天然"倒向"消库存的一侧。

订单簿视角:挂在哪、怎么撤

做市还要从订单簿视角考虑"挂在第几档"。常见策略:

  • 挂最优档(第1档):成交概率最高、价差最小,但容易被自动做市商抢得激烈,库存变动快。
  • 挂更深档:成交慢、单笔价差大,库存稳定,但要忍受流动性不足时长时间不成交。
  • 撤单重挂节奏:价格变动超过某个阈值(比如中间价偏离了你上次报价基准)就撤单重挂,避免挂着"过时价格"。

核心是一句话:让报价始终贴着最优市场价,但又留出赚价差的空间,同时用价格倾斜控制库存。

完整可运行做市策略 + 输出

下面是一个可运行的做市策略骨架(回测友好,逻辑完整但参数已简化便于演示)。策略在 on_quote 收到两档报价后,计算中间价,按"库存倾斜 + 价差"下双边限价单,并在价格漂移过大时撤单重挂:

from decimal import Decimal
from nautilus_trader.model.data import QuoteTick
from nautilus_trader.model.orders import LimitOrder
from nautilus_trader.model.enums import OrderSide, TimeInForce
from nautilus_trader.model.objects import Quantity, Price
from nautilus_trader.strategy.strategy import Strategy


class MMStrategy(Strategy):
    """加密货币做市:双边报价 + 库存倾斜 + 撤单重挂。"""

    def __init__(self, config):
        super().__init__(config)
        self.instrument_id = config.instrument_id
        self.spread = Decimal("2.0")        # 价差(以价格单位计,示意)
        self.qty = Quantity.from_str("100")
        self.max_inv = 500                  # 净库存上限(单位)
        self.buy_order = None
        self.sell_order = None
        self.last_mid = None

    def on_quote(self, quote: QuoteTick):
        mid = (quote.bid + quote.ask) / 2
        major_move = self.last_mid is not None and abs(mid - self.last_mid) > Decimal("5.0")
        self.last_mid = mid
        if major_move:
            self.cancel_all_orders(self.instrument_id)   # 行情大幅漂移,先撤单重挂

        # 计算净库存
        inv = 0.0
        pos = self.portfolio.position(self.instrument_id)
        if pos is not None:
            inv = pos.net_quantity.as_double()

        # 库存倾斜:多库存 -> 卖价下压(更易成交);空库存相反
        tilt = 0.0
        if inv > self.max_inv:
            tilt = -1.0           # 想卖:卖价压到更贴近市场
        elif inv < -self.max_inv:
            tilt = 1.0            # 想买:买价抬到更贴近市场

        buy_px = Price(mid.as_double() - self.qty.as_double() / Decimal("100") * 1.0)  # 示意基准
        # 更直观:买价 = 中间价 - 半价差 + 库存倾斜
        buy_px = Price.from_str(str(mid.as_double() - self.spread/2 + Decimal(tilt)))
        sell_px = Price.from_str(str(mid.as_double() + self.spread/2 + Decimal(tilt)))

        # 改写/重挂双边限价单
        self._replace(self.buy_order, OrderSide.BUY, buy_px)
        self._replace(self.sell_order, OrderSide.SELL, sell_px)

    def _replace(self, order, side, px):
        if order is not None and order.is_working and order.price == px:
            return
        if order is not None:
            self.cancel_order(order)
        new = LimitOrder(
            instrument_id=self.instrument_id,
            order_side=side,
            quantity=self.qty,
            price=px,
            time_in_force=TimeInForce.GTC,
        )
        self.submit_order(new)
        if side == OrderSide.BUY:
            self.buy_order = new
        else:
            self.sell_order = new
        self.log.info(f"[MM] {side} @ {px}  inv={self._inv()}")

    def _inv(self):
        pos = self.portfolio.position(self.instrument_id)
        return pos.net_quantity.as_double() if pos else 0.0

    def on_order_filled(self, event):
        self.log.info(f"[MM] 成交 {event.order.order_side} @ {event.execution_price}, 净库存 {self._inv():.1f}")

喂几拍双边报价,前段正常双边成交,中段注入"单边下跌"看库存如何倾斜报价去库存:

控制台输出(示意):

[MM] BUY @ 99.00  inv=0.0       <- 正常双边挂单
[MM] SELL @ 101.00  inv=0.0
[MM] 成交 SELL @ 101.00, 净库存 -100.0   <- 卖单先成交
[MM] 成交 BUY @ 99.00, 净库存 0.0
...(中段单边下跌, 买单反复成交)
[MM] 成交 BUY @ 96.50, 净库存 800.0      <- 库存飙高, 触发倾斜
[MM] SELL @ 97.60  inv=800.0    <- 卖价下压更贴市场, 加速去库存
[MM] 成交 SELL @ 97.60, 净库存 700.0
[MM] 成交 SELL @ 98.10, 净库存 600.0     <- 库存开始回落

看到关键了吗:当净库存超过上限,卖价被压得比正常更靠近市场,推动自己更快把囤积的币卖掉——这就是"库存倾斜报价"在干活。

架构图与小结

行情 QuoteTick / 订单簿

策略 on_quote
算中间价 mid

库存状态读取
portfolio.position 净头寸

净库存超过上限?

价格倾斜: 偏向去库存一侧
(卖多压卖价/买空抬买价)

正常双边报价
mid ± spread/2

撤单重挂 双边限价单

成交回报 → 更新库存

  • 做市赚的是价差 + 返佣,不赌方向,靠高频微利累积。
  • 双边报价用中间价 ± 半价差;行情漂移要撤单重挂贴住市场。
  • 库存风险是做市生死线:单边行情会让库存单边堆积。
  • 库存管理用报价倾斜(非对称报价)+ 阈值清仓把净头寸拉回中性。
  • 进阶方向:对冲、跨交易所做市、订单簿档位优化、做市返佣协议。

下节预告

到这里,四个商业场景——加密套利、外汇趋势、大单VWAP、做市机器人——你都见过了,分别覆盖了"速度、方向、执行、流动性"四条不同的商业逻辑。下一章(第28章)我们把前面所有能力收拢,从 0 到 1 搭建一套可复用的量化策略框架:项目结构、配置管理、日志、回测编排,让你以后做任何策略都能复用这套骨架,而不是每次都从零写。


做市这行看着简单,门道全在库存里。这篇能读完说明你对量化商业真的有兴趣——觉得有帮助就点赞、收藏、关注三连,下一章我们把整个课程收束成一套可用框架。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐