Exercises - Constrained Optimization#

\[ \newcommand{\E}{E} \newcommand{\rbar}{\bar{r}} \newcommand{\rvec}{\boldsymbol{r}} \newcommand{\rvecbar}{\boldsymbol{\bar{r}}} \newcommand{\Ntime}{N} \newcommand{\Nt}{N} \newcommand{\rmat}{\boldsymbol{R}} \newcommand{\riskmeasure}{\varrho} \newcommand{\wt}{w} \newcommand{\Nassets}{K} \newcommand{\muvec}{\boldsymbol{\mu}} \newcommand{\onevecNt}{\boldsymbol{1}_{\Ntime\times 1}} \newcommand{\covest}{\hat{\boldsymbol{\Sigma}}} \newcommand{\meanest}{\hat{\mu}} \newcommand{\meanestvec}{\hat{\boldsymbol{\mu}}} \newcommand{\covmat}{\boldsymbol{\Sigma}} \newcommand{\rf}{r_f} \newcommand{\VaR}{\text{VaR}} \newcommand{\VaRqtau}{\VaR_{q,\tau}} \newcommand{\pnlVaR}{\pnl^{\VaR}} \newcommand{\pnlVaRqtau}{\pnl^{\VaR_{q,\tau}}} \newcommand{\rVaR}{r^{\VaR}} \newcommand{\rVaRqtau}{r^{\VaR_{q,\tau}}} \newcommand{\loss}{L} \newcommand{\Pr}{\mathbb{P}} \newcommand{\quant}{q} \newcommand{\port}{\Pi} \newcommand{\pnl}{\Gamma} \newcommand{\cdf}{\Phi} \newcommand{\pdf}{\phi} \newcommand{\zscore}{\texttt{z}} \newcommand{\cdfz}{\cdf_{\zscore}} \newcommand{\pdfz}{\pdf_{\zscore}} \newcommand{\rlog}{\texttt{r}} \newcommand{CVaR}{\text{CVaR}} \newcommand{CVaRqtau}{\CVaR_{q,\tau}} \newcommand{\pnlCVaR}{\pnl^\CVaR} \newcommand{\pnlCVaRqtau}{\pnl^{\CVaR_{q,\tau}}} \newcommand{\rCVaR}{r^\CVaR} \newcommand{\rCVaRqtau}{r^{\CVaR_{q,\tau}}} \newcommand{\rx}{\tilde{r}} \newcommand{\mux}{\tilde{\mu}} \newcommand{\sigx}{\tilde{\sigma}} \newcommand{\Nsec}{K} \newcommand{\avg}{\text{avg}} \newcommand{\wtvec}{\boldsymbol{\wt}} \newcommand{\muxvec}{\boldsymbol{\mux}} \newcommand{\tan}{\text{tan}} \]

Data#

All the analysis below applies to the data set,

  • data/spx_returns_weekly.xlsx

  • The file has weekly returns.

  • For annualization, use 52 periods per year.

Consider only the following 10 stocks…

TICKS =  ['AAPL','NVDA','MSFT','GOOGL','AMZN','META','TSLA','AVGO','BRK/B','LLY']

As well as the ETF,

TICK_ETF = 'SPY'

Data Processing#

import pandas as pd
INFILE = '../data/spx_returns_weekly.xlsx'
SHEET_INFO = 'spx names'
SHEET_RETURNS = 'spx returns'
SHEET_BENCH = 'additional returns'

FREQ = 52
info = pd.read_excel(INFILE,sheet_name=SHEET_INFO)
info.set_index('ticker',inplace=True)
info.loc[TICKS]
name mkt cap
ticker
AAPL Apple Inc 3.008822e+12
NVDA NVIDIA Corp 3.480172e+12
MSFT Microsoft Corp 3.513735e+12
GOOGL Alphabet Inc 2.145918e+12
AMZN Amazon.com Inc 2.303536e+12
META Meta Platforms Inc 1.745094e+12
TSLA Tesla Inc 9.939227e+11
AVGO Broadcom Inc 1.148592e+12
BRK/B Berkshire Hathaway Inc 1.064240e+12
LLY Eli Lilly & Co 7.332726e+11
rets = pd.read_excel(INFILE,sheet_name=SHEET_RETURNS)
rets.set_index('date',inplace=True)
rets = rets[TICKS]
bench = pd.read_excel(INFILE,sheet_name=SHEET_BENCH)
bench.set_index('date',inplace=True)
rets[TICK_ETF] = bench[TICK_ETF]

1 Constrained Optimization for Mean-Variance#

Suppose we want to constrain the weights such that

  • there are no short positions beyond negative 10%, \(w_i\ge -.10\) for all \(i\)

  • none of the positions may have weight over 20%, \(w_i \le .20\) for all \(i\).

Solve in terms of

  • excess returns, so no constraint on the sum of the weights.

Furthermore,

  • The targeted mean return is 30% per year.

  • Be careful; the target is an annualized mean.

Consider using the code below as a starting point.

1.1.#

Report the weights of the constrained portfolio.

Report the mean, volatility, and Sharpe ratio of the resulting portfolio.

1.2.#

Compare these weights to the assets’ Sharpe ratios and means.

Do the most extreme positions also have the most extreme Sharpe ratios and means?

Why?

1.3.#

Compare the bounded portfolio weights to the unbounded portfolio weights (obtained from optimizing without the inequality constraints, keeping the equality constraints.)

Report the mean, volatility, and Sharpe ratio of both.

Code Help#

The minimize function will be how we optimize.

from scipy.optimize import minimize

Build the objective functions.

Before doing this, you will need to define

  • TARGET_MEAN

  • FREQ

  • cov

  • mean

# def objective(w):        
#     return (w.T @ cov @ w)

# def fun_constraint_mean(w):
#     return (mean @ w) - TARGET_MEAN

Build the constraints

  • weighted average of means is the target mean

# constraint_mean = {'type': 'eq', 'fun': fun_constraint_mean}

# constraints = ([constraint_mean])

Note#

If we used total returns, instead of excess returns, then we would add a constraint that the weights add to one.

# def fun_constraint_capital(w):
#     return np.sum(w) - 1

# constraint_capital = {'type': 'eq', 'fun': fun_constraint_capital}

# constraints = ([constraint_capital, constraint_mean])

Build the upper and lower bounds on each asset.

You will need to use the minimize function along with these contraints, bounds, and an initial guess.