功能
投资组合优化
用自己的函数或优化器库配置资产,并回测再平衡
如何随时间给资产分配权重,再平衡成本是多少?VBT 在 Python 中按任意计划运行投资组合优化:编写自己的配置函数,或调用 PyPortfolioOpt、Riskfolio-Lib、Universal Portfolios,再对权重进行包含手续费的回测。
symbols = ["SPY", "QQQ", "IWM", "EFA", "EEM", "TLT", "IEF", "GLD", "VNQ", "DBC"]
data = vbt.YFData.pull(symbols, start="2016-01-01", end="2025-01-01")
def inverse_vol(returns):
inv = 1 / returns.std()
return inv / inv.sum()
wrapper = data.symbol_wrapper
returns = vbt.RepEval("data.returns.iloc[index_slice]", context=dict(data=data))
schedule = dict(every="M", lookback_period="365D")
optimizers = {
"Equal weight": vbt.PFO.from_uniform(wrapper, every="M", start="2017"),
"Inverse volatility": vbt.PFO.from_optimize_func(
wrapper, inverse_vol, returns, **schedule
),
"Max Sharpe": vbt.PFO.from_pypfopt(
prices=data.close, target="max_sharpe", **schedule
),
}
pfs = {
name: pfo.simulate(data, init_cash=10_000, fees=0.001).loc["2017":]
for name, pfo in optimizers.items()
}
metrics = ["total_return", "sharpe_ratio", "max_dd", "total_fees_paid"]
stats = {name: pf.stats(metrics) for name, pf in pfs.items()}
pd.DataFrame(stats).astype(float).round(2) Equal weight Inverse volatility Max Sharpe
Total Return [%] 85.49 60.56 58.00
Sharpe Ratio 0.85 0.79 0.59
Max Drawdown [%] 23.17 21.80 28.11
Total Fees Paid 46.63 53.14 479.83最大夏普优化器每次选择过去一年夏普比率最高的权重,却在之后月份表现最差:夏普最低、回撤最深、手续费为十倍,因为权重在少数资产间跳动。最简单的等权重配置表现最好。这类结果说明为何每个优化器旁都应有基线。
回测自己的权重
已有模型、电子表格或其他优化器生成的配置?以 pandas DataFrame 传入,资产为列,再平衡日期为索引。VBT 将这些目标转为投资组合模拟,让您检查交易、手续费和绩效。
此示例使用虚构价格和预先给定的两次权重变化。组合最初持有 60% 的 A 和 40% 的 B,在 1 月 3 日切换为 30% 和 70%。
close = pd.DataFrame(
{"A": [100, 120, 110, 121], "B": [100, 100, 110, 110]},
index=pd.date_range("2025-01-01", periods=4),
)
weights = pd.DataFrame(
[[0.6, 0.4], [0.3, 0.7]],
index=close.index[[0, 2]],
columns=close.columns,
)
pfo = vbt.PFO.from_allocations(close.vbt.wrapper, weights)
portfolio = pfo.simulate(close, init_cash=1000, fees=0.001, freq="1D")
pfo.allocations A B
2025-01-01 0.6 0.4
2025-01-03 0.3 0.7portfolio.get_allocations().round(3) A B
2025-01-01 0.600 0.400
2025-01-02 0.642 0.358
2025-01-03 0.300 0.700
2025-01-04 0.321 0.679round(portfolio.orders.fees.sum(), 2)1.66目标权重仅在指定的两个日期改变。其间实际权重随价格漂移,模拟记录手续费为 1.66。一次买入后持有的篮子,可用 from_initial 和一组权重;需要持续回到固定权重,则用
from_allocations 和再平衡计划。
配置与优化函数
VBT 将何时再平衡与如何分配权重分开:
| 构造器 | 权重来源 |
|---|---|
vbt.PFO.from_allocate_func | 在每个再平衡日调用您的函数 |
vbt.PFO.from_optimize_func | 在每个日期之前的历史窗口上调用您的函数 |
vbt.PFO.from_uniform、vbt.PFO.from_random | 等权重或固定随机种子的随机权重 |
vbt.PFO.from_initial、vbt.PFO.from_allocations | 您提供的固定权重,一次应用或按计划应用 |
vbt.PFO.from_filled_allocations | 预先计算的逐 K 线权重表,每行变化时再平衡 |
优化函数接收您传入的任意参数,index_slice、index_start、index_end
等模板会填入当前窗口信息。它为每个资产返回一个权重,因此波动率目标、风险平价或按预测比例分配权重,都只需几行 pandas。lookback_period
设置窗口历史长度,独立于调度计划;alloc_wait 控制窗口结束多少根 K 线后新权重生效。用 vbt.Param 封装任意参数即可一次测试多个值;Numba 函数配合
jitted_loop=True 可快速运行数千个窗口。
函数也可在每个窗口内筛选资产池,例如计算剩余权重前排除历史或成交量不足的资产。请为每个资产返回权重,要排除的资产返回零。
每个窗口从没有此前历史的状态开始,因此在函数内部计算滚动指标会损失每个窗口的初始 K 线。应先在完整历史上计算一次指标,再使用 index_slice 切片。
权重是投资组合价值的比例,不必加总为一。未配置部分保留为现金,让波动率目标只需几行代码:
def vol_target(returns, target=0.08):
vol = returns.mean(axis=1).std() * np.sqrt(252)
weight = min(1, target / vol) / returns.shape[1]
return np.full(returns.shape[1], weight)
pfo = vbt.PFO.from_optimize_func(wrapper, vol_target, returns, **schedule)
pf_target = pfo.simulate(data, init_cash=10_000, fees=0.001).loc["2017":]
compare = {"Equal weight": pfs["Equal weight"], "Vol target 8%": pf_target}
pd.DataFrame({
name: {
"invested": pf.get_allocations().sum(axis=1).mean(),
"volatility": pf.annualized_volatility,
"sharpe": pf.sharpe_ratio,
"max_dd": pf.max_drawdown,
}
for name, pf in compare.items()
}).T.round(2) invested volatility sharpe max_dd
Equal weight 1.00 0.14 0.85 -0.23
Vol target 8% 0.75 0.11 0.57 -0.23目标方案约将四分之一组合保留为现金,并降低波动,但由于过去一年滞后于市场,少获得了 8% 的收益。它也损失了三分之一夏普比率,却未减少回撤:新冠暴跌发生在平静年份之后,当时目标方案满仓;随后的恢复阶段反而持有现金。
优化器库
vbt.PFO.from_pypfopt、vbt.PFO.from_riskfolio 和 vbt.PFO.from_universal_algo 按与自定义函数相同的计划运行
PyPortfolioOpt、Riskfolio-Lib 和
Universal Portfolios。各库模型、约束和目标通过关键词参数传入,因此可像其他参数一样参数化。某窗口没有返回权重时,VBT
发出警告并丢弃该窗口,投资组合保持当前持仓至下一日期。上方最大夏普运行中发生过一次。优化器抛出错误会终止运行,除非设置 ignore_errors=True,以同样方式丢弃该窗口。
PyPortfolioOpt 集成也接受自定义目标、权重边界和行业限制。例如,可比较默认配置与限制单一资产或行业权重的配置,并在再平衡成本后衡量差异。库在约束下选择权重,VBT 则评估这些权重随时间产生的交易。
Universal Portfolios 工作方式不同:算法在整个价格历史上运行一次,为每根 K 线返回权重,因此除非通过 on 指定再平衡日期,VBT 会在权重变化时再平衡。该库使用普通
Python 逐 K 线迭代,因此在长历史上远慢于另外两者。
再平衡计划
every 接受周、月、季等 pandas 频率。配置函数还可通过 on 或 index_points 接受准确日期,优化函数可通过 index_ranges
接受准确窗口。测试多种计划可以显示再平衡成本:
uniform = vbt.PFO.from_uniform(
data.symbol_wrapper, every=vbt.Param(["W", "M", "Q", "Y"]), start="2017"
)
pf = uniform.simulate(data, init_cash=10_000, fees=0.001).loc["2017":]
pd.DataFrame({
"sharpe": pf.sharpe_ratio,
"fees": pf.orders.fees.sum(),
"orders": pf.orders.count(),
}).round(2) sharpe fees orders
every
W 0.85 87.74 4180
M 0.85 46.63 960
Q 0.88 32.06 320
Y 0.86 20.07 80从每季改为每周再平衡,手续费几乎增加至三倍,却未提高夏普比率。如果仅在权重漂移超过阈值时再平衡,或根据模拟组合当前状态计算权重,请通过 vbt.PF.from_order_func
将再平衡逻辑写为模拟回调,详见会员专属的动态投资组合优化(英文)教程。
从配置到投资组合
pfo.simulate(data) 将配置历史转为使用共享现金的目标百分比订单,默认使用 vbt.PF.from_orders。传入 pf_method="from_signals"
可添加止损与止盈页面中的止损及其他退出。模拟前也可检查优化器本身:
pfo.allocations保存各再平衡日期的权重,pfo.alloc_records将每组权重关联到来源数据窗口。pfo.mean_allocation、pfo.stats()和pfo.plot()汇总并绘制权重。rescale_to将正负权重映射到一个范围,例如多空组合的 -1 至 1;pf.get_allocations()显示实际权重在再平衡日间如何漂移。pfo.replace(allocations=...)替换为编辑后的权重;vbt.PFO.column_stack将在不同列上分别构建的优化器(例如每策略一个)组合为一次模拟。
比较与组合配置方法
配置历史在模拟前已可访问,因此可平均两个模型的权重或应用自己的调整,再回测修改后的目标。组合前请匹配资产和再平衡日期。这适合在相同手续费和计划下,将各模型与混合配置比较。
比较回看期时,请像开头示例一样,在所有方法都有足够历史后,使用共同区间评估。检查订单,了解目标实际交易频率,并在收益之外比较回撤和手续费。前向滚动与交叉验证页面展示如何跨时间重复选择与评估。
新权重在窗口结束 alloc_wait 根 K 线后生效,默认为一根。因此,结束于数据最后一根 K 线的窗口没有位置放置权重,VBT 会发出警告。实盘使用时,如需获取下一笔交易的权重,可传入
alloc_wait=0,让权重落在最后一根 K 线上。
教程
会员专属的投资组合优化(英文)教程介绍配置与优化函数、索引点和区间、库集成以及动态再平衡。
✅ Riskfolio-Lib 是另一个日益流行、已集成到 VBT 的投资组合优化库。集成将其典型流程自动化并放入单个函数,让许多优化问题可用一组关键词参数表达,并轻松参数化。
data = vbt.YFData.pull(
["SPY", "TLT", "XLF", "XLE", "XLU", "XLK", "XLB", "XLP", "XLY", "XLI", "XLV"],
start="2020",
end="2023",
missing_index="drop"
)
pfo = vbt.PFO.from_riskfolio(
returns=data.close.vbt.to_returns(),
port_cls="hc",
every="M"
)
pfo.plot().show()教程
更多内容请参阅投资组合优化教程。
✅ 投资组合优化是构建资产组合以追求收益最大化、风险最小化的过程。通常定期执行,生成新权重来对现有组合再平衡。与 VBT 大多数功能一样,用户将权重生成步骤实现为回调,由优化器定期调用。最终结果是返回的配置权重集合,可分析、可视化并用于实际模拟 🥧
def regime_change_optimize_func(data):
returns = data.returns
total_return = returns.vbt.returns.total()
weights = data.symbol_wrapper.fill_reduced(0)
pos_mask = total_return > 0
if pos_mask.any():
weights[pos_mask] = total_return[pos_mask] / total_return.abs().sum()
neg_mask = total_return < 0
if neg_mask.any():
weights[neg_mask] = total_return[neg_mask] / total_return.abs().sum()
return -1 * weights
data = vbt.YFData.pull(
["SPY", "TLT", "XLF", "XLE", "XLU", "XLK", "XLB", "XLP", "XLY", "XLI", "XLV"],
start="2020",
end="2023",
missing_index="drop"
)
pfo = vbt.PFO.from_optimize_func(
data.symbol_wrapper,
regime_change_optimize_func,
vbt.RepEval("data[index_slice]", context=dict(data=data)),
every="M"
)
pfo.plot().show()教程
更多内容请参阅投资组合优化教程。
✅ PyPortfolioOpt 是流行的金融投资组合优化包,涵盖经典方法(Markowitz 1952 与 Black-Litterman)、建议的最佳实践(例如协方差收缩),以及许多近期发展和新功能,例如 L2 正则化、收缩协方差和层次风险平价。
data = vbt.YFData.pull(
["SPY", "TLT", "XLF", "XLE", "XLU", "XLK", "XLB", "XLP", "XLY", "XLI", "XLV"],
start="2020",
end="2023",
missing_index="drop"
)
pfo = vbt.PFO.from_pypfopt(
returns=data.returns,
optimizer="hrp",
target="optimize",
every="M"
)
pfo.plot().show()教程
更多内容请参阅投资组合优化教程。
✅ Universal Portfolios 汇集多种在线投资组合选择(OLPS)算法。
data = vbt.YFData.pull(
["SPY", "TLT", "XLF", "XLE", "XLU", "XLK", "XLB", "XLP", "XLY", "XLI", "XLV"],
start="2020",
end="2023",
missing_index="drop"
)
pfo = vbt.PFO.from_universal_algo(
"MPT",
data.resample("W").close,
window=52,
min_history=4,
mu_estimator='historical',
cov_estimator='empirical',
method='mpt',
q=0
)
pfo.plot().show()教程
更多内容请参阅投资组合优化教程。
版权所有 © 2021–2026 Oleg Polakow。保留所有权利。
本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。