## Context

**Current State:**
`backtest.py` (284 lines) 是单一文件，包含：
- 命令行参数解析
- 数据库连接和数据加载
- 策略动态加载和验证
- 回测执行逻辑
- 结果格式化输出
- 图表生成

**Constraints:**
- 需要保持现有功能完整性（数据加载、策略加载、回测执行、结果展示）
- 需要支持多股票回测（串行执行）
- 不考虑并发实现（保持简单）
- 错误处理采用立即失败策略
- 数据库配置明文存储，不考虑环境变量

**Stakeholders:**
- 开发者：需要清晰的模块划分和可复用的接口
- 终端用户：需要友好的 CLI 输出（进度条、表格化结果）

## Goals / Non-Goals

**Goals:**
1. 分离核心逻辑与 CLI 界面，提升代码复用性
2. 提供标准化函数接口，供其他模块调用回测功能
3. 支持多股票批量回测（串行执行）
4. 集中管理配置（数据库、参数、配色）
5. 优化 CLI 输出体验（tabulate 表格化、tqdm 进度条）

**Non-Goals:**
1. 并行执行多股票回测（性能优化非目标）
2. 环境变量管理配置（配置明文存储即可）
3. 复杂的聚合统计（仅单股票结果拼接）
4. 图表文件合并（每个股票生成独立 HTML）
5. 配置文件热重载（启动时加载一次）

## Decisions

### Decision 1: 三层模块划分
**选择：** 分离为 `config.py`、`backtest_core.py`、`backtest_command.py` 三个文件

**理由：**
- **config.py**：集中管理所有配置，避免硬编码分散
- **backtest_core.py**：纯粹的业务逻辑，提供可复用的函数接口
- **backtest_command.py**：CLI 界面，负责参数解析和结果展示

**替代方案：**
- 方案 A：保留单一文件，但改进内部结构（函数分离）
  - 拒绝理由：仍无法复用，CLI 和业务逻辑耦合
- 方案 B：使用类封装（如 `BacktestEngine` 类）
  - 拒绝理由：增加复杂度，函数接口已足够

### Decision 2: BacktestResult 数据类
**选择：** 使用 `dataclasses.dataclass` 定义 `BacktestResult`

**理由：**
- 结构化返回结果，便于序列化和导出
- 类型提示支持，提升代码可读性
- 自动生成 `__init__`、`__repr__` 等方法，减少样板代码

**替代方案：**
- 方案 A：直接返回原始 `stats` 对象（backtesting 库返回）
  - 拒绝理由：依赖 backtesting 库内部结构，耦合度高
- 方案 B：返回字典
  - 拒绝理由：缺乏类型提示，容易拼写错误

### Decision 3: 批量回测策略
**选择：** 串行执行（`for` 循环），立即失败

**理由：**
- 简单可靠，易于调试
- 错误处理清晰（第一个失败就停止）
- 避免并发带来的资源竞争和复杂度

**替代方案：**
- 方案 A：并行执行（ThreadPoolExecutor）
  - 拒绝理由：性能非目标，并发增加复杂度
- 方案 B：继续执行其他股票，最后统一报告错误
  - 拒绝理由：用户需求是立即失败

### Decision 4: CLI 参数设计
**选择：** `--codes` 多值参数（`nargs='+'`），`--output-dir` 目录参数

**理由：**
- `--codes` 支持传入多个股票代码，如 `--codes 000001.SZ 600000.SH`
- `--output-dir` 为每个股票生成 `{code}.html`，如 `output/000001.SZ.html`
- 保持原有参数（`--start-date`、`--end-date`、`--strategy-file`、`--cash`、`--commission`、`--warmup-days`）

**替代方案：**
- 方案 A：`--code` 逗号分隔（如 `--code 000001.SZ,600000.SH`）
  - 拒绝理由：需要额外解析逻辑，不直观
- 方案 B：`--code` 多次调用（如 `--code 000001.SZ --code 600000.SH`）
  - 拒绝理由：argparse 的 `nargs='+'` 更符合习惯

