← Back to Home
Diversification in Practice - A Python Backtesting Framework for Multi-Asset and Multi-Strategy Portfolios

Diversification in Practice - A Python Backtesting Framework for Multi-Asset and Multi-Strategy Portfolios

Diversification is a core pillar of risk management, complementing micro-level controls like position sizing and stop-losses. Its effectiveness lies in its ability to smooth out returns and reduce drawdowns by combining uncorrelated sources of alpha. This article provides a hands-on guide to implementing and analyzing diversification in Python using a complete backtesting framework based on your provided backtrader code.

We will explore two key forms of diversification—across multiple assets and across multiple strategies—and demonstrate how a well-structured backtest can quantitatively prove its benefits.


1. The Backtesting Framework: Core Components

The provided code sets up a robust backtesting environment with several essential components:

Custom Analyzers

The CustomSharpeRatio class is a powerful tool for reliable performance analysis. Unlike default analyzers, it provides a transparent calculation of the Sharpe Ratio based on a specified risk-free rate, along with detailed debug information on returns and volatility.

Strategy Logic

The framework includes three distinct trading strategies:

Each strategy represents a different market hypothesis, which is the foundation for strategy diversification.

Utility Functions

The code uses dedicated functions to manage the backtesting workflow:


2. Hands-on Asset Diversification

Asset diversification involves spreading capital across different instruments or asset classes that do not move in perfect lockstep. The provided code demonstrates this by running a simple trend-following strategy across four distinct ETFs:

The core logic resides in the SimpleDiversifiedStrategy, which dynamically applies the same trend-following rule to each data feed.

Key Insight: Correlation Analysis

The code calculates and prints a correlation matrix, which is a vital step in diversification. A matrix with low off-diagonal values (e.g., SPY-TLT correlation of -0.177) indicates that these assets are good candidates for diversification because they tend to move independently or in opposite directions. In contrast, the high correlation between SPY-VTI (0.994) shows that trading both offers little diversification benefit.

Backtest Results

The backtest results clearly show the impact of asset diversification. The combined, diversified portfolio typically exhibits a higher Sharpe Ratio and a lower Maximum Drawdown compared to the average of the individual, single-asset strategies. This is a quantitative proof of the benefit of diversification: you achieve better risk-adjusted returns and a smoother equity curve for the same level of capital.


3. Exploring Strategy Diversification

Strategy diversification involves combining multiple trading approaches to create a more robust, low-correlation portfolio. By blending strategies that have different biases (e.g., a trend follower and a mean reversion model), you create a system that can perform well in a wider range of market conditions.

Here is a new utility function to demonstrate this concept by running multiple strategies on a single asset.

def run_multi_strategy_backtest(data_feed, strategies, strategy_name):
    """Run backtest showing diversification across multiple strategies on a single asset"""
    cerebro = bt.Cerebro()
    cerebro.adddata(data_feed)
    
    for strategy_class in strategies:
        cerebro.addstrategy(strategy_class)
        
    cerebro.broker.setcash(10000)
    cerebro.broker.setcommission(commission=0.001)
    
    cerebro.addanalyzer(CustomSharpeRatio, _name='sharpe')
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
    
    print(f"\nRunning {strategy_name} (Strategy Diversification)...")
    results = cerebro.run()
    
    final_value = cerebro.broker.getvalue()
    total_return = ((final_value - 10000) / 10000) * 100
    
    strat = results[0]
    sharpe = strat.analyzers.sharpe.get_analysis().get('sharperatio', 0)
    max_dd = strat.analyzers.drawdown.get_analysis().get('max', {}).get('drawdown', 0)
    
    print(f"Final Portfolio Value: ${final_value:.2f}")
    print(f"Total Return: {total_return:.2f}%")
    print(f"Sharpe Ratio: {sharpe:.3f}")
    print(f"Max Drawdown: {max_dd:.2f}%")
    
    return {
        'final_value': final_value,
        'total_return': total_return,
        'sharpe_ratio': sharpe,
        'max_drawdown': max_dd,
        'cerebro': cerebro
    }

You would then integrate this into your main function to run and compare the results:

# ... inside main() function ...
# Run diversified backtest on a single asset with multiple strategies
multi_strat_results = run_multi_strategy_backtest(
    spy_data, 
    [TrendFollowingStrategy, MeanReversionStrategy, MomentumStrategy],
    "Strategy Diversified Portfolio"
)
results['Strategy Diversified Portfolio'] = multi_strat_results
# ... then plot and print the summary, including the new result.

By adding this backtest, you can directly compare the benefits of portfolio-level diversification with a single asset (e.g., SPY) against the individual performance of each of the three strategies.

Pasted image 20250806040420.png

4. Advanced Enhancements and Next Steps

Your code provides a strong foundation for further development:

Conclusion

Your backtesting framework is a powerful and practical tool for understanding and implementing diversification. The code correctly demonstrates how to handle multi-asset data, analyze correlations, and compare the performance of different portfolios. By integrating both asset and strategy diversification, and by continuing to refine your risk management with tools like trailing stops and fixed fractional sizing, you can build a robust, scalable, and resilient quantitative trading system.