阅读提示
这篇讲解 Betalens 的择时策略模式——它和截面因子有什么不同、YAML 配置怎么写、权重是怎么生成的。如果你对单标的择时感兴趣,这篇是入门的最佳起点。
导言
Betalens 的因子策略分为两大类:
- 截面因子(
strategy_type: cross_sectional,默认值):在每个调仓日对全市场股票打分排名,买入高分组、卖出低分组。多空均可,适合选股型策略。 - 择时策略(
strategy_type: timing):针对单个标的,根据技术指标/情绪指标生成仓位信号(0%~100%),满仓/空仓/半仓操作。多为纯多头,适合交易型策略。
两种模式的核心区别在于权重是怎么生成的:
1 | 截面因子:因子值 → 分组标签 → 组内等权/加权 → 权重矩阵(多头合计1,空头合计-1) |
截面 vs 择时:关键差异
| 维度 | 截面因子 | 择时策略 |
|---|---|---|
| 标的数量 | 多只股票(截面) | 单标的 |
| 权重范围 | -1(空头)~ +1(多头) | 0(空仓)~ 1(满仓) |
| 调仓逻辑 | 分组后等权配比 | 信号触发后直接给仓位 |
YAML strategy_type |
缺省或 cross_sectional |
timing |
YAML weight.mode |
classic-long-short 等 |
freeplay(占位) |
| Dashboard 归属 | “截面因子”页面 | “择时策略”页面 |
XICHOU_timing 完整解析
以 betalens-factor/tdx/XICHOU/factor_XICHOU_timing.yaml 为例,逐段讲解:
1 | meta: |
1 | factor_spec: |
1 | weight: |
1 | run: |
择时策略的计算逻辑
择时策略的 compute 函数不走分组,而是直接生成目标仓位:
1 | # 伪代码(参考 factor_XICHOU_timing.py) |
核心特点:
- 不分组:不把股票分成 Top/Bottom 组,直接生成一个 0~1 的仓位。
- 带历史信息:用历史触发事件的分布(分位数)来决定当前仓位,避免冷启动过拟合。
- 有最保守的默认持仓天数(
default_exit_wait_days):即使统计样本不足,也能有基本的行为基准。
Dashboard 中跑择时策略
- 打开 Dashboard:
http://127.0.0.1:5173 - 进入”择时策略“页面(不是”截面因子”页面)
- 选择因子:
tdx/XICHOU_timing - 修改参数(最常改的是
stock_code、trigger_threshold、max_weight) - 点击”运行”
Dashboard 会显示:
- 信号记录:每次开仓/平仓的时间点
- 仓位曲线:每日目标仓位(0~1)
- 累计净值:策略净值 vs 基准净值
- 胜率/赔率:开仓次数、盈利次数、亏损次数、平均盈利、平均亏损
- 平均仓位:年化平均持仓比例(衡量择时信号的积极性)
开发者侧:择时 YAML 的约束
如果要给现有的截面因子新增一个择时版本(比如 factor_ROE_timing.py),需要注意以下约束:
- YAML 必须有
strategy_type: timing:否则 Dashboard 不会把它放到择时策略页面。 - compute 函数直接生成权重矩阵:不走
get_single_factor_weight,而是返回一个DataFrame(index=DatetimeIndex,columns=[stock_code, “cash”])。 - 权重范围 0~1(纯多头),如果要做空,可以输出负值。
weight.mode: freeplay只是占位:Dashboard 配置结构要求有这个字段,但择时脚本不会使用它。- 返回标准的
RunResult:FactorPipeline.run()仍然返回RunResult(backtest=bt, analyst=analyst, factor_values=...),不要另建第二套返回结构。
1 | # 择时因子脚本的 compute 函数签名 |
常见问题
1. 择时策略在 Dashboard 里找不到
检查 YAML:strategy_type: timing 是否拼写正确,大小写敏感。
1 | # 错 |
2. 仓位一直是 0
原因可能是:
trigger_threshold设太高,信号从未触发。min_history_events设太高,历史触发事件不足 3 个。- 看
factor_values输出里的”是否触发”列,确认触发逻辑。
3. 择时回测和手动计算不一致
择时策略的调仓执行时间会加 10 分钟(pd.Timedelta(minutes=10)),这是框架约定。如果你在 Jupyter 里手动算信号和仓位,注意对齐到交易日边界。
延伸阅读
- Betalens 新手系列 · 13:因子库巡礼——tdx 家族因子概览。
- Betalens 新手系列 · 20:自建因子端到端——如何新增一个因子(含择时版)。
docs/guide/factor-pipeline.rst——因子 YAML 规范。docs/guide/dashboard.rst——Dashboard 使用文档。