完成回测脚本
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
# Spec: Strategy Loading
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 策略文件接口
|
||||
策略文件 SHALL 提供两个必需的接口:指标计算函数和策略类获取函数。
|
||||
|
||||
#### Scenario: 标准策略文件结构
|
||||
- **WHEN** 用户创建策略文件
|
||||
- **THEN** 文件 SHALL 包含 `calculate_indicators(data)` 函数
|
||||
- **THEN** 文件 SHALL 包含 `get_strategy()` 函数
|
||||
- **THEN** 文件 SHALL 包含一个继承 `backtesting.Strategy` 的类
|
||||
- **THEN** 所有三个组件 SHALL 在同一文件中
|
||||
|
||||
#### Scenario: calculate_indicators 函数签名
|
||||
- **WHEN** 主流程调用 `calculate_indicators(data)`
|
||||
- **THEN** 函数接收一个参数:data (pandas.DataFrame)
|
||||
- **THEN** 函数返回一个 pandas.DataFrame
|
||||
- **THEN** 返回的 DataFrame SHALL 包含原始列和新增的指标列
|
||||
- **THEN** 函数 SHALL 修改输入的 DataFrame(不创建副本)
|
||||
|
||||
#### Scenario: get_strategy 函数签名
|
||||
- **WHEN** 主流程调用 `get_strategy()`
|
||||
- **THEN** 函数不接收参数
|
||||
- **THEN** 函数返回一个类对象
|
||||
- **THEN** 返回的类 SHALL 继承自 `backtesting.Strategy`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 指标计算函数
|
||||
`calculate_indicators` 函数 SHALL 计算策略所需的技术指标,并将结果添加到 DataFrame 中。
|
||||
|
||||
#### Scenario: SMA 指标计算
|
||||
- **WHEN** 策略需要简单移动平均线指标
|
||||
- **THEN** 函数使用 `data['Close'].rolling(window=N).mean()` 计算
|
||||
- **THEN** 函数将结果存储为 `data['smaN']` 列
|
||||
- **THEN** N 为具体的周期(如 10, 30, 60, 120)
|
||||
|
||||
#### Scenario: MACD 指标计算
|
||||
- **WHEN** 策略需要 MACD 指标
|
||||
- **THEN** 函数使用 `data['Close'].ewm(span=12).mean()` 计算 EMA12
|
||||
- **THEN** 函数使用 `data['Close'].ewm(span=26).mean()` 计算 EMA26
|
||||
- **THEN** 函数计算 MACD = EMA12 - EMA26
|
||||
- **THEN** 函数计算 Signal = MACD.ewm(span=9).mean()
|
||||
- **THEN** 函数将结果存储为 `data['macd']`, `data['macd_signal']`, `data['macd_hist']` 列
|
||||
|
||||
#### Scenario: RSI 指标计算
|
||||
- **WHEN** 策略需要 RSI 指标
|
||||
- **THEN** 函数计算价格变化 delta = data['Close'].diff()
|
||||
- **THEN** 函数计算 gain = delta.where(delta > 0, 0)
|
||||
- **THEN** 函数计算 loss = -delta.where(delta < 0, 0)
|
||||
- **THEN** 函数计算平均收益和平均损失
|
||||
- **THEN** 函数计算 RS = average_gain / average_loss
|
||||
- **THEN** 函数计算 RSI = 100 - (100 / (1 + RS))
|
||||
- **THEN** 函数将结果存储为 `data['rsi']` 列
|
||||
|
||||
#### Scenario: 多指标计算
|
||||
- **WHEN** 策略需要多个技术指标
|
||||
- **THEN** 函数按顺序计算每个指标
|
||||
- **THEN** 函数将所有指标列添加到 DataFrame
|
||||
- **THEN** DataFrame 最终包含原始列 + 所有指标列
|
||||
- **THEN** 计算顺序 SHALL 遵循指标间的依赖关系(如 MACD 依赖 EMA)
|
||||
|
||||
#### Scenario: 指标列命名约定
|
||||
- **WHEN** 函数添加指标列到 DataFrame
|
||||
- **THEN** 列名 SHALL 使用小写和下划线(如 `sma10`, `macd_signal`)
|
||||
- **THEN** 列名 SHALL 与策略类的 `init()` 方法中引用的名称一致
|
||||
- **THEN** 列名 SHALL 避免与原始列冲突
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 策略类定义
|
||||
策略类 SHALL 继承 `backtesting.Strategy`,并实现 `init()` 和 `next()` 方法。
|
||||
|
||||
#### Scenario: 策略类继承
|
||||
- **WHEN** 用户定义策略类
|
||||
- **THEN** 类 SHALL 显式继承 `backtesting.Strategy`
|
||||
- **THEN** 类 SHALL 定义类属性作为可配置参数
|
||||
- **THEN** 类名 SHALL 使用大驼峰命名(如 `SmaCross`, `MacdStrategy`)
|
||||
|
||||
#### Scenario: init 方法实现
|
||||
- **WHEN** Backtest 框架初始化策略时
|
||||
- **THEN** 系统调用策略类的 `init()` 方法
|
||||
- **THEN** `init()` 方法 SHALL 使用 `self.I()` 注册指标
|
||||
- **THEN** `self.I(lambda x: x, self.data.column_name)` SHALL 引用 DataFrame 中的指标列
|
||||
- **THEN** `init()` 方法 SHALL 不执行数据计算
|
||||
|
||||
#### Scenario: next 方法实现 - 金叉买入
|
||||
- **WHEN** 短期均线上穿长期均线(金叉)
|
||||
- **THEN** `next()` 方法 SHALL 调用 `self.position.close()` 平仓
|
||||
- **THEN** `next()` 方法 SHALL 调用 `self.buy()` 开多仓
|
||||
- **THEN** `next()` 方法 SHALL 使用 `crossover()` 函数检测交叉
|
||||
|
||||
#### Scenario: next 方法实现 - 死叉卖出
|
||||
- **WHEN** 短期均线下穿长期均线(死叉)
|
||||
- **THEN** `next()` 方法 SHALL 调用 `self.position.close()` 平仓
|
||||
- **THEN** `next()` 方法 SHALL 调用 `self.sell()` 开空仓
|
||||
- **THEN** `next()` 方法 SHALL 使用 `crossover()` 函数检测交叉
|
||||
|
||||
#### Scenario: next 方法实现 - 避免重复开仓
|
||||
- **WHEN** 策略已持有多仓,且买入信号触发
|
||||
- **THEN** `next()` 方法 SHALL 先调用 `self.position.close()`
|
||||
- **THEN** `next()` 方法 SHALL 再调用 `self.buy()`
|
||||
- **THEN** 系统 SHALL 自动处理仓位管理(不重复开仓)
|
||||
|
||||
#### Scenario: 可配置策略参数
|
||||
- **WHEN** 策略类定义类属性
|
||||
- **THEN** 类属性 SHALL 作为策略参数(如 `short_period = 10`)
|
||||
- **THEN** Backtest 框架 SHALL 自动访问这些属性
|
||||
- **THEN** 参数 SHALL 可通过 Backtest 构造函数覆盖
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 策略类指标引用
|
||||
策略类的 `init()` 方法 SHALL 正确引用 DataFrame 中计算好的指标列。
|
||||
|
||||
#### Scenario: 引用 SMA 指标
|
||||
- **WHEN** DataFrame 包含 `sma10` 和 `sma30` 列
|
||||
- **THEN** `init()` 方法注册 `self.sma_short = self.I(lambda x: x, self.data.sma10)`
|
||||
- **THEN** `init()` 方法注册 `self.sma_long = self.I(lambda x: x, self.data.sma30)`
|
||||
- **THEN** `next()` 方法 SHALL 通过 `self.data.sma10` 和 `self.data.sma30` 访问指标
|
||||
|
||||
#### Scenario: 引用 MACD 指标
|
||||
- **WHEN** DataFrame 包含 `macd` 和 `macd_signal` 列
|
||||
- **THEN** `init()` 方法注册 `self.macd = self.I(lambda x: x, self.data.macd)`
|
||||
- **THEN** `init()` 方法注册 `self.signal = self.I(lambda x: x, self.data.macd_signal)`
|
||||
- **THEN** `next()` 方法 SHALL 通过 `self.data.macd` 和 `self.data.macd_signal` 访问指标
|
||||
|
||||
#### Scenario: 引用 RSI 指标
|
||||
- **WHEN** DataFrame 包含 `rsi` 列
|
||||
- **THEN** `init()` 方法注册 `self.rsi = self.I(lambda x: x, self.data.rsi)`
|
||||
- **THEN** `next()` 方法 SHALL 通过 `self.data.rsi` 访问指标
|
||||
- **THEN** 策略逻辑 SHALL 使用 RSI 阈值生成信号(如 RSI > 70 超买)
|
||||
|
||||
#### Scenario: 指标列不存在
|
||||
- **WHEN** 策略类引用的列名不存在于 DataFrame
|
||||
- **THEN** Backtest 框架抛出 KeyError
|
||||
- **THEN** 主流程捕获异常并输出错误信息:"指标列 {column} 不存在"
|
||||
- **THEN** 系统退出并返回非零状态码
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 动态加载机制
|
||||
主流程 SHALL 使用 importlib 动态加载策略文件模块。
|
||||
|
||||
#### Scenario: 加载顶层策略文件
|
||||
- **WHEN** 用户指定 `--strategy-file strategy.py`
|
||||
- **THEN** 系统使用 `spec_from_file_location('strategy', 'strategy.py')` 创建规范
|
||||
- **THEN** 系统使用 `module_from_spec(spec)` 创建模块对象
|
||||
- **THEN** 系统使用 `spec.loader.exec_module(module)` 执行模块
|
||||
- **THEN** 系统成功获取 `module.calculate_indicators` 和 `module.get_strategy`
|
||||
|
||||
#### Scenario: 加载子目录策略文件
|
||||
- **WHEN** 用户指定 `--strategy-file strategies/macd_strategy.py`
|
||||
- **THEN** 系统使用 `spec_from_file_location('strategies.macd_strategy', 'strategies/macd_strategy.py')`
|
||||
- **THEN** 模块名使用点号分隔(反映目录结构)
|
||||
- **THEN** 系统成功加载子目录中的策略模块
|
||||
|
||||
#### Scenario: 模块命名空间隔离
|
||||
- **WHEN** 系统动态加载多个策略文件
|
||||
- **THEN** 每个策略模块 SHALL 有独立的命名空间
|
||||
- **THEN** 模块间 SHALL 不共享全局变量
|
||||
- **THEN** 系统通过 `getattr(module, name)` 明确访问函数和类
|
||||
|
||||
#### Scenario: 策略文件导入错误
|
||||
- **WHEN** 策略文件包含语法错误或导入错误
|
||||
- **THEN** `exec_module()` 抛出 ImportError 或 SyntaxError
|
||||
- **THEN** 主流程捕获异常
|
||||
- **THEN** 系统输出错误信息:"策略文件 {file} 加载失败: {error}"
|
||||
- **THEN** 系统退出并返回非零状态码
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 策略接口验证
|
||||
主流程 SHALL 验证策略文件是否符合接口要求。
|
||||
|
||||
#### Scenario: 验证 calculate_indicators 存在
|
||||
- **WHEN** 系统加载策略模块
|
||||
- **THEN** 系统使用 `hasattr(module, 'calculate_indicators')` 检查函数
|
||||
- **WHEN** 函数不存在
|
||||
- **THEN** 系统抛出 AttributeError
|
||||
- **THEN** 主流程捕获并输出:"策略文件 {file} 缺少 calculate_indicators 函数"
|
||||
|
||||
#### Scenario: 验证 get_strategy 存在
|
||||
- **WHEN** 系统加载策略模块
|
||||
- **THEN** 系统使用 `hasattr(module, 'get_strategy')` 检查函数
|
||||
- **WHEN** 函数不存在
|
||||
- **THEN** 系统抛出 AttributeError
|
||||
- **THEN** 主流程捕获并输出:"策略文件 {file} 缺少 get_strategy 函数"
|
||||
|
||||
#### Scenario: 验证 get_strategy 返回类
|
||||
- **WHEN** 系统调用 `get_strategy()`
|
||||
- **THEN** 系统使用 `isinstance(returned, type)` 检查返回值
|
||||
- **WHEN** 返回值不是类
|
||||
- **THEN** 系统抛出 TypeError
|
||||
- **THEN** 主流程捕获并输出:"get_strategy() 必须返回一个类"
|
||||
|
||||
#### Scenario: 验证策略类继承
|
||||
- **WHEN** 系统获取策略类
|
||||
- **THEN** 系统使用 `issubclass(strategy_class, backtesting.Strategy)` 检查继承
|
||||
- **WHEN** 策略类未继承 `backtesting.Strategy`
|
||||
- **THEN** 系统抛出 TypeError
|
||||
- **THEN** 主流程捕获并输出:"策略类必须继承 backtesting.Strategy"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 策略文件示例
|
||||
系统 SHALL 提供策略模板文件作为开发者参考。
|
||||
|
||||
#### Scenario: 提供策略模板
|
||||
- **WHEN** 用户查看 strategy.py 文件
|
||||
- **THEN** 文件 SHALL 包含完整的策略示例(SMA 双均线交叉)
|
||||
- **THEN** 文件 SHALL 包含清晰的注释说明每个接口的用途
|
||||
- **THEN** 文件 SHALL 包含代码示例(指标计算函数、get_strategy、策略类)
|
||||
|
||||
#### Scenario: 策略文件文档
|
||||
- **WHEN** 策略文件开头有文档字符串
|
||||
- **THEN** 文档 SHALL 描述策略逻辑
|
||||
- **THEN** 文档 SHALL 列出需要的指标
|
||||
- **THEN** 文档 SHALL 说明参数含义(如 `short_period`, `long_period`)
|
||||
|
||||
#### Scenario: 策略参数说明
|
||||
- **WHEN** 策略类定义类属性
|
||||
- **THEN** 每个属性 SHALL 有注释说明(如 `short_period = 10 # 短期均线周期`)
|
||||
- **THEN** 参数 SHALL 使用有意义的名称(不是 param1, param2)
|
||||
Reference in New Issue
Block a user