### Decision 5: 输出优化库
**选择：** 使用 `tabulate` 表格化批量结果，使用 `tqdm` 显示进度条

**理由：**
- **tabulate**：提供美观的表格输出，支持多种格式（grid、simple 等）
- **tqdm**：提供实时进度条，提升用户体验
- 两个库都是轻量级，不引入复杂依赖

**替代方案：**
- 方案 A：手动格式化表格（字符串拼接）
  - 拒绝理由：代码冗余，格式不够美观
- 方案 B：不使用进度条（仅输出完成提示）
  - 拒绝理由：多股票回测耗时较长，用户需要进度反馈

### Decision 6: 结果展示策略
**选择：** 单股票使用详细格式（现有），多股票使用表格格式（新增）

**理由：**
- 单股票：保持原有的详细输出（每个指标单独一行）
- 多股票：使用 `tabulate` 表格横向对比，节省垂直空间

**替代方案：**
- 方案 A：所有情况都使用详细格式（拼接）
  - 拒绝理由：多股票时输出过长，难以阅读
- 方案 B：所有情况都使用表格格式
  - 拒绝理由：单股票时表格优势不明显，详细格式更清晰

### Decision 7: 配置管理方式
**选择：** 明文常量存储在 `config.py`

**理由：**
- 满足用户需求（不考虑信息安全）
- 避免引入 `python-dotenv` 依赖
- 代码简洁，修改直接

**替代方案：**
- 方案 A：环境变量（`os.getenv`）
  - 拒绝理由：用户明确不需要
- 方案 B：配置文件（JSON/YAML）
  - 拒绝理由：增加文件管理和解析复杂度

### Decision 8: 数据访问接口
**选择：** `load_data_from_db(code, start_date, end_date)` 函数签名保持不变

**理由：**
- 现有接口已满足需求（单次查询一个股票）
- 迁移成本低，直接复制到 `backtest_core.py`

**替代方案：**
- 方案 A：批量查询（`load_data_from_db(codes, start_date, end_date)`）
  - 拒绝理由：需要修改 SQL 为 `IN` 子句，且结果聚合复杂
- 方案 B：连接池复用（全局 engine 对象）
  - 拒绝理由：每次创建引擎的开销可接受（串行执行）

### Decision 9: 策略加载接口
**选择：** `load_strategy(strategy_file)` 返回 `(calculate_indicators, strategy_class)` 元组

**理由：**
- 保持现有接口，迁移成本低
- 函数返回两个值符合 Python 惯例

**替代方案：**
- 方案 A：返回类对象（策略类自带指标计算方法）
  - 拒绝理由：现有策略文件结构分离了两者，修改成本高
- 方案 B：返回命名空间对象（封装两个属性）
  - 拒绝理由：增加复杂度，元组足够

### Decision 10: 错误处理策略
**选择：** 立即失败（不捕获部分错误继续执行）

**理由：**
- 符合用户需求
- 简化错误追踪（第一个错误直接暴露）
- 避免"部分成功"的歧义状态

**替代方案：**
- 方案 A：捕获错误但继续执行，最后统一报告
  - 拒绝理由：用户明确要求立即失败

## Risks / Trade-offs

### Risk 1: CLI 命令变化导致用户习惯中断
**风险：** 用户习惯使用 `python backtest.py`，需要切换到 `uv run python backtest_command.py`

**缓解：**
- 在项目根目录创建软链接 `backtest.py -> backtest_command.py`（可选）
- 或在 README 中明确说明新的使用方式
- 提供迁移指南（参数变化说明）

### Risk 2: 多股票串行执行耗时较长
**风险：** 10 个股票可能需要 10 倍时间（每个 30 秒 → 总计 5 分钟）

**缓解：**
- 使用 `tqdm` 进度条提供实时反馈
- 在 README 中说明性能限制
- 未来可扩展为并行执行（非当前目标）

