9.9 KiB
9.9 KiB
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)