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%的极端行情,就能让你爆仓。所以,盯住尾部风险,才是压力测试的核心。

压力测试方法论对比 情景分析法 核心:编故事 • 历史重现型 • 假设型 优点: 灵活、直观、边界测试 缺点: 主观性强、覆盖不全 适用: 快速验证、边界测试 历史模拟法 核心:历史重演 • 真实数据回放 • 按天/按小时回测 优点: 客观、简单、可复现 缺点: 依赖数据、无法测未知 适用: 常规压力测试 蒙特卡洛模拟法 核心:随机生成 • 几何布朗运动 • 大量模拟路径 优点: 覆盖广、可量化风险 缺点: 依赖模型、计算量大 适用: 深度风险量化 建议:三种方法组合使用,才能全面评估极端行情风险

专注资料整理