新建回测系统,并提交

This commit is contained in:
2026-01-17 21:37:42 +08:00
commit fe50ea935a
68 changed files with 108208 additions and 0 deletions

309
README.md Normal file
View File

@@ -0,0 +1,309 @@
# 策略回测平台使用说明
## 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` 即可用新策略回测。