# Spec: Backtest CLI

## ADDED Requirements

### Requirement: 命令行参数解析
回测脚本 SHALL 通过命令行参数接收用户输入，参数 SHALL 包含股票代码、时间范围、策略文件、回测参数等。

#### Scenario: 基础回测执行
- **WHEN** 用户执行 `python backtest.py --code 000001.SZ --start-date 2024-01-01 --end-date 2025-12-31 --strategy-file strategy.py`
- **THEN** 系统解析所有必需参数，无错误提示
- **THEN** 开始执行回测流程
- **THEN** 回测完成后输出统计信息到控制台

#### Scenario: 可选参数未指定
- **WHEN** 用户未指定 `--cash` 参数
- **THEN** 系统使用默认值 100000 作为初始资金
- **WHEN** 用户未指定 `--commission` 参数
- **THEN** 系统使用默认值 0.002 作为手续费率
- **WHEN** 用户未指定 `--output` 参数
- **THEN** 系统不生成 HTML 图表文件

#### Scenario: 必需参数缺失
- **WHEN** 用户未提供 `--code` 参数
- **THEN** 系统输出错误信息："错误: 需要以下参数: --code"
- **THEN** 系统退出并返回非零状态码
- **WHEN** 用户未提供 `--start-date` 或 `--end-date` 参数
- **THEN** 系统输出对应的错误信息
- **THEN** 系统退出并返回非零状态码

#### Scenario: 自定义参数值
- **WHEN** 用户指定 `--cash 500000 --commission 0.001 --output result.html`
- **THEN** 系统使用指定的 500000 作为初始资金
- **THEN** 系统使用指定的 0.001 作为手续费率
- **THEN** 回测完成后生成 HTML 图表到 result.html

---

### Requirement: 数据库数据加载
回测脚本 SHALL 从 PostgreSQL 数据库加载指定股票的历史价格数据，并自动处理复权。

#### Scenario: 成功加载数据
- **WHEN** 用户指定有效的股票代码和时间范围
- **THEN** 系统连接数据库并执行查询
- **THEN** 返回 DataFrame，包含列: [Open, High, Low, Close, Volume, factor]
- **THEN** DataFrame 的索引为 trade_date (DatetimeIndex)
- **THEN** 数据已应用复权计算（price * factor）

#### Scenario: 数据库连接失败
- **WHEN** 数据库连接失败（凭证错误、网络问题等）
- **THEN** 系统捕获异常并输出错误信息："数据库连接失败: {error}"
- **THEN** 系统退出并返回非零状态码

#### Scenario: 未找到股票数据
- **WHEN** 指定的股票代码或时间范围内无数据
- **THEN** 系统抛出 ValueError: "未找到股票 {code} 在指定时间范围内的数据"
- **THEN** 主流程捕获异常并输出友好错误信息
- **THEN** 系统退出并返回非零状态码

#### Scenario: 数据验证
- **WHEN** 数据库返回的 DataFrame 为空
- **THEN** 系统提示数据为空并退出
- **WHEN** 数据库返回的 DataFrame 少于 10 条记录
- **THEN** 系统提示数据不足并退出

---

### Requirement: 策略动态加载
回测脚本 SHALL 支持动态加载指定路径的策略文件，并验证策略接口。

#### Scenario: 加载有效策略文件
- **WHEN** 用户指定 `--strategy-file strategy.py`
- **THEN** 系统通过 importlib 加载该模块
- **THEN** 系统获取模块的 `calculate_indicators` 函数
- **THEN** 系统调用模块的 `get_strategy()` 函数获取策略类
- **THEN** 系统返回 (calculate_indicators, strategy_class) 元组

#### Scenario: 策略文件不存在
- **WHEN** 用户指定的策略文件路径不存在
- **THEN** 系统捕获 FileNotFoundError
- **THEN** 输出错误信息："策略文件 {file} 不存在"
- **THEN** 系统退出并返回非零状态码

#### Scenario: 策略接口不完整
- **WHEN** 策略文件缺少 `calculate_indicators` 函数
- **THEN** 系统捕获 AttributeError
- **THEN** 输出错误信息："策略文件 {file} 缺少 calculate_indicators 函数"
- **THEN** 系统退出并返回非零状态码
- **WHEN** 策略文件缺少 `get_strategy` 函数
- **THEN** 系统捕获 AttributeError
- **THEN** 输出错误信息："策略文件 {file} 缺少 get_strategy 函数"
- **THEN** 系统退出并返回非零状态码

#### Scenario: 加载子目录中的策略
- **WHEN** 用户指定 `--strategy-file strategies/macd_strategy.py`
- **THEN** 系统正确加载子目录中的策略模块
- **THEN** 系统成功获取策略类和指标计算函数

