Skip to content

cvxgrp/cvxrisk

Repository files navigation

cvxrisk: Convex Optimization for Portfolio Risk Management

PyPI version Apache 2.0 License Downloads Renovate enabled

Open in GitHub Codespaces

πŸ“‹ Overview

cvxrisk is a Python library for portfolio risk management using convex optimization. It provides a flexible framework for implementing various risk models that can be used with CVXPY to solve portfolio optimization problems.

The library is built around an abstract Model class that standardizes the interface for different risk models, making it easy to swap between them in your optimization problems.

πŸš€ Installation

# Install from PyPI (without any convex solver)
pip install cvxrisk

# Install with Clarabel solver
pip install cvxrisk[clarabel]

# Install with Mosek solver
pip install cvxrisk[mosek]

# For development installation
git clone https://github.com/cvxgrp/cvxrisk.git
cd cvxrisk
make install

# For experimenting with the notebooks (after cloning)
make marimo

⚠️ Warning! The package does not install a convex solver if not explicitly desired. It relies on cvxpy-base. If you use cvxrisk as a dependency in your projects you may want to install clarabel using pip install cvxrisk[clarabel] or mosek using pip install cvxrisk[mosek].

πŸ”§ Quick Start

cvxrisk makes it easy to formulate and solve portfolio optimization problems:

>> > import cvxpy as cp
>> > import numpy as np
>> > from cvxrisk.sample import SampleCovariance
>> > from cvxrisk.portfolio import minrisk_problem
>> >
>> >  # Create a risk model
>> > riskmodel = SampleCovariance(num=2)
>> >
>> >  # Update the model with data
>> > riskmodel.update(
    ...
cov = np.array([[1.0, 0.5], [0.5, 2.0]]),
...
lower_assets = np.zeros(2),
...
upper_assets = np.ones(2)
... )
>> >
>> >  # Define portfolio weights variable
>> > weights = cp.Variable(2)
>> >
>> >  # Create and solve the optimization problem
>> > problem = minrisk_problem(riskmodel, weights)
>> > problem.solve()
>> >
>> >  # Print the optimal weights
>> > print(np.round(weights.value, 2))
[0.8 0.2]

πŸ“Š Features

cvxrisk provides several risk models:

Sample Covariance

The simplest risk model based on the sample covariance matrix:

>> > from cvxrisk.sample import SampleCovariance
>> > import numpy as np
>> >
>> > riskmodel = SampleCovariance(num=2)
>> > riskmodel.update(cov=np.array([[1.0, 0.5], [0.5, 2.0]]))
>> > riskmodel.parameter["cov"].value
array([[1., 0.5],
       [0.5, 2.]])

Factor Risk Models

Factor models reduce dimensionality by projecting asset returns onto a smaller set of factors:

>> > import numpy as np
>> > from cvxrisk.factor import FactorModel
>> > from cvxrisk.linalg import pca
>> > import pandas as pd
>> >
>> >  # Create some sample returns data
>> > returns = pd.DataFrame(np.random.randn(100, 25))
>> >
>> >  # Compute principal components
>> > factors = pca(returns, n_components=10)
>> >
>> >  # Create and update the factor model
>> > model = FactorModel(assets=25, k=10)
>> > model.update(
    ...
cov = factors.cov.values,
...
exposure = factors.exposure.values,
...
idiosyncratic_risk = factors.idiosyncratic.std().values
... )
>> >
>> >  # Verify the model has the correct dimensions
>> > model.parameter["exposure"].value.shape
(10, 25)

Factor risk models use the projection of the weight vector into a lower dimensional subspace, e.g. each asset is the linear combination of $k$ factors.

$$r_i = \sum_{j=1}^k f_j \beta_{ji} + \epsilon_i$$

The factor time series are $f_1, \ldots, f_k$. The loadings are the coefficients $\beta_{ji}$. The residual returns $\epsilon_i$ are assumed to be uncorrelated with the factors.

Any position $w$ in weight space projects to a position $y = \beta^T w$ in factor space. The variance for a position $w$ is the sum of the variance of the systematic returns explained by the factors and the variance of the idiosyncratic returns.

$$Var(r) = Var(\beta^T w) + Var(\epsilon w)$$

We assume the residual returns are uncorrelated and hence

$$Var(r) = y^T \Sigma_f y + \sum_i w_i^2 Var(\epsilon_i)$$

where $\Sigma_f$ is the covariance matrix of the factors and $Var(\epsilon_i)$ is the variance of the idiosyncratic returns.

Conditional Value at Risk (CVaR)

CVaR measures the expected loss in the worst-case scenarios:

>> > import numpy as np
>> > from cvxrisk.cvar import CVar
>> >
>> >  # Create some sample historical returns
>> > historical_returns = np.random.randn(50, 14)
>> >
>> >  # Create and update the CVaR model
>> > model = CVar(alpha=0.95, n=50, m=14)
>> > model.update(returns=historical_returns)
>> >
>> >  # Verify the model parameters
>> > model.alpha
0.95
>> > model.parameter["returns"].value.shape
(50, 14)

πŸ“š Documentation

For more detailed documentation and examples, visit our documentation site.

πŸ› οΈ Development

cvxrisk uses modern Python development tools:

# Install development dependencies
make install

# Run tests
make test

# Format code
make fmt

# Start interactive notebooks
make marimo

πŸ“„ License

cvxrisk is licensed under the Apache License 2.0. See LICENSE for details.

πŸ‘₯ Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

For more information, see CONTRIBUTING.md.