# 多策略投资组合 (/zh/features/strategies/multi-strategy-portfolios)

如何组合交易策略并在它们之间分配资金？在 VBT 中，每个策略的投资组合都可以视为一项资产：将策略堆叠为多列，再使用等权或优化器分配权重，最后通过另一次回测，按计划再平衡并计入手续费。

```python title="Combine a trend and a mean-reversion strategy, equally and by inverse volatility"
>>> spy = vbt.YFData.pull("SPY", start="2010-01-01", end="2025-01-01")
>>> fast = spy.close.vbt.rolling_mean(50)
>>> slow = spy.close.vbt.rolling_mean(200)
>>> trend = vbt.PF.from_signals(spy, fast > slow, fast < slow, fees=0.001)
>>> rsi = vbt.RSI.run(spy.close, window=2).rsi
>>> reversion = vbt.PF.from_signals(spy, rsi < 10, rsi > 70, fees=0.001)

>>> strategies = pd.concat(  # (1)
...     {"trend": trend.value, "reversion": reversion.value}, axis=1
... )
>>> returns = vbt.RepEval(
...     "strategies.pct_change().iloc[index_slice]", context=dict(strategies=strategies)
... )

>>> def inverse_vol(returns):
...     inv = 1 / returns.std()
...     return inv / inv.sum()

>>> wrapper = vbt.ArrayWrapper.from_obj(strategies)
>>> fifty = vbt.PFO.from_uniform(wrapper, every="M")  # (2)
>>> inv_vol = vbt.PFO.from_optimize_func(
...     wrapper, inverse_vol, returns, every="M", lookback_period="63D"
... )
>>> period = slice("2010-05", None)  # (3)
>>> pfs = {
...     "Trend": trend,
...     "Mean reversion": reversion,
...     "50/50 monthly": fifty.simulate(strategies, fees=0.001),
...     "Inverse volatility": inv_vol.simulate(strategies, fees=0.001),
... }
>>> metrics = ["total_return", "sharpe_ratio", "max_dd"]
>>> stats = {k: pf.loc[period].stats(metrics) for k, pf in pfs.items()}
>>> pd.DataFrame(stats).astype(float).round(2)
                   Trend  Mean reversion  50/50 monthly  Inverse volatility
Total Return [%]  285.49           51.41         145.83               86.76
Sharpe Ratio        0.89            0.39           0.74                0.57
Max Drawdown [%]   33.72           31.79          27.95               28.11
```

1.  各策略投资组合的净值成为合成资产的价格。
2.  每个月，将两个策略重新配置为各占组合净值的一半。
3.  从 2010 年 5 月，即首次按波动率倒数配置之后，开始比较所有投资组合。

2010 至 2024 年 SPY 趋势策略、均值回归策略及两种组合方案的净值增长. [Figure data (JSON)](/assets/figures/features/strategies/multi-strategy-value.561b3ace1ecb.json)

将两个策略组合后，回撤低于任一单独策略，但收益和夏普比率均未超过趋势策略，因为两者的相关性为 62%，且都持有
SPY。波动率倒数配置不如等权：均值回归策略多数时间持有现金，表面波动更低，因此尽管收益更少，却获得了更多资金。策略交易不同市场或行为差异更大时，组合通常更有帮助。

## 选择策略之间的协作方式 \[#choose-how-strategies-work-together]

您可以组合不同入场规则、参数集或市场。需要决定的是，它们的资金如何交互：

| 您想测试什么               | 如何组合策略                    |
| -------------------- | ------------------------- |
| 为各策略分配固定初始资金，让它们自行增长 | 堆叠已完成的投资组合，并应用静态权重。       |
| 按计划在策略之间调配资金         | 将策略投资组合的净值视为价格，对配置方案进行回测。 |
| 让策略在交易时竞争同一笔现金       | 在同一现金共享组中，一起模拟它们的订单。      |

## 将策略作为列 \[#strategies-as-columns]

具有相同索引的策略可以组成一个投资组合的不同列。即使一个策略使用信号，另一个使用显式订单等不同模拟方法，也能一起分析。`vbt.PF.column_stack`
合并已完成的投资组合，`group_by=True` 将它们视为一个永不再平衡的账户，因此各策略保留各自的初始资金，并随自身结果增长或缩减。`apply_weights`
可按固定权重缩放每个策略的资金、订单和现金流，无须重新模拟：

