3、压力测试方法论:情景分析法、历史模拟法、蒙特卡洛模拟法
做市策略最怕什么?怕黑天鹅。怕那种你模型里根本没考虑过的极端行情。
我入行第三年,就碰上过一次。某个小币种在凌晨三点突然闪崩,我的做市策略还在傻傻地挂单接货,结果一晚上亏掉了三个月的利润。那次之后我才真正意识到——压力测试不是可选项,是必修课。
今天咱们就聊聊三种主流的压力测试方法。说白了,就是三种「找茬」的方式,看看你的策略在极端情况下会不会崩。
3.1 情景分析法
情景分析,就是「编故事」。你设想一个极端场景,然后看策略怎么反应。
比如:
- 「如果比特币突然暴跌30%,我的做市策略会怎样?」
- 「如果交易所API挂了10分钟,我的库存会不会爆?」
- 「如果两个交易所的价差突然拉大到5%,我的套利逻辑还成立吗?」
我个人习惯把情景分成两类:
- 历史重现型:把过去发生过的极端行情搬过来。比如2020年3月12日的「312暴跌」,或者2021年5月的「519事件」。
- 假设型:自己编一个更狠的场景。比如「交易所被黑客攻击,暂停提币24小时」。
优点:
- 灵活。你想测什么场景就测什么场景
- 直观。结果一看就懂,老板也能理解
- 适合做「边界测试」,看看策略的极限在哪
缺点:
- 主观性强。你编的场景可能不够极端
- 覆盖不全。你没想到的场景,就测不到
- 难以量化概率。你没法说「这个场景发生的概率是5%」
我的经验:做情景分析时,别只盯着价格波动。流动性枯竭、延迟飙升、订单簿厚度变化,这些才是做市策略的真正杀手。我曾经就漏掉了「流动性瞬间消失」这个场景,结果回测时好好的,实盘一跑就出问题。
3.2 历史模拟法
历史模拟法,说白了就是「把过去发生过的事,再演一遍」。
你拿过去5年的行情数据,把做市策略放进去跑一遍。看看它在312暴跌、519事件、FTX暴雷这些历史极端行情下,表现如何。
代码实现其实很简单:
import pandas as pd
import numpy as np
def historical_simulation(strategy, historical_data):
"""
历史模拟法压力测试
strategy: 你的做市策略函数
historical_data: 历史行情DataFrame
"""
results = []
for date, data in historical_data.groupby(historical_data.index.date):
# 按天回放
daily_pnl = strategy(data)
results.append({
'date': date,
'pnl': daily_pnl,
'max_drawdown': calculate_max_drawdown(data)
})
# 找出最差的5%交易日
results_df = pd.DataFrame(results)
worst_days = results_df.nsmallest(int(len(results_df)*0.05), 'pnl')
return worst_days
嗯,这里要注意一点:历史模拟法假设「历史会重演」。但现实是,每次极端行情都不一样。2020年的暴跌和2022年的暴跌,背后的原因完全不同。
优点:
- 客观。用的是真实数据,不是编的
- 简单。不需要复杂的数学模型
- 可复现。同样的数据,跑出来的结果一样
缺点:
- 依赖历史数据。数据不够长,结果就不够准
- 无法覆盖「从未发生过」的场景
- 市场结构变了,历史数据可能失效
避坑指南:我曾经用2018-2020年的数据做历史模拟,结果策略表现很好。但2021年市场结构变了——做市商变多了,竞争更激烈了,历史模拟的结果完全不准。所以,历史模拟法更适合「短期」压力测试,比如最近3-6个月的数据。
3.3 蒙特卡洛模拟法
蒙特卡洛模拟,听起来高大上,其实原理很简单:随机生成大量可能的行情路径,然后看策略在每条路径上的表现。
你想想看,历史模拟法只能测「已经发生过」的场景。但蒙特卡洛可以测「可能发生但还没发生」的场景。比如:
- 价格波动率突然翻倍
- 买卖价差持续扩大
- 订单簿深度骤降
代码示例:
import numpy as np
def monte_carlo_simulation(strategy, initial_price, volatility, days, n_simulations=10000):
"""
蒙特卡洛模拟法压力测试
strategy: 做市策略函数
initial_price: 初始价格
volatility: 波动率
days: 模拟天数
n_simulations: 模拟次数
"""
results = []
for i in range(n_simulations):
# 生成随机价格路径(几何布朗运动)
prices = [initial_price]
for day in range(days):
drift = 0 # 假设无趋势
shock = np.random.normal(0, volatility)
new_price = prices[-1] * np.exp(drift + shock)
prices.append(new_price)
# 运行策略
pnl = strategy(prices)
results.append(pnl)
# 计算VaR(在险价值)
results = np.array(results)
var_95 = np.percentile(results, 5)
var_99 = np.percentile(results, 1)
return {
'VaR_95': var_95,
'VaR_99': var_99,
'worst_case': results.min(),
'all_results': results
}
优点:
- 覆盖范围广。可以生成无数种场景
- 可量化风险。能算出VaR、CVaR等指标
- 灵活。可以调整参数,模拟不同市场环境
缺点:
- 依赖模型假设。如果模型错了,结果就全错
- 计算量大。模拟次数越多,越耗时间
- 难以模拟「跳跃」场景。比如突然的闪崩
我的建议:蒙特卡洛模拟里,别用标准的正态分布。金融数据有「肥尾」特征——极端行情比正态分布预测的更频繁。我一般用t分布或者混合分布来模拟,效果会好很多。
3.4 三种方法对比
好了,三种方法都讲完了。咱们来做个对比:
| 维度 | 情景分析法 | 历史模拟法 | 蒙特卡洛模拟法 |
|---|---|---|---|
| 数据需求 | 低(靠经验) | 高(需要历史数据) | 中(需要参数估计) |
| 覆盖范围 | 有限(靠人想) | 有限(靠历史) | 广泛(靠随机) |
| 客观性 | 低(主观性强) | 高(真实数据) | 中(依赖模型) |
| 计算复杂度 | 低 | 中 | 高 |
| 适用场景 | 快速验证、边界测试 | 常规压力测试 | 深度风险量化 |
| 能否测「未知」 | 能(但靠人想) | 不能 | 能(靠随机) |
我个人习惯是三种方法都用。先用情景分析法快速扫一遍,找出明显的漏洞。然后用历史模拟法验证一下,看看过去极端行情下策略表现如何。最后用蒙特卡洛模拟做深度量化,算出VaR和CVaR。
说白了,没有一种方法是完美的。组合使用,才能把风险看清楚。
最后提醒一句:压力测试的结果,别只看「平均」或者「中位数」。要看「最差情况」。做市策略在99%的时间里可能都表现很好,但那1%的极端行情,就能让你爆仓。所以,盯住尾部风险,才是压力测试的核心。