### Risk 3: BacktestResult 字段可能与 backtesting 库不兼容
**风险：** backtesting 库升级后，stats 对象的键名可能变化

**缓解：**
- 使用 `.get(key, default)` 方法访问，避免 KeyError
- 提供默认值（0 或空字符串）
- 在文档中说明依赖的 backtesting 版本

### Risk 4: tabulate/tqdm 依赖未安装
**风险：** 用户运行时缺少依赖，导致 ImportError

**缓解：**
- 使用 `uv add` 明确添加依赖到 pyproject.toml
- 在 README 中说明安装步骤
- 错误信息中提示安装命令（`uv add tabulate tqdm`）

### Risk 5: 策略文件路径处理不一致
**风险：** 策略文件路径可能是相对路径或绝对路径，导致加载失败

**缓解：**
- 使用 `os.path.abspath()` 转换为绝对路径
- 在错误信息中提示用户检查路径
- 测试相对路径和绝对路径两种情况

### Risk 6: 图表输出目录不存在
**风险：** 用户指定的 `--output-dir` 不存在，导致保存失败

**缓解：**
- 使用 `os.makedirs(output_dir, exist_ok=True)` 自动创建
- 在错误信息中提示用户检查目录权限

### Risk 7: 内存占用（多股票同时加载数据）
**风险：** 如果同时加载多个股票数据，内存占用可能较高

**缓解：**
- 串行执行确保一次只加载一个股票的数据
- 单个股票的数据量可控（10 年约几 MB）
- future 可考虑流式处理（非当前目标）

## Migration Plan

### Step 1: 创建 config.py
1. 从 `backtest.py` 提取数据库配置
2. 添加默认回测参数
3. 添加图表配色配置
4. 测试导入无错误

### Step 2: 创建 backtest_core.py
1. 迁移 `load_data_from_db()` 函数（导入 config）
2. 迁移 `load_strategy()` 函数
3. 迁移 `apply_color_scheme()` 函数（使用 config 配置）
4. 定义 `BacktestResult` 数据类
5. 实现 `run_backtest()` 函数
6. 实现 `run_batch_backtest()` 函数
7. 单元测试核心函数

### Step 3: 创建 backtest_command.py
1. 实现 `parse_arguments()` 函数（支持 `--codes`）
2. 实现 `format_single_result()` 函数（详细格式）
3. 实现 `format_batch_results()` 函数（使用 tabulate）
4. 实现 `main()` 函数（调用 `run_batch_backtest()`）
5. 测试单股票回测
6. 测试多股票回测

### Step 4: 更新依赖
1. 运行 `uv add tabulate` 添加依赖
2. 运行 `uv add tqdm` 添加依赖
3. 运行 `uv sync` 同步依赖

### Step 5: 删除 backtest.py
1. 确认新功能完整（单股票、多股票、图表输出）
2. 确认错误处理正确（立即失败）
3. 删除 `backtest.py` 文件
4. 更新 README 说明新的使用方式

### Rollback Strategy
如果迁移过程中发现问题：
1. 保留 `backtest.py` 直到 `backtest_command.py` 完全可用
2. 使用 `git` 版本控制，可随时回退
3. 逐步迁移（先核心函数，后 CLI），确保每步可验证

## Open Questions

1. **BacktestResult 字段完整性：** 是否需要包含所有 backtesting.stats 的键，或仅包含当前用到的字段？
   - 倾向：仅包含当前用到的字段（未来可扩展）

2. **表格格式选择：** tabulate 支持多种格式（grid、simple、pipe、html），多股票结果使用哪种？
   - 倾向：grid（美观的边框格式）

3. **进度条粒度：** tqdm 进度条应该显示每个股票的回测进度，还是仅显示批量回测的总进度？
   - 倾向：仅显示批量回测的总进度（股票 N/M）

4. **图表输出目录结构：** 多股票图表是平铺在 `output/` 下，还是按日期/策略分组？
   - 倾向：平铺在 `output/` 下（简单）
