# 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