这篇的目标是让你在 5 分钟内跑出一个完整的因子回测——从查数据到分组、从分组到权重、从权重到净值报告。跑完之后,你会对”Datafeed → Factor → Backtest → Analyst”这条流水线有一个感性认识。
导言
假设你已经装好了 Betalens(如果没装,先去看 03 篇),现在想知道一件事:股息率高的股票,是不是长期来看真的比股息率低的股票更赚钱?
这个问题用 Betalens 来回答,只需要 30 行代码。
完整代码
1 | from betalens.datafeed import get_absolute_trade_days |
运行完毕,当前目录下会多出 report.xlsx 和 report.html。用浏览器打开 HTML 就能看到净值曲线、收益统计、持仓明细等图表。
代码解读:流水线上的五个步骤
步骤 1:调仓日 + 可交易池
1 | days = get_absolute_trade_days("2020-04-30", "2024-04-30", "Y") |
get_absolute_trade_days:返回交易日列表。"Y"表示每年,落在 4 月 30 日(或其最近交易日)。get_tradable_pool:根据交易日列表,返回哪些股票在哪些日期是”可交易的”(剔除停牌、未上市)。返回两个对象——date_ranges(日期×可交易证券)和code_ranges(证券代码范围),这两个对象会在后续查询中作为过滤条件。
步骤 2:查询因子并分组
1 | data = pre_query_characteristic_data( |
pre_query_characteristic_data:从数据库预取原始因子值。默认从fundamentals表取,time_tolerance默认24*2*365(两年)。single_characteristic:按分位数把股票分成 10 组(分位点 10),返回带分组标签的 DataFrame。注意函数名是characteristic不是factor,这是最容易记错的命名坑。{"股息率(报告期)": 10}表示按股息率从低到高分成 10 组。
步骤 3:生成多空权重
1 | weights = get_single_factor_weight(labeled, { |
classic-long-short:做多最高股息率组(第 10 组),做空最低组(第 1 组),多头合计 1、空头合计 -1。- 权重矩阵的列是证券代码,行是调仓日(
DatetimeIndex)。最后必须补一列cash(现金比例)。 - 这里的
cash=0表示资金全部用上了(多头用了 100%,空头对冲)。
步骤 4:回测
1 | engine = BacktestBase( |
BacktestBase接收目标权重矩阵,按整数手(每手 100 股)成交,模拟日频净值。engine.actual_weight才是真正参与净值计算的权重(输入的weight可能会因停牌、整数手等原因被调整)。check_trade_status=True时,停牌股票会自动转现金(trade_status_mode="to_cash")。
步骤 5:绩效评价
1 | Analyst.from_backtest(engine, name="Dividend").report( |
Analyst.from_backtest从回测实例生成完整报告。report()同时输出 Excel 和 HTML,HTML 用 plotly 渲染成交互图表。- 也可用
Analyst.from_excel(path)从已有的 Excel 报告文件重新分析。
开发者侧:流水线的数据流
从开发者的角度理解,上面的 quickstart 其实走的是这样一条路径:
1 | PostgreSQL (fundamentals) ──→ pre_query_characteristic_data |
流水线中每一个函数都有明确的输入输出约定,不要在中间插入自定义的 pandas 操作——如果需要自定义,在 betalens-factor/ 下写因子脚本,通过 FactorPipeline 接入。
常见错误
1. 忘记补 cash 列
1 | # 报错:KeyError: 'cash' 不在 columns 中 |
解决:无论哪种权重模式,都要在权重矩阵末尾加一列 cash = 1 - sum(stock_weights)(多空模式 cash=0,纯多头模式 cash 大于 0)。
2. single_characteristic 函数名记错
1 | # 错:没有 single_factor 这个函数 |
3. 回测表名写错
BacktestBase 默认 table_name="daily_market"、metric="收盘价(元)"。如果你的数据库用的是其他表名或字段名,需要显式传入参数。
4. 初始资金太小,买不了任何股票
amount=1_000_000(100 万)可以买大多数 A 股;如果设成 10_000(1 万),整数手约束下可能凑不出任何持仓,导致 actual_weight 全是 0。
延伸阅读
- Betalens 新手系列 · 01:Betalens 是什么——宏观架构。
- Betalens 新手系列 · 04:调试第一策略——当回测结果不对时,从哪里开始查。
docs/guide/factor.rst——因子 API 详细说明。docs/guide/backtest.rst——回测引擎参数说明。