功能
横截面策略
每期为资产池排序、轮动至领先资产,并通过五分位组合测试因子
如何为资产池排序,并交易其中最强和最弱的品种?本页用 Python 回测横截面动量轮动:每月按过去一年的收益为 37 只 ETF 排序,持有领先者,并在成分随时间变化的资产池中,通过五分位组合检验因子。
symbols = [
"XLB", "XLE", "XLF", "XLI", "XLK", "XLP", "XLU", "XLV", "XLY", "XLRE", "XLC",
"EWA", "EWC", "EWG", "EWH", "EWJ", "EWU", "EWZ", "EWW", "EWY", "EWT", "EWS",
"EWL", "EWQ", "EWP", "EWI", "EWD", "EWN", "FXI", "INDA", "EIDO",
"TLT", "IEF", "GLD", "SLV", "DBC", "VNQ",
]
close = vbt.YFData.pull(symbols, start="2007-01-01", end="2025-01-01").close
month = pd.Series(close.index.month, index=close.index)
dates = close.index[month != month.shift(-1)]
momentum = (close.shift(21) / close.shift(252) - 1).loc[dates]
top = momentum.rank(axis=1, ascending=False) <= 5
eligible = momentum.notna()
def simulate(weights):
return vbt.PF.from_orders(
close,
size=weights.loc["2008":].reindex(close.index),
size_type="targetpercent",
group_by=True,
cash_sharing=True,
call_seq="auto",
fees=0.001,
)
pfs = {
"Top 5 momentum": simulate(top.div(top.sum(axis=1), axis=0)),
"Equal weight": simulate(eligible.div(eligible.sum(axis=1), axis=0)),
}
metrics = ["total_return", "sharpe_ratio", "max_dd"]
pd.DataFrame({k: pf.stats(metrics) for k, pf in pfs.items()}).astype(float).round(2) Top 5 momentum Equal weight
Total Return [%] 84.61 152.43
Sharpe Ratio 0.35 0.45
Max Drawdown [%] 37.41 51.902008 至 2024 年,每月持有最强五只 ETF 的收益为 85%,而等权持有全部合资格 ETF 的收益为 152%,前者回撤更小。在这里,动量降低了风险,但没有增加收益。
同一工作流也接受自己的得分:技术指标、基本面因子或模型预测。提供按日期和交易品种排列的得分表,选择持有数量,再比较得到的投资组合。动量只是其中一个例子。
排序与选择
排序是在每个交易品种占一列的表格上执行 pandas 操作,因此 rank(axis=1) 可一次为所有日期排序,rank <= 5 选择领先者。某日没有数据的品种得到
NaN,不参与排序。选中结果转化为权重后,可像上例一样交给 vbt.PF.from_orders,以共享现金按目标百分比交易;也可转化为 vbt.PF.from_signals
的信号。投资组合优化页用 vbt.PFO.from_allocate_func
按任意计划计算相同权重。负权重表示做空,因此多空组合的权重是领先组权重减去落后组权重。对于数千个品种,可按时间切分得分表,逐期排序,再通过 split_apply
合并。每期保留完整资产池,使股票仍与全部同类比较,同时减少中间排序表的内存占用。
若整个选择在 K 线开始前已知,排序表就足够。若入场来自各品种自身信号,限制又依赖已有持仓,例如最多五个未平仓头寸,则决策需要投资组合状态,参见事件驱动回测。
因子研究
有效因子的排名应能区分未来收益,因此可按排名将资产池分成五组并比较。每个五分位组都是同一投资组合内的一组列:
quintile = np.ceil(momentum.rank(axis=1, pct=True) * 5)
members = {q: quintile == q for q in range(1, 6)}
q_weights = pd.concat(
{q: m.div(m.sum(axis=1), axis=0) for q, m in members.items()},
axis=1,
names=["quintile"],
)
q_close = pd.concat({q: close for q in range(1, 6)}, axis=1, names=["quintile"])
q_pf = vbt.PF.from_orders(
q_close,
size=q_weights.loc["2008":].reindex(close.index),
size_type="targetpercent",
group_by="quintile",
cash_sharing=True,
call_seq="auto",
fees=0.001,
)
q_pf.stats(metrics, agg_func=None).astype(float).round(2) Total Return [%] Sharpe Ratio Max Drawdown [%]
quintile
1 132.68 0.38 57.79
2 67.94 0.29 55.03
3 100.37 0.36 58.13
4 136.40 0.42 51.06
5 118.17 0.42 40.87如果因子有效,收益应从动量最弱的第 1 组到最强的第 5 组逐步上升。这里收益没有按顺序排列,只有回撤随动量增强而下降。排名信息系数,即每月排名与下月收益的相关性,可对此给出数值衡量:
fwd = close.loc[dates].pct_change().shift(-1)
ic = momentum.corrwith(fwd, axis=1, method="spearman").loc["2008":].dropna()
t_stat = ic.mean() / ic.std() * np.sqrt(ic.count())
print(round(ic.mean(), 3), round(t_stat, 2), round((ic > 0).mean(), 2))0.006 0.29 0.52平均 IC 为 0.006,在 52% 的月份为正,与零没有明显区别。买入第 5 组、做空第 1 组且两边各分配一半资金的多空组合,同期损失约四分之一净值。VBT 没有 Alphalens 那样内置的因子分析报告,但排名、未来收益和五分位投资组合都是普通表格,因此各项统计量只需一行 pandas 代码。
若要单独检验某个因子,应先剔除它对其他特征的暴露:在每个日期将该因子对其他特征回归并保留残差,或减去其所在组(例如行业)的均值。指标表达式提供 cs_rank、cs_demean 和
cs_rescale,用于排序、组内去均值及重新缩放。cs_rank
输出百分位排名,在合资格品种数量变化时很有用。这些函数也用于技术指标页中的 WorldQuant
101 Alphas。
动态资产池
加入或退出资产池的品种在整个时期始终保留在表中,加入前和退出后填充 NaN,因此资产轴不会变化,每次模拟都保持向量化。这里的动量还要求一年历史,新的 ETF 在满足条件前不会入选。对于股票指数,时点成分表会在各股票非成分股期间屏蔽它们,避免只用今天仍存续的股票回测,具体见真实回测。一次只持有一个品种的策略,也可将所选品种的价格拼接为一条序列,并在每次切换时平仓。
只为合资格资产排序
资格条件可结合指数成员资格、足够的价格历史和自定义流动性筛选。应在排序前应用这些条件,避免不可交易资产占据选中名额。下面是三个资产在四个再平衡日的虚构得分:
dates = pd.to_datetime(["2025-01-31", "2025-02-28", "2025-03-31", "2025-04-30"])
scores = pd.DataFrame(
{"A": [8, 9, 7, 8], "B": [6, 5, 8, 9], "C": [4, 10, 9, 7]},
index=dates,
)
eligible = pd.DataFrame(
{"A": [True, False, False, False], "B": [True, True, False, False], "C": [True, True, True, False]},
index=dates,
)
ranks = scores.where(eligible).rank(axis=1, ascending=False, method="first")
selected = ranks <= 2
weights = selected.div(selected.sum(axis=1).replace(0, np.nan), axis=0).fillna(0.0)
pfo = vbt.PFO.from_allocations(scores.vbt.wrapper, weights)
pfo.allocations A B C
2025-01-31 0.5 0.5 0.0
2025-02-28 0.0 0.5 0.5
2025-03-31 0.0 0.0 1.0
2025-04-30 0.0 0.0 0.0二月,尽管 A 的得分高于 B,它仍退出候选。三月,C 是唯一合资格资产,获得全部目标配置。四月没有合资格资产,因此目标为全现金。method="first"
使并列得分优先选择靠前的列。随后可将配置表与价格一起模拟,并计入手续费和滑点,参见投资组合优化。
比较轮动规则
结果是否依赖持有五个品种、使用一年回看期或每月再平衡?将得分、选择和模拟步骤封装为函数,再通过参数优化比较这些选项。像开篇示例一样,在排名投资组合旁保留等权投资组合,观察选择相较于持有整个资产池带来了什么。
将因子从排名 IC 推进到实际交易组合。比较扣除成本后的表现,再检查订单,了解持仓更换频率和轮动成本。五分位投资组合有助于区分两类因子:一类能在整个资产池中区分收益,另一类的结果主要依赖少数排名靠前的品种。
教程
会员专享的投资组合优化(英文)教程使用自定义函数和优化库,在资产池中分配资金。
版权所有 © 2021–2026 Oleg Polakow。保留所有权利。
本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。