Skip to content

Python Financial Analysis for Beginners Case 2 - The efficient frontier is a concept in modern portfolio theory (MPT)

The efficient frontier is a concept in modern portfolio theory (MPT) that represents the set of optimal portfolios that offer the highest expected return for a given level of risk or the lowest risk for a given level of expected return. MPT assumes that investors are rational and risk-averse, seeking to maximize returns while minimizing risk.

MPT uses statistical measures such as variance and covariance to calculate the risk and return of different portfolios. It argues that by combining different assets with varying degrees of risk and return, investors can create a portfolio that maximizes their expected return for a given level of risk or minimizes their risk for a given level of expected return.

The efficient frontier is derived by plotting different portfolios on a graph, with expected return on the y-axis and risk (usually measured by standard deviation) on the x-axis. The efficient frontier represents the set of portfolios that offer the highest expected return for a given level of risk or the lowest risk for a given level of expected return.

Investors can use the efficient frontier to find their optimal portfolio by selecting a point on the frontier that matches their desired level of risk and return. This approach is widely used by investors, financial advisors, and fund managers to design and manage investment portfolios

The selected ETFs + Bitcoin are:

VTI - US Stock Market

VXUS - International Stock ETF (exUS)

BND - Total Bond Market

BTC - Bitcoin in USD

Step 1: Import the required libraries

import numpy as np
import pandas as pd
import plotly.express as px
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime

Step 2: Load the data into a pandas DataFrame

# Load the data from a CSV file
data = pd.read_csv('./monthly_returns.csv', index_col='Date')
data

Step 3: Calculate the expected returns and the covariance matrix

# calculate annual returns of the stocks
returns_annual = data.mean() * 12

# get covariance of returns of the stock
cov_annual = data.cov() * 12
(round(returns_annual * 100,2))

Step 4: Simulate 10,000 portfolios with random weights and get the Return, Volatility and Sharpe ratio

np.random.seed(42)
num_portfolios = 10000
num_assets = len(data.columns)
stock_weights = np.zeros((num_portfolios, num_assets))
port_returns = np.zeros(num_portfolios)
port_volatility = np.zeros(num_portfolios)
sharpe_ratio = np.zeros(num_portfolios)

for x in range(num_portfolios):
    # Weights
    weights = np.array(np.random.random(4))
    weights = weights/np.sum(weights)
    
    # Save weights
    stock_weights[x,:] = weights
    
    # Expected return
    port_returns[x] = np.dot(weights, returns_annual)
    
    # Expected volatility
    port_volatility[x] = np.sqrt(np.dot(weights.T, np.dot(cov_annual, weights)))
    
    # Sharpe Ratio
    sharpe_ratio[x] = port_returns[x]/port_volatility[x]
# a dictionary for Returns and Risk values of each portfolio
portfolio = {'Returns': port_returns,
             'Volatility': port_volatility,
             'Sharpe Ratio': sharpe_ratio}
for counter,symbol in enumerate(data):
    portfolio[symbol+' Weight'] = [Weight[counter] for Weight in stock_weights]

# make a nice dataframe of the extended dictionary
df = pd.DataFrame(portfolio)

# get better labels for desired arrangement of columns
column_order = ['Returns', 'Volatility', 'Sharpe Ratio'] + [stock+' Weight' for stock in data]

# reorder dataframe columns
df = df[column_order]
df.head(10)

Step 5: select the portfolios with 1) Min Volatility, 2) Max Sharpe Ratio and 3) Max Return