最小可跑的首次回测怎么写?
下面这段来自官方 README(原文照录),用两条合成的价格序列做等权月度再平衡,不需要任何行情源。
import numpy as np
import pandas as pd
import bt
prices = pd.DataFrame(
{
"asset_a": np.linspace(100, 120, 252),
"asset_b": np.linspace(100, 110, 252),
},
index=pd.bdate_range("2020-01-01", periods=252),
)
strategy = bt.Strategy(
"equal_weight",
[
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance(),
],
)
result = bt.run(bt.Backtest(strategy, prices))
result.display()首次回测怎么分五步跑通?
每步都给代码、一句话说明与可核对的预期输出;报错原文以本地终端提示为准。
- 准备价格矩阵
prices = pd.DataFrame({...}, index=pd.bdate_range("2020-01-01", periods=252)):行是日期索引(DatetimeIndex),每列一个标的的收盘价。预期:prices.shape返回(252, 2)。 - 写算法链
bt.Strategy("equal_weight", [RunMonthly(), SelectAll(), WeighEqually(), Rebalance()]):一个列表就是一条算法链,顺序即执行顺序。预期:strategy对象创建成功,无异常。 - 绑定数据并执行
result = bt.run(bt.Backtest(strategy, prices)):Backtest把策略与价格绑定,bt.run执行并返回结果对象。预期:返回bt.backtest.Result(继承 ffn 的组合统计类)。 - 打印统计
result.display():输出组合统计表与净值曲线。预期:看到总收益、年化、回撤等统计行。 - 取权重与交易明细
result.get_weights()、result.get_transactions();单个回测的换手与集中度用Backtest.turnover()、Backtest.herfindahl_index()。预期:拿到权重矩阵与交易记录,用来核对「调仓是否按月发生」。
bt 1.2.3 的 bt/backtest.py(2026-09-20 核验):Result 继承 ffn.GroupStats,公开 get_weights()、get_transactions()、get_security_weights()、plot_weights() 等方法;Backtest 提供 run()、weights()、positions()、turnover()、herfindahl_index()。字段口径以官方文档与 ffn 文档为准。示例里的四个算法各自负责什么?
理解这四个类,就能把示例改造成自己的策略:换掉其中一个,策略行为就随之改变。
| 算法 | 管什么 | 换掉它会变成什么 | 注意点 |
|---|---|---|---|
RunMonthly() | 决定什么时候动手(每月执行一次) | 换 RunDaily 变每日调仓、换 RunQuarterly 变季度 | 放在算法链最前;没有调度类算法时不会按预期频率触发 |
SelectAll() | 决定动哪些标的(选中全部可用证券) | 换 SelectN 取前 N 名、换 SelectMomentum 按动量选 | 它会写入 temp['selected'],默认排除当日无数据、价格 ≤0 的证券 |
WeighEqually() | 决定各分多少(等权) | 换 WeighInvVol 逆波动、WeighERC 风险平价 | 依赖上一步的 selected:没有选券结果时权重无从分配 |
Rebalance() | 把目标权重变成交易(差额调仓) | 换 RebalanceOverTime 分步成交、LimitDeltas 限制换手 | 通常放在算法链最后;不写它就不会产生任何交易 |
| 四者的顺序 | 调度 → 选券 → 权重 → 再平衡 | 顺序颠倒会得到完全不同的调仓行为 | 调试时插入 Debug / PrintInfo 观察中间状态 |
| 结果去哪了 | 算法链只改「目标权重」,成交由再平衡算法负责 | — | 统计与图表由 ffn 提供,口径以 ffn 文档为准 |
跑完的结果怎么读?
bt 的结果对象把「组合层统计」与「单次回测明细」分成两层,下表说明各自用什么入口读。
| 想看什么 | 用什么读 | 适用场景 | 注意点 |
|---|---|---|---|
| 整体统计(收益、年化、回撤) | result.display();统计来自 ffn.GroupStats | 快速判断一次回测是否合理 | 统计口径由 ffn 决定,与自己手算不一致时先对齐口径 |
| 组合权重矩阵 | result.get_weights() | 核对权重是否按预期分配 | 权重是目标值还是成交后值,以文档与实现为准 |
| 交易记录 | result.get_transactions() | 核对调仓频率与成交量 | 无交易时先查算法链是否缺 Rebalance |
| 单个回测的换手 | Backtest.turnover() | 评估成本敏感度 | 高换手必须叠加成本模型再判断 |
| 持仓集中度 | Backtest.herfindahl_index() | 判断是否退化成少数标的 | 等权与集中化策略的读数含义不同 |
| 净值与回撤曲线 | plot_weights() / plot_histogram() 等绘图方法 | 出图写报告 | 出图依赖 ffn / matplotlib 环境 |
换成自己的数据要做什么?
README 明确写了「回测结果依赖数据质量与建模假设」。换成真实数据前,先把下面四项准备好。
| 要素 | 要求 | 常见错法 |
|---|---|---|
| 索引 | 必须是日期索引(DatetimeIndex),按时间升序 | 用字符串或整数当索引导致排序错乱 |
| 列 | 每列一个标的的收盘价,列名即标的标识 | 列里混入成交量或成交额 |
| 缺失值 | 显式处理(对齐、前向填充或剔除),不要留下 NaN | NaN 参与计算导致净值跳变或统计失真 |
| 复权口径 | 自己决定并记录(前复权 / 后复权 / 不复权) | 不同标的口径不一致,收益不可比 |
| 数据来源 | bt 不提供行情;自备数据或用本机财经数据类技能取数 | 把来源不明的数据当权威数据使用 |
| 区间与频率 | 先固定一个区间与频率做基线,再改变量 | 同时改区间、标的与算法,结论无法归因 |
首跑最容易卡在哪六处?
下面六种现象覆盖首次运行最常见的失败方式,处理方式以官方文档与本地提示为准。
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 跑完没有任何交易 | 算法链没有再平衡算法 | 在链尾补 Rebalance() |
| 报索引类型相关错误 | 价格表的索引不是日期序列 | 用 pd.to_datetime 或 pd.bdate_range 重建索引并排序 |
| 结果数字明显失真 | 价格表里有 NaN 或重复日期 | 先 prices.isna().sum()、prices.index.duplicated().sum() 自检 |
| 权重与预期不符 | 算法顺序写反(权重算法在选券之前) | 按调度 → 选券 → 权重 → 再平衡顺序检查 |
| 想把合成价格的收益当结论 | 合成价格是人为直线,不具市场特征 | 只用于验证流程;结论必须基于真实数据并注明区间与口径 |
| 报缺少模块 | 环境缺 pandas / numpy(bt 未直接声明这两个依赖) | 显式安装并记录版本,见安装页依赖边界 |
bt 首次回测常见问题
首次回测一定要有行情数据吗?
不需要。官方 README 的入门示例用 np.linspace 合成价格,目的就是让你在没有数据源的情况下先跑通流程;但合成价格只能验证代码与流程,不能用于任何结论。
为什么我的回测一笔交易都没有?
最常见的原因是算法链里缺再平衡算法(如 Rebalance()):算法链只负责算目标权重,把权重变成交易是再平衡算法的职责。其次检查调度算法是否真的触发了调仓。
能回测 A 股 / 港股 / 美股吗?
bt 不提供行情,只要求你把数据整理成「日期 × 标的」的收盘价矩阵,因此市场不受库的限制;标的选择、复权口径与交易日差异都要你自己处理。
怎么把交易成本算进去?
通过 Backtest 的成本模型参数配置(CostModel / SqrtCostModel / AlmgrenChrissCostModel),详见「成本与固收」页;具体参数名与默认值以官方文档为准。
结果里的统计是谁算的?
绩效统计与图表由 ffn(Financial Functions for Python)提供,bt 的结果对象继承 ffn 的组合统计类。因此统计字段名与口径应以 ffn 文档为准,不要假设 bt 自己实现了一套。
回测结果能当收益预期吗?
不能。官方 README 写明回测结果依赖数据质量与建模假设、不预测未来表现;本站示例仅用于说明用法,不构成投资建议。