Features
Multi-strategy portfolios
Stack strategies as one portfolio, weight them, and compare recurring investing plans
How do you combine trading strategies and allocate between them? In VBT, each strategy's portfolio can be treated as an asset: stack the strategies as columns, then weight them equally or by an optimizer and rebalance on a schedule, with fees, in one more backtest.
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(
{"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")
inv_vol = vbt.PFO.from_optimize_func(
wrapper, inverse_vol, returns, every="M", lookback_period="63D"
)
period = slice("2010-05", None)
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.11Combining the two strategies lowered the drawdown below that of either one, but it did not beat the trend strategy on return or Sharpe ratio, since the strategies were 62% correlated and both held SPY. Inverse volatility did worse than equal weights: the mean-reversion strategy spends most days in cash, looks less volatile, and so received more capital despite earning less. Combinations help most when strategies trade different markets or behave differently.
Choose how strategies work together
You can combine different entry rules, parameter sets, or markets. The choice is how their capital should interact:
| What you want to test | How to combine the strategies |
|---|---|
| Give each strategy a fixed starting allocation and let it grow | Stack finished portfolios and apply static weights. |
| Move capital between strategies on a schedule | Treat their portfolio values as prices and backtest the allocations. |
| Let strategies compete for the same cash as they trade | Simulate their orders together in one cash-sharing group. |
Strategies as columns
Strategies with the same index are columns of one portfolio. They can use different simulation
methods, such as signals for one and explicit orders for another, and still be analyzed together.
vbt.PF.column_stack joins finished portfolios, and group_by=True treats them as one account that
never rebalances, so each strategy keeps the capital it started with and grows or shrinks with its
own results. apply_weights scales each strategy's capital, orders, and cash flows by a fixed
weight without simulating again:
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),
}
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.70Left alone, the 50/50 mix drifted toward the trend strategy as it outgrew mean reversion, which earned more than rebalancing monthly back to half each, with a slightly deeper drawdown. Weights apply per strategy for the whole period, not per bar. To rebalance between strategies, use their values as prices for a portfolio optimizer, as in the first example. Every allocation method on the Portfolio optimization page applies, from fixed weights to PyPortfolioOpt and Riskfolio-Lib, and the fees of rebalancing are charged on the strategy level.
Share cash while strategies trade
When strategies must draw from one cash balance, put their strategy and asset columns in the same
simulation group with group_by=True and cash_sharing=True. Separate groups give each group its
own cash. The Portfolio accounting page shows how
cash, positions, and value are tracked.
This lets one strategy use cash released by another. Position sizes and the order sequence decide how that cash is used when several signals arrive together. For rules that depend on current holdings, such as a limit on total open positions, use the callbacks described in Event-driven backtesting.
Inspect the mix and its components
Stacking keeps the original order records, so you can inspect a strategy's trades as well as the combined return and drawdown. Give the stacked portfolios strategy names to select them later, including when several trade the same symbol. Compare their return series and correlations to see whether another strategy adds different behavior or mostly repeats an existing one.
The rebalanced example has two levels of results: the original trend and mean-reversion portfolios contain their SPY trades, while the combined portfolio records allocations between those strategies. Keep both to connect the combined performance to the underlying trades. The Trade analytics page covers winners, losers, and holding periods.
Compare candidate weights and rebalance schedules over several periods, including periods held out from fitting the allocation. Walk-forward optimization applies that process to rolling training and test windows.
Recurring investing and dollar-cost averaging
Cash deposits model saving on a schedule. Depositing $100 at the start of each month and buying with it compares to investing the same total at once:
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),
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.85In a market that rose for most of the decade, investing everything at once ended with 58% more. Monthly deposits buy at many prices, which smooths the entry, but leave most of the money uninvested for years. The same deposits work with any strategy, so a rule such as buying more after drawdowns can be tested against plain monthly buying. Deposits can also fund a portfolio optimizer's scheduled allocations, so recurring contributions and rebalancing can be tested together.
Use portfolio returns to compare strategy performance alongside final account values. VBT's return calculation accounts for cash deposits and withdrawals, so adding money is not counted as a trading gain. Portfolio accounting covers the cash-flow side of the comparison.
Tutorial
The members-only Portfolio optimization tutorial builds allocation and optimization functions, including allocations that react to the simulated portfolio.
Related pages
- Optimization and validation › Portfolio optimizationAllocate with your own functions or optimizer libraries and backtest rebalancing
- Research toolkit › Multidimensional researchRun assets, parameters, and strategies as labeled columns, then index and stack them
- Backtesting › Portfolio accountingTrack shared cash, deposits, dividends, positions, records, and exposure per bar
- Cross-sectional strategiesRank a universe each period, rotate into the leaders, and test factors by quintile
Copyright © 2021–2026 Oleg Polakow. All rights reserved.
Site content and documentation are provided for using and evaluating VectorBT PRO and for educational purposes. Any other use, including building or supporting competing products or services, requires prior written consent.