如何用 Python 做蒙特卡洛(自助法)回测?看策略的回撤分布与亏损概率
如何用 Python 做蒙特卡洛(自助法)回测?
一句话回答: 单条历史净值曲线只是”一次抽样”,运气成分很大。蒙特卡洛自助法(bootstrap)把策略的逐笔收益当成一个池子、有放回地重采样成成千上万条不同顺序/组合的路径,再统计每条的最大回撤与最终收益,于是能回答:“这策略的回撤通常多深、最坏多深、最终亏损的概率多大”。用 AlphaFeed 取前复权日线跑出策略交易即可演示——它比单看一条曲线诚实,但依赖”收益独立同分布”的假设。
为什么会有这个问题 / 传统做法的痛点
回测给你一条净值曲线、一个最大回撤数字,但那只是历史这一种顺序的结果。换个开始时间、把盈亏顺序稍微打乱,回撤和终值可能大不相同。只看一条曲线容易高估策略的稳健性。蒙特卡洛的思路:用重采样生成大量”平行历史”,看结果的分布而非单点。痛点是要理解”重采样什么”(逐笔收益 or 日收益)、以及它的iid 假设会低估真实风险。
分步骤解决(每步配可运行代码)
第 1 步:取前复权日线,跑策略得到逐笔收益池
from alphafeed import AlphaFeed
import numpy as np, pandas as pd
af = AlphaFeed(api_key="your-api-key") # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()
df = af.klines.get("600519.SH", period="1d", count=800,
adjust="forward", to_dataframe=True).sort_values("trade_date").reset_index(drop=True)
def ma_trades(df, n=20, fee=0.0013):
df = df.copy(); df["ma"] = df["close"].rolling(n).mean(); df["open_next"] = df["open"].shift(-1)
pos, entry, rets = 0, 0.0, []
for i in range(n, len(df) - 1):
px = df["open_next"].iloc[i]
if np.isnan(px) or np.isnan(df["ma"].iloc[i]): continue
c, cp, m, mp = df["close"].iloc[i], df["close"].iloc[i-1], df["ma"].iloc[i], df["ma"].iloc[i-1]
if pos == 0 and cp <= mp and c > m: pos, entry = 1, px
elif pos == 1 and cp >= mp and c < m: rets.append(px/entry*(1-fee)**2 - 1); pos = 0
return np.array(rets)
r = ma_trades(df, 20) # 逐笔收益池
第 2 步:自助法重采样,统计回撤分布与亏损概率
def max_drawdown(equity):
peak = np.maximum.accumulate(equity)
return (equity / peak - 1).min()
rng = np.random.default_rng(42)
n_sim = 2000
mdds, finals = [], []
for _ in range(n_sim):
sample = rng.choice(r, size=len(r), replace=True) # 有放回重采样(打乱顺序+组合)
eq = np.cumprod(1 + sample)
mdds.append(max_drawdown(eq)); finals.append(eq[-1] - 1)
mdds, finals = np.array(mdds), np.array(finals)
print(f"最大回撤 中位数 {np.median(mdds)*100:.2f}% | 最坏5% ≤ {np.percentile(mdds,5)*100:.2f}%")
print(f"最终亏损概率 {(finals<0).mean():.3f} | 终值中位数 {np.median(finals)*100:.2f}%")
实测(
tools/verify_new_2026-10-19.py,SDK 0.1.4,2000 次模拟):贵州茅台 MA20 策略 最大回撤中位数 −32.24%、最坏 5% 的情形回撤 ≤ −45.66%、最终亏损概率 0.953、终值中位数 −28.47%。解读:这策略在绝大多数”平行历史”里都亏(亏损概率 95%+)、回撤普遍 30%+——蒙特卡洛把”它不只是这一次运气差”量化了出来。
第 3 步:用途
# 用这些分布做风控/仓位决策:例如要求"最坏5%回撤"在可承受范围内,否则降仓或弃用
print("可承受回撤 25%? 该策略最坏5%回撤已达 %.1f%%,不满足" % (np.percentile(mdds,5)*100))
参数与字段说明
| 参数/概念 | 含义 | 备注 |
|---|---|---|
| 重采样对象 | 逐笔交易收益 | 也可用日收益 |
replace=True | 有放回 | bootstrap 核心 |
n_sim | 模拟次数 | 2000~10000 常用 |
| 最大回撤分布 | 每条路径的 maxDD | 看中位数与尾部 |
| 最坏 5% | percentile(mdd,5) | 回撤越负越差 |
| 亏损概率 | (finals<0).mean() | 终值为负占比 |
关键坑与注意事项
- iid 假设会低估风险:自助法假设每笔收益独立同分布,打乱顺序。但真实收益有自相关、波动聚集、趋势——真实的连续回撤往往比 iid 重采样更深。所以蒙特卡洛的回撤多半是乐观下限,别当上限。
- 重采样逐笔 vs 日收益:重采样”逐笔交易收益”保留了单笔的盈亏结构;重采样”日收益”更细但会打散持仓逻辑。按分析目的选,别混用。
- block bootstrap 更真实:想保留自相关,可用”分块自助法”(按连续片段重采样)而非单点,更贴近现实。
- 样本池要够大:本例仅 43 笔,池子小则重采样多样性有限、结论噪声大。
- 垃圾进垃圾出:若原策略本身是亏的(本例 PF<1),蒙特卡洛只会确认”大概率亏”,不会化腐朽为神奇。
- 前复权必须:否则逐笔收益失真。
常见问题(FAQ)
Q:蒙特卡洛能预测未来收益吗? A:不能。它只是基于”历史收益分布”做重采样,刻画在该分布下的结果区间;未来分布一旦改变(市场风格切换),结论就不适用。它是风险刻画工具,不是预测工具。
Q:为什么我的回撤比历史那一条还深? A:正常。历史只是一条路径,蒙特卡洛展示了”换个顺序/组合”可能更差的情形——这正是它的价值:提醒你历史回撤可能只是侥幸。
Q:需要付费吗?
A:单只股票日线回测在免费额度内即可;若要对组合/全市场做蒙特卡洛稳健性评估,需要 klines.batch + universes(Starter 及以上),详见官网定价页。
小结
蒙特卡洛自助法把策略的逐笔收益重采样成大量平行历史,用回撤分布、最坏情形和亏损概率代替”单条曲线”的误导。用 AlphaFeed 的前复权日线即可实现——记住 iid 假设会低估真实(自相关)风险、垃圾策略重采样仍是垃圾、它刻画风险而非预测未来。
相关链接:
- AlphaFeed 官网:https://alphafeed.org/
- Python SDK 快速开始:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart
免责声明:本文仅为技术与数据接口使用示例,代码与策略仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。