---

### Requirement: 指标计算
回测脚本 SHALL 在执行回测前调用策略的指标计算函数，将技术指标添加到数据集中。

#### Scenario: 成功计算指标
- **WHEN** 系统调用 `calculate_indicators(data)`
- **THEN** 函数接收包含 [Open, High, Low, Close, Volume, factor] 的 DataFrame
- **THEN** 函数计算策略所需的指标（如 SMA, MACD, RSI）
- **THEN** 函数返回添加了指标列的 DataFrame
- **THEN** DataFrame 保留原始列，新增指标列

#### Scenario: 指标计算产生 NaN 值
- **WHEN** 滚动窗口计算导致前 N 行的指标值为 NaN
- **THEN** DataFrame 包含 NaN 值（系统不自动删除）
- **THEN** Backtest 框架在回测时会跳过 NaN 值的行

#### Scenario: 指标计算函数抛出异常
- **WHEN** `calculate_indicators(data)` 执行时抛出异常
- **THEN** 主流程捕获异常
- **THEN** 输出错误信息："指标计算失败: {error}"
- **THEN** 系统退出并返回非零状态码

---

### Requirement: 回测执行
回测脚本 SHALL 使用 backtesting 库执行回测，传入数据、策略和参数。

#### Scenario: 成功执行回测
- **WHEN** 系统调用 `Backtest(data, strategy_class, cash=..., commission=...).run()`
- **THEN** Backtest 初始化时调用策略类的 `init()` 方法
- **THEN** Backtest 逐个时间步调用策略类的 `next()` 方法
- **THEN** 系统返回包含回测统计信息的 stats 对象

#### Scenario: 回测参数传递
- **WHEN** 用户指定 `--cash 500000 --commission 0.001`
- **THEN** Backtest 实例化时使用 cash=500000
- **THEN** Backtest 实例化时使用 commission=0.001
- **THEN** Backtest 实例化时使用 finalize_trades=True

#### Scenario: 回测运行时错误
- **WHEN** 策略的 `next()` 方法执行时抛出异常
- **THEN** backtesting 库捕获异常
- **THEN** 系统输出错误信息和堆栈跟踪
- **THEN** 系统退出并返回非零状态码

---

### Requirement: 结果输出
回测脚本 SHALL 将回测统计信息格式化输出到控制台，并可选生成 HTML 图表文件。

#### Scenario: 控制台输出
- **WHEN** 回测成功完成
- **THEN** 系统调用 `print_stats(stats)` 函数
- **THEN** 系统输出回测统计信息，使用中文标签
- **THEN** 输出内容包括：最终收益、总收益率、年化收益率、最大回撤、胜率等
- **THEN** 数值格式化（保留 2 位小数）

#### Scenario: 生成 HTML 图表
- **WHEN** 用户指定 `--output result.html`
- **THEN** 系统调用 `bt.plot(filename='result.html', show=False)`
- **THEN** 系统生成 HTML 文件到 result.html
- **THEN** 系统输出提示："图表已保存到: result.html"
- **THEN** 图表包含价格曲线、资金曲线、买卖信号等

#### Scenario: 不生成 HTML 图表
- **WHEN** 用户未指定 `--output` 参数
- **THEN** 系统不调用 bt.plot() 方法
- **THEN** 系统不生成任何图表文件
- **THEN** 系统仅输出控制台统计信息

#### Scenario: 图表生成失败
- **WHEN** bt.plot() 方法执行时抛出异常
- **THEN** 系统捕获异常
- **THEN** 系统输出警告："图表生成失败，但回测已完成: {error}"
- **THEN** 系统不影响控制台统计信息的输出
- **THEN** 系统正常退出（返回状态码 0）

---

### Requirement: 错误处理
回测脚本 SHALL 对所有可能的错误进行捕获和处理，提供友好的错误提示。

#### Scenario: 数据库错误
- **WHEN** 数据库操作抛出 sqlalchemy.exc.SQLAlchemyError
- **THEN** 系统输出错误信息："数据库错误: {error}"
- **THEN** 系统退出并返回状态码 2

#### Scenario: 文件操作错误
- **WHEN** 图表文件保存失败（权限、磁盘空间等）
- **THEN** 系统输出错误信息："文件操作错误: {error}"
- **THEN** 系统退出并返回状态码 3

#### Scenario: 未预期的错误
- **WHEN** 发生其他未捕获的异常
- **THEN** 系统输出错误信息："未知错误: {error}"
- **THEN** 系统输出完整的堆栈跟踪
- **THEN** 系统退出并返回状态码 1
