Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/sandbox/RFinance2014/optimize.R
1433 views
1
# script used to run the portfolio optimizations
2
3
# Examples to consider
4
# Example 1: Consider a portfolio of stocks. Full investment and long
5
# only (or box) constraints. Objective to minimize portfolio variance.
6
# Demonstrate a custom moments function to compare a sample covariance
7
# matrix estimate and a robust covariance matrix estimate. An alternative
8
# to a MCD estimate is ledoit-wolf shrinkage, DCC GARCH model,
9
# factor model, etc.
10
11
# Example 2: Consider a portfolio of stocks. Dollar neutral, beta
12
# neutral, box constraints, and leverage_exposure constraints. Objective
13
# to minimize portfolio StdDev. This will demonstrate some of the
14
# more advanced constraint types. Could also introduce position limit
15
# constraints here in this example.
16
17
# Example 3: Consider an allocation to hedge funds using the
18
# EDHEC-Risk Alternative Index as a proxy. This will be an extended
19
# example starting with an objective to minimize portfolio expected
20
# shortfall, then risk budget percent contribution limit, then equal
21
# risk contribution limit.
22
23
# Example 4: Consider an allocation to hedge funds using the
24
# EDHEC-Risk Alternative Index as a proxy.
25
26
# Option 1 for example 4
27
# Objective to maximize a risk adjusted return measure
28
# (e.g.Calmar Ratio, Sterling Ratio, Sortino Ratio, or Upside Potential
29
# Ratio)
30
31
# I prefer doing this option
32
# Option 2 for example 4
33
# Objective to maximize the
34
# fourth order expansion of the Constant Relative Risk Aversion (CRRA)
35
# expected utility function. Demonstrate a custom moment function and
36
# a custom objective function.
37
38
# Set the directory to save the optimization results
39
results.dir <- "optimization_results"
40
41
# mix of blue, green, and red hues
42
my_colors <- c("#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c")
43
44
# Load the packages
45
library(PortfolioAnalytics)
46
library(foreach)
47
library(ROI)
48
library(ROI.plugin.quadprog)
49
50
# for running via Rscript
51
library(methods)
52
53
# Source in the lwShrink function
54
source("R/lwShrink.R")
55
56
# Example 1 and Example 2 will use the crsp_weekly data
57
# Example 3 and Example 4 will use the edhec data
58
source("data_prep.R")
59
60
61
##### Example 1 #####
62
stocks <- colnames(equity.data)
63
# Specify an initial portfolio
64
portf.init <- portfolio.spec(stocks)
65
# Add constraints
66
# weights sum to 1
67
portf.minvar <- add.constraint(portf.init, type="full_investment")
68
# box constraints
69
portf.minvar <- add.constraint(portf.minvar, type="box", min=0.01, max=0.45)
70
71
# Add objective
72
# objective to minimize portfolio variance
73
portf.minvar <- add.objective(portf.minvar, type="risk", name="var")
74
75
# Backtesting parameters
76
# Set rebalancing frequency
77
rebal.freq <- "quarters"
78
# Training Period
79
training <- 400
80
# Trailing Period
81
trailing <- 250
82
83
# Run optimization
84
# Sample Covariance Matrix Estimate
85
86
# By default, momentFUN uses set.portfolio.moments which computes the sample
87
# moment estimates
88
89
cat("Example 1: running minimum variance with sample covariance matrix
90
estimate backtest\n")
91
if(file.exists(paste(results.dir, "opt.minVarSample.rda", sep="/"))){
92
cat("file already exists\n")
93
} else {
94
opt.minVarSample <- optimize.portfolio.rebalancing(equity.data, portf.minvar,
95
optimize_method="ROI",
96
rebalance_on=rebal.freq,
97
training_period=training,
98
trailing_periods=trailing)
99
cat("opt.minVarSample complete. Saving results to ", results.dir, "\n")
100
save(opt.minVarSample, file=paste(results.dir, "opt.minVarSample.rda", sep="/"))
101
}
102
103
# Custom moment function to use Ledoit-Wolf shinkage covariance matrix estimate
104
lw.sigma <- function(R, ...){
105
out <- list()
106
# estimate covariance matrix via robust covariance matrix estimate,
107
# ledoit-wolf shrinkage, GARCH, factor model, etc.
108
# set.seed(1234)
109
# out$sigma <- MASS::cov.rob(R, method="mcd", ...)$cov
110
out$sigma <- lwShrink(R)$cov
111
#print(index(last(R)))
112
return(out)
113
}
114
115
cat("Example 1: running minimum variance with Ledoit-Wolf shrinkage covariance
116
matrix estimate backtest\n")
117
if(file.exists(paste(results.dir, "opt.minVarLW.rda", sep="/"))){
118
cat("file already exists\n")
119
} else{
120
# Using Ledoit-Wolf Shrinkage Covariance Matrix Estimate
121
opt.minVarLW <- optimize.portfolio.rebalancing(equity.data, portf.minvar,
122
optimize_method="ROI",
123
momentFUN=lw.sigma,
124
rebalance_on=rebal.freq,
125
training_period=training,
126
trailing_periods=trailing)
127
cat("opt.minVarLW complete. Saving results to ", results.dir, "\n")
128
save(opt.minVarLW, file=paste(results.dir, "opt.minVarLW.rda", sep="/"))
129
}
130
131
##### Example 2 #####
132
portf.init <- portfolio.spec(stocks)
133
134
# weights sum to 0
135
portf.dn <- add.constraint(portf.init, type="weight_sum",
136
min_sum=-0.01, max_sum=0.01)
137
138
# Add box constraints such that no stock has weight less than -20% or
139
# greater than 20%
140
portf.dn <- add.constraint(portf.dn, type="box",
141
min=-0.2, max=0.2)
142
# Add position limit constraint such that the portfolio has a maximum
143
# of 20 non-zero positions
144
portf.dn <- add.constraint(portf.dn, type="position_limit", max_pos=20)
145
146
# Compute the betas of each stock
147
betas <- t(CAPM.beta(equity.data, market, Rf))
148
149
# Add factor exposure constraint to limit portfolio beta
150
portf.dn <- add.constraint(portf.dn, type="factor_exposure", B=betas,
151
lower=-0.25, upper=0.25)
152
# portf.dn <- add.constraint(portf.dn, type="leverage_exposure", leverage=2)
153
154
# generate random portfolios
155
if(file.exists(paste(results.dir, "rp.rda", sep="/"))){
156
cat("random portfolios already generated\n")
157
} else {
158
cat("generating random portfolios\n")
159
rp <- random_portfolios(portf.dn, 10000, eliminate=TRUE)
160
cat("random portfolios generated. Saving rp to ", results.dir, "\n")
161
save(rp, file=paste(results.dir, "rp.rda", sep="/"))
162
}
163
164
# Add objective to maximize return
165
portf.dn.StdDev <- add.objective(portf.dn, type="return", name="mean",
166
target=0.0015)
167
# Add objective to target a portfolio standard deviation of 2%
168
portf.dn.StdDev <- add.objective(portf.dn.StdDev, type="risk", name="StdDev",
169
target=0.02)
170
171
cat("Example 2: running dollar neutral optimization\n")
172
if(file.exists(paste(results.dir, "opt.dn.rda", sep="/"))){
173
cat("file already exists\n")
174
} else {
175
# Run optimization
176
opt.dn <- optimize.portfolio(equity.data, portf.dn.StdDev,
177
optimize_method="random", rp=rp,
178
trace=TRUE)
179
cat("opt.dn complete. Saving results to ", results.dir, "\n")
180
save(opt.dn, file=paste(results.dir, "opt.dn.rda", sep="/"))
181
}
182
183
##### Example 3 #####
184
# Example 3 will consider three portfolios
185
# - minES
186
# - minES with component contribution limit
187
# - minES with equal risk contribution
188
189
funds <- colnames(R)
190
portf.init <- portfolio.spec(funds)
191
portf.init <- add.constraint(portf.init, type="weight_sum",
192
min_sum=0.99, max_sum=1.01)
193
194
portf.init <- add.constraint(portf.init, type="box",
195
min=0.05, max=0.4)
196
197
# Set multiplier=0 so that it is calculated, but does not affect the optimization
198
portf.init <- add.objective(portf.init, type="return",
199
name="mean", multiplier=0)
200
201
# Add objective to minimize expected shortfall
202
portf.minES <- add.objective(portf.init, type="risk", name="ES")
203
204
# Add risk budget objective with upper limit on percentage contribution
205
portf.minES.RB <- add.objective(portf.minES, type="risk_budget",
206
name="ES", max_prisk=0.3)
207
208
# Relax the box constraint
209
portf.minES.RB$constraints[[2]]$max <- rep(1,ncol(R))
210
# print.default(portf.minES.RB$constraints[[2]])
211
212
# Add risk budget objective to minimize concentration of percentage component
213
# contribution to risk. Concentration is defined as the Herfindahl-Hirschman
214
# Index (HHI). $\sum_i x_i^2$
215
portf.minES.EqRB <- add.objective(portf.minES, type="risk_budget",
216
name="ES", min_concentration=TRUE)
217
# relax the box constraint
218
portf.minES.EqRB <- add.constraint(portf.minES.EqRB, type="box",
219
min=0.05, max=1, indexnum=2)
220
# portf.minES.RB$constraints[[2]]$max <- rep(1,ncol(R))
221
# print.default(portf.minES.EqRB$constraints[[2]])
222
223
# Add risk budget objective to minES portfolio with multiplier=0 so that it
224
# is calculated, but does not affect optimization
225
portf.minES <- add.objective(portf.minES, type="risk_budget",
226
name="ES", multiplier=0)
227
228
# Combine the portfolios so we can make a single call to
229
# optimize.portfolio
230
portf <- combine.portfolios(list(minES=portf.minES,
231
minES.RB=portf.minES.RB,
232
minES.EqRB=portf.minES.EqRB))
233
234
cat("Example 3: running minimum ES optimizations\n")
235
if(file.exists(paste(results.dir, "opt.minES.rda", sep="/"))){
236
cat("file already exists\n")
237
} else {
238
# Run the optimization
239
opt.minES <- optimize.portfolio(R, portf, optimize_method="DEoptim",
240
search_size=5000, trace=TRUE, traceDE=0,
241
message=TRUE)
242
cat("opt.minES complete. Saving results to ", results.dir, "\n")
243
save(opt.minES, file=paste(results.dir, "opt.minES.rda", sep="/"))
244
}
245
246
# Now we want to evaluate the optimization through time
247
248
# Rebalancing parameters
249
# Set rebalancing frequency
250
rebal.freq <- "quarters"
251
# Training Period
252
training <- 120
253
# Trailing Period
254
trailing <- 72
255
256
cat("Example 3: running minimum ES backtests\n")
257
if(file.exists(paste(results.dir, "bt.opt.minES.rda", sep="/"))){
258
cat("file already exists\n")
259
} else {
260
# Backtest
261
bt.opt.minES <- optimize.portfolio.rebalancing(R, portf,
262
optimize_method="DEoptim",
263
rebalance_on=rebal.freq,
264
training_period=training,
265
trailing_periods=trailing,
266
search_size=5000,
267
traceDE=0, message=TRUE)
268
cat("bt.opt.minES complete. Saving results to ", results.dir, "\n")
269
save(bt.opt.minES, file=paste(results.dir, "bt.opt.minES.rda", sep="/"))
270
}
271
272
##### Example 4 #####
273
274
# Simple function to compute the moments used in CRRA
275
crra.moments <- function(R, ...){
276
out <- list()
277
out$mu <- colMeans(R)
278
out$sigma <- cov(R)
279
out$m3 <- PerformanceAnalytics:::M3.MM(R)
280
out$m4 <- PerformanceAnalytics:::M4.MM(R)
281
out
282
}
283
284
285
# Fourth order expansion of CRRA expected utility
286
CRRA <- function(R, weights, lambda, sigma, m3, m4){
287
weights <- matrix(weights, ncol=1)
288
M2.w <- t(weights) %*% sigma %*% weights
289
M3.w <- t(weights) %*% m3 %*% (weights %x% weights)
290
M4.w <- t(weights) %*% m4 %*% (weights %x% weights %x% weights)
291
term1 <- 0.5 * lambda * M2.w
292
term2 <- (1 / 6) * lambda * (lambda + 1) * M3.w
293
term3 <- (1 / 24) * lambda * (lambda + 1) * (lambda + 2) * M4.w
294
out <- -term1 + term2 - term3
295
out
296
}
297
298
# test the CRRA function
299
portf.crra <- portfolio.spec(funds)
300
portf.crra <- add.constraint(portf.crra, type="weight_sum",
301
min_sum=0.99, max_sum=1.01)
302
303
portf.crra <- add.constraint(portf.crra, type="box",
304
min=0.05, max=0.4)
305
306
portf.crra <- add.objective(portf.crra, type="return",
307
name="CRRA", arguments=list(lambda=10))
308
309
# I just want these for plotting
310
# Set multiplier=0 so that it is calculated, but does not affect the optimization
311
portf.crra <- add.objective(portf.crra, type="return", name="mean", multiplier=0)
312
portf.crra <- add.objective(portf.crra, type="risk", name="ES", multiplier=0)
313
portf.crra <- add.objective(portf.crra, type="risk", name="StdDev", multiplier=0)
314
315
cat("Example 4: running maximum CRRA optimization\n")
316
if(file.exists(paste(results.dir, "opt.crra.rda", sep="/"))){
317
cat("file already exists\n")
318
} else {
319
# Run the optimization
320
opt.crra <- optimize.portfolio(R, portf.crra, optimize_method="DEoptim",
321
search_size=5000, trace=TRUE, traceDE=0,
322
momentFUN="crra.moments")
323
cat("opt.crra complete. Saving results to ", results.dir, "\n")
324
save(opt.crra, file=paste(results.dir, "opt.crra.rda", sep="/"))
325
}
326
327
cat("Example 4: running maximum CRRA backtest\n")
328
if(file.exists(paste(results.dir, "bt.opt.crra.rda", sep="/"))){
329
cat("file already exists\n")
330
} else {
331
# Run the optimization with rebalancing
332
bt.opt.crra <- optimize.portfolio.rebalancing(R, portf.crra,
333
optimize_method="DEoptim",
334
search_size=5000, trace=TRUE,
335
traceDE=0,
336
momentFUN="crra.moments",
337
rebalance_on=rebal.freq,
338
training_period=training,
339
trailing_periods=trailing)
340
cat("bt.opt.crra complete. Saving results to ", results.dir, "\n")
341
save(bt.opt.crra, file=paste(results.dir, "bt.opt.crra.rda", sep="/"))
342
}
343
344
##### RP Demo #####
345
cat("Random portfolio method comparison\n")
346
if(file.exists("figures/rp_plot.png") & file.exists("figures/rp_viz.rda")){
347
cat("file already exists\n")
348
} else {
349
portf.lo <- portfolio.spec(colnames(R))
350
portf.lo <- add.constraint(portf.lo, type="weight_sum",
351
min_sum=0.99, max_sum=1.01)
352
353
portf.lo <- add.constraint(portf.lo, type="long_only")
354
355
# Use the long only portfolio previously created
356
# Generate random portfolios using the 3 methods
357
rp1 <- random_portfolios(portf.lo, permutations=2000,
358
rp_method='sample')
359
rp2 <- random_portfolios(portf.lo, permutations=2000,
360
rp_method='simplex')
361
rp3 <- random_portfolios(portf.lo, permutations=2000,
362
rp_method='grid')
363
364
# Calculate the portfolio mean return and standard deviation
365
rp1_mean <- apply(rp1, 1, function(x) mean(R %*% x))
366
rp1_StdDev <- apply(rp1, 1, function(x) StdDev(R, weights=x))
367
rp2_mean <- apply(rp2, 1, function(x) mean(R %*% x))
368
rp2_StdDev <- apply(rp2, 1, function(x) StdDev(R, weights=x))
369
rp3_mean <- apply(rp3, 1, function(x) mean(R %*% x))
370
rp3_StdDev <- apply(rp3, 1, function(x) StdDev(R, weights=x))
371
372
x.assets <- StdDev(R)
373
y.assets <- colMeans(R)
374
###
375
require(rCharts)
376
# create an interactive plot using rCharts and nvd3 scatterChart
377
tmp1 <- data.frame(name="sample", mean=rp1_mean, sd=rp1_StdDev)
378
tmp2 <- data.frame(name="simplex", mean=rp2_mean, sd=rp2_StdDev)
379
tmp3 <- data.frame(name="grid", mean=rp3_mean, sd=rp3_StdDev)
380
tmp <- rbind(tmp1, tmp2, tmp3)
381
rp_viz <- nPlot(mean ~ sd, group="name", data=tmp, type="scatterChart")
382
rp_viz$xAxis(
383
axisLabel = 'Risk (std. dev.)'
384
,tickFormat = "#!d3.format('0.4f')!#"
385
)
386
rp_viz$yAxis(
387
axisLabel = 'Return'
388
,tickFormat = "#!d3.format('0.4f')!#"
389
)
390
rp_viz$chart(color = my_colors[c(2,4,6)])
391
#set left margin so y axis label will show up
392
rp_viz$chart( margin = list(left = 100) )
393
# rp_viz$chart(
394
# tooltipContent = "#!
395
# function(a,b,c,d) {
396
# //d has all the info you need
397
# return( '<h3>' + d.point.series + '</h3>Return: ' + d.point.y + '<br>Risk: ' + d.point.x)
398
# }
399
# !#")
400
####if you do not want fisheye/magnify
401
####let me know, and will show how to remove
402
####this will solve the tooltip problem
403
save(rp_viz, file="figures/rp_viz.rda")
404
###
405
x.lower <- min(x.assets) * 0.9
406
x.upper <- max(x.assets) * 1.1
407
y.lower <- min(y.assets) * 0.9
408
y.upper <- max(y.assets) * 1.1
409
410
png("figures/rp_plot.png", height = 500, width = 1000)
411
# plot feasible portfolios
412
plot(x=rp1_StdDev, y=rp1_mean, col=my_colors[2], main="Random Portfolio Methods",
413
ylab="mean", xlab="StdDev", xlim=c(x.lower, x.upper),
414
ylim=c(y.lower, y.upper))
415
points(x=rp2_StdDev, y=rp2_mean, col=my_colors[4], pch=2)
416
points(x=rp3_StdDev, y=rp3_mean, col=my_colors[6], pch=5)
417
points(x=x.assets, y=y.assets)
418
text(x=x.assets, y=y.assets, labels=colnames(R), pos=4, cex=0.8)
419
legend("bottomright", legend=c("sample", "simplex", "grid"),
420
col=my_colors[c(2,4,6)],
421
pch=c(1, 2, 5), bty="n")
422
dev.off()
423
}
424
425
cat("Random portfolio simplex method fev biasing\n")
426
if(file.exists("figures/fev_plot.png")){
427
cat("file already exists\n")
428
} else {
429
png("figures/fev_plot.png", height = 500, width = 1000)
430
fev <- 0:5
431
x.assets <- StdDev(R)
432
y.assets <- colMeans(R)
433
par(mfrow=c(2, 3))
434
for(i in 1:length(fev)){
435
rp <- rp_simplex(portfolio=portf.lo, permutations=2000, fev=fev[i])
436
tmp.mean <- apply(rp, 1, function(x) mean(R %*% x))
437
tmp.StdDev <- apply(rp, 1, function(x) StdDev(R=R, weights=x))
438
x.lower <- min(c(tmp.StdDev, x.assets)) * 0.85
439
x.upper <- max(c(tmp.StdDev, x.assets)) * 1.15
440
y.lower <- min(c(tmp.mean, y.assets)) * 0.85
441
y.upper <- max(c(tmp.mean, y.assets)) * 1.15
442
plot(x=tmp.StdDev, y=tmp.mean, main=paste("FEV =", fev[i]),
443
ylab="mean", xlab="StdDev", col=rgb(0, 0, 100, 50, maxColorValue=255),
444
xlim=c(x.lower, x.upper),
445
ylim=c(y.lower, y.upper))
446
points(x=x.assets, y=y.assets)
447
text(x=x.assets, y=y.assets, labels=colnames(R), pos=4, cex=0.8)
448
}
449
par(mfrow=c(1,1))
450
dev.off()
451
}
452
453
# # Calculate the turnover per period
454
# turnover.rebalancing <- function(object){
455
# weights <- extractWeights(object)
456
# n <- nrow(weights)
457
# out <- vector("numeric", n)
458
# out[1] <- NA
459
# for(i in 2:n){
460
# out[i] <- out[i] <- sum(abs(as.numeric(weights[i,]) - as.numeric(weights[i-1,])))
461
# }
462
# xts(out, index(weights))
463
# }
464
#
465
# # Calculate the diversification per period
466
# diversification.rebalancing <- function(object){
467
# weights <- extractWeights(object)
468
# n <- nrow(weights)
469
# out <- vector("numeric", n)
470
# for(i in 1:n){
471
# out[i] <- 1 - sum(weights[i,]^2)
472
# }
473
# xts(out, index(weights))
474
# }
475
476