第十一章:蒙特卡洛模拟在利润预测中的应用
各位做市商的朋友,今天我们来聊聊蒙特卡洛模拟。说实话,这玩意儿听起来高大上,但说白了就是——用大量随机路径来模拟未来可能发生的情况。
我个人习惯把蒙特卡洛模拟叫做“穷举未来法”。你想想看,未来有无数种可能,我们没法预测哪一条路会走,但我们可以把所有可能的路都走一遍,然后看看结果分布长什么样。这就是蒙特卡洛的核心思想。
11.1 路径生成:模拟价格怎么走
做蒙特卡洛模拟,第一步就是生成价格路径。我常用的模型是几何布朗运动(GBM),公式长这样:
dS = μS dt + σS dW
其中μ是漂移率,σ是波动率,dW是维纳过程。嗯,这里要注意,μ不是简单的历史收益率,而是风险中性下的预期收益率。我在项目中遇到过有人直接用历史均值做μ,结果模拟出来的价格路径严重偏离市场,最后利润预测完全失真。
实际代码实现时,我习惯这么写:
import numpy as np
def generate_paths(S0, mu, sigma, T, N, M):
"""
S0: 初始价格
mu: 漂移率
sigma: 波动率
T: 时间期限
N: 时间步数
M: 路径数量
"""
dt = T / N
paths = np.zeros((M, N+1))
paths[:, 0] = S0
for i in range(1, N+1):
z = np.random.standard_normal(M)
paths[:, i] = paths[:, i-1] * np.exp(
(mu - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * z
)
return paths
这段代码看起来简单,但坑不少。我曾经因为忘记调整漂移率,导致模拟出的价格路径长期趋势明显偏离实际市场,利润预测偏差高达30%。
11.2 损益分布:从路径到利润
路径生成只是第一步。真正的重头戏是——把这些路径映射到我们的损益上。
做市商的利润来源主要有三块:
- 买卖价差收入:每次成交赚的点差
- 库存损益:持有头寸的价格变动
- 对冲成本:调整Delta产生的交易费用
对于每条模拟路径,我们都要计算这三个部分的累计值。我习惯把结果存成一个矩阵,行是路径,列是时间点。
def calculate_pnl(paths, spread, hedge_cost, position_size):
M, N = paths.shape
pnl = np.zeros((M, N-1))
for i in range(M):
for j in range(N-1):
# 价差收入
spread_income = spread * position_size
# 库存损益
inventory_pnl = (paths[i, j+1] - paths[i, j]) * position_size
# 对冲成本
hedge_expense = hedge_cost * abs(position_size)
pnl[i, j] = spread_income + inventory_pnl - hedge_expense
return pnl
你想想看,每条路径跑下来,我们得到的是一个损益时间序列。把所有路径的最终损益汇总,就得到了损益分布。
11.3 压力测试:极端情况下的生存能力
做市商最怕什么?不是正常波动,而是极端行情。我记得2020年3月那次市场暴跌,很多做市商因为没做压力测试,直接爆仓了。
压力测试的核心是——模拟极端市场条件下的表现。我常用的方法有三种:
| 方法 | 说明 | 适用场景 |
|---|---|---|
| 历史情景法 | 直接使用历史极端行情数据 | 有类似历史事件可参考 |
| 参数极端化 | 将波动率、相关性等参数设为极端值 | 需要量化极端影响 |
| 蒙特卡洛压力测试 | 在模拟中引入跳跃过程或厚尾分布 | 需要全面覆盖尾部风险 |
我个人最推荐第三种。为什么?因为历史不会简单重复,参数极端化又太主观。蒙特卡洛压力测试可以让我们自由定义极端事件的频率和幅度。
def stress_test_paths(S0, mu, sigma, T, N, M, jump_prob, jump_size):
"""
带跳跃过程的压力测试路径生成
jump_prob: 每次跳跃的概率
jump_size: 跳跃幅度(百分比)
"""
dt = T / N
paths = np.zeros((M, N+1))
paths[:, 0] = S0
for i in range(1, N+1):
z = np.random.standard_normal(M)
# 判断是否发生跳跃
jump = np.random.uniform(0, 1, M) < jump_prob
# 跳跃幅度
jump_effect = jump * jump_size * S0
paths[:, i] = paths[:, i-1] * np.exp(
(mu - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * z
) + jump_effect
return paths
11.4 核心逻辑框架
说了这么多,我们来画个图,把整个流程串起来:
核心要点:蒙特卡洛模拟不是预测未来,而是量化不确定性。路径生成要合理,损益计算要全面,压力测试要极端但不过分。三者缺一不可。
11.5 实战中的避坑指南
做了这么多年量化,我总结了几条蒙特卡洛模拟的常见坑:
- 路径数量不够:我曾经只用1000条路径做模拟,结果损益分布严重偏斜。建议至少10000条起步。
- 忽略相关性:如果你做的是多资产组合,一定要考虑资产间的相关性。我见过有人把股票和债券当成独立变量模拟,结果风险估计完全错误。
- 过度拟合历史:参数全部来自历史数据,但市场结构会变。我习惯在参数中加入一些主观调整,比如未来波动率比历史高10%。
💡 小技巧:做压力测试时,可以同时跑三组模拟——基准情景、温和压力、极端压力。这样既能看正常情况,也能看最坏情况。我个人习惯把极端压力的VaR控制在总资本的5%以内。
好了,蒙特卡洛模拟就聊到这里。记住,模拟只是工具,真正的价值在于你如何解读结果。别让数据骗了你。
无相订单流研究社 微信Lucian808555