# 策略回测平台使用说明

## 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` 即可用新策略回测。
