Files
strategy_backtest/README.md

310 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 策略回测平台使用说明
## 1. 项目结构
当前目录 `strategy_backtest/` 结构如下:
- `main.py`:回测入口脚本。
- `requirements.txt`Python 依赖列表。
- `config/settings.py`:全局配置:路径、日期、初始资金、策略参数等。
- `utils/`:工具模块。
- `logger.py`:统一日志模块,所有文件使用 `setup_logger` 获取 logger。
- `data_loader.py`:行情数据加载模块,从 `data/day/*.txt` 读取日线数据。
- `performance.py`:根据资金曲线计算收益、夏普、最大回撤等指标。
- `plotter.py`:根据资金曲线 CSV 绘制资金曲线图。
- `strategies/`:策略模块。
- `base_strategy.py`:抽象基类,封装账户与持仓、主回测循环等。
- `ma_cross.py`:示例策略:均线交叉(持有 5 天,最多 2 只)。
- `results/`:回测输出。
- `logs/app.log`:统一运行日志。
- `equity/{strategy_name}_equity.csv`:资金曲线 CSV。
- `plots/{strategy_name}_curve.png`:资金曲线图。
## 2. 环境准备
1. 安装 Python 3.10+(推荐)。
2.`strategy_backtest/` 目录执行:
```bash
pip install -r requirements.txt
```
## 3. 行情数据准备
1. 将日线 TXT 行情文件放在项目根目录上一级的 `data/day/` 下(即 `d:/data/jq_hc/data/day/`
- 文件名格式:`{ts_code}_daily_data.txt`,例如:`000001.SZ_daily_data.txt`
- 编码UTF-8 无 BOM
- 分隔符:制表符 `\t`
- 首行必须包含至少这些列:`ts_code`, `trade_date`, `open`, `high`, `low`, `close`, `vol`
- 日期列 `trade_date` 格式为 `YYYYMMDD`。
2. 可选:在 `data/code/all_stock_codes.txt` 中按行列出股票代码(含后缀),例如:
```text
000001.SZ
600000.SH
```
若该文件存在,则回测股票池从该文件读取;否则会扫描 `data/day/` 目录下的文件名自动推断。
## 4. 配置说明config/settings.py
主要配置项说明如下:
- `BASE_DIR`:项目根目录(`d:/data/jq_hc/`)。
- `DATA_DAY_DIR`:日线数据目录(`BASE_DIR / "data" / "day"`)。
- `RESULTS_DIR`:回测结果目录(`BASE_DIR / "strategy_backtest" / "results"`)。
- `START_DATE` / `END_DATE`:回测起止日期(`YYYYMMDD`)。
- `INITIAL_CASH`:初始资金金额。
- `MAX_POSITIONS`:最多持仓股票数。
- `HOLD_DAYS`:单只股票持有天数。
- `MA_SHORT` / `MA_LONG`:均线参数。
- `STRATEGY`:策略配置,包含模块名、类名与参数。
如需调整策略参数或回测时间窗口,直接修改 `settings.py` 中对应变量即可。
## 5. 运行回测
### 5.1 单策略回测模式
在 `strategy_backtest/` 目录执行:
```bash
python main.py
```
程序将自动执行以下步骤:
1. 加载股票代码列表(优先使用 `data/code/all_stock_codes.txt`,否则扫描 `data/day/`)。
2. 对股票池中的每只股票,从 `DATA_DAY_DIR` 加载日线数据,并按日期升序整理。
3. **性能优化**预计算日期索引O(1) 查询)、均线指标、交易信号(金叉+放量布尔掩码)。
4. 动态加载 `STRATEGY` 中配置的策略类(默认 `MaCrossStrategy`),并运行回测。
5. 将资金曲线保存到 `results/equity/{strategy_name}_equity.csv`。
6. 计算绩效指标(累计收益、年化收益、夏普比率、最大回撤、胜率、盈亏比等)并记录到日志中。
7. 生成资金曲线图 `results/plots/{strategy_name}_curve.png`。
### 5.2 参数优化模式
使用多进程网格搜索找到最优参数组合:
```bash
# 使用默认配置4核保存前20组按夏普比率排序
python main.py --optimize
# 自定义配置
python main.py --optimize --jobs 8 --top 50 --metric sharpe
# 可选排序指标sharpe / total_return / max_drawdown / annual_return
python main.py --optimize --metric total_return --top 10
```
**参数说明**
- `--optimize`:启用参数优化模式
- `--strategy`策略名称默认ma_cross
- `--jobs`并行进程数默认4
- `--top`保存前N个最优结果默认20
- `--metric`排序指标默认sharpe
**优化结果**
- 优化过程会显示进度条和实时完成数
- 结果保存在 `results/optimization/grid_search_{timestamp}.csv`
- 日志中打印 Top 5 最优参数组合及其绩效
## 6. 日志说明
- 日志格式:`YYYY-MM-DD HH:MM:SS [LEVEL] 文件名.py:行号 - 消息内容`
- 输出位置:
- 终端标准输出;
- 文件:`results/logs/app.log`
- 关键日志:
- `utils/data_loader.py`:加载成功/失败/缺失文件;
- `strategies/base_strategy.py`:回测起止;
- `strategies/ma_cross.py`:买卖信号;
- `main.py`:主流程异常等。
## 7. 参数优化配置config/settings.py
### 7.1 参数空间定义
在 `config/settings.py` 中配置参数空间:
```python
PARAM_SPACES: dict = {
"ma_cross": {
"ma_short": [3, 20, 2], # 短期均线3-20步长2 -> [3,5,7,9,...,19]
"ma_long": [20, 60, 5], # 长期均线20-60步长5 -> [20,25,30,...,55]
"hold_days": [3, 10, 1], # 持有天数3-10步长1 -> [3,4,5,...,9]
"position_pct_per_stock": [0.4, 0.5, 0.6], # 单股仓位:离散值
},
}
```
**格式说明**
- **范围格式**`[min, max, step]` - 生成等差数列
- **离散格式**`[value1, value2, ...]` - 直接指定取值
- 支持整数和浮点数
### 7.2 参数约束条件
使用 lambda 函数过滤无意义的参数组合:
```python
PARAM_CONSTRAINTS: dict = {
"ma_cross": lambda params: (
# 约束1短期均线必须小于长期均线
params.get("ma_short", 0) < params.get("ma_long", 999)
# 可以添加更多约束,例如:
# and params.get("hold_days", 0) >= 3
# and params.get("position_pct_per_stock", 0) <= 0.6
),
}
```
**约束示例**
- 避免 `ma_short >= ma_long`(短期≥长期无意义)
- 限制参数范围如仓位不超过60%
- 参数之间的逻辑关系
**优化效果**
- 自动过滤无效组合,减少计算量
- 例648 组 -> 过滤后 400 组(节省 38% 时间)
### 7.3 优化方法
当前实现的是 **网格搜索Grid Search** 方法:
| 特性 | 说明 |
|------|------|
| **方法** | 全遍历(笛卡尔积) |
| **优点** | 简单直观,保证找到全局最优 |
| **缺点** | 参数多时计算量大(指数增长) |
| **适用** | 参数维度低2-4个、参数范围小 |
| **加速** | 多进程并行默认4核可配置 |
**参数组合数计算**
```
总数 = ma_short的取值数 × ma_long的取值数 × hold_days的取值数 × ...
9 × 9 × 7 × 3 = 1701 组
```
**其他可选方法**(未实现):
- 贝叶斯优化:高维空间高效,智能搜索
- 遗传算法:适合复杂约束条件
- 随机搜索:快速探索可行域
## 8. 性能优化详解
### 8.1 优化策略
回测系统实现了多层次性能优化:
#### ① 日期索引预计算Date Index Dict
**问题**:原始方法使用 `df[df['trade_date'] == date]` 过滤,每次 O(n) 复杂度
**解决**:预先构建 `{ts_code: {date: row_index}}` 字典,查询时间降为 O(1)
```python
date_index_dict = {
"000001.SZ": {"20230101": 0, "20230102": 1, ...},
"600000.SH": {"20230101": 0, "20230102": 1, ...},
}
# 使用:
idx = date_index_dict[ts_code][current_date]
row = df.iloc[idx] # 直接定位,无需过滤
```
**提升**727天 × 3776股 = 274万次查询每次从 O(n) 降为 O(1)
#### ② 信号预计算Signal Precomputation
**问题**:回测时每天重复计算 `rolling_mean()`、`pct_change()` 等
**解决**:使用 pandas 向量化操作一次性计算所有信号,存储为布尔列
```python
# 预计算金叉信号
df["golden_cross"] = (
(df["ma_short"] > df["ma_long"]) &
(df["ma_short"].shift(1) <= df["ma_long"].shift(1))
)
# 预计算放量信号
df["volume_surge"] = df["vol"] > df["vol"].rolling(5).mean() * 1.5
# 组合买入信号
df["buy_signal"] = df["golden_cross"] & df["volume_surge"]
```
**提升**:避免 274万次重复计算约节省 80% CPU 时间
#### ③ 买入信号索引Buy Signal Index
**问题**:每天遍历所有 3776 只股票检查是否有信号
**解决**:构建反向索引 `{date: [stock_list]}`,只遍历有信号的股票
```python
buy_signal_index = {
"20230103": ["000001.SZ", "600000.SH"], # 只有20只股票有信号
"20230104": ["000002.SZ", "600519.SH"],
}
# 使用:
signal_stocks = buy_signal_index.get(current_date, [])
for ts_code in signal_stocks: # 只遍历 20-50 只,而非 3776 只
# 处理买入逻辑
```
**提升**:每天从遍历 3776 只减少到 20-50 只,**减少 99% 循环**
#### ④ 避免 DataFrame.attrs deepcopy
**问题**:将索引存储在 `df.attrs` 中,`iloc` 切片时触发 deepcopy
**解决**:将索引字典作为独立参数传递,不使用 `df.attrs`
```python
# 错误做法:
df.attrs["date_index"] = date_index # 切片时 deepcopy 整个字典
# 正确做法:
strategy = Strategy(date_index_dict=date_index_dict) # 作为参数传递
```
**提升**:消除 deepcopy 开销,避免程序卡顿
### 8.2 性能对比
| 优化阶段 | 回测耗时 | 提升倍数 |
|----------|----------|----------|
| 原始版本 | 约 40 分钟 | - |
| +日期索引 | 约 5 分钟 | 8x |
| +信号预计算 | 约 1 分钟 | 40x |
| +信号索引 | **约 40 秒** | **60x** |
**注**:其中数据加载约 28 秒,实际回测执行 < 1 秒
### 8.3 使用建议
1. **大规模回测**1000+ 股票):必须启用所有优化
2. **参数优化**利用多核并行8核可将 1701 组从 40 分钟降至 5 分钟
3. **内存优化**:优化后自动 `gc.collect()`,但大数据集建议 16GB+ 内存
## 9. 扩展新策略
1. 在 `strategies/` 新建文件,例如 `my_strategy.py`,继承 `BaseStrategy` 并实现 `on_bar` 方法;
2. 在 `config/settings.py` 中修改 `STRATEGY`
```python
STRATEGY = {
"name": "MyStrategy",
"module": "strategies.my_strategy",
"params": {
# 自定义参数
},
}
```
3. 重新运行 `python main.py` 即可用新策略回测。