# batch-backtest Specification ## Purpose TBD - created by archiving change refactor-backtest-separate-cli. Update Purpose after archive. ## Requirements ### Requirement: 多股票回测参数 系统 SHALL 支持通过命令行参数传入多个股票代码进行批量回测。 #### Scenario: 传入多个股票代码 - **WHEN** 用户执行 `python backtest_command.py --codes 000001.SZ 600000.SH --start-date 2024-01-01 --end-date 2025-12-31 --strategy-file strategies/macd_strategy.py` - **THEN** 系统解析所有股票代码到列表 `['000001.SZ', '600000.SH']` - **THEN** 系统按顺序依次执行每个股票的回测 - **THEN** 系统为每个股票生成独立的回测结果 #### Scenario: 传入单个股票代码 - **WHEN** 用户执行 `python backtest_command.py --codes 000001.SZ --start-date 2024-01-01 --end-date 2025-12-31 --strategy-file strategies/macd_strategy.py` - **THEN** 系统解析为单个股票代码列表 `['000001.SZ']` - **THEN** 系统执行单个股票回测 - **THEN** 系统输出详细格式的回测结果 #### Scenario: 缺少 --codes 参数 - **WHEN** 用户未提供 `--codes` 参数 - **THEN** 系统输出错误信息:"错误: 需要以下参数: --codes" - **THEN** 系统退出并返回非零状态码 --- ### Requirement: 批量回测执行 系统 SHALL 串行执行多个股票的回测,每次加载一个股票的数据并执行回测。 #### Scenario: 成功执行多个股票回测 - **WHEN** 用户传入 N 个股票代码 - **THEN** 系统循环 N 次,每次加载一个股票的数据 - **THEN** 系统每次执行完整的回测流程(数据加载、指标计算、回测执行) - **THEN** 系统每次执行完成后生成 `BacktestResult` 对象 - **THEN** 系统返回包含 N 个 `BacktestResult` 的列表 #### Scenario: 每个股票独立预热期 - **WHEN** 系统执行第 i 个股票的回测 - **THEN** 系统使用 `start_date - warmup_days` 计算该股票的预热开始日期 - **THEN** 系统独立加载该股票的预热期数据 - **THEN** 不同股票的预热期互不影响 #### Scenario: 第一个股票回测失败 - **WHEN** 系统执行第一个股票回测时发生错误(数据库连接失败、策略加载失败等) - **THEN** 系统捕获异常并输出错误信息 - **THEN** 系统停止执行后续股票的回测 - **THEN** 系统退出并返回非零状态码(立即失败策略) #### Scenario: 中间股票回测失败 - **WHEN** 系统执行第 i 个股票回测时发生错误 - **THEN** 系统输出错误信息(包含股票代码) - **THEN** 系统停止执行后续股票的回测 - **THEN** 系统退出并返回非零状态码 #### Scenario: 资源管理 - **WHEN** 系统完成第 i 个股票的回测 - **THEN** 系统关闭该股票的数据库连接(`engine.dispose()`) - **THEN** 系统释放该股票的数据内存 - **THEN** 系统开始加载第 i+1 个股票的数据 --- ### Requirement: 批量回测进度显示 系统 SHALL 使用 tqdm 显示批量回测的实时进度,提供用户反馈。 #### Scenario: 显示进度条 - **WHEN** 系统开始执行 N 个股票的批量回测 - **THEN** 系统显示进度条格式:`回测进度: 25%|█████▌ | 1/4 [00:30<01:30, 12.5s/it]` - **THEN** 系统在完成每个股票回测后更新进度条 - **THEN** 进度条显示当前进度(i/N)、已用时间、预计剩余时间 - **THEN** 进度条在所有股票回测完成后消失 #### Scenario: 单股票回测不显示进度条 - **WHEN** 用户传入单个股票代码 - **THEN** 系统不显示 tqdm 进度条 - **THEN** 系统直接输出回测结果 #### Scenario: 进度条描述文本 - **WHEN** 系统显示批量回测进度 - **THEN** 进度条描述 SHALL 为 "回测进度"(中文) - **THEN** 进度条显示已完成/总数(如 "1/4", "2/4") --- ### Requirement: 批量回测结果展示 系统 SHALL 使用 tabulate 将多个股票的回测结果格式化为表格,便于横向对比。 #### Scenario: 表格化输出多股票结果 - **WHEN** 用户传入多个股票代码且回测成功 - **THEN** 系统使用 tabulate 生成表格 - **THEN** 表格格式 SHALL 为 grid(带边框) - **THEN** 表格列 SHALL 包含:股票代码、收益率%、胜率%、最大回撤%、交易次数、SQN - **THEN** 系统在表格上方显示表头(中文列名) - **THEN** 数值保留 2 位小数(交易次数为整数) #### Scenario: 表格内容填充 - **WHEN** 系统格式化第 i 个股票的结果 - **THEN** 系统从 `BacktestResult` 对象提取字段 - **THEN** "股票代码" 列填充 `result.code` - **THEN** "收益率%" 列填充 `result.return_pct` - **THEN** "胜率%" 列填充 `result.win_rate` - **THEN** "最大回撤%" 列填充 `result.max_drawdown` - **THEN** "交易次数" 列填充 `result.trades` - **THEN** "SQN" 列填充 `result.sqn` #### Scenario: 单股票回测不使用表格 - **WHEN** 用户传入单个股票代码 - **THEN** 系统不使用 tabulate 生成表格 - **THEN** 系统使用详细格式输出(每个指标单独一行) - **THEN** 系统保持原有 `print_stats()` 的输出格式 #### Scenario: 表格示例输出 - **WHEN** 用户传入 2 个股票代码 - **THEN** 系统输出格式 SHALL 为: ``` +-------------+-----------+--------+------------+----------+-------+ | 股票代码 | 收益率% | 胜率% | 最大回撤% | 交易次数 | SQN | +-------------+-----------+--------+------------+----------+-------+ | 000001.SZ | 20.35 | 55.00 | -8.50 | 45 | 1.85 | | 600000.SH | 15.00 | 48.00 | -12.30 | 38 | 1.42 | +-------------+-----------+--------+------------+----------+-------+ ``` --- ### Requirement: 多股票图表输出 系统 SHALL 为每个股票生成独立的 HTML 图表文件,文件名格式为 `{code}.html`。 #### Scenario: 指定 --output-dir 参数 - **WHEN** 用户传入 `--output-dir output/` - **THEN** 系统为每个股票生成 HTML 文件到 `output/{code}.html` - **THEN** 文件名 SHALL 为股票代码,如 `000001.SZ.html`, `600000.SH.html` - **THEN** 系统自动创建 `output/` 目录(`exist_ok=True`) - **THEN** 系统在完成后输出提示:"图表已保存到目录: output/" 后列出所有文件 #### Scenario: 未指定 --output-dir 参数 - **WHEN** 用户未传入 `--output-dir` 参数 - **THEN** 系统不为任何股票生成图表文件 - **THEN** 系统仅输出控制台统计信息 #### Scenario: 图表文件覆盖 - **WHEN** 系统再次执行相同的批量回测 - **THEN** 系统覆盖已存在的 HTML 文件 - **THEN** 系统不提示文件已存在 --- ### Requirement: 结构化回测结果 系统 SHALL 返回标准化的 `BacktestResult` 对象,包含所有关键指标。 #### Scenario: BacktestResult 对象创建 - **WHEN** 系统完成单股票回测 - **THEN** 系统从 `stats` 对象提取指标到 `BacktestResult` - **THEN** `BacktestResult.code` 设置为股票代码 - **THEN** `BacktestResult.start_date` 设置为回测开始日期 - **THEN** `BacktestResult.end_date` 设置为回测结束日期 - **THEN** `BacktestResult.equity_final` 设置为最终权益 - **THEN** `BacktestResult.equity_peak` 设置为峰值收益 - **THEN** `BacktestResult.return_pct` 设置为总收益率 - **THEN** `BacktestResult.buy_hold_return` 设置为买入持有收益率 - **THEN** `BacktestResult.return_annual` 设置为年化收益率 - **THEN** `BacktestResult.volatility_annual` 设置为年化波动率 - **THEN** `BacktestResult.max_drawdown` 设置为最大回撤 - **THEN** `BacktestResult.avg_drawdown` 设置为平均回撤 - **THEN** `BacktestResult.max_drawdown_duration` 设置为最大回撤持续时长 - **THEN** `BacktestResult.avg_drawdown_duration` 设置为平均回撤持续时长 - **THEN** `BacktestResult.sortino_ratio` 设置为索提诺比率 - **THEN** `BacktestResult.calmar_ratio` 设置为卡尔玛比率 - **THEN** `BacktestResult.trades` 设置为交易次数 - **THEN** `BacktestResult.win_rate` 设置为胜率 - **THEN** `BacktestResult.sqn` 设置为系统质量数 - **THEN** `BacktestResult.cash` 设置为初始资金 - **THEN** `BacktestResult.commission` 设置为手续费率 #### Scenario: BacktestResult 列表返回 - **WHEN** 系统完成批量回测 - **THEN** 系统返回 `List[BacktestResult]` - **THEN** 列表顺序 SHALL 与输入股票代码顺序一致 - **THEN** 列表长度 SHALL 等于输入股票代码数量(成功时) #### Scenario: BacktestResult 数据类型 - **WHEN** 系统创建 `BacktestResult` 对象 - **THEN** 数值字段 SHALL 为 float 类型(除 `trades`, `max_drawdown_duration` 为 int) - **THEN** 日期字段 SHALL 为 str 类型(YYYY-MM-DD 格式) - **THEN** 系统支持 `result.to_dict()` 方法(dataclass 自动生成) --- ### Requirement: 可复用回测引擎接口 系统 SHALL 提供标准化的函数接口,供其他模块调用回测功能。 #### Scenario: run_backtest 函数调用 - **WHEN** 其他模块调用 `run_backtest(code, start_date, end_date, strategy_file, cash, commission, warmup_days, output_file)` - **THEN** 函数接收股票代码、日期范围、策略文件、回测参数、输出文件路径 - **THEN** 函数执行完整回测流程(数据加载、策略加载、指标计算、回测执行) - **THEN** 函数返回 `BacktestResult` 对象 - **THEN** 函数不打印任何输出(纯函数) #### Scenario: run_batch_backtest 函数调用 - **WHEN** 其他模块调用 `run_batch_backtest(codes, start_date, end_date, strategy_file, cash, commission, warmup_days, output_dir)` - **THEN** 函数接收股票代码列表、日期范围、策略文件、回测参数、输出目录 - **THEN** 函数串行执行每个股票的回测 - **THEN** 函数返回 `List[BacktestResult]` - **THEN** 函数显示 tqdm 进度条(批量时) #### Scenario: 函数参数默认值 - **WHEN** 调用者不指定可选参数 - **THEN** `cash` 默认为 100000 - **THEN** `commission` 默认为 0.002 - **THEN** `warmup_days` 默认为 365 - **THEN** `output_file` 默认为 None(不生成图表) - **THEN** `output_dir` 默认为 None(不生成图表) #### Scenario: 函数异常抛出 - **WHEN** `run_backtest` 或 `run_batch_backtest` 执行时发生错误 - **THEN** 函数 SHALL 抛出异常(不捕获) - **THEN** 异常类型 SHALL 为 ValueError、TypeError 或原始异常 - **THEN** 异常信息 SHALL 包含具体错误原因 - **THEN** 调用者负责捕获和处理异常 --- ### Requirement: 集中配置管理 系统 SHALL 在 config.py 中集中管理数据库配置、默认回测参数、图表配色。 #### Scenario: 数据库配置访问 - **WHEN** backtest_core.py 需要数据库连接参数 - **THEN** 模块从 config 导入 `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` - **THEN** 模块使用这些常量构建连接字符串 - **THEN** 模块不重复定义数据库配置 #### Scenario: 默认参数访问 - **WHEN** backtest_core.py 需要默认回测参数 - **THEN** 模块从 config 导入 `DEFAULT_CASH`, `DEFAULT_COMMISSION`, `DEFAULT_WARMUP_DAYS` - **THEN** 模块使用这些常量作为函数默认值 - **THEN** 模块不重复定义默认参数 #### Scenario: 图表配色访问 - **WHEN** backtest_core.py 需要设置图表配色 - **THEN** 模块从 config 导入 `BULL_COLOR`, `BEAR_COLOR` - **THEN** 模块使用这些颜色设置 `plotting.BULL_COLOR` 和 `plotting.BEAR_COLOR` - **THEN** 模块不重复定义颜色配置 #### Scenario: 配置文件内容 - **WHEN** 查看 config.py 文件 - **THEN** 文件包含数据库配置(DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD) - **THEN** 文件包含默认回测参数(DEFAULT_CASH, DEFAULT_COMMISSION, DEFAULT_WARMUP_DAYS) - **THEN** 文件包含图表配色(BULL_COLOR, BEAR_COLOR) - **THEN** 所有配置使用明文常量(不使用环境变量) --- ### Requirement: 错误处理策略 系统 SHALL 在批量回测失败时立即停止执行,不继续处理后续股票。 #### Scenario: 数据加载失败 - **WHEN** 系统加载第 i 个股票数据时失败(数据库错误、数据不存在) - **THEN** 系统捕获异常 - **THEN** 系统输出错误信息:"回测失败 [{code}]: {error}" - **THEN** 系统停止执行后续股票的回测 - **THEN** 系统退出并返回非零状态码 #### Scenario: 策略加载失败 - **WHEN** 系统加载策略文件时失败(文件不存在、接口不完整) - **THEN** 系统捕获异常 - **THEN** 系统输出错误信息:"策略加载失败: {error}" - **THEN** 系统停止执行所有股票的回测 - **THEN** 系统退出并返回非零状态码 #### Scenario: 回测执行失败 - **WHEN** 系统执行第 i 个股票回测时失败(策略逻辑错误) - **THEN** 系统捕获异常 - **THEN** 系统输出错误信息和完整堆栈跟踪 - **THEN** 系统停止执行后续股票的回测 - **THEN** 系统退出并返回非零状态码 #### Scenario: 图表生成失败 - **WHEN** 系统生成第 i 个股票图表时失败 - **THEN** 系统捕获异常 - **THEN** 系统输出警告:"图表生成失败 [{code}]: {error},但回测已完成" - **THEN** 系统继续执行后续股票的回测 - **THEN** 系统在返回的 `BacktestResult` 中设置 `error` 字段(如果设计支持) --- ### Requirement: 依赖管理 系统 SHALL 在 pyproject.toml 中添加 tabulate 和 tqdm 依赖。 #### Scenario: 添加 tabulate 依赖 - **WHEN** 查看 pyproject.toml 文件 - **THEN** 文件包含 `tabulate` 依赖 - **THEN** 依赖版本 SHALL 为兼容当前 Python 版本的版本 - **THEN** 系统可以导入 `import tabulate` 无错误 #### Scenario: 添加 tqdm 依赖 - **WHEN** 查看 pyproject.toml 文件 - **THEN** 文件包含 `tqdm` 依赖 - **THEN** 依赖版本 SHALL 为兼容当前 Python 版本的版本 - **THEN** 系统可以导入 `from tqdm import tqdm` 无错误 #### Scenario: 依赖安装 - **WHEN** 用户运行 `uv sync` 或 `pip install -e .` - **THEN** 系统自动安装 tabulate 和 tqdm - **THEN** 系统显示依赖安装进度 - **THEN** 系统完成安装后可以正常使用回测工具 #### Scenario: 依赖缺失提示 - **WHEN** 系统导入 tabulate 或 tqdm 时失败 - **THEN** 系统输出友好错误信息:"缺少依赖: {package_name},请运行: uv add {package_name}" - **THEN** 系统退出并返回非零状态码