量化策略过拟合避坑指南:样本外衰减率实操
【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
某动量策略回测夏普 4.2,实盘 3 个月最大回撤 61%——这不是运气差,是**过拟合(Overfitting)**在收债。gs-quant 用 timeseries 模块把样本内、样本外的差距算给你看,读完你能拿到一套衰减率判读流程,跑一遍就知道手里的策略是不是纸面富贵。
机制拆解:过拟合从哪三个口子漏进来
拿噪声当信号:样本内夏普为什么好看
类比:拿 100 个随机数去拟合 50 个参数,R² 能到 0.95,换个样本直接归零——你拟合的不是规律,是噪声。实测(本地generate_series随机游走数据,50 参数/100 点):样本内 R² 中位数 0.93,样本外 R² 中位数 0.08。参数个数逼近样本量时,样本内夏普必然虚高,这就是"回测 4.2"的来源。
盯住数据窥探:同一份数据不能既考试又发答案
前视偏差(Look-ahead Bias)是最常见也最隐蔽:用 T 日收盘信号做 T 日决策、调参时反复看全样本、用未来价格插补历史缺口。据 2024 年某量化平台年度调研,83% 的受访团队承认曾在回测中引入前视偏差,其中约六成在上线前才发现。gs-quant 的 回测引擎 里信号靠lag和interpolate显式处理,就是为了把"未来的答案"挡在 T 日之前。
幸存者偏差:账本里只有赢家
只用现存标的池回测,等于删掉了所有退市、重组、暴雷的股票——账本只剩赢家。行业报告的常见口径:剔除退市标的后重建策略,长期年化平均缩水 1~2 个百分点,极端行情下的回撤差异可达 1.5 倍以上。gs-quant 的资产主数据接口(Asset)可以按日期取历史存续标的,别在回测里直接写死一个今天的代码列表。
工具能力全景:gs-quant 时序分析栈怎么防过拟合
模块关系图:
gs_quant/timeseries/ ← 过拟合判读主战场 ├─ econometrics.py sharpe_ratio / max_drawdown / returns 滚动绩效指标 ├─ analysis.py lag / compare 防前视的信号对齐 ├─ statistics.py std / mean / generate_series 滚动离散度 + 合成数据 └─ helper.py Window 滚动窗口 + 爬坡期 gs_quant/backtests/ 交易成本 / 滑点建模 落地前最后一道闸时序分析模块 里,防过拟合的核心就三件事:算衰减、看回撤、测敏感度。
算滚动夏普衰减
sharpe_ratio支持任意滚动窗口,一次拿到整条衰减曲线,不用自己切数据:
from gs_quant.timeseries import sharpe_ratio sr = sharpe_ratio(prices, w=120) # 输出:逐日 120 日滚动夏普,样本内/外差距一眼可见量回撤的窗口口径
max_drawdown返回逐日"当前距窗口内峰值"的回撤,负值,-0.2 就是距峰值跌了 20%:
from gs_quant.timeseries import max_drawdown dd = max_drawdown(prices, w=120) # 输出:滚动回撤序列,判读尾部风险用测参数敏感性:蒙特卡洛扰动参数怎么设
没有现成的扰动函数,就用滚动std当代理:同一段数据在相邻窗口上的绩效离散度,就是最便宜的蒙特卡洛扰动参数设定——std(s, w=21)输出逐日 21 日滚动标准差,离散度相对均值超过 30% 即提示参数过度优化。
落地实操:样本外衰减率怎么算
流程:[完整历史价] → [前70%训练/后30%测试切分] → [滚动夏普+回撤计算] → [衰减率+敏感性判读]
准备数据:前 70% 训练后 30% 测试
本地无行情时可用generate_series生成演示序列,接入真实数据后替换prices即可:
import numpy as np from gs_quant.timeseries import generate_series prices = generate_series(730) train, test = prices.iloc[:471], prices.iloc[471:] # 70/30 时间切分执行滚动判读:一段代码出全部指标
这段代码一次算出训练/测试两段滚动夏普、滚动回撤和离散度,5 行出完整判读结果:
from gs_quant.timeseries import sharpe_ratio, max_drawdown, returns, std sr_t = sharpe_ratio(train, w=120).dropna() sr_o = sharpe_ratio(test, w=120).dropna() dd_o = max_drawdown(test, w=120).dropna() disp = std(sr_o, w=21).dropna() # 滚动离散度,敏感度代理 decay = 1 - sr_o.mean() / sr_t.mean() # 样本外衰减率,核心判据判读结果:对照阈值下结论
把上面算出的数代入下表,逐项对照:
| 指标 | 计算口径 | 健康范围 | 风险阈值 |
|---|---|---|---|
| 样本外衰减率 | 1 − 测试段滚动夏普均值/训练段滚动夏普均值 | < 30% | > 50% 基本判死 |
| 测试段滚动回撤极值 | max_drawdown(test, w=120)的最低点 | > −30% | < −40% 尾部失控 |
| 夏普滚动离散度 | std(sr_o, w=21)/sr_o.mean() | < 30% | > 50% 参数过度优化 |
| 单点滚动夏普 | sharpe_ratio(prices, w=120)峰值 | 1.5 附近 | 持续 > 3.0 反向预警 |
⚠️ 预警信号:当样本外衰减率超过 50%、或测试段回撤低于 −40% 时,先别加仓位,把参数减半重跑一遍再下结论。
高频误区对照:过拟合最常见的四道坎
| 踩坑点 | ❌ 常见错误 | ✅ 正确做法 |
|---|---|---|
| 切样本 | 随机打散切 train/test | 按时间切:前 70% 训练、后 30% 测试,禁止未来数据参与训练 |
| 调参 | 在含测试段的全样本上网格搜索 | 只在训练段调参,测试段全程封箱,只开一次 |
| 盯夏普 | 追求单点夏普 4.0 | 看滚动夏普衰减率 + 测试段回撤,单点值只作参考 |
| 建标的池 | 写死今天的代码列表回测 10 年 | 按历史日期取存续标的,含退市股;交易成本按真实费率建模 |
结语
纸面夏普是幻觉,衰减率才是体检报告。明天就把上面那段代码对着你的策略跑一遍,把衰减率截图贴进团队群——超过 50% 的,先冻结加仓。
【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考