Skip to content

Issue 1: Unrealistic Mean-Variance Strategy #4

@PrinceSajjadHussain

Description

@PrinceSajjadHussain

Title: The Mean-Variance Strategy is oversimplified and produces unrealistic results.

Body:
The MeanVarianceStrategy in strategy-update-backtest.py calculates portfolio weights using a very simplistic method weights = mean_returns / mean_returns.sum(). This has multiple issues:

  1. It doesn't consider risk. A proper mean-variance optimization seeks to maximize returns for a given level of risk or minimize risk for a given level of return. This simplified version completely ignores the covariance matrix.
  2. It can lead to extreme and unstable weights. If one asset has a slightly higher mean return than the others, it will receive a disproportionately large weight, potentially close to or exceeding 100%, and other weights will become negative. This behavior is unrealistic and can lead to disastrous backtesting results.
  3. It might lead to zero or negative weights. Depending on the sign of the returns (positive or negative), the resulting weights can be positive, zero or even negative which is not always ideal

A proper implementation requires using a quadratic programming solver to find the optimal weights, constrained by a target return level or risk tolerance. Using libraries like scipy.optimize and constraints.

Here's a basic example using scipy.optimize. Note this requires further tuning and likely needs a constraints on short selling:

import scipy.optimize as optimize

class MeanVarianceStrategy(bt.Algo):
    def __init__(self, returns_data, risk_aversion=1.0):
        self.returns_data = returns_data
        self.risk_aversion = risk_aversion

    def __call__(self, target):
        returns = self.returns_data.pct_change().dropna()
        mean_returns = returns.mean()
        cov_matrix = returns.cov()
        num_assets = len(returns.columns)

        def portfolio_variance(weights):
            return weights.T @ cov_matrix @ weights * self.risk_aversion - weights.T @ mean_returns # Adjust by risk aversion

        # Initial guess (equal weights)
        initial_weights = np.array([1/num_assets] * num_assets)

        # Constraints:  Weights must sum to 1.  No short selling (optional).
        constraints = ({'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1})
        bounds = [(0, 1) for _ in range(num_assets)]  # Non-negative weights (no shorting)

        # Optimization
        result = optimize.minimize(portfolio_variance, initial_weights,
                                    method='SLSQP',  # Choose a suitable method
                                    bounds=bounds,
                                    constraints=constraints)

        if result.success:
            optimized_weights = result.x
            target.temp['weights'] = {asset: weight for asset, weight in zip(self.returns_data.columns, optimized_weights)}
        else:
            print(f"Optimization failed: {result.message}")
            target.temp['weights'] = {asset: 1/num_assets for asset in self.returns_data.columns}  # Fallback to equal weights

        return True

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions