Portfolio Theory with CVXOPT

In this tutorial, we will use the cvxopt library in Python to implement Markowitz Portfolio Theory, which allows us to optimize a portfolio of assets by calculating the efficient frontier. The efficient frontier represents the set of optimal portfolios that offer the highest expected return for a given level of risk (standard deviation).

You'll need to install the cvxopt library, which can be done via pip:

pip install numpy pandas matplotlib cvxopt

First, we need to import the necessary libraries.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from cvxopt import matrix, solvers

For demonstration, let’s assume you have historical price data for several assets. You can load the data into a DataFrame and calculate daily returns.

# Load the data (Replace 'your_data.csv' with your actual data file)
data = pd.read_csv('your_data.csv', index_col='Date', parse_dates=True)

# Calculate daily returns
returns = data.pct_change().dropna()

Ensure that the DataFrame data contains asset prices with dates as the index and asset tickers as columns.

Next, we will set up our expected returns, the covariance matrix of returns, and other important parameters.

# Calculate expected returns and covariance matrix
# Assuming we want the mean returns based on the historical data
expected_returns = returns.mean() * 252  # Annualizing the expected return
cov_matrix = returns.cov() * 252  # Annualizing the covariance

# Number of assets
num_assets = len(expected_returns)

Now we define a function to solve for the optimal weights that minimize portfolio risk for a given target return using quadratic programming.

def optimize_portfolio(target_return, expected_returns, cov_matrix):
    # Define the problem size
    n = num_assets
    # Define the problem parameters
    P = matrix(cov_matrix.values)  # Quadratic term
    q = matrix(np.zeros(n))  # Linear term

    # Constraints
    G = matrix(np.vstack((-np.eye(n), np.eye(n))))  # -w_i <= 0, w_i >= 0
    h = matrix(np.hstack((np.zeros(n), [1.0] * n)))  # weights should sum to 1

    A = matrix(expected_returns.values, (1, n))  # Equal to 1
    b = matrix(target_return)  # Target return

    # Solve the optimization problem
    sol = solvers.qp(P, q, G, h, A, b)
    return np.array(sol['x']).flatten()  # Return the weights as a flat array

Now let's iterate over a range of target returns to calculate the efficient frontier.

# Set ranges for target returns
target_returns = np.linspace(0.1, expected_returns.max(), 50)
portfolio_returns = []
portfolio_volatilities = []

for target in target_returns:
    weights = optimize_portfolio(target, expected_returns, cov_matrix)
    portfolio_return = np.dot(weights, expected_returns)
    portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))

    portfolio_returns.append(portfolio_return)
    portfolio_volatilities.append(portfolio_volatility)

Finally, we'll plot the efficient frontier.

# Convert results to numpy arrays for easier plotting
portfolio_returns = np.array(portfolio_returns)
portfolio_volatilities = np.array(portfolio_volatilities)

# Plot the efficient frontier
plt.figure(figsize=(10, 6))
plt.plot(portfolio_volatilities, portfolio_returns, marker='o', linestyle='-', color='b')
plt.title('Efficient Frontier')
plt.xlabel('Portfolio Volatility (Standard Deviation)')
plt.ylabel('Portfolio Expected Return')
plt.grid()
plt.show()

And that's it! If you want to read more the book Active Portfolio Management by Richard Grinold and Ronald Kahn is a great intro to the concepts.