Files
strategy-lab/to_explore/pyquantnews/89_OptimalPortfolioAgainstIndex.ipynb
David Brazda e3da60c647 daily update
2024-10-21 20:57:56 +02:00

12 KiB

No description has been provided for this image

This notebook demonstrates how to construct and optimize a portfolio using historical asset returns and various risk measures. It utilizes riskfolio to model the portfolio and yfinance to fetch historical stock data. The notebook calculates expected returns and covariance matrices, integrates benchmark indices, and applies tracking error constraints. It then optimizes the portfolio to maximize risk-adjusted returns, plotting the efficient frontier and optimal asset allocations. This is practical for portfolio managers and financial analysts to enhance investment strategies.

In [ ]:
import riskfolio as rp
import pandas as pd
import yfinance as yf
import warnings
warnings.filterwarnings("ignore")

Define the list of assets for portfolio construction

In [ ]:
assets = [
    "JCI",
    "TGT",
    "CMCSA",
    "CPB",
    "MO",
    "APA",
    "MMC",
    "JPM",
    "ZION",
    "PSA",
    "BAX",
    "BMY",
    "LUV",
    "PCAR",
    "TXT",
    "TMO",
    "DE",
    "MSFT",
    "HPQ",
    "SEE",
    "VZ",
    "CNP",
    "NI",
    "T",
    "BA",
    "^GSPC",
]

Fetch historical stock data for the defined assets from Yahoo Finance

In [ ]:
data = yf.download(assets, start="2016-01-01", end="2019-12-30")

Select adjusted close prices and rename columns to match asset tickers

In [ ]:
data = data.loc[:, ("Adj Close", slice(None))]
data.columns = assets

Calculate daily returns of the assets and isolate benchmark returns

In [ ]:
returns = data.pct_change().dropna()
bench_returns = returns.pop("^GSPC").to_frame()

Build the portfolio object and load the calculated returns into it

In [ ]:
port = rp.Portfolio(returns=returns)

Estimate expected returns and covariance matrix using historical data

In [ ]:
port.assets_stats(method_mu="hist", method_cov="hist", d=0.94)

Set to False to indicate no benchmark weights, using index instead

In [ ]:
port.kindbench = False

Load benchmark returns into the portfolio object

In [ ]:
port.benchindex = bench_returns

Enable the use of tracking error constraints in the optimization

In [ ]:
port.allowTE = True

Define the maximum allowed tracking error relative to benchmark returns

In [ ]:
port.TE = 0.008

Explain the goal of calculating optimal portfolios using different risk measures

Define the model type for optimization, here using historical data

In [ ]:
model = "Classic"

Specify the risk measure to use, in this case, Conditional Value at Risk (CVaR)

In [ ]:
rm = "CVaR"

Set the objective function to maximize Sharpe ratio

In [ ]:
obj = "Sharpe"

Use historical scenarios for risk measures that depend on scenarios

In [ ]:
hist = True

Define the risk-free rate for portfolio optimization calculations

In [ ]:
rf = 0

Set the risk aversion factor, relevant only when the objective is 'Utility'

In [ ]:
l = 0

Perform the portfolio optimization to maximize the Sharpe ratio using CVaR

In [ ]:
w = port.optimization(
    model=model,
    rm=rm,
    obj=obj,
    rf=rf,
    l=l,
    hist=hist
)

Plot the optimized portfolio allocation using a pie chart

In [ ]:
ax = rp.plot_pie(
    w=w,
    title="Sharpe Mean CVaR",
    others=0.05,
    nrow=25,
    cmap="tab20",
    height=6,
    width=10,
    ax=None,
)

Calculate the efficient frontier for the portfolio using the defined risk measure

In [ ]:
frontier = port.efficient_frontier(
    model=model, 
    rm=rm, 
    points=50, 
    rf=rf, 
    hist=hist
)

Plot the efficient frontier and the position of the optimized portfolio

In [ ]:
ax = rp.plot_frontier(
    w_frontier=frontier,
    mu=port.mu,
    cov=port.cov,
    returns=port.returns,
    rm=rm,
    rf=rf,
    cmap="viridis",
    w=w,
    label="Max Risk Adjusted Return Portfolio",
    marker="*",
)

Define the list of different risk measures to use for portfolio optimization

In [ ]:
rms = [
    "MV",
    "MAD",
    "MSV",
    "FLPM",
    "SLPM",
    "CVaR",
    "EVaR",
    "WR",
    "MDD",
    "ADD",
    "CDaR",
    "UCI",
    "EDaR",
]

Initialize an empty DataFrame to store the weights of optimized portfolios

In [ ]:
w_s = pd.DataFrame([])

Loop through each risk measure, optimize the portfolio, and store the weights

In [ ]:
for i in rms:
    w = port.optimization(model=model, rm=i, obj=obj, rf=rf, l=l, hist=hist)
    w_s = pd.concat([w_s, w], axis=1)

Assign the columns of the DataFrame to the respective risk measures and format the output

In [ ]:
w_s.columns = rms
w_s.style.format("{:.2%}").background_gradient(cmap="YlGn")

PyQuant News is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to get started with Python for quant finance. For educational purposes. Not investment advise. Use at your own risk.