180 lines
4.8 KiB
Plaintext
180 lines
4.8 KiB
Plaintext
标题: 量化回测中的常见陷阱及规避方法
|
||
链接: https://www.joinquant.com/view/community/detail/3
|
||
分类: 回测
|
||
================================================================================
|
||
|
||
# 量化回测中的常见陷阱及规避方法
|
||
|
||
## 一、过拟合陷阱
|
||
|
||
### 1.1 什么是过拟合
|
||
|
||
过拟合是指策略在历史数据上表现优异,但在实盘或样本外数据上表现糟糕的现象。
|
||
|
||
**表现特征**:
|
||
- 历史回测收益率很高,但样本外测试急剧下降
|
||
- 参数微调对结果影响巨大
|
||
- 交易次数过多,过度优化
|
||
|
||
### 1.2 规避方法
|
||
|
||
**方法1:样本外验证**
|
||
|
||
```
|
||
三段式回测验证:
|
||
- 训练集:60%数据(用于策略开发和参数优化)
|
||
- 验证集:20%数据(用于参数验证和策略选择)
|
||
- 测试集:20%数据(最终验证,只能用一次)
|
||
```
|
||
|
||
**方法2:参数敏感性分析**
|
||
|
||
不要只看最优参数,要分析参数周围的表现:
|
||
|
||
```python
|
||
# 参数敏感性测试
|
||
for param in range(5, 30, 5):
|
||
# 测试不同参数下的策略表现
|
||
result = backtest(strategy, param=param)
|
||
print(f'参数{param}: 收益率{result['return']}, 夏普比率{result['sharpe']}')
|
||
```
|
||
|
||
**方法3:简化策略逻辑**
|
||
|
||
- 避免过多的条件判断
|
||
- 减少参数数量
|
||
- 策略逻辑应该有经济学或行为金融学解释
|
||
|
||
## 二、幸存者偏差
|
||
|
||
### 2.1 什么是幸存者偏差
|
||
|
||
幸存者偏差是指回测时只使用了当前还在上市的股票,而忽略了已经退市的股票,导致回测结果过于乐观。
|
||
|
||
**影响**:
|
||
- 高估策略收益率
|
||
- 低估策略风险
|
||
- 实盘表现远不如回测
|
||
|
||
### 2.2 规避方法
|
||
|
||
**在聚宽平台上的正确做法**:
|
||
|
||
```python
|
||
# 使用包含退市股票的完整数据集
|
||
from jqdata import *
|
||
|
||
def get_stock_pool(date):
|
||
# 获取指定日期的指数成分股(包含当时的成分股,而不是当前的)
|
||
return get_index_stocks('000300.XSHG', date=date)
|
||
|
||
# 或者使用all_securities获取所有股票(包含退市的)
|
||
all_stocks = list(all_securities)
|
||
```
|
||
|
||
**注意事项**:
|
||
- 不要使用当前的股票池回测历史数据
|
||
- 使用`get_index_stocks`时要指定日期参数
|
||
- 考虑ST股票、停牌股票的处理
|
||
|
||
## 三、未来函数陷阱
|
||
|
||
### 3.1 什么是未来函数
|
||
|
||
未来函数是指在策略中使用了当时还无法获得的数据。
|
||
|
||
**常见类型**:
|
||
1. 使用未来的财务数据
|
||
2. 知道未来的最高价、最低价进行交易
|
||
3. 使用停牌后的数据
|
||
|
||
### 3.2 规避方法
|
||
|
||
**检查清单**:
|
||
|
||
```
|
||
1. 数据获取日期检查
|
||
- 确保使用的数据在交易决策时间点之前
|
||
|
||
2. 避免"先知先觉"
|
||
- 不要使用未来才能知道的信息
|
||
|
||
3. 模拟真实决策流程
|
||
- 按照实际交易时的信息获取顺序编写策略
|
||
```
|
||
|
||
**示例对比**:
|
||
|
||
```python
|
||
# 错误示例(使用未来数据)
|
||
def handle_data(context, data):
|
||
for stock in g.stock_pool:
|
||
# 获取未来一天的数据(这在实际交易中是不可能的)
|
||
future_price = get_price(stock, count=1, end_date=context.current_date + timedelta(days=1))
|
||
if future_price > data[stock].close:
|
||
order(stock, 100)
|
||
|
||
# 正确示例
|
||
def handle_data(context, data):
|
||
for stock in g.stock_pool:
|
||
# 只使用当前及之前的数据
|
||
hist = history(20, '1d', 'close', [stock])
|
||
current_price = data[stock].close
|
||
ma20 = hist.mean()
|
||
if current_price > ma20:
|
||
order(stock, 100)
|
||
```
|
||
|
||
## 四、交易成本低估
|
||
|
||
### 4.1 常见问题
|
||
|
||
- 忽略交易手续费
|
||
- 低估滑点影响
|
||
- 不考虑市场冲击成本
|
||
|
||
### 4.2 规避方法
|
||
|
||
**合理设置交易成本**:
|
||
|
||
```python
|
||
def initialize(context):
|
||
# 设置手续费
|
||
set_commission(PerTrade(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
|
||
# 设置滑点
|
||
set_slippage(FixedSlippage(0.002)) # 0.2%的固定滑点
|
||
```
|
||
|
||
**成本参考**:
|
||
- 买入佣金:万分之三
|
||
- 卖出佣金:万分之三 + 千分之一印花税
|
||
- 滑点:0.1% - 0.5%(根据股票流动性调整)
|
||
|
||
## 五、实践案例
|
||
|
||
某投资者的教训:
|
||
|
||
**回测表现**:
|
||
- 年化收益率:60%
|
||
- 最大回撤:15%
|
||
- 夏普比率:2.5
|
||
|
||
**实盘表现**(3个月):
|
||
- 实际收益率:-5%
|
||
- 最大回撤:20%
|
||
|
||
**问题诊断**:
|
||
1. 过拟合:参数过度优化,只在特定历史时期表现好
|
||
2. 幸存者偏差:回测时只用了当前股票,没考虑退市股票
|
||
3. 交易成本低估:回测时设置的成本过低
|
||
|
||
**改进措施**:
|
||
1. 采用三段式回测验证
|
||
2. 使用包含退市股票的完整数据集
|
||
3. 提高交易成本设置
|
||
4. 简化策略逻辑,减少参数
|
||
|
||
---
|
||
|
||
**总结**:回测陷阱是量化交易中最常见的问题,需要从数据质量、策略逻辑、回测设置等多方面进行严格把关。记住:"回测表现只是起点,实盘验证才是关键"。
|