策略接入分层方案
本文档定义 zelqor 下一阶段的策略接入方案。
目标不是把所有策略都强行收敛成同一种接口,而是明确区分不同复杂度策略的接入层级,让仓库同时具备:
- 标准化运行能力
- 复杂策略表达能力
- 研究脚本保留空间
当前仓库已经具备:
SelectorContextDailyPoolSelectorbuild_daily_pools(...)run_selector_backtest(...)step_backtest_day(...)
但还缺少一套清晰的“策略接入分层模型”,导致当前策略能力与运行方式之间存在脱节:
- 简单 selector 已有雏形
- 复杂跨日状态策略还没有正式接口
- 研究脚本只能通过手写入口运行
因此下一阶段建议把策略接入分成三类。
当前落地状态
本页中的方案已经部分落地。
当前已经完成:
SelectorContextDailyPoolSelectorrun_selector_backtest(...)StrategyStepContextStrategyDayDecisionStatefulStepStrategyrun_stateful_step_backtest(...)- 基于 Python 文件或模块路径的最小 strategy loader
run_backtest(...)对 selector / stateful step 的统一分发examples/run_stateful_step_limit_up_then_limit_up.py示例- 最小可加载策略示例:
examples/loadable_selector_strategy.pyexamples/loadable_stateful_step_strategy.pyzelqor.strategy.examples.loadable_selector_module
当前仍未完成:
- 更完整的 loader 导出约定、示例与错误诊断
1. 设计目标
这一阶段希望解决三个问题:
- 让简单策略可以被统一加载和批量运行
- 让复杂策略不必为了适配最窄接口而牺牲表达力
- 保留脚本式研究和演示入口,避免过早把探索型开发锁死
一句话总结:
策略加载的目标不是减少策略自由度,而是把“运行方式”标准化,同时按复杂度保留不同层级的表达空间。
2. 三类策略形态
2.1 selector 型策略
selector 型策略负责:
- 在某个交易日读取上下文
- 返回当天股票池代码列表
典型接口形态:
class DailyPoolSelector(Protocol):
def select(self, context: SelectorContext) -> list[str]:
...
适用场景:
- 截面选股
- 因子打分后取前 N
- 当天满足条件就进入股票池
- 无需维护复杂跨日内部状态的日频策略
优点:
- 接口最简单
- 最容易批量运行
- 最适合接入 CLI / API / 参数扫描
- 与现有
run_selector_backtest(...)最契合
边界:
- selector 只负责“今天选谁”
- 持仓、成交、资金和持有期仍由回测引擎处理
- 不适合承载复杂观察池、待确认池、超期淘汰等跨日状态机
2.2 stateful step 型策略
stateful step 型策略负责:
- 维护自身跨日状态
- 在每个交易日推进策略状态机
- 产出当天可交给回测引擎执行的决策结果
它与 selector 的最大区别是:
- selector 强调“今日选股结果”
- stateful step 强调“今日状态推进 + 今日决策输出”
这类策略通常会维护:
- 观察池
- 待确认池
- 信号冷却期
- 过期淘汰记录
- 其他需要跨日累积的中间状态
推荐接口形态:
@dataclass(slots=True)
class StrategyDayDecision:
pool_codes: list[str]
notes: list[str] = field(default_factory=list)
debug: dict[str, object] = field(default_factory=dict)
class StatefulStepStrategy(Protocol):
def on_day(self, context: StrategyStepContext) -> StrategyDayDecision:
...
其中 StrategyStepContext 推荐包含:
trade_datetrade_datesdate_to_indexproviderparams- 可选的只读回测状态摘要
推荐职责划分:
- 策略对象负责决定今天哪些标的进入买入池
- 回测引擎负责成交、持仓、资金和权益计算
- 策略对象只维护“策略自己的状态”
- 引擎只维护“账户和交易执行状态”
这种划分尤其适合当前仓库内的 stateful step 示例,因为它已经具备:
watchlistpending_watchlistpending_confirmation_watchlist- 超期移除
- 买入后从观察池移除
也就是说,这份策略本质上更像:
- 有状态逐日推进策略
而不是:
- 纯
select(context) -> list[str]selector
2.3 script/demo 型入口
script/demo 型入口不是统一协议,而是一种明确保留的自由运行方式。
它的特点是:
- 可以直接写脚本
- 可以手动构造 provider
- 可以手动控制参数、日志、调试输出
- 可以直接驱动
step_backtest_day(...) - 不要求一定实现统一策略接口
典型形态就是当前仓库内:
strategies/double_limit_next_limit/run_double_limit_next_limit_step.py
适用场景:
- 研究验证
- 原型开发
- API 示例
- 调试某类数据查询或状态推进逻辑
保留这类入口的原因是:
- 不是所有研究逻辑都应该第一时间沉淀成框架接口
- 复杂策略在定型前需要足够高的实验自由度
- 文档和示例也需要真实可运行脚本作为教学材料
3. 三类策略的职责边界
推荐按下表理解:
| 形态 | 策略负责什么 | 引擎负责什么 | 标准化程度 |
|---|---|---|---|
| selector | 今天选谁 | 买卖、持仓、资金、权益 | 高 |
| stateful step | 今天状态怎么推进、今天买谁 | 买卖、持仓、资金、权益 | 中 |
| script/demo | 全部由脚本自行组织 | 只提供底层能力 | 低 |
更直观地说:
- selector:今天选谁
- stateful step:今天状态怎么推进,今天买谁
- script/demo:今天整套怎么跑都由脚本自己控制
4. 推荐的正式分层
下一阶段建议正式形成下面三层:
4.1 第一层:Selector API
继续保留并完善现有:
SelectorContextDailyPoolSelectorbuild_daily_pools(...)run_selector_backtest(...)
这层作为:
- 最低成本接入方式
- 默认推荐接入方式
- 文档与示例的基础形态
4.2 第二层:Stateful Step Strategy API
新增正式接口,例如:
@dataclass(slots=True)
class StrategyStepContext:
trade_date: str
trade_dates: list[str]
date_to_index: dict[str, int]
provider: MarketDataProvider
params: dict[str, object] = field(default_factory=dict)
@dataclass(slots=True)
class StrategyDayDecision:
pool_codes: list[str] = field(default_factory=list)
notes: list[str] = field(default_factory=list)
debug: dict[str, object] = field(default_factory=dict)
class StatefulStepStrategy(Protocol):
def on_day(self, context: StrategyStepContext) -> StrategyDayDecision:
...
同时新增对应驱动器,例如:
def run_stateful_step_backtest(
*,
trade_dates: list[str],
strategy: StatefulStepStrategy,
provider: MarketDataProvider,
initial_cash: float,
...
) -> BacktestResult:
...
它的内部职责可以明确为:
- 初始化回测 state
- 遍历交易日
- 每天调用
strategy.on_day(...) - 将
decision.pool_codes交给step_backtest_day(...) - 汇总
BacktestResult
4.3 第三层:Script / Demo Entrypoint
明确允许继续存在:
examples/*.pystrategies/*/run_*.py
这层不作为统一加载目标,而作为:
- 研究脚本
- 手工演示入口
- 策略孵化入口
建议文档里明确其定位:
- 合法
- 推荐用于探索
- 不等于正式标准接入
5. 与策略加载的关系
策略加载并不要求三类策略都用同一种加载方式。
推荐分层加载:
5.1 selector 加载
支持:
- 从 Python 文件加载 selector 对象
- 从模块路径加载 selector 对象
- 从工厂函数构造 selector
推荐约定之一:
- 导出
selector - 或导出
build_selector(params) -> DailyPoolSelector
5.2 stateful step strategy 加载
支持:
- 从 Python 文件加载策略对象
- 从模块路径加载策略类或工厂
推荐约定之一:
- 导出
strategy - 或导出
build_strategy(params) -> StatefulStepStrategy
5.3 script/demo 入口
不强行纳入统一 loader。
原因:
- 它本来就是自由脚本
- 目标是演示和研究,而不是作为稳定宿主协议
也就是说:
- loader 应服务标准接口
- script/demo 应继续允许存在,但不要求被标准 loader 接管
6. 对当前仓库内策略的判断
当前仓库内的 double_limit_next_limit 和 examples/run_stateful_step_limit_up_then_limit_up.py 都可以这样理解:
- 策略内容形态:stateful step 型策略
- 当前落地方式:script/demo 型入口
这意味着它的合理演进路径不是:
- 强行改写成最简单 selector
而是:
- 先把跨日状态机从脚本中抽离成正式策略对象
- 保留一个薄的运行脚本作为 demo 入口
- 再为这类策略接入标准化驱动器和加载器
7. 推荐实施顺序
建议按下面顺序推进:
- 文档化三类策略边界与定位
- 保持现有 selector API 不变
- 新增
StatefulStepStrategy相关接口定义 - 新增
run_stateful_step_backtest(...) - 新增一个完整的 stateful step 示例脚本
- 保留现有
run_double_limit_next_limit_step.py作为原策略 demo 脚本 - 最后实现基于文件或模块路径的 loader
这样做的好处是:
- 先把接口分层设计清楚
- 再做实现
- 避免为了 loader 反向把策略能力削平
8. 非目标
这一阶段不建议做:
- 把所有策略统一成一个最窄协议
- 让 script/demo 必须适配 loader
- 为了抽象而重写现有策略脚本
- 在没有 stateful strategy 正式接口前,强行把复杂策略塞进 selector
9. 结论
zelqor 下一阶段的策略接入不应只有一条路,而应形成:
- selector 作为标准默认入口
- stateful step 作为复杂日频策略正式入口
- script/demo 作为研究与教学入口继续保留
这三层共同构成一个渐进式策略接入模型:
- 从研究脚本出发
- 向正式 stateful strategy 演进
- 对简单场景则直接落在 selector
这样既不会过早牺牲策略表达能力,也能为后续 loader、CLI、批量回测和模拟盘演进建立清晰边界。