实战案例四:多品种做市策略的分散化回测
做市策略做到一定程度,你会发现一个尴尬的问题——单品种回测再漂亮,实盘也容易翻车。为什么?因为一个品种的流动性、波动率、订单流特征,随时可能变脸。我早年吃过这个亏,一个ETH做市策略回测年化40%,结果上实盘第三天就被一波异常波动打穿了库存。
所以,多品种分散化不是锦上添花,是生存刚需。这一章,我们就来聊聊怎么对多品种做市策略做分散化回测。
为什么必须做多品种分散?
说白了,做市商赚的是「流动性溢价」和「价差收益」。但单一品种的风险敞口太大了。你想想看,如果只做BTC,一旦市场出现极端行情,你的库存方向判断失误,可能一夜回到解放前。
多品种分散化,核心逻辑就三点:
- 降低非系统性风险:不同品种的波动周期、驱动因素不同,可以互相对冲
- 平滑收益曲线:A品种亏钱时,B品种可能正在赚钱
- 提高资金利用率:不同品种的保证金可以共享,整体资金效率更高
核心观点:多品种做市不是简单地把资金平分到几个币上,而是基于相关性、波动率、流动性等维度做组合优化。
多品种回测的难点在哪?
单品种回测你只要管好订单簿和库存就行。多品种回测,复杂度直接翻倍。我个人习惯把难点归纳为三个:
- 资金分配问题:每个品种分配多少资金?是按市值等权,还是按波动率加权?
- 相关性管理:品种之间如果高度相关,分散化效果会大打折扣
- 订单簿同步:多个品种的订单簿数据需要时间对齐,否则回测结果失真
嗯,这里要注意——很多新手做多品种回测时,直接把单品种策略复制粘贴到多个品种上,然后简单加总收益。这种做法其实很危险,因为忽略了品种之间的资金竞争关系。
多品种做市策略的框架设计
先画一张图,把整体逻辑理清楚。我习惯用一张流程图来展示多品种做市的核心流程:
这张图展示了我个人比较推崇的多品种做市回测流程。注意看那个反馈箭头——回测不是一次性的,需要反复迭代。
实战:用Python做多品种回测
直接上代码。这里我以三个品种为例:BTC、ETH、SOL。数据用的是1分钟级别的订单簿快照。
import pandas as pd
import numpy as np
from typing import Dict, List
class MultiAssetMarketMaker:
"""多品种做市回测器"""
def __init__(self,
symbols: List[str],
initial_capital: float = 100000,
allocation_method: str = 'volatility_weighted'):
"""
:param symbols: 品种列表
:param initial_capital: 初始资金
:param allocation_method: 资金分配方式
"""
self.symbols = symbols
self.initial_capital = initial_capital
self.allocation_method = allocation_method
self.positions = {s: 0 for s in symbols}
self.capital = initial_capital
def allocate_capital(self,
volatility: Dict[str, float]) -> Dict[str, float]:
"""动态资金分配"""
if self.allocation_method == 'equal':
# 等权分配
alloc = {s: self.capital / len(self.symbols)
for s in self.symbols}
elif self.allocation_method == 'volatility_weighted':
# 波动率加权:波动率越低,分配越多
total_inv_vol = sum(1/v for v in volatility.values())
alloc = {s: (1/v) / total_inv_vol * self.capital
for s, v in volatility.items()}
else:
raise ValueError(f"未知分配方式: {self.allocation_method}")
return alloc
def run_backtest(self,
data: Dict[str, pd.DataFrame],
spread_multiplier: float = 1.5) -> pd.DataFrame:
"""
执行多品种回测
:param data: 每个品种的订单簿数据
:param spread_multiplier: 价差倍数
"""
results = []
for timestamp in data[self.symbols[0]].index:
# 1. 计算各品种当前波动率
vol = {}
for s in self.symbols:
window = data[s].loc[:timestamp].tail(100)
vol[s] = window['mid_price'].pct_change().std()
# 2. 动态分配资金
alloc = self.allocate_capital(vol)
# 3. 各品种独立做市
daily_pnl = 0
for s in self.symbols:
row = data[s].loc[timestamp]
bid = row['bid_price']
ask = row['ask_price']
mid = (bid + ask) / 2
# 计算理论做市价差
spread = (ask - bid) * spread_multiplier
our_bid = mid - spread / 2
our_ask = mid + spread / 2
# 模拟成交(简化版)
trade_volume = alloc[s] / our_ask * 0.1 # 每次交易10%资金
pnl = trade_volume * (our_ask - our_bid) / 2
daily_pnl += pnl
results.append({
'timestamp': timestamp,
'total_pnl': daily_pnl,
'total_capital': self.capital + daily_pnl
})
return pd.DataFrame(results).set_index('timestamp')
个人经验:代码里的波动率加权分配,是我在实际项目中比较常用的方式。波动率低的品种分配更多资金,因为做市收益更稳定。但要注意——如果某个品种波动率突然飙升,资金分配需要快速响应,否则容易出事。
回测结果分析:别只看总收益
多品种回测跑完之后,很多人只看总收益曲线。这其实不够。我建议至少看这几个指标:
| 指标 | 说明 | 我的经验阈值 |
|---|---|---|
| 夏普比率 | 衡量风险调整后收益 | > 1.5 算合格 |
| 最大回撤 | 组合净值从峰值回落的最大幅度 | < 15% 可接受 |
| 品种相关性 | 各品种收益率的相关系数矩阵 | 平均 < 0.3 为佳 |
| 资金利用率 | 实际使用资金 / 总资金 | > 70% 较好 |
我曾经遇到过一个案例,三个品种回测总收益很好,但仔细一看,三个品种的收益率相关性高达0.85。这意味着什么?说白了,它们几乎同涨同跌,分散化效果等于零。后来我换成了BTC、黄金、国债期货,相关性才降到0.2以下。
避坑指南:多品种回测的常见陷阱
陷阱一:幸存者偏差
我曾经只选了当前流动性好的品种做回测,结果忽略了那些已经退市或流动性枯竭的品种。回测曲线漂亮,但实盘时发现很多品种根本做不了。正确做法是:回测时把历史上所有出现过的品种都纳入候选池,哪怕后来退市了。
陷阱二:资金分配过于静态
有些策略在回测开始时一次性分配好资金,之后就不再调整。但市场是动态的,一个品种的波动率可能从10%飙到50%。如果不动态调整,资金会过度暴露在高风险品种上。我建议至少每4小时重新计算一次资金分配。
陷阱三:忽略交易成本
多品种做市的交易成本比单品种高得多。每个品种都要挂单、撤单、吃单,手续费和滑点会吃掉大量利润。我在回测时,会额外加一个「摩擦成本系数」,通常是理论收益的10%-20%。
优化方向:从回测到实盘的最后一公里
回测跑通了,不代表实盘就能赚钱。我个人习惯在回测之后,再做三件事:
- 压力测试:模拟极端行情,比如2020年3月12日的暴跌,看看组合能不能扛住
- 品种淘汰机制:设定一个阈值,如果某个品种连续N天亏损,自动剔除出组合
- 实盘模拟:用模拟账户跑1-2周,观察回测结果和实盘结果的偏差
嗯,说到实盘模拟,我记得有一次回测收益很好,但模拟盘跑了一周就亏了5%。后来发现是回测时忽略了网络延迟和订单簿深度变化。从那以后,我每次回测都会加入「延迟模拟」模块。
多品种做市策略的分散化回测,本质上是在「收益」和「风险」之间找平衡。没有完美的策略,只有不断迭代的优化过程。希望这一章的实战案例,能帮你少走一些弯路。
本章小结:多品种做市回测的核心在于资金动态分配、相关性管理和风险监控。代码实现上,建议采用面向对象的方式,把每个品种的做市逻辑封装成独立模块,方便后续扩展和优化。