Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/random_portfolios.R
1433 views
1
###############################################################################
2
# R (https://r-project.org/) Numeric Methods for Optimization of Portfolios
3
#
4
# Copyright (c) 2004-2021 Brian G. Peterson, Peter Carl, Ross Bennett, Kris Boudt
5
#
6
# This library is distributed under the terms of the GNU Public License (GPL)
7
# for full details see the file COPYING
8
#
9
# $Id$
10
#
11
###############################################################################
12
13
# functions to build portfolios for use by the optimizer
14
15
# this code may be made obsolete by the advanced (non-linear, MIP) fPortfolio or roi optimizers, but for now, they are beta code at best
16
17
# requireNamespace(LSPM) # for the un-exported .nPri functions
18
# generate all feasible portfolios
19
#LSPM:::.nPri(n=13,r=45,i=n^r,replace=TRUE)
20
# not likely to actually BE feasible for any portfolio of real size, but I'll write the grid generator anyway that will generate all the permutations, and kick out only the feasible portfolios
21
22
23
# random portfolios
24
25
26
#' create a sequence of possible weights for random or brute force portfolios
27
#'
28
#' This function creates the sequence of min<->max weights for use by
29
#' random or brute force optimization engines.
30
#'
31
#' The sequence created is not constrained by asset.
32
#'
33
#' @param min minimum value of the sequence
34
#' @param max maximum value of the sequence
35
#' @param by number to increment the sequence by
36
#' @param rounding integrer how many decimals should we round to
37
#' @author Peter Carl, Brian G. Peterson
38
#' @seealso \code{\link{constraint}}, \code{\link{objective}}
39
#' @export
40
generatesequence <- function (min=.01, max=1, by=min/max, rounding=3 )
41
{
42
# this creates the sequence of possible weights, not constrained by asset
43
ret <- seq(from = round(min,rounding), to = round(max,rounding), by = by)
44
return(ret)
45
}
46
47
#' Random portfolio sample method
48
#'
49
#' This function generates random permutations of a portfolio seed meeting
50
#' leverage and box constraints. The final step is to run \code{\link{fn_map}}
51
#' on the random portfolio weights to transform the weights so they satisfy
52
#' other constraints such as group or position limit constraints. This is the
53
#' 'sample' method for random portfolios and is based on an idea by Pat Burns.
54
#'
55
#' @param rpconstraints an object of type "constraints" specifying the constraints for the optimization, see \code{\link{constraint}}
56
#' @param max_permutations integer: maximum number of iterations to try for a valid portfolio, default 200
57
#' @param rounding integer how many decimals should we round to
58
#' @return named weights vector
59
#' @author Peter Carl, Brian G. Peterson, (based on an idea by Pat Burns)
60
#' @export
61
randomize_portfolio_v1 <- function (rpconstraints, max_permutations=200, rounding=3)
62
63
{ # @author: Peter Carl, Brian Peterson (based on an idea by Pat Burns)
64
# generate random permutations of a portfolio seed meeting your constraints on the weights of each asset
65
# set the portfolio to the seed
66
seed=rpconstraints$assets
67
nassets= length(seed)
68
min_mult=rpconstraints$min_mult
69
if(is.null(min_mult)) min_mult= rep(-Inf,nassets)
70
max_mult=rpconstraints$max_mult
71
if(is.null(max_mult)) max_mult= rep(Inf,nassets)
72
min_sum =rpconstraints$min_sum
73
max_sum =rpconstraints$max_sum
74
weight_seq=rpconstraints$weight_seq
75
portfolio=as.vector(seed)
76
max = rpconstraints$max
77
min = rpconstraints$min
78
rownames(portfolio)<-NULL
79
weight_seq=as.vector(weight_seq)
80
# initialize our loop
81
permutations=1
82
83
# create a temporary portfolio so we don't return a non-feasible portfolio
84
tportfolio=portfolio
85
# first randomly permute each element of the temporary portfolio
86
random_index <- sample(1:length(tportfolio),length(tportfolio))
87
for (i in 1:length(tportfolio)) {
88
cur_index<-random_index[i]
89
cur_val <- tportfolio[cur_index]
90
# randomly permute a random portfolio element
91
tportfolio[cur_index]<-sample(weight_seq[(weight_seq>=cur_val*min_mult[cur_index]) & (weight_seq<=cur_val*max_mult[cur_index]) & (weight_seq<=max[cur_index]) & (weight_seq>=min[cur_index])],1)
92
}
93
94
#while portfolio is outside min/max sum and we have not reached max_permutations
95
while ((sum(tportfolio)<=min_sum | sum(tportfolio)>=max_sum) & permutations<=max_permutations) {
96
permutations=permutations+1
97
# check our box constraints on total portfolio weight
98
# reduce(increase) total portfolio size till you get a match
99
# 1> check to see which bound you've failed on, brobably set this as a pair of while loops
100
# 2> randomly select a column and move only in the direction *towards the bound*, maybe call a function inside a function
101
# 3> check and repeat
102
random_index <- sample(1:length(tportfolio), length(tportfolio))
103
i = 1
104
while (sum(tportfolio)<=min_sum & i<=length(tportfolio)) {
105
# randomly permute and increase a random portfolio element
106
cur_index<-random_index[i]
107
cur_val <- tportfolio[cur_index]
108
if (length(weight_seq[(weight_seq>=cur_val)&(weight_seq<=max[cur_index])])>1)
109
{
110
# randomly sample one of the larger weights
111
tportfolio[cur_index]<-sample(weight_seq[(weight_seq>=cur_val)&(weight_seq<=max[cur_index])],1)
112
# print(paste("new val:",tportfolio[cur_index]))
113
} else {
114
if (length(weight_seq[(weight_seq>=cur_val)&(weight_seq<=max[cur_index])]) == 1) {
115
tportfolio[cur_index]<-weight_seq[(weight_seq>=cur_val)&(weight_seq<=max[cur_index])]
116
}
117
}
118
i=i+1 # increment our counter
119
} # end increase loop
120
while (sum(tportfolio)>=max_sum & i<=length(tportfolio)) {
121
# randomly permute and decrease a random portfolio element
122
cur_index<-random_index[i]
123
cur_val <- tportfolio[cur_index]
124
if (length(weight_seq[(weight_seq<=cur_val) & (weight_seq>=min[cur_index])] )>1) {
125
tportfolio[cur_index]<-sample(weight_seq[(weight_seq<=cur_val) & (weight_seq>=min[cur_index] )],1)
126
} else {
127
if (length(weight_seq[(weight_seq<=cur_val) & (weight_seq>=min[cur_index])] )==1) {
128
tportfolio[cur_index]<-weight_seq[(weight_seq<=cur_val) & (weight_seq>=min[cur_index])]
129
}
130
}
131
i=i+1 # increment our counter
132
} # end decrease loop
133
} # end final walk towards the edges
134
135
portfolio<-tportfolio
136
137
colnames(portfolio)<-colnames(seed)
138
if (sum(portfolio)<=min_sum | sum(tportfolio)>=max_sum){
139
portfolio <- seed
140
warning("Infeasible portfolio created, defaulting to seed, perhaps increase max_permutations.")
141
}
142
if(isTRUE(all.equal(seed,portfolio))) {
143
if (sum(seed)>=min_sum & sum(seed)<=max_sum) {
144
warning("Unable to generate a feasible portfolio different from seed, perhaps adjust your parameters.")
145
return(seed)
146
} else {
147
warning("Unable to generate a feasible portfolio, perhaps adjust your parameters.")
148
return(NULL)
149
}
150
}
151
return(portfolio)
152
}
153
154
#' deprecated random portfolios wrapper until we write a random trades function
155
#'
156
#'
157
#' @param ... any other passthru parameters
158
#' @author bpeterson
159
#' @export
160
random_walk_portfolios <-function(...) {
161
# wrapper function protect older code for now?
162
random_portfolios(...=...)
163
}
164
165
#' generate an arbitary number of constrained random portfolios
166
#'
167
#' repeatedly calls \code{\link{randomize_portfolio}} to generate an
168
#' arbitrary number of constrained random portfolios.
169
#'
170
#' @param rpconstraints an object of type "constraints" specifying the constraints for the optimization, see \code{\link{constraint}}
171
#' @param permutations integer: number of unique constrained random portfolios to generate
172
#' @param \dots any other passthru parameters
173
#' @return matrix of random portfolio weights
174
#' @seealso \code{\link{constraint}}, \code{\link{objective}}, \code{\link{randomize_portfolio}}
175
#' @author Peter Carl, Brian G. Peterson, (based on an idea by Pat Burns)
176
#' @examples
177
#' rpconstraint<-constraint_v1(assets=10,
178
#' min_mult=-Inf,
179
#' max_mult=Inf,
180
#' min_sum=.99,
181
#' max_sum=1.01,
182
#' min=.01,
183
#' max=.4,
184
#' weight_seq=generatesequence())
185
#'
186
#' rp<- random_portfolios_v1(rpconstraints=rpconstraint,permutations=1000)
187
#' head(rp)
188
#' @export
189
random_portfolios_v1 <- function (rpconstraints,permutations=100,...)
190
{ #
191
# this function generates a series of portfolios that are a "random walk" from the current portfolio
192
seed=rpconstraints$assets
193
result <- matrix(nrow=permutations, ncol=length(seed))
194
result[1,]<-seed
195
result[2,]<-rep(1/length(seed),length(seed))
196
# rownames(result)[1]<-"seed.portfolio"
197
# rownames(result)[2]<-"equal.weight"
198
i <- 3
199
while (i<=permutations) {
200
result[i,] <- as.matrix(randomize_portfolio_v1(rpconstraints=rpconstraints, ...))
201
if(i==permutations) {
202
result = unique(result)
203
i = nrow(result)
204
result = rbind(result, matrix(nrow=(permutations-i),ncol=length(seed)))
205
}
206
i<-i+1
207
}
208
colnames(result)<-names(seed)
209
return(result)
210
}
211
212
#' version 2 generate random permutations of a portfolio seed meeting your constraints on the weights of each asset
213
#'
214
#' @param portfolio an object of type "portfolio" specifying the constraints for the optimization, see \code{\link{portfolio.spec}}
215
#' @param max_permutations integer: maximum number of iterations to try for a valid portfolio, default 200
216
#' @return named weighting vector
217
#' @author Peter Carl, Brian G. Peterson, (based on an idea by Pat Burns)
218
#' @aliases randomize_portfolio randomize_portfolio_v2
219
#' @rdname randomize_portfolio
220
#' @export randomize_portfolio
221
#' @export randomize_portfolio_v2
222
randomize_portfolio <- randomize_portfolio_v2 <- function (portfolio, max_permutations=200) {
223
# generate random permutations of a portfolio seed meeting your constraints on the weights of each asset
224
# set the portfolio to the seed
225
seed <- portfolio$assets
226
nassets <- length(seed)
227
228
# get the constraints from the portfolio object
229
constraints <- get_constraints(portfolio)
230
231
min_mult <- constraints$min_mult
232
if(is.null(min_mult)) min_mult <- rep(-Inf,nassets)
233
max_mult <- constraints$max_mult
234
if(is.null(max_mult)) max_mult <- rep(Inf,nassets)
235
min_sum <- constraints$min_sum
236
max_sum <- constraints$max_sum
237
# randomize_portfolio will rarely find a feasible portfolio if there is not some
238
# 'wiggle room' between min_sum and max_sum
239
if((max_sum - min_sum) < 0.02){
240
min_sum <- min_sum - 0.01
241
max_sum <- max_sum + 0.01
242
}
243
weight_seq <- portfolio$weight_seq
244
if(is.null(weight_seq)){
245
weight_seq <- generatesequence(min=min(constraints$min), max=max(constraints$max), by=0.002)
246
}
247
weight_seq <- as.vector(weight_seq)
248
249
# box constraints
250
max <- constraints$max
251
min <- constraints$min
252
253
# If any of the constraints below do not exist in the constraints object,
254
# then they are NULL values which rp_transform can handle in its checks.
255
256
# group constraints
257
groups <- constraints$groups
258
cLO <- constraints$cLO
259
cUP <- constraints$cUP
260
group_pos <- constraints$group_pos
261
262
# position limit constraints
263
max_pos <- constraints$max_pos
264
max_pos_long <- constraints$max_pos_long
265
max_pos_short <- constraints$max_pos_short
266
267
# leverage constraint
268
leverage <- constraints$leverage
269
270
# initial portfolio
271
iportfolio <- as.vector(seed)
272
rownames(iportfolio) <- NULL
273
274
# initialize our loop
275
permutations <- 1
276
277
# create a temporary portfolio so we don't return a non-feasible portfolio
278
tportfolio <- iportfolio
279
# first randomly permute each element of the temporary portfolio
280
random_index <- sample(1:length(tportfolio), length(tportfolio))
281
for (i in 1:length(tportfolio)) {
282
cur_index <- random_index[i]
283
cur_val <- tportfolio[cur_index]
284
# randomly permute a random portfolio element
285
tportfolio[cur_index] <- sample(weight_seq[(weight_seq >= cur_val * min_mult[cur_index]) & (weight_seq <= cur_val * max_mult[cur_index]) & (weight_seq <= max[cur_index]) & (weight_seq >= min[cur_index])], 1)
286
}
287
288
# random portfolios algorithm designed to handle multiple constraint types
289
fportfolio <- rp_transform(w=tportfolio,
290
min_sum=min_sum,
291
max_sum=max_sum,
292
min_box=min,
293
max_box=max,
294
groups=groups,
295
cLO=cLO,
296
cUP=cUP,
297
max_pos=max_pos,
298
group_pos=group_pos,
299
max_pos_long=max_pos_long,
300
max_pos_short=max_pos_short,
301
leverage=leverage,
302
weight_seq=weight_seq,
303
max_permutations=max_permutations)
304
305
# #while portfolio is outside min/max sum and we have not reached max_permutations
306
# while ((sum(tportfolio) <= min_sum | sum(tportfolio) >= max_sum) & permutations <= max_permutations) {
307
# permutations <- permutations+1
308
# # check our box constraints on total portfolio weight
309
# # reduce(increase) total portfolio size till you get a match
310
# # 1> check to see which bound you've failed on, brobably set this as a pair of while loops
311
# # 2> randomly select a column and move only in the direction *towards the bound*, maybe call a function inside a function
312
# # 3> check and repeat
313
# random_index <- sample(1:length(tportfolio), length(tportfolio))
314
# i <- 1
315
# while (sum(tportfolio) <= min_sum & i <= length(tportfolio)) {
316
# # randomly permute and increase a random portfolio element
317
# cur_index <- random_index[i]
318
# cur_val <- tportfolio[cur_index]
319
# tmp_seq <- weight_seq[(weight_seq >= cur_val) & (weight_seq <= max[cur_index])]
320
# n_tmp_seq <- length(tmp_seq)
321
# if(n_tmp_seq > 1){
322
# # randomly sample one of the larger weights
323
# tportfolio[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]
324
# # print(paste("new val:",tportfolio[cur_index]))
325
# } else {
326
# if(n_tmp_seq == 1){
327
# tportfolio[cur_index] <- tmp_seq
328
# }
329
# }
330
# i <- i + 1 # increment our counter
331
# } # end increase loop
332
# while (sum(tportfolio) >= max_sum & i <= length(tportfolio)) {
333
# # randomly permute and decrease a random portfolio element
334
# cur_index <- random_index[i]
335
# cur_val <- tportfolio[cur_index]
336
# tmp_seq <- weight_seq[(weight_seq <= cur_val) & (weight_seq >= min[cur_index])]
337
# n_tmp_seq <- length(tmp_seq)
338
# if(n_tmp_seq > 1) {
339
# # randomly sample one of the smaller weights
340
# tportfolio[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]
341
# } else {
342
# if(n_tmp_seq == 1){
343
# tportfolio[cur_index] <- tmp_seq
344
# }
345
# }
346
# i <- i + 1 # increment our counter
347
# } # end decrease loop
348
# } # end final walk towards the edges
349
# # final portfolio
350
# fportfolio <- fn_map(weights=tportfolio, portfolio=portfolio, relax=FALSE)$weights
351
352
colnames(fportfolio) <- colnames(seed)
353
if (sum(fportfolio) < min_sum | sum(fportfolio) > max_sum){
354
fportfolio <- seed
355
warning("Infeasible portfolio created, defaulting to seed, perhaps increase max_permutations.")
356
}
357
if(isTRUE(all.equal(seed, fportfolio))) {
358
if (sum(seed) >= min_sum & sum(seed) <= max_sum) {
359
warning("Unable to generate a feasible portfolio different from seed, perhaps adjust your parameters.")
360
return(seed)
361
} else {
362
warning("Unable to generate a feasible portfolio, perhaps adjust your parameters.")
363
return(NULL)
364
}
365
}
366
return(fportfolio)
367
}
368
369
#' version 2 generate an arbitary number of constrained random portfolios
370
#'
371
#' Generate random portfolios using the 'sample', 'simplex', or 'grid' method.
372
#' See details.
373
#'
374
#' @details
375
#' Random portfolios can be generate using one of three methods.
376
#' \describe{
377
#' \item{sample: }{The 'sample' method to generate random portfolios is based
378
#' on an idea pioneerd by Pat Burns. This is the most flexible method, but
379
#' also the slowest, and can generate portfolios to satisfy leverage, box,
380
#' group, position limit, and leverage exposure constraints.}
381
#' \item{simplex: }{The 'simplex' method to generate random portfolios is
382
#' based on a paper by W. T. Shaw. The simplex method is useful to generate
383
#' random portfolios with the full investment constraint, where the sum of the
384
#' weights is equal to 1, and min box constraints. Values for \code{min_sum}
385
#' and \code{max_sum} of the leverage constraint will be ignored, the sum of
386
#' weights will equal 1. All other constraints such as group and position
387
#' limit constraints will be handled by elimination. If the constraints are
388
#' very restrictive, this may result in very few feasible portfolios remaining.}
389
#' \item{grid: }{The 'grid' method to generate random portfolios is based on
390
#' the \code{gridSearch} function in package 'NMOF'. The grid search method
391
#' only satisfies the \code{min} and \code{max} box constraints. The
392
#' \code{min_sum} and \code{max_sum} leverage constraints will likely be
393
#' violated and the weights in the random portfolios should be normalized.
394
#' Normalization may cause the box constraints to be violated and will be
395
#' penalized in \code{constrained_objective}.}
396
#' }
397
#'
398
#' The constraint types checked are leverage, box, group, position limit, and
399
#' leverage exposure. Any
400
#' portfolio that does not satisfy all these constraints will be eliminated. This
401
#' function is particularly sensitive to \code{min_sum} and \code{max_sum}
402
#' leverage constraints. For the sample method, there should be some
403
#' "wiggle room" between \code{min_sum} and \code{max_sum} in order to generate
404
#' a sufficient number of feasible portfolios. For example, \code{min_sum=0.99}
405
#' and \code{max_sum=1.01} is recommended instead of \code{min_sum=1}
406
#' and \code{max_sum=1}. If \code{min_sum=1} and \code{max_sum=1}, the number of
407
#' feasible portfolios may be 1/3 or less depending on the other constraints.
408
#'
409
#'
410
#' @param portfolio an object of class 'portfolio' specifying the constraints for the optimization, see \code{\link{portfolio.spec}}
411
#' @param permutations integer: number of unique constrained random portfolios to generate
412
#' @param \dots any other passthru parameters
413
#' @param rp_method method to generate random portfolios. Currently "sample", "simplex", or "grid". See Details.
414
#' @param eliminate TRUE/FALSE, eliminate portfolios that do not satisfy constraints
415
#' @return matrix of random portfolio weights
416
#' @seealso \code{\link{portfolio.spec}},
417
#' \code{\link{objective}},
418
#' \code{\link{rp_sample}},
419
#' \code{\link{rp_simplex}},
420
#' \code{\link{rp_grid}}
421
#' @author Peter Carl, Brian G. Peterson, Ross Bennett
422
#' @aliases random_portfolios random_portfolios_v2
423
#' @rdname random_portfolios
424
#' @export random_portfolios
425
#' @export random_portfolios_v2
426
random_portfolios <- random_portfolios_v2 <- function( portfolio, permutations=100, rp_method="sample", eliminate=TRUE, ...){
427
if(hasArg(fev)) fev=match.call(expand.dots=TRUE)$fev else fev=0:5
428
if(hasArg(normalize)) normalize=match.call(expand.dots=TRUE)$normalize else normalize=TRUE
429
switch(rp_method,
430
sample = {rp <- rp_sample(portfolio, permutations)
431
},
432
simplex = {rp <- rp_simplex(portfolio, permutations, fev)
433
},
434
grid = {rp <- rp_grid(portfolio, permutations, normalize)
435
}
436
)
437
if(eliminate){
438
# eliminate portfolios that do not satisfy constraints
439
check <- vector("numeric", nrow(rp))
440
for(i in 1:nrow(rp)){
441
check[i] <- check_constraints(weights=rp[i,], portfolio=portfolio)
442
}
443
# We probably don't need or want to do this part in parallel. It could
444
# also interfere with optimize.portfolio.parallel since this function
445
# will likely be called. Not sure how foreach handles nested loops
446
# in parallel so it is best to avoid that altogether.
447
#stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly = TRUE))
448
#check <- foreach(i=1:nrow(rp), .combine=c) %dopar% {
449
# # check_constraint returns TRUE if all constraints are satisfied
450
# check_constraints(weights=rp[i,], portfolio=portfolio)
451
#}
452
rp <- rp[which(check==TRUE),]
453
}
454
return(rp)
455
}
456
457
#' Generate random portfolios using the sample method
458
#'
459
#' This function generates random portfolios based on an idea by Pat Burns.
460
#'
461
#' @details
462
#' The 'sample' method to generate random portfolios is based
463
#' on an idea pioneerd by Pat Burns. This is the most flexible method, but also
464
#' the slowest, and can generate portfolios to satisfy leverage, box, group,
465
#' and position limit constraints.
466
#' @param portfolio an object of type "portfolio" specifying the constraints for the optimization, see \code{\link{portfolio.spec}}
467
#' @param permutations integer: number of unique constrained random portfolios to generate
468
#' @param max_permutations integer: maximum number of iterations to try for a valid portfolio, default 200
469
#' @return a matrix of random portfolio weights
470
#' @export
471
rp_sample <- function(portfolio, permutations, max_permutations=200){
472
# this function generates a series of portfolios that are a "random walk" from the current portfolio
473
seed <- portfolio$assets
474
result <- matrix(nrow=permutations, ncol=length(seed))
475
result[1,] <- seed
476
result[2,] <- rep(1/length(seed),length(seed))
477
# rownames(result)[1]<-"seed.portfolio"
478
# rownames(result)[2]<-"equal.weight"
479
for(i in 3:permutations) {
480
#result[i,] <- as.matrix(randomize_portfolio_v2(portfolio=portfolio, ...))
481
result[i,] <- randomize_portfolio_v2(portfolio=portfolio, max_permutations=max_permutations)
482
}
483
result <- unique(result)
484
# i <- nrow(result)
485
# result <- rbind(result, matrix(nrow=(permutations-i), ncol=length(seed)))
486
colnames(result) <- names(seed)
487
return(result)
488
}
489
490
#' Generate random portfolios using the simplex method
491
#'
492
#' This function generates random portfolios based on the method outlined in the
493
#' Shaw paper. Need to add reference.
494
#'
495
#' @details
496
#' The simplex method is useful to generate random portfolios with the full
497
#' investment constraint where the sum of the weights is equal to 1 and min
498
#' box constraints with no upper bound on max constraints. Values for min_sum
499
#' and max_sum will be ignored, the sum of weights will equal 1. All other
500
#' constraints such as group and position limit constraints will be handled by
501
#' elimination. If the constraints are very restrictive, this may result in
502
#' very few feasible portfolios remaining.
503
#'
504
#' The random portfolios are created by first generating a set of uniform
505
#' random numbers.
506
#' \deqn{U \sim [0, 1]}
507
#' The portfolio weights are then transformed to satisfy the min of the
508
#' box constraints.
509
#' \deqn{w_{i} = min_{i} + (1 - \sum_{j=1}^{N} min_{j}) \frac{log(U_{i}^{q}}{\sum_{k=1}^{N}log(U_{k}^{q}}}
510
#'
511
#' \code{fev} controls the Face-Edge-Vertex (FEV) biasing where \deqn{q=2^{fev}}
512
#' As \code{q} approaches infinity, the set of weights will be concentrated in a
513
#' single asset. To sample the interior and exterior, \code{fev} can be passed
514
#' in as a vector. The number of portfolios, \code{permutations}, and the
515
#' length of \code{fev} affect how the random portfolios are generated. For
516
#' example, if \code{permutations=10000} and \code{fev=0:4}, 2000 portfolios will
517
#' be generated for each value of \code{fev}.
518
#'
519
#' @param portfolio an object of class 'portfolio' specifying the constraints for the optimization, see \code{\link{portfolio.spec}}
520
#' @param permutations integer: number of unique constrained random portfolios to generate
521
#' @param fev scalar or vector for FEV biasing
522
#' @return a matrix of random portfolio weights
523
#' @export
524
rp_simplex <- function(portfolio, permutations, fev=0:5){
525
# get the assets from the portfolio
526
assets <- portfolio$assets
527
nassets <- length(assets)
528
529
# get the constraints
530
# the simplex method for generating random portfolios requires that the sum of weights is equal to 1
531
# ignore the min_sum and max_sum constraints
532
constraints <- get_constraints(portfolio)
533
L <- constraints$min
534
535
# number of portfolios for each fev to generate
536
k <- ceiling(permutations / length(fev))
537
538
# generate uniform[0, 1] random numbers
539
U <- runif(n=k*nassets, 0, 1)
540
Umat <- matrix(data=U, nrow=k, ncol=nassets)
541
542
# do the transformation to the set of weights to satisfy lower bounds
543
stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly = TRUE))
544
j <- 1
545
i <- 1
546
out <- foreach::foreach(j = 1:length(fev), .combine=c) %:% foreach::foreach(i=1:nrow(Umat)) %dopar% {
547
q <- 2^fev[j]
548
tmp <- L + (1 - sum(L)) * log(Umat[i,])^q / sum(log(Umat[i,])^q)
549
tmp
550
}
551
# the foreach loop returns a list of each random portfolio
552
out <- do.call(rbind, out)
553
return(out)
554
}
555
556
#' Generate random portfolios based on grid search method
557
#'
558
#' This function generates random portfolios based on the \code{gridSearch}
559
#' function from the 'NMOF' package.
560
#'
561
#' @details
562
#' The number of levels is calculated based on permutations and number of assets.
563
#' The number of levels must be an integer and may not result in the exact number
564
#' of permutations. We round up to the nearest integer for the levels so the
565
#' number of portfolios generated will be greater than or equal to permutations.
566
#'
567
#' The grid search method only satisfies the \code{min} and \code{max} box
568
#' constraints. The \code{min_sum} and \code{max_sum} leverage constraints will
569
#' likely be violated and the weights in the random portfolios should be
570
#' normalized. Normalization may cause the box constraints to be violated and
571
#' will be penalized in \code{constrained_objective}.
572
#'
573
#' @param portfolio an object of class 'portfolio' specifying the constraints for the optimization, see \code{\link{portfolio.spec}}
574
#' @param permutations integer: number of unique constrained random portfolios to generate
575
#' @param normalize TRUE/FALSE to normalize the weghts to satisfy min_sum or max_sum
576
#' @return matrix of random portfolio weights
577
#' @export
578
rp_grid <- function(portfolio, permutations=2000, normalize=TRUE){
579
580
# get the constraints from the portfolio
581
constraints <- get_constraints(portfolio)
582
583
# box constraints to generate the grid
584
min <- constraints$min
585
max <- constraints$max
586
587
# number of parameters and length.out levels to generate
588
npar <- length(min)
589
n <- ceiling(exp(log(permutations) / npar))
590
591
levels <- vector("list", length = length(min))
592
for (i in seq_len(npar)){
593
levels[[i]] <- seq(min[[i]], max[[i]], length.out = max(n, 2L))
594
}
595
np <- length(levels)
596
res <- vector("list", np)
597
rep.fac <- 1L
598
nl <- sapply(levels, length)
599
nlp <- prod(nl)
600
601
# create the grid
602
for (i in seq_len(np)) {
603
x <- levels[[i]]
604
nx <- length(x)
605
nlp <- nlp/nx
606
res[[i]] <- x[rep.int(rep.int(seq_len(nx), rep.int(rep.fac, nx)), nlp)]
607
rep.fac <- rep.fac * nx
608
}
609
610
# create the random portfolios from the grid
611
nlp <- prod(nl)
612
lstLevels <- vector("list", length = nlp)
613
for (r in seq_len(nlp)) {
614
lstLevels[[r]] <- sapply(res, `[[`, r)
615
}
616
# lstLevels is a list of random portfolios, rbind into a matrix
617
rp <- do.call(rbind, lstLevels)
618
619
# min_sum and max_sum will likely be violated
620
# Normalization will likely cause min and max to be violated. This can be
621
# handled by the penalty in constrained_objective.
622
if(normalize){
623
normalize_weights <- function(weights){
624
# normalize results if necessary
625
if(!is.null(constraints$min_sum) | !is.null(constraints$max_sum)){
626
# the user has passed in either min_sum or max_sum constraints for the portfolio, or both.
627
# we'll normalize the weights passed in to whichever boundary condition has been violated
628
# NOTE: this means that the weights produced by a numeric optimization algorithm like DEoptim
629
# might violate your constraints, so you'd need to renormalize them after optimizing
630
# we'll create functions for that so the user is less likely to mess it up.
631
632
# NOTE: need to normalize in the optimization wrapper too before we return, since we've normalized in here
633
# In Kris' original function, this was manifested as a full investment constraint
634
if(!is.null(constraints$max_sum) & constraints$max_sum != Inf ) {
635
max_sum=constraints$max_sum
636
if(sum(weights)>max_sum) { weights<-(max_sum/sum(weights))*weights } # normalize to max_sum
637
}
638
639
if(!is.null(constraints$min_sum) & constraints$min_sum != -Inf ) {
640
min_sum=constraints$min_sum
641
if(sum(weights)<min_sum) { weights<-(min_sum/sum(weights))*weights } # normalize to min_sum
642
}
643
644
} # end min_sum and max_sum normalization
645
return(weights)
646
}
647
648
stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly = TRUE))
649
out <- foreach::foreach(i=1:nrow(rp)) %dopar% {
650
tmp <- normalize_weights(weights=rp[i,])
651
tmp
652
}
653
out <- do.call(rbind, out)
654
out <- na.omit(out)
655
}
656
if(normalize) return(out) else return(rp)
657
}
658
659
# function to generate a set of random portfolios for each portfolio and return the superset
660
# this is primarily for use in optimize.portfolio.rebalancing
661
rp.regime.portfolios <- function(regime, permutations=100, rp_method="sample", eliminate=TRUE, ...){
662
if(!inherits(regime, "regime.portfolios")) stop("regime must be an object of class 'regime.portfolios'")
663
portf <- regime$portfolio.list
664
nportf <- length(portf)
665
rp.list <- vector("list", nportf)
666
for(i in 1:nportf){
667
rp.list[[i]] <- random_portfolios(portf[[i]], permutations=permutations, rp_method=rp_method, eliminate=eliminate, ...=...)
668
}
669
# rbind the list of matrices together and remove any duplicates
670
out <- unique(do.call("rbind", rp.list))
671
return(out)
672
}
673
674
# EXAMPLE: start_t<- Sys.time(); x=random_walk_portfolios(rep(1/5,5), generatesequence(min=0.01, max=0.30, by=0.01), max_permutations=500, permutations=5000, min_sum=.99, max_sum=1.01); end_t<-Sys.time(); end_t-start_t;
675
# > nrow(unique(x))
676
# [1] 4906
677
# > which(rowSums(x)<.99 | rowSums(x)>1.01)
678
# integer(0)
679
680
# start_t <- Sys.time(); s<-foreach(seed=iter(weights, by='row'),.combine=rbind) %dopar% random_walk_portfolios(seed,xseq,permutations=10000); end_t <- Sys.time(); save.image(); start_t-end_t;
681
682
# TODO: write a function for random trades that only makes n trades and increases/decreases other elements to compensate.
683
684