```python title="Hold the two strategies without rebalancing"
>>> combined = vbt.PF.column_stack(
...     [trend, reversion], wrapper_kwargs=dict(group_by=True)
... )
>>> static = {
...     "50/50 static": combined,
...     "70/30 static": combined.apply_weights([0.7, 0.3], rescale=True),  # (1)
... }
>>> stats.update({k: pf.loc[period].stats(metrics) for k, pf in static.items()})
>>> pd.DataFrame(stats).iloc[:, 2:].astype(float).round(2)
                  50/50 monthly  Inverse volatility  50/50 static  70/30 static
Total Return [%]         145.83               86.76        166.90        213.95
Sharpe Ratio               0.74                0.57          0.77          0.83
Max Drawdown [%]          27.95               28.11         29.88         31.70
```

1.  `rescale=True` 保持总资金不变：0.7 和 0.3 的权重转化为 1.4 和 0.6 的乘数。

不进行调整时，50/50 组合会随着趋势策略增长更快而逐渐偏向它。与每月恢复各占一半相比，这种做法收益更高，但回撤略深。权重按策略应用于整个时期，并非逐根 K
线设置。若要在策略间再平衡，可像首个示例一样，将它们的净值作为投资组合优化器的价格。[投资组合优化](/zh/features/optimization/portfolio-optimization/)页的所有配置方法都适用，从固定权重到
PyPortfolioOpt 和 Riskfolio-Lib，再平衡手续费在策略层面收取。

### 策略交易时共享现金 \[#share-cash-while-strategies-trade]

若策略必须使用同一现金余额，可通过 `group_by=True` 和
`cash_sharing=True`，将它们的策略列与资产列放入同一模拟组。分开的组各自持有独立现金。[投资组合记账](/zh/features/backtesting/portfolio-accounting/)页说明如何跟踪现金、持仓和净值。

这样，一个策略可以使用另一策略释放的现金。多个信号同时出现时，仓位大小和订单顺序决定如何分配资金。若规则依赖当前持仓，例如限制未平仓头寸总数，可使用[事件驱动回测](/zh/features/backtesting/event-driven-backtesting/#stateful-trading-rules)中介绍的回调。

## 检查组合及其组成部分 \[#inspect-the-mix-and-its-components]

堆叠会保留原始订单记录，因此可以同时检查单个策略的交易以及组合收益和回撤。为堆叠后的投资组合添加策略名称，便于之后选择，即使多个策略交易同一品种也不受影响。比较收益序列与相关性，判断新增策略是否带来不同行为，还是主要重复现有策略。

再平衡示例有两层结果：原始趋势和均值回归投资组合包含各自的 SPY
交易，组合投资组合则记录策略之间的资金配置。同时保留两者，就能将组合表现追溯到底层交易。[交易分析](/zh/features/analytics/trade-analytics/)页介绍盈利交易、亏损交易和持有期分析。

在多个时期比较候选权重和再平衡计划，包括未参与配置拟合的留出时期。[前向滚动优化](/zh/features/optimization/time-series-cross-validation/)将这一过程应用于滚动训练和测试窗口。

## 定期投资与定额投资 \[#recurring-investing-and-dollar-cost-averaging]

现金入金可以模拟定期储蓄。下面比较每月初存入并买入 100 美元，与一次性投入相同总额：

```python title="Invest $100 every month, or $12,000 at the start"
>>> close = spy.close.loc["2015":"2024"]
>>> month = pd.Series(close.index.month, index=close.index)
>>> first_days = month != month.shift(1)
>>> deposits = pd.Series(np.where(first_days, 100.0, 0.0), index=close.index)
>>> dca = vbt.PF.from_orders(
...     close,
...     size=np.where(first_days, np.inf, np.nan),  # (1)
...     init_cash=0,
...     cash_deposits=deposits,
... )
>>> lump = vbt.PF.from_orders(
...     close,
...     size=np.where(np.arange(len(close)) == 0, np.inf, np.nan),
...     init_cash=deposits.sum(),
... )
>>> print(deposits.sum(), round(dca.final_value, 2), round(lump.final_value, 2))
12000.0 25724.02 40748.85
```

1.  每个入金日用全部可用现金买入，其他日期不操作。

在这十年大部分时间上涨的市场中，一次性投入的最终净值高出
58%。每月投入会在不同价格买入，使入场价格更平滑，但也使大部分资金多年处于未投资状态。同一入金计划适用于任何策略，因此可以将回撤后加大买入等规则与普通月度买入比较。入金也可为投资组合优化器的定期配置提供资金，从而一同测试定期投入和再平衡。

比较策略表现时，除最终账户净值外，也应使用投资组合收益率。VBT
的收益率计算考虑入金和出金，不会将新增资金当作交易收益。[投资组合记账](/zh/features/backtesting/portfolio-accounting/#cash-deposits)介绍这种比较中的现金流处理。

!!! info "教程"
    会员专享的[投资组合优化（英文）](https://members.vectorbt.pro/tutorials/portfolio-optimization/)教程介绍配置和优化函数的构建，包括根据模拟投资组合状态调整的配置方法。
