Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/vignettes/risk_budget_optimization.Rnw
1433 views
1
\documentclass[a4paper]{article}
2
\usepackage[round]{natbib}
3
\usepackage{bm}
4
\usepackage{verbatim}
5
\usepackage[latin1]{inputenc}
6
7
\usepackage{url}
8
9
\let\proglang=\textsf
10
\newcommand{\pkg}[1]{{\fontseries{b}\selectfont #1}}
11
\newcommand{\R}[1]{{\fontseries{b}\selectfont #1}}
12
\newcommand{\email}[1]{\href{mailto:#1}{\normalfont\texttt{#1}}}
13
\newcommand{\E}{\mathsf{E}}
14
\newcommand{\VAR}{\mathsf{VAR}}
15
\newcommand{\COV}{\mathsf{COV}}
16
\newcommand{\Prob}{\mathsf{P}}
17
18
\renewcommand{\topfraction}{0.85}
19
\renewcommand{\textfraction}{0.1}
20
\renewcommand{\baselinestretch}{1.5}
21
\setlength{\textwidth}{15cm} \setlength{\textheight}{22cm} \topmargin-1cm \evensidemargin0.5cm \oddsidemargin0.5cm
22
23
\usepackage[latin1]{inputenc}
24
% or whatever
25
26
\usepackage{lmodern}
27
\usepackage[T1]{fontenc}
28
% Or whatever. Note that the encoding and the font should match. If T1
29
% does not look nice, try deleting the line with the fontenc.
30
31
% \VignetteIndexEntry{Portfolio Optimization with CVaR budgets in PortfolioAnalytics}
32
33
\begin{document}
34
\SweaveOpts{concordance=TRUE}
35
36
\title{Portfolio Optimization with CVaR budgets in PortfolioAnalytics}
37
\author{Kris Boudt, Peter Carl and Brian Peterson }
38
\date{June 1, 2010}
39
40
\maketitle
41
\tableofcontents
42
43
44
\bigskip
45
46
\section{General information}
47
48
Risk budgets are a central tool to estimate and manage the portfolio risk allocation. They decompose total portfolio risk into the risk contribution of each position. \citet{BoudtCarlPeterson2010} propose several portfolio allocation strategies that use an appropriate transformation of the portfolio Conditional Value at Risk (CVaR) budget as an objective or constraint in the portfolio optimization problem. This document explains how risk allocation optimized portfolios can be obtained under general constraints in the \verb"PortfolioAnalytics" package of \citet{PortfolioAnalytics}.
49
50
\verb"PortfolioAnalytics" is designed to provide numerical solutions for portfolio problems with complex constraints and objective sets comprised of any R function. It can e.g.~construct portfolios that minimize a risk objective with (possibly non-linear) per-asset constraints on returns and drawdowns \citep{CarlPetersonBoudt2010}. The generality of possible constraints and objectives is a distinctive characteristic of the package with respect to RMetrics \verb"fPortfolio" of \citet{fPortfolioBook}. For standard Markowitz optimization problems, use of \verb"fPortfolio" rather than \verb"PortfolioAnalytics" is recommended.
51
52
\verb"PortfolioAnalytics" solves the following type of problem
53
\begin{equation} \min_w g(w) \ \ s.t. \ \
54
\left\{ \begin{array}{l} h_1(w)\leq 0 \\ \vdots \\ h_q(w)\leq 0. \end{array} \right. \label{optimproblem}\end{equation} \verb"PortfolioAnalytics" first merges the objective function and constraints into a penalty augmented objective function
55
\begin{equation} L(w) = g(w) + \mbox{penalty}\sum_{i=1}^q \lambda_i \max(h_i(w),0), \label{eq:constrainedobj} \end{equation}
56
where $\lambda_i$ is a multiplier to tune the relative importance of the constraints. The default values of penalty and $\lambda_i$ (called \verb"multiplier" in \verb"PortfolioAnalytics") are 10000 and 1, respectively.
57
58
The minimum of this function is found through the \emph{Differential Evolution} (DE) algorithm of \citet{StornPrice1997} and ported to R by \citet{MullenArdiaGilWindoverCline2009}. DE is known for remarkable performance regarding continuous numerical problems \citep{PriceStornLampinen2006}. It has recently been advocated for optimizing portfolios under non-convex settings by \citet{Ardia2010} and \citet{Yollin2009}, among others. We use the R implementation of DE in the \verb"DEoptim" package of \citet{DEoptim}.
59
60
The latest version of the \verb"PortfolioAnalytics" package can be downloaded from R-forge through the following command:
61
\begin{verbatim}
62
install.packages("PortfolioAnalytics", repos="http://R-Forge.R-project.org")
63
\end{verbatim}
64
65
Its principal functions are:
66
\begin{itemize}
67
\item \verb"portfolio.spec(assets)": the portfolio specification starts with creating a \verb"portfolio" object with information about the assets. The first argument \verb"assets" is either a number indicating the number of portfolio assets or a vector holding the names of the assets. The \verb"portfolio" object is a list holding the constraints and objectives.
68
69
\item \verb"add.constraint(portfolio, type)": Constraints are added to the \verb"portfolio" object by the function \verb"add.constraint". Basic constraint types include leverage constraints that specify the sum of the weights have to be between \verb"min_sum" and \verb"max_sum" and box constraints where the asset weights have to be between \verb"min" and \verb"max".
70
71
\item \verb"add.objective(portfolio, type, name)": New objectives are added to the \verb"portfolio" objected with the function \verb"add.objective". Many common risk budget objectives and constraints are prespecified and can be identified by specifying the \verb"type" and \verb"name".
72
73
\item \verb"constrained_objective(w, R, portfolio)": given the portfolio weight and return data, it evaluates the penalty augmented objective function in (\ref{eq:constrainedobj}).
74
75
\item \verb"optimize.portfolio(R, portfolio)": this function returns the portfolio weight that solves the problem in (\ref{optimproblem}). {\it R} is the multivariate return series of the portfolio components.
76
77
\item \verb"optimize.portfolio.rebalancing(R, portfolio, rebalance_on, trailing_periods)": this function solves the multiperiod optimization problem. It returns for each rebalancing period the optimal weights and allows the estimation sample to be either from inception or a moving window.
78
79
\end{itemize}
80
81
Next we illustrate these functions on monthly return data for bond, US equity, international equity and commodity indices, which are the first 4 series
82
in the dataset \verb"indexes". The first step is to load the package \verb"PortfolioAnalytics" and the dataset. An important first note is that some of the functions (especially \verb" optimize.portfolio.rebalancing") requires the dataset to be a \verb"xts" object \citep{xts}.
83
84
85
<<echo=FALSE>>=
86
options(width=80)
87
@
88
89
<<echo=TRUE>>=
90
library(PortfolioAnalytics)
91
library(DEoptim)
92
library(robustbase)
93
data(indexes)
94
class(indexes)
95
indexes <- indexes[,1:4]
96
head(indexes,2)
97
tail(indexes,2)
98
@
99
100
In what follows, we first illustrate the construction of the penalty augmented objective function. Then we present the code for solving the optimization problem.
101
102
\section{Setting of the objective function}
103
104
\subsection{Weight constraints}
105
106
<<echo=TRUE, tidy=FALSE>>=
107
# Create the portfolio specification object
108
Wcons <- portfolio.spec( assets = colnames(indexes) )
109
# Add box constraints
110
Wcons <- add.constraint( portfolio=Wcons, type='box', min = 0, max=1 )
111
# Add the full investment constraint that specifies the weights must sum to 1.
112
Wcons <- add.constraint( portfolio=Wcons, type="full_investment")
113
@
114
115
Given the weight constraints, we can call the value of the function to be minimized. We consider the case of no violation and a case of violation. By default, \verb"normalize=TRUE" which means that if the sum of weights exceeds \verb"max_sum", the weight vector is normalized by multiplying it with \verb"sum(weights)/max_sum" such that the weights evaluated in the objective function satisfy the \verb"max_sum" constraint.
116
<<echo=TRUE, tidy=FALSE>>=
117
constrained_objective( w = rep(1/4,4) , R = indexes, portfolio = Wcons)
118
constrained_objective( w = rep(1/3,4) , R = indexes, portfolio = Wcons)
119
constrained_objective( w = rep(1/3,4) , R = indexes, portfolio = Wcons,
120
normalize=FALSE)
121
@
122
123
The latter value can be recalculated as penalty times the weight violation, that is: $10000 \times 1/3.$
124
125
\subsection{Minimum CVaR objective function}
126
127
Suppose now we want to find the portfolio that minimizes the 95\% portfolio CVaR subject to the weight constraints listed above.
128
129
<<echo=TRUE, tidy=FALSE>>=
130
ObjSpec = add.objective( portfolio = Wcons , type="risk",name="CVaR",
131
arguments=list(p=0.95), enabled=TRUE)
132
@
133
134
The value of the objective function is:
135
<<echo=TRUE, tidy=FALSE>>=
136
constrained_objective( w = rep(1/4,4) , R = indexes, portfolio = ObjSpec)
137
@
138
139
This is the CVaR of the equal-weight portfolio as computed by the function \verb"ES" in the \verb"PerformanceAnalytics" package of \citet{ Carl2007}
140
<<echo=TRUE, tidy=FALSE>>=
141
library(PerformanceAnalytics)
142
out<-ES(indexes, weights = rep(1/4,4),p=0.95,
143
portfolio_method="component")
144
out$MES
145
@
146
All arguments in the function \verb"ES" can be passed on through \verb"arguments". E.g. to reduce the impact of extremes on the portfolio results, it is recommended to winsorize the data using the option clean="boudt".
147
148
<<echo=TRUE, tidy=FALSE>>=
149
out<-ES(indexes, weights = rep(1/4,4),p=0.95, clean="boudt",
150
portfolio_method="component")
151
out$MES
152
@
153
154
For the formulation of the objective function, this implies setting:
155
<<echo=TRUE, tidy=FALSE>>=
156
ObjSpec = add.objective( portfolio = Wcons , type="risk",name="CVaR",
157
arguments=list(p=0.95,clean="boudt"), enabled=TRUE)
158
constrained_objective( w = rep(1/4,4) , R = indexes[,1:4] , portfolio = ObjSpec)
159
@
160
161
An additional argument that is not available for the moment in \verb"ES" is to estimate the conditional covariance matrix through the constant conditional correlation model of \citet{Bollerslev90}.
162
163
For the formulation of the objective function, this implies setting:
164
<<echo=TRUE, tidy=FALSE>>=
165
ObjSpec = add.objective( portfolio = Wcons , type="risk",name="CVaR",
166
arguments=list(p=0.95,clean="boudt"),
167
enabled=TRUE, garch=TRUE)
168
constrained_objective( w = rep(1/4,4) , R = indexes[,1:4] , portfolio = ObjSpec)
169
@
170
171
\subsection{Minimum CVaR concentration objective function}
172
173
Add the minimum 95\% CVaR concentration objective to the objective function:
174
<<echo=TRUE, tidy=FALSE>>=
175
ObjSpec = add.objective( portfolio = Wcons , type="risk_budget_objective",
176
name="CVaR", arguments=list(p=0.95, clean="boudt"),
177
min_concentration=TRUE, enabled=TRUE)
178
@
179
180
The value of the objective function is:
181
<<echo=TRUE, tidy=FALSE>>=
182
constrained_objective( w = rep(1/4,4) , R = indexes,
183
portfolio = ObjSpec, trace=TRUE)
184
@
185
186
We can verify that this is effectively the largest CVaR contribution of that portfolio as follows:
187
<<echo=TRUE, tidy=FALSE>>=
188
ES(indexes[,1:4],weights = rep(1/4,4),p=0.95,clean="boudt",
189
portfolio_method="component")
190
@
191
192
\subsection{Risk allocation constraints}
193
194
We see that in the equal-weight portfolio, the international equities and commodities investment cause more than 30\% of total risk. We could specify as a constraint that no asset can contribute more than 30\% to total portfolio risk with the argument \verb"max_prisk=0.3". This involves the construction of the following objective function:
195
196
<<echo=TRUE, tidy=FALSE>>=
197
ObjSpec = add.objective( portfolio = Wcons , type="risk_budget_objective",
198
name="CVaR", max_prisk = 0.3,
199
arguments=list(p=0.95,clean="boudt"), enabled=TRUE)
200
constrained_objective( w = rep(1/4,4) , R = indexes, portfolio = ObjSpec)
201
@
202
203
This value corresponds to the penalty parameter which has by default the value of 10000 times the exceedances: $ 10000*(0.045775103+0.054685023)\approx 1004.601.$
204
205
\section{Optimization}
206
207
The penalty augmented objective function is minimized through Differential Evolution. Two parameters are crucial in tuning the optimization: \verb"search_size" and \verb"itermax". The optimization routine
208
\begin{enumerate}
209
\item First creates the initial generation of \verb"NP = search_size/itermax" guesses for the optimal value of the parameter vector, using the \verb"random_portfolios" function generating random weights satisfying the weight constraints.
210
\item Then DE evolves over this population of candidate solutions using alteration and selection operators in order to minimize the objective function. It restarts \verb"itermax" times.
211
\end{enumerate}
212
It is important that \verb"search_size/itermax" is high enough. It is generally recommended that this ratio is at least ten times the length of the weight vector. For more details on the use of DE strategy in portfolio allocation, we refer the
213
reader to \citet{Ardia2010}.
214
215
\subsection{Minimum CVaR portfolio under an upper 40\% CVaR allocation constraint}
216
217
The portfolio object and functions needed to obtain the minimum CVaR portfolio under an upper 40\% CVaR allocation objective are the following:
218
<<echo=TRUE, tidy=FALSE>>=
219
# Create the portfolio specification object
220
ObjSpec <- portfolio.spec(assets=colnames(indexes[,1:4]))
221
# Add box constraints
222
ObjSpec <- add.constraint(portfolio=ObjSpec, type='box', min = 0, max=1)
223
# Add the full investment constraint that specifies the weights must sum to 1.
224
ObjSpec <- add.constraint(portfolio=ObjSpec, type="weight_sum",
225
min_sum=0.99, max_sum=1.01)
226
# Add objective to minimize CVaR
227
ObjSpec <- add.objective(portfolio=ObjSpec, type="risk", name="CVaR",
228
arguments=list(p=0.95, clean="boudt"))
229
# Add objective for an upper 40% CVaR allocation
230
ObjSpec <- add.objective(portfolio=ObjSpec, type="risk_budget_objective",
231
name="CVaR", max_prisk=0.4,
232
arguments=list(p=0.95, clean="boudt"))
233
@
234
235
After the call to these functions it starts to explore the feasible space iteratively and is shown in the output. Iterations are given as intermediate output and by default every iteration will be printed. We set \verb"traceDE=5" to print every 5 iterations and \verb"itermax=50" for a maximum of 50 iterations.
236
237
<<echo=TRUE, tidy=FALSE>>=
238
set.seed(1234)
239
out <- optimize.portfolio(R=indexes, portfolio=ObjSpec,
240
optimize_method="DEoptim", search_size=2000,
241
traceDE=5, itermax=50, trace=TRUE)
242
print(out)
243
@
244
245
246
If \verb"trace=TRUE" in \verb"optimize.portfolio", additional output from the DEoptim solver is included in the \verb"out" object created by \verb"optimize.portfolio". The additional elements in the output are \verb"DEoptim_objective_results" and \verb"DEoutput". The \verb"DEoutput" element contains output from the function \verb"DEoptim". The \verb"DEoptim_objective_results" element contains the weights, value of the objective measures, and other data at each iteration.
247
248
<<echo=TRUE>>=
249
names(out)
250
# View the DEoptim_objective_results information at the last iteration
251
out$DEoptim_objective_results[[length(out$DEoptim_objective_results)]]
252
253
# Extract stats from the out object into a matrix
254
xtract <- extractStats(out)
255
dim(xtract)
256
head(xtract)
257
@
258
259
It can be seen from the charts that although US Bonds has a higher weight allocation, the percentage contribution to risk is the lowest of all four indexes.
260
261
<<tidy=FALSE, fig.height=4, fig.width=6>>=
262
plot.new()
263
chart.Weights(out)
264
@
265
266
<<tidy=FALSE, fig.height=4, fig.width=6>>=
267
plot.new()
268
chart.RiskBudget(out, risk.type="pct_contrib", col="blue", pch=18)
269
@
270
271
272
\subsection{Minimum CVaR concentration portfolio}
273
274
The functions needed to obtain the minimum CVaR concentration portfolio are the following:
275
276
<<echo=TRUE, tidy=FALSE>>=
277
# Create the portfolio specification object
278
ObjSpec <- portfolio.spec(assets=colnames(indexes))
279
# Add box constraints
280
ObjSpec <- add.constraint(portfolio=ObjSpec, type='box', min = 0, max=1)
281
# Add the full investment constraint that specifies the weights must sum to 1.
282
ObjSpec <- add.constraint(portfolio=ObjSpec, type="weight_sum",
283
min_sum=0.99, max_sum=1.01)
284
# Add objective for min CVaR concentration
285
ObjSpec <- add.objective(portfolio=ObjSpec, type="risk_budget_objective",
286
name="CVaR", arguments=list(p=0.95, clean="boudt"),
287
min_concentration=TRUE)
288
289
set.seed(1234)
290
out <- optimize.portfolio(R=indexes, portfolio=ObjSpec,
291
optimize_method="DEoptim", search_size=5000,
292
itermax=50, traceDE=5, trace=TRUE)
293
@
294
295
296
This portfolio has the near equal risk contribution characteristic:
297
<<echo=TRUE, tidy=FALSE>>=
298
print(out)
299
300
# Verify results with ES function
301
ES(indexes[,1:4], weights=out$weights, p=0.95, clean="boudt",
302
portfolio_method="component")
303
@
304
305
The 95\% CVaR percent contribution to risk is near equal for all four indexes. The neighbor portfolios can be plotted to view other near optimal portfolios. Alternatively, the contribution to risk in absolute terms can plotted by setting \verb"risk.type="absolute".
306
307
<<tidy=FALSE, fig.height=4.5, fig.width=6>>=
308
plot.new()
309
chart.RiskBudget(out, neighbors=25, risk.type="pct_contrib",
310
col="blue", pch=18)
311
@
312
313
314
\subsection{Dynamic optimization}
315
316
Dynamic rebalancing of the risk budget optimized portfolio is possible through the function \verb"optimize.portfolio.rebalancing". Additional arguments are \verb"rebalance_on" which indicates the rebalancing frequency (years, quarters, months). The estimation is either done from inception (\verb"trailing_periods=0") or through moving window estimation, where each window has \verb"trailing_periods" observations. The minimum number of observations in the estimation sample is specified by \verb"training_period". Its default value is 36, which corresponds to three years for monthly data.
317
318
As an example, consider the minimum CVaR concentration portfolio, with estimation from inception and monthly rebalancing. Since we require a minimum estimation length of total number of observations -1, we can optimize the portfolio only for the last two months.
319
320
<<echo=TRUE, tidy=FALSE>>=
321
set.seed(1234)
322
out <- optimize.portfolio.rebalancing(R=indexes, portfolio=ObjSpec,
323
optimize_method="DEoptim", search_size=5000,
324
rebalance_on="quarters",
325
training_period=nrow(indexes)-12,
326
traceDE=0)
327
@
328
329
330
The output of \verb"optimize.portfolio.rebalancing" in the \verb"opt_rebalancing" slot is a list of objects created by \verb"optimize.portfolio", one for each rebalancing period.
331
332
<<echo=TRUE>>=
333
names(out)
334
names(out$opt_rebalancing[[1]])
335
out
336
@
337
338
The \verb"summary" method provides a brief output of the optimization result along with return and risk measures.
339
<<echo=TRUE>>=
340
opt.summary <- summary(out)
341
names(opt.summary)
342
opt.summary
343
@
344
345
The optimal weights for each rebalancing period can be extracted fron the object with \verb"extractWeights" and are charted with \verb"chart.Weights".
346
347
<<echo=TRUE>>=
348
extractWeights(out)
349
plot.new()
350
chart.Weights(out, colorset=bluemono)
351
@
352
353
354
Also, the value of the objective function at each rebalancing date is extracted with \verb"extractObjectiveMeasures".
355
<<echo=TRUE>>=
356
head(extractObjectiveMeasures(out))
357
@
358
359
360
The first and last observation from the estimation sample:
361
<<echo=TRUE>>=
362
out$opt_rebalancing[[1]]$data_summary
363
out$opt_rebalancing[[2]]$data_summary
364
@
365
366
The component contribution to risk at each rebalance date can be charted with \verb"chart.RiskBudget". The component contribution to risk in absolute or percentage.
367
<<echo=TRUE>>=
368
plot.new()
369
chart.RiskBudget(out, match.col="CVaR", risk.type="percentage", col=bluemono)
370
@
371
372
<<echo=TRUE>>=
373
plot.new()
374
chart.RiskBudget(out, match.col="CVaR", risk.type="absolute", col=bluemono)
375
@
376
377
Of course, DE is a stochastic optimizer and typically will only find a near-optimal solution that depends on the seed. The function \verb"optimize.portfolio.parallel" in \verb"PortfolioAnalytics" allows to run an arbitrary number of portfolio sets in parallel in order to develop "confidence bands" around your solution. It is based on Revolution's \verb"foreach" package \citep{foreach}.
378
379
\bibliographystyle{abbrvnat}
380
\bibliography{PA}
381
382
383
\end{document}
384
385
386