Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/constraints.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
#' @rdname constraint
14
#' @export
15
constraint_v1 <- function(assets=NULL, ... ,min,max,min_mult,max_mult,min_sum=.99,max_sum=1.01,weight_seq=NULL)
16
{ # based on GPL R-Forge pkg roi by Stefan Thuessel,Kurt Hornik,David Meyer
17
if (hasArg(min) & hasArg(max)) {
18
if (is.null(assets) & (!length(min)>1) & (!length(max)>1)) {
19
stop("You must either specify the assets or pass a vector for both min and max")
20
}
21
}
22
23
if(!is.null(assets)){
24
# TODO FIXME this doesn't work quite right on matrix of assets
25
if(is.numeric(assets)){
26
if (length(assets) == 1) {
27
nassets=assets
28
#we passed in a number of assets, so we need to create the vector
29
message("assuming equal weighted initial portfolio")
30
assets<-rep(1/nassets,nassets)
31
} else {
32
nassets = length(assets)
33
}
34
# and now we may need to name them
35
if (is.null(names(assets))) {
36
for(i in 1:length(assets)){
37
names(assets)[i]<-paste("Asset",i,sep=".")
38
}
39
}
40
}
41
if(is.character(assets)){
42
nassets=length(assets)
43
assetnames=assets
44
message("assuming equal weighted initial portfolio")
45
assets<-rep(1/nassets,nassets)
46
names(assets)<-assetnames # set names, so that other code can access it,
47
# and doesn't have to know about the character vector
48
# print(assets)
49
}
50
# if assets is a named vector, we'll assume it is current weights
51
}
52
53
if(hasArg(min) | hasArg(max)) {
54
if (length(min)>1 & length(max)>1){
55
if (length(min)!=length(max)) { stop("length of min and max must be the same") }
56
}
57
58
if (length(min)==1) {
59
message("min not passed in as vector, replicating min to length of length(assets)")
60
min <- rep(min,nassets)
61
}
62
if (length(min)!=nassets) stop(paste("length of min must be equal to 1 or the number of assets",nassets))
63
64
if (length(max)==1) {
65
message("max not passed in as vector, replicating max to length of length(assets)")
66
max <- rep(max,nassets)
67
}
68
if (length(max)!=nassets) stop(paste("length of max must be equal to 1 or the number of assets",nassets))
69
70
} else {
71
message("no min or max passed in, assuming 0 and 1")
72
min <- rep(0,nassets)
73
max <- rep(1,nassets)
74
}
75
76
names(min)<-names(assets)
77
names(max)<-names(assets)
78
79
if(hasArg(min_mult) | hasArg(max_mult)) {
80
if (length(min_mult)>1 & length(max_mult)>1){
81
if (length(min_mult)!=length(max_mult) ) { stop("length of min_mult and max_mult must be the same") }
82
} else {
83
message("min_mult and max_mult not passed in as vectors, replicating min_mult and max_mult to length of assets vector")
84
min_mult = rep(min_mult,nassets)
85
max_mult = rep(max_mult,nassets)
86
}
87
}
88
89
if(!hasArg(min_sum) | !hasArg(max_sum)) {
90
min_sum = NULL
91
max_sum = NULL
92
}
93
94
if (!is.null(names(assets))) {
95
assetnames<-names(assets)
96
if(hasArg(min)){
97
names(min)<-assetnames
98
names(max)<-assetnames
99
} else {
100
min = NULL
101
max = NULL
102
}
103
if(hasArg(min_mult)){
104
names(min_mult)<-assetnames
105
names(max_mult)<-assetnames
106
} else {
107
min_mult = NULL
108
max_mult = NULL
109
}
110
}
111
##now adjust min and max to account for min_mult and max_mult from initial
112
if(!is.null(min_mult) & !is.null(min)) {
113
tmp_min <- assets*min_mult
114
#TODO FIXME this creates a list, and it should create a named vector or matrix
115
min[which(tmp_min>min)]<-tmp_min[which(tmp_min>min)]
116
}
117
if(!is.null(max_mult) & !is.null(max)) {
118
tmp_max <- assets*max_mult
119
#TODO FIXME this creates a list, and it should create a named vector or matrix
120
max[which(tmp_max<max)]<-tmp_max[which(tmp_max<max)]
121
}
122
123
## now structure and return
124
return(structure(
125
list(
126
assets = assets,
127
min = min,
128
max = max,
129
min_mult = min_mult,
130
max_mult = max_mult,
131
min_sum = min_sum,
132
max_sum = max_sum,
133
weight_seq = weight_seq,
134
objectives = list(),
135
call = match.call()
136
),
137
class=c("v1_constraint","constraint")
138
))
139
}
140
141
142
#' constructors for class constraint
143
#'
144
#' See main documentation entry in \code{\link{add.constraint}}.
145
#'
146
#' This includes the deprecated constructor for the \code{v1_constraint} object for backwards compatibility.
147
#'
148
#' @param assets number of assets, or optionally a named vector of assets specifying initial weights
149
#' @param min numeric or named vector specifying minimum weight box constraints
150
#' @param max numeric or named vector specifying minimum weight box constraints
151
#' @param min_mult numeric or named vector specifying minimum multiplier box constraint from initial weight in \code{assets}
152
#' @param max_mult numeric or named vector specifying maximum multiplier box constraint from initial weight in \code{assets}
153
#' @param min_sum minimum sum of all asset weights, default .99
154
#' @param max_sum maximum sum of all asset weights, default 1.01
155
#' @param weight_seq seed sequence of weights, see \code{\link{generatesequence}}
156
#' @param type character type of the constraint to add or update
157
#' @param enabled TRUE/FALSE to enabled the constraint
158
#' @param \dots any other passthru parameters
159
#' @param constrclass name of class for the constraint
160
#' @author Peter Carl, Brian G. Peterson, Ross Bennett
161
#' @seealso \code{\link{add.constraint}}
162
#' @aliases constraint constraint_v2
163
#' @rdname constraint
164
#' @export constraint
165
#' @export constraint_v2
166
constraint <- constraint_v2 <- function(type, enabled=TRUE, ..., constrclass="v2_constraint"){
167
if(!hasArg(type)) stop("you must specify a constraint type")
168
if (hasArg(type)) if(is.null(type)) stop("you must specify a constraint type")
169
170
## now structure and return
171
return(structure( c(list(type = type,
172
enabled=enabled),
173
list(...)),
174
class=c(constrclass, "constraint")
175
) # end structure
176
)
177
}
178
179
#' General interface for adding and/or updating optimization constraints.
180
#'
181
#' This is the main function for adding and/or updating constraints to the \code{\link{portfolio.spec}} object.
182
#'
183
#' The following constraint types may be specified:
184
#' \describe{
185
#' \item{\code{weight_sum}, \code{weight}, \code{leverage}}{ Specify constraint on the sum of the weights, see \code{\link{weight_sum_constraint}} }
186
#' \item{\code{full_investment}}{ Special case to set \code{min_sum=1} and \code{max_sum=1} of weight sum constraints }
187
#' \item{\code{dollar_neutral}, \code{active}}{ Special case to set \code{min_sum=0} and \code{max_sum=0} of weight sum constraints }
188
#' \item{\code{box}}{ box constraints for the individual asset weights, see \code{\link{box_constraint}} }
189
#' \item{\code{long_only}}{ Special case to set \code{min=0} and \code{max=1} of box constraints }
190
#' \item{\code{group}}{ specify the sum of weights within groups and the number of assets with non-zero weights in groups, see \code{\link{group_constraint}} }
191
#' \item{\code{turnover}}{ Specify a constraint for target turnover. Turnover is calculated from a set of initial weights, see \code{\link{turnover_constraint}} }
192
#' \item{\code{diversification}}{ target diversification of a set of weights, see \code{\link{diversification_constraint}} }
193
#' \item{\code{position_limit}}{ Specify the number of non-zero, long, and/or short positions, see \code{\link{position_limit_constraint}} }
194
#' \item{\code{return}}{ Specify the target mean return, see \code{\link{return_constraint}}}
195
#' \item{\code{factor_exposure}}{ Specify risk factor exposures, see \code{\link{factor_exposure_constraint}}}
196
#' \item{\code{leverage_exposure}}{ Specify a maximum leverage exposure, see \code{\link{leverage_exposure_constraint}}}
197
#' }
198
#'
199
#' @param portfolio an object of class 'portfolio' to add the constraint to, specifying the constraints for the optimization, see \code{\link{portfolio.spec}}
200
#' @param type character type of the constraint to add or update, currently 'weight_sum' (also 'leverage' or 'weight'), 'box', 'group', 'turnover', 'diversification', 'position_limit', 'return', 'factor_exposure', or 'leverage_exposure'
201
#' @param enabled TRUE/FALSE. The default is enabled=TRUE.
202
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
203
#' @param \dots any other passthru parameters to specify constraints
204
#' @param indexnum if you are updating a specific constraint, the index number in the $constraints list to update
205
#' @author Ross Bennett
206
#' @seealso
207
#' \code{\link{portfolio.spec}}
208
#' \code{\link{weight_sum_constraint}},
209
#' \code{\link{box_constraint}},
210
#' \code{\link{group_constraint}},
211
#' \code{\link{turnover_constraint}},
212
#' \code{\link{diversification_constraint}},
213
#' \code{\link{position_limit_constraint}},
214
#' \code{\link{return_constraint}},
215
#' \code{\link{factor_exposure_constraint}},
216
#' \code{\link{leverage_exposure_constraint}}
217
#' @examples
218
#' data(edhec)
219
#' returns <- edhec[, 1:4]
220
#' fund.names <- colnames(returns)
221
#' pspec <- portfolio.spec(assets=fund.names)
222
#'
223
#' # Add the full investment constraint that specifies the weights must sum to 1.
224
#' pspec <- add.constraint(portfolio=pspec, type="weight_sum", min_sum=1, max_sum=1)
225
#'
226
#' # The full investment constraint can also be specified with type="full_investment"
227
#' pspec <- add.constraint(portfolio=pspec, type="full_investment")
228
#'
229
#' # Another common constraint is that portfolio weights sum to 0.
230
#' pspec <- add.constraint(portfolio=pspec, type="weight_sum", min_sum=0, max_sum=0)
231
#' pspec <- add.constraint(portfolio=pspec, type="dollar_neutral")
232
#' pspec <- add.constraint(portfolio=pspec, type="active")
233
#'
234
#' # Add box constraints
235
#' pspec <- add.constraint(portfolio=pspec, type="box", min=0.05, max=0.4)
236
#'
237
#' # min and max can also be specified per asset
238
#' pspec <- add.constraint(portfolio=pspec,
239
#' type="box",
240
#' min=c(0.05, 0, 0.08, 0.1),
241
#' max=c(0.4, 0.3, 0.7, 0.55))
242
#'
243
#' # A special case of box constraints is long only where min=0 and max=1
244
#' # The default action is long only if min and max are not specified
245
#' pspec <- add.constraint(portfolio=pspec, type="box")
246
#' pspec <- add.constraint(portfolio=pspec, type="long_only")
247
#'
248
#' # Add group constraints
249
#' pspec <- add.constraint(portfolio=pspec,
250
#' type="group",
251
#' groups=list(c(1, 2, 1), 4),
252
#' group_min=c(0.1, 0.15),
253
#' group_max=c(0.85, 0.55),
254
#' group_labels=c("GroupA", "GroupB"),
255
#' group_pos=c(2, 1))
256
#'
257
#' # Add position limit constraint such that we have a maximum number
258
#' # of three assets with non-zero weights.
259
#' pspec <- add.constraint(portfolio=pspec, type="position_limit", max_pos=3)
260
#'
261
#' # Add diversification constraint
262
#' pspec <- add.constraint(portfolio=pspec, type="diversification", div_target=0.7)
263
#'
264
#' # Add turnover constraint
265
#' pspec <- add.constraint(portfolio=pspec, type="turnover", turnover_target=0.2)
266
#'
267
#' # Add target mean return constraint
268
#' pspec <- add.constraint(portfolio=pspec, type="return", return_target=0.007)
269
#'
270
#' # Example using the indexnum argument
271
#' portf <- portfolio.spec(assets=fund.names)
272
#' portf <- add.constraint(portf, type="full_investment")
273
#' portf <- add.constraint(portf, type="long_only")
274
#'
275
#' # indexnum corresponds to the index number of the constraint
276
#' # The full_investment constraint was the first constraint added and has
277
#' # indexnum=1
278
#' portf$constraints[[1]]
279
#'
280
#' # View the constraint with indexnum=2
281
#' portf$constraints[[2]]
282
#'
283
#' # Update the constraint to relax the sum of weights constraint
284
#' portf <- add.constraint(portf, type="weight_sum",
285
#' min_sum=0.99, max_sum=1.01,
286
#' indexnum=1)
287
#'
288
#' # Update the constraint to modify the box constraint
289
#' portf <- add.constraint(portf, type="box",
290
#' min=0.1, max=0.8,
291
#' indexnum=2)
292
#' @export
293
add.constraint <- function(portfolio, type, enabled=TRUE, message=FALSE, ..., indexnum=NULL){
294
# Check to make sure that the portfolio passed in is a portfolio object
295
if (!is.portfolio(portfolio)) {stop("portfolio passed in is not of class portfolio")}
296
297
# Check to make sure a type is passed in as an argument
298
if (!hasArg(type)) stop("you must supply a type of constraints to create")
299
300
assets <- portfolio$assets
301
tmp_constraint = NULL
302
303
# Currently supports box and group constraints. Will add more later.
304
switch(type,
305
# Box constraints
306
box = {tmp_constraint <- box_constraint(assets=assets,
307
type=type,
308
enabled=enabled,
309
message=message,
310
...=...)
311
},
312
# special case of box constraints for long_only
313
long_only = {tmp_constraint <- box_constraint(assets=assets,
314
type=type,
315
enabled=enabled,
316
message=message,
317
min=0,
318
max=1,
319
...=...)
320
},
321
# Group constraints
322
group = {tmp_constraint <- group_constraint(assets=assets,
323
type=type,
324
enabled=enabled,
325
message=message,
326
...=...)
327
},
328
# Sum of weights constraints
329
weight=, leverage=, weight_sum = {tmp_constraint <- weight_sum_constraint(type=type,
330
enabled=enabled,
331
message=message,
332
...=...)
333
},
334
# Special case of weight_sum constraint for full investment
335
full_investment = {tmp_constraint <- weight_sum_constraint(type=type,
336
min_sum=1,
337
max_sum=1,
338
enabled=enabled,
339
message=message,
340
...=...)
341
},
342
# Special case of weight_sum constraint for dollar neutral or active
343
dollar_neutral=, active= {tmp_constraint <- weight_sum_constraint(type=type,
344
min_sum=0,
345
max_sum=0,
346
enabled=enabled,
347
message=message,
348
...=...)
349
},
350
# Turnover constraint
351
turnover = {tmp_constraint <- turnover_constraint(type=type,
352
enabled=enabled,
353
message=message,
354
...=...)
355
},
356
# Diversification constraint
357
diversification = {tmp_constraint <- diversification_constraint(type=type,
358
enabled=enabled,
359
message=message,
360
...=...)
361
},
362
# Position limit constraint
363
position_limit = {tmp_constraint <- position_limit_constraint(assets=assets,
364
type=type,
365
enabled=enabled,
366
message=message,
367
...=...)
368
},
369
# Return constraint
370
return = {tmp_constraint <- return_constraint(type=type,
371
enabled=enabled,
372
message=message,
373
...=...)
374
},
375
# factor exposure constraint
376
factor_exposure=, factor_exposures = {tmp_constraint <- factor_exposure_constraint(assets=assets,
377
type=type,
378
enabled=enabled,
379
message=message,
380
...=...)
381
},
382
# transaction cost constraint
383
transaction=, transaction_cost = {tmp_constraint <- transaction_cost_constraint(assets=assets,
384
type=type,
385
enabled=enabled,
386
message=message,
387
...=...)
388
},
389
# leverage exposure constraint
390
leverage_exposure = {tmp_constraint <- leverage_exposure_constraint( type=type,
391
enabled=enabled,
392
message=message,
393
...=...)
394
},
395
filter = {tmp_constraint <- filter_constraint(assets=assets,
396
type=type,
397
enabled=enabled,
398
message=message,
399
...=...)
400
},
401
# Do nothing and return the portfolio object if type is NULL
402
null = {return(portfolio)}
403
)
404
if(is.constraint(tmp_constraint)) {
405
if(!hasArg(indexnum) | (hasArg(indexnum) & is.null(indexnum))) indexnum <- length(portfolio$constraints)+1
406
tmp_constraint$call <- match.call()
407
portfolio$constraints[[indexnum]] <- tmp_constraint
408
}
409
return(portfolio)
410
}
411
412
#' constructor for box_constraint.
413
#'
414
#' Box constraints specify the upper and lower bounds on the weights of the assets.
415
#' This function is called by add.constraint when type="box" is specified. See \code{\link{add.constraint}}.
416
#'
417
#' @param type character type of the constraint
418
#' @param assets number of assets, or optionally a named vector of assets specifying initial weights
419
#' @param min numeric or named vector specifying minimum weight box constraints
420
#' @param max numeric or named vector specifying minimum weight box constraints
421
#' @param min_mult numeric or named vector specifying minimum multiplier box constraint from initial weight in \code{assets}
422
#' @param max_mult numeric or named vector specifying maximum multiplier box constraint from initial weight in \code{assets}
423
#' @param enabled TRUE/FALSE
424
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
425
#' @param \dots any other passthru parameters to specify box constraints
426
#' @return an object of class 'box_constraint'
427
#' @author Ross Bennett
428
#' @seealso \code{\link{add.constraint}}
429
#' @examples
430
#' data(edhec)
431
#' ret <- edhec[, 1:4]
432
#'
433
#' pspec <- portfolio.spec(assets=colnames(ret))
434
#'
435
#' # defaults to min=0 and max=1
436
#' pspec <- add.constraint(pspec, type="box")
437
#'
438
#' # specify box constraints as a scalar
439
#' pspec <- add.constraint(pspec, type="box", min=0.05, max=0.45)
440
#'
441
#' # specify box constraints per asset
442
#' pspec <- add.constraint(pspec,
443
#' type="box",
444
#' min=c(0.05, 0.10, 0.08, 0.06),
445
#' max=c(0.45, 0.55, 0.35, 0.65))
446
#'
447
#' @export
448
box_constraint <- function(type="box", assets, min, max, min_mult, max_mult, enabled=TRUE, message=FALSE, ...){
449
# Based on the constraint function for object of class constraint_v1 that
450
# included specifying box constraints.
451
452
# Get the length of the assets vector
453
nassets <- length(assets)
454
455
if(type=="long_only"){
456
min <- rep(0, nassets)
457
max <- rep(1, nassets)
458
}
459
460
# Check that the length of min and max are the same
461
if(hasArg(min) | hasArg(max)) {
462
if (length(min) > 1 & length(max) > 1){
463
if (length(min) != length(max)) { stop("length of min and max must be the same") }
464
}
465
466
# If the user passes in a scalar for min, then create a min vector
467
if (length(min) == 1) {
468
if(message) message("min not passed in as vector, replicating min to length of length(assets)")
469
min <- rep(min, nassets)
470
}
471
if (length(min) != nassets) stop(paste("length of min must be equal to 1 or the number of assets:", nassets))
472
473
# If the user passes in a scalar for max, then create a max vector
474
if (length(max) == 1) {
475
if(message) message("max not passed in as vector, replicating max to length of length(assets)")
476
max <- rep(max, nassets)
477
}
478
if (length(max) != nassets) stop(paste("length of max must be equal to 1 or the number of assets:", nassets))
479
480
} else {
481
# Default to min=0 and max=1 if min or max are not passed in
482
if(message) message("no min or max passed in, assuming 0 and 1")
483
min <- rep(0, nassets)
484
max <- rep(1, nassets)
485
}
486
487
# Set the names of the min and max vector to the names of the assets vector
488
names(min) <- names(assets)
489
names(max) <- names(assets)
490
491
# Checks for min_mult and max_mult
492
if(hasArg(min_mult) | hasArg(max_mult)) {
493
if (length(min_mult) > 1 & length(max_mult) > 1){
494
if (length(min_mult) != length(max_mult) ) { stop("length of min_mult and max_mult must be the same") }
495
} else {
496
if(message) message("min_mult and max_mult not passed in as vectors, replicating min_mult and max_mult to length of assets vector")
497
min_mult = rep(min_mult, nassets)
498
max_mult = rep(max_mult, nassets)
499
}
500
}
501
502
if (!is.null(names(assets))) {
503
assetnames <- names(assets)
504
if(hasArg(min)){
505
names(min) <- assetnames
506
names(max) <- assetnames
507
} else {
508
min = NULL
509
max = NULL
510
}
511
if(hasArg(min_mult)){
512
names(min_mult) <- assetnames
513
names(max_mult) <- assetnames
514
} else {
515
min_mult = NULL
516
max_mult = NULL
517
}
518
}
519
520
# now adjust min and max to account for min_mult and max_mult from initial
521
if(!is.null(min_mult) & !is.null(min)) {
522
tmp_min <- assets * min_mult
523
#TODO FIXME this creates a list, and it should create a named vector or matrix
524
min[which(tmp_min > min)] <- tmp_min[which(tmp_min > min)]
525
}
526
if(!is.null(max_mult) & !is.null(max)) {
527
tmp_max <- assets * max_mult
528
#TODO FIXME this creates a list, and it should create a named vector or matrix
529
max[which(tmp_max < max)] <- tmp_max[which(tmp_max < max)]
530
}
531
532
Constraint <- constraint_v2(type=type, enabled=enabled, constrclass="box_constraint", ...)
533
Constraint$min <- min
534
Constraint$max <- max
535
return(Constraint)
536
}
537
538
#' constructor for group_constraint
539
#'
540
#' Group constraints specify the grouping of the assets, weights of the groups, and number of postions (i.e. non-zero weights) iof the groups.
541
#' This function is called by add.constraint when type="group" is specified. see \code{\link{add.constraint}}
542
#'
543
#' @param type character type of the constraint
544
#' @param assets number of assets, or optionally a named vector of assets specifying initial weights
545
#' @param groups list of vectors specifying the groups of the assets
546
#' @param group_labels character vector to label the groups (e.g. size, asset class, style, etc.)
547
#' @param group_min numeric or vector specifying minimum weight group constraints
548
#' @param group_max numeric or vector specifying minimum weight group constraints
549
#' @param group_pos vector specifying the number of non-zero weights per group
550
#' @param enabled TRUE/FALSE
551
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
552
#' @param \dots any other passthru parameters to specify group constraints
553
#' @return an object of class 'group_constraint'
554
#' @author Ross Bennett
555
#' @seealso \code{\link{add.constraint}}
556
#' @examples
557
#' data(edhec)
558
#' ret <- edhec[, 1:4]
559
#'
560
#' pspec <- portfolio.spec(assets=colnames(ret))
561
#'
562
#' # Assets 1 and 3 are groupA
563
#' # Assets 2 and 4 are groupB
564
#' pspec <- add.constraint(portfolio=pspec,
565
#' type="group",
566
#' groups=list(groupA=c(1, 3),
567
#' groupB=c(2, 4)),
568
#' group_min=c(0.15, 0.25),
569
#' group_max=c(0.65, 0.55))
570
#'
571
#' # 2 levels of grouping (e.g. by sector and geography)
572
#' pspec <- portfolio.spec(assets=5)
573
#' # Assets 1, 3, and 5 are Tech
574
#' # Assets 2 and 4 are Oil
575
#' # Assets 2, 4, and 5 are UK
576
#' # Assets 1 and are are US
577
#' group_list <- list(group1=c(1, 3, 5),
578
#' group2=c(2, 4),
579
#' groupA=c(2, 4, 5),
580
#' groupB=c(1, 3))
581
#'
582
#' pspec <- add.constraint(portfolio=pspec,
583
#' type="group",
584
#' groups=group_list,
585
#' group_min=c(0.15, 0.25, 0.2, 0.1),
586
#' group_max=c(0.65, 0.55, 0.5, 0.4))
587
#'
588
#' @export
589
group_constraint <- function(type="group", assets, groups, group_labels=NULL, group_min, group_max, group_pos=NULL, enabled=TRUE, message=FALSE, ...) {
590
if(!is.list(groups)) stop("groups must be passed in as a list")
591
nassets <- length(assets)
592
ngroups <- length(groups)
593
groupnames <- names(groups)
594
595
# comment out so the user can pass in multiple levels of groups
596
# may want a warning message
597
# count <- sum(sapply(groups, length))
598
# if(count != nassets) {
599
# message("count of assets in groups must be equal to the number of assets")
600
# }
601
602
# Checks for group_min
603
if (length(group_min) == 1) {
604
if(message) message("group_min not passed in as vector, replicating group_min to length of groups")
605
group_min <- rep(group_min, ngroups)
606
}
607
if (length(group_min) != ngroups) stop(paste("length of group_min must be equal to 1 or the length of groups:", ngroups))
608
609
# Checks for group_max
610
if (length(group_max) == 1) {
611
if(message) message("group_max not passed in as vector, replicating group_max to length of groups")
612
group_max <- rep(group_max, ngroups)
613
}
614
if (length(group_max) != ngroups) stop(paste("length of group_max must be equal to 1 or the length of groups:", ngroups))
615
616
# construct the group_label vector if groups is a named list
617
if(!is.null(groupnames)){
618
group_labels <- groupnames
619
}
620
621
# Construct the group_label vector if it is not passed in
622
if(is.null(group_labels) & is.null(groupnames)){
623
group_labels <- paste(rep("group", ngroups), 1:ngroups, sep="")
624
}
625
626
if(length(group_labels) != length(groups)) stop("length of group_labels must be equal to the length of groups")
627
628
# Construct group_pos vector
629
if(!is.null(group_pos)){
630
# Check the length of the group_pos vector
631
if(length(group_pos) != length(groups)) stop("length of group_pos must be equal to the length of groups")
632
# Check for negative values in group_pos
633
if(any(group_pos < 0)) stop("all elements of group_pos must be positive")
634
# Elements of group_pos cannot be greater than count of assets in groups
635
if(any(group_pos > sapply(groups, length))){
636
group_pos <- pmin(group_pos, sapply(groups, length))
637
}
638
}
639
640
Constraint <- constraint_v2(type, enabled=enabled, constrclass="group_constraint", ...)
641
Constraint$groups <- groups
642
Constraint$group_labels <- group_labels
643
Constraint$cLO <- group_min
644
Constraint$cUP <- group_max
645
Constraint$group_pos <- group_pos
646
return(Constraint)
647
}
648
649
#' constructor for weight_sum_constraint
650
#'
651
#' The constraint specifies the upper and lower bound on the sum of the weights.
652
#' This function is called by add.constraint when "weight_sum", "leverage", "full_investment", "dollar_neutral", or "active" is specified as the type. see \code{\link{add.constraint}}
653
#'
654
#' Special cases for the weight_sum constraint are "full_investment" and "dollar_nuetral" or "active"
655
#'
656
#' If \code{type="full_investment"}, \code{min_sum=1} and \code{max_sum=1}
657
#'
658
#' If \code{type="dollar_neutral"} or \code{type="active"}, \code{min_sum=0}, and \code{max_sum=0}
659
#'
660
#' @param type character type of the constraint
661
#' @param min_sum minimum sum of all asset weights, default 0.99
662
#' @param max_sum maximum sum of all asset weights, default 1.01
663
#' @param enabled TRUE/FALSE
664
#' @param \dots any other passthru parameters to specify weight_sum constraints
665
#' @return an object of class 'weight_sum_constraint'
666
#' @author Ross Bennett
667
#' @seealso \code{\link{add.constraint}}
668
#' @examples
669
#' data(edhec)
670
#' ret <- edhec[, 1:4]
671
#'
672
#' pspec <- portfolio.spec(assets=colnames(ret))
673
#'
674
#' # min_sum and max_sum can be specified with type="weight_sum" or type="leverage"
675
#' pspec <- add.constraint(pspec, type="weight_sum", min_sum=1, max_sum=1)
676
#'
677
#' # Specify type="full_investment" to set min_sum=1 and max_sum=1
678
#' pspec <- add.constraint(pspec, type="full_investment")
679
#'
680
#' # Specify type="dollar_neutral" or type="active" to set min_sum=0 and max_sum=0
681
#' pspec <- add.constraint(pspec, type="dollar_neutral")
682
#' pspec <- add.constraint(pspec, type="active")
683
#' @export
684
weight_sum_constraint <- function(type="weight_sum", min_sum=0.99, max_sum=1.01, enabled=TRUE, ...){
685
switch(type,
686
full_investment = {
687
max_sum <- 1
688
min_sum <- 1
689
},
690
dollar_neutral = {
691
max_sum <- 0
692
min_sum <- 0
693
},
694
active = {
695
max_sum <- 0
696
min_sum <- 0
697
}
698
)
699
Constraint <- constraint_v2(type, enabled=enabled, constrclass="weight_sum_constraint", ...)
700
Constraint$min_sum <- min_sum
701
Constraint$max_sum <- max_sum
702
return(Constraint)
703
}
704
705
#' check function for constraints
706
#'
707
#' @param x object to test for type \code{constraint}
708
#' @author Brian G. Peterson
709
#' @export
710
is.constraint <- function( x ) {
711
inherits( x, "constraint" )
712
}
713
714
#' Helper function to get the enabled constraints out of the portfolio object
715
#'
716
#' When the v1_constraint object is instantiated via constraint, the arguments
717
#' min_sum, max_sum, min, and max are either specified by the user or default
718
#' values are assigned. These are required by other functions such as
719
#' \code{optimize.portfolio} and \code{constrained_objective} . This function
720
#' will check that these variables are in the portfolio object in the
721
#' constraints list. We will default to \code{min_sum=1} and \code{max_sum=1}
722
#' if leverage constraints are not specified. We will default to \code{min=-Inf}
723
#' and \code{max=Inf} if box constraints are not specified.
724
#' This function is used at the beginning of optimize.portfolio and other
725
#' functions to extract the constraints from the portfolio object. We Use the
726
#' same naming as the v1_constraint object.
727
#'
728
#' @param portfolio an object of class 'portfolio'
729
#' @return an object of class 'constraint' which is a flattened list of enabled constraints
730
#' @author Ross Bennett
731
#' @seealso \code{\link{portfolio.spec}}
732
get_constraints <- function(portfolio){
733
if(!is.portfolio(portfolio)) stop("portfolio passed in is not of class portfolio")
734
735
if(length(portfolio$constraints) == 0) stop("No constraints passed in")
736
737
out <- list()
738
out$min_sum <- NA
739
out$max_sum <- NA
740
out$min <- NA
741
out$max <- NA
742
743
for(constraint in portfolio$constraints) {
744
if(constraint$enabled){
745
if(inherits(constraint, "weight_sum_constraint")){
746
out$min_sum <- constraint$min_sum
747
out$max_sum <- constraint$max_sum
748
}
749
if(inherits(constraint, "box_constraint")){
750
out$min <- constraint$min
751
out$max <- constraint$max
752
}
753
if(inherits(constraint, "group_constraint")){
754
out$groups <- constraint$groups
755
out$group_labels <- constraint$group_labels
756
out$cLO <- constraint$cLO
757
out$cUP <- constraint$cUP
758
out$group_pos <- constraint$group_pos
759
}
760
if(inherits(constraint, "turnover_constraint")){
761
out$turnover_target <- constraint$turnover_target
762
out$turnover_penalty <- constraint$turnover_penalty
763
out$weight_initial <- constraint$weight_initial
764
}
765
if(inherits(constraint, "diversification_constraint")){
766
out$div_target <- constraint$div_target
767
out$conc_aversion <- constraint$conc_aversion
768
}
769
if(inherits(constraint, "position_limit_constraint")){
770
out$max_pos <- constraint$max_pos
771
out$max_pos_long <- constraint$max_pos_long
772
out$max_pos_short <- constraint$max_pos_short
773
}
774
if(inherits(constraint, "return_constraint")){
775
out$return_target <- constraint$return_target
776
}
777
if(inherits(constraint, "factor_exposure_constraint")){
778
out$B <- constraint$B
779
out$lower <- constraint$lower
780
out$upper <- constraint$upper
781
}
782
if(inherits(constraint, "transaction_cost_constraint")){
783
out$ptc <- constraint$ptc
784
}
785
if(inherits(constraint, "leverage_exposure_constraint")){
786
out$leverage <- constraint$leverage
787
}
788
}
789
}
790
791
# min_sum, max_sum, min, and max are required to be passed in and enabled
792
if(is.na(out$min_sum) | is.na(out$max_sum)) {
793
# return(NULL)
794
# stop("Leverage constraint min_sum and max_sum are not enabled or passed in")
795
# Default to full investment constraint
796
out$min_sum <- 1
797
out$max_sum <- 1
798
}
799
if(length(out$min) == 1 | length(out$max) == 1) {
800
if(is.na(out$min) | is.na(out$max)){
801
# return(NULL)
802
# stop("Box constraints min and max are not enabled or passed in")
803
# Default to min=-Inf and max=Inf for unconstrained weights
804
nassets <- length(portfolio$assets)
805
out$min <- rep(-Inf, nassets)
806
out$max <- rep(Inf, nassets)
807
}
808
}
809
# structure and return class of type constraint
810
return(structure(out, class="constraint"))
811
}
812
813
#' constructor for turnover_constraint
814
#'
815
#' The turnover constraint specifies a target turnover value.
816
#' This function is called by add.constraint when type="turnover" is specified, see \code{\link{add.constraint}}.
817
#' Turnover is calculated from a set of initial weights. Turnover is
818
#' computed as \code{sum(abs(initial_weights - weights)) / N} where \code{N} is
819
#' the number of assets.
820
#'
821
#' Note that with the ROI solvers, turnover constraint is currently only
822
#' supported for the global minimum variance and quadratic utility problems
823
#' with ROI quadprog plugin.
824
#'
825
#' @param type character type of the constraint
826
#' @param turnover_target target turnover value
827
#' @param enabled TRUE/FALSE
828
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
829
#' @param \dots any other passthru parameters to specify box and/or group constraints
830
#' @return an object of class 'turnover_constraint'
831
#' @author Ross Bennett
832
#' @seealso \code{\link{add.constraint}}
833
#' @examples
834
#' data(edhec)
835
#' ret <- edhec[, 1:4]
836
#'
837
#' pspec <- portfolio.spec(assets=colnames(ret))
838
#'
839
#' pspec <- add.constraint(portfolio=pspec, type="turnover", turnover_target=0.6)
840
#' @export
841
turnover_constraint <- function(type="turnover", turnover_target, turnover_penalty=NULL, weight_initial=NULL, enabled=TRUE, message=FALSE, ...){
842
Constraint <- constraint_v2(type, enabled=enabled, constrclass="turnover_constraint", ...)
843
Constraint$turnover_target <- turnover_target
844
Constraint$weight_initial <- weight_initial
845
Constraint$turnover_penalty <- turnover_penalty
846
return(Constraint)
847
}
848
849
#' constructor for diversification_constraint
850
#'
851
#' The diversification constraint specifies a target diversification value.
852
#' This function is called by add.constraint when type="diversification" is
853
#' specified, see \code{\link{add.constraint}}. Diversification is computed
854
#' as \code{1 - sum(weights^2)}.
855
#'
856
#' @param type character type of the constraint
857
#' @param div_target diversification target value
858
#' @param enabled TRUE/FALSE
859
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
860
#' @param \dots any other passthru parameters to specify diversification constraint
861
#' an object of class 'diversification_constraint'
862
#' @author Ross Bennett
863
#' @seealso \code{\link{add.constraint}}
864
#' @examples
865
#' data(edhec)
866
#' ret <- edhec[, 1:4]
867
#'
868
#' pspec <- portfolio.spec(assets=colnames(ret))
869
#'
870
#' pspec <- add.constraint(portfolio=pspec, type="diversification", div_target=0.7)
871
#' @export
872
diversification_constraint <- function(type="diversification", div_target=NULL, enabled=TRUE, message=FALSE, ...){
873
Constraint <- constraint_v2(type, enabled=enabled, constrclass="diversification_constraint", ...)
874
Constraint$div_target <- div_target
875
return(Constraint)
876
}
877
878
#' constructor for return_constraint
879
#'
880
#' The return constraint specifes a target mean return value.
881
#' This function is called by add.constraint when type="return" is specified, \code{\link{add.constraint}}
882
#'
883
#' @param type character type of the constraint
884
#' @param return_target return target value
885
#' @param enabled TRUE/FALSE
886
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
887
#' @param \dots any other passthru parameters
888
#' @return an object of class 'return_constraint'
889
#' @author Ross Bennett
890
#' @seealso \code{\link{add.constraint}}
891
#' @examples
892
#' data(edhec)
893
#' ret <- edhec[, 1:4]
894
#'
895
#' pspec <- portfolio.spec(assets=colnames(ret))
896
#'
897
#' pspec <- add.constraint(portfolio=pspec, type="return", return_target=mean(colMeans(ret)))
898
#' @export
899
return_constraint <- function(type="return", return_target, enabled=TRUE, message=FALSE, ...){
900
Constraint <- constraint_v2(type, enabled=enabled, constrclass="return_constraint", ...)
901
Constraint$return_target <- return_target
902
return(Constraint)
903
}
904
905
#' constructor for filter_constraint
906
#'
907
#' This function is called by add.constraint when type="filter" is specified, \code{\link{add.constraint}}
908
#'
909
#' Allows the user to specify a filter function which will take returns, weights,
910
#' and constraints as inputs, and can return a modified weights vector as output.
911
#'
912
#' Fundamentally, it could be used to filter out certain assets, or to ensure
913
#' that they must be long or short.
914
#'
915
#' Typically, filter functions will be called by the random portfolio simulation
916
#' function or via the fn_map function.
917
#'
918
#' @param type character type of the constraint
919
#' @param filter_name either a function to apply, or a name of a function to apply
920
#' @param enabled TRUE/FALSE
921
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
922
#' @param \dots any other passthru parameters to specify position limit constraints
923
#' @return an object of class 'position_limit_constraint'
924
#' @author Ross Bennett
925
#' @seealso \code{\link{add.constraint}}
926
#' @examples
927
#' data(edhec)
928
#' ret <- edhec[, 1:4]
929
#'
930
#' pspec <- portfolio.spec(assets=colnames(ret))
931
#'
932
#' pspec <- add.constraint(portfolio=pspec, type="position_limit", max_pos=3)
933
#' pspec <- add.constraint(portfolio=pspec, type="position_limit", max_pos_long=3, max_pos_short=1)
934
#' @export
935
position_limit_constraint <- function(type="position_limit", filter_name=NULL, enabled=TRUE, message=FALSE, ...){
936
937
# check that filter_name either is a function or describes a function
938
#
939
Constraint <- constraint_v2(type, enabled=enabled, constrclass="position_limit_constraint", ...)
940
Constraint$filter_name <- filter_name
941
return(Constraint)
942
}
943
944
#' Constructor for factor exposure constraint
945
#'
946
#' The factor exposure constraint sets upper and lower bounds on exposures to risk factors.
947
#' This function is called by add.constraint when type="factor_exposure" is specified, see \code{\link{add.constraint}}
948
#'
949
#' \code{B} can be either a vector or matrix of risk factor exposures (i.e. betas).
950
#' If \code{B} is a vector, the length of \code{B} must be equal to the number of
951
#' assets and lower and upper must be scalars. If \code{B} is passed in as a vector,
952
#' it will be converted to a matrix with one column.
953
#'
954
#' If \code{B} is a matrix, the number of rows must be equal to the number
955
#' of assets and the number of columns represent the number of factors. The length
956
#' of lower and upper must be equal to the number of factors. The \code{B} matrix should
957
#' have column names specifying the factors and row names specifying the assets.
958
#' Default column names and row names will be assigned if the user passes in a
959
#' \code{B} matrix without column names or row names.
960
#'
961
#' @param type character type of the constraint
962
#' @param assets named vector of assets specifying initial weights
963
#' @param B vector or matrix of risk factor exposures
964
#' @param lower vector of lower bounds of constraints for risk factor exposures
965
#' @param upper vector of upper bounds of constraints for risk factor exposures
966
#' @param enabled TRUE/FALSE
967
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
968
#' @param \dots any other passthru parameters to specify risk factor exposure constraints
969
#' @return an object of class 'factor_exposure_constraint'
970
#' @author Ross Bennett
971
#' @seealso \code{\link{add.constraint}}
972
#' @export
973
factor_exposure_constraint <- function(type="factor_exposure", assets, B, lower, upper, enabled=TRUE, message=FALSE, ...){
974
# Number of assets
975
nassets <- length(assets)
976
977
# Assume the user has passed in a vector of betas
978
if(is.vector(B)){
979
# The number of betas must be equal to the number of assets
980
if(length(B) != nassets) stop("length of B must be equal to number of assets")
981
# The user passed in a vector of betas, lower and upper must be scalars
982
if(length(lower) != 1) stop("lower must be a scalar")
983
if(length(upper) != 1) stop("upper must be a scalar")
984
bnames <- names(B)
985
B <- matrix(B, ncol=1, dimnames=list(bnames))
986
}
987
# The user has passed in a matrix for B
988
if(is.matrix(B)){
989
# The number of rows in B must be equal to the number of assets
990
if(nrow(B) != nassets) stop("number of rows of B must be equal to number of assets")
991
# The user passed in a matrix for B --> lower and upper must be equal to the number of columns in the beta matrix
992
if(length(lower) != ncol(B)) stop("length of lower must be equal to the number of columns in the B matrix")
993
if(length(upper) != ncol(B)) stop("length of upper must be equal to the number of columns in the B matrix")
994
if(is.null(colnames(B))){
995
# The user has passed in a B matrix without column names specifying factors
996
colnames(B) <- paste("factor", 1:ncol(B), sep="")
997
}
998
if(is.null(rownames(B))){
999
# The user has passed in a B matrix without row names specifying assets
1000
rownames(B) <- names(assets)
1001
}
1002
}
1003
1004
Constraint <- constraint_v2(type=type, enabled=enabled, constrclass="factor_exposure_constraint", ...)
1005
Constraint$B <- B
1006
Constraint$lower <- lower
1007
Constraint$upper <- upper
1008
return(Constraint)
1009
}
1010
1011
#' constructor for transaction_cost_constraint
1012
#'
1013
#' The transaction cost constraint specifies a proportional cost value.
1014
#' This function is called by add.constraint when type="transaction_cost" is specified, see \code{\link{add.constraint}}.
1015
#'
1016
#' Note that with the ROI solvers, proportional transaction cost constraint is
1017
#' currently only supported for the global minimum variance and quadratic
1018
#' utility problems with ROI quadprog plugin.
1019
#'
1020
#' @param type character type of the constraint
1021
#' @param assets number of assets, or optionally a named vector of assets specifying initial weights
1022
#' @param ptc proportional transaction cost value
1023
#' @param enabled TRUE/FALSE
1024
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
1025
#' @param \dots any other passthru parameters to specify box and/or group constraints
1026
#' @return an object of class 'transaction_cost_constraint'
1027
#' @author Ross Bennett
1028
#' @seealso \code{\link{add.constraint}}
1029
#' @examples
1030
#' data(edhec)
1031
#' ret <- edhec[, 1:4]
1032
#'
1033
#' pspec <- portfolio.spec(assets=colnames(ret))
1034
#'
1035
#' pspec <- add.constraint(portfolio=pspec, type="transaction_cost", ptc=0.01)
1036
#' @export
1037
transaction_cost_constraint <- function(type="transaction_cost", assets, ptc, enabled=TRUE, message=FALSE, ...){
1038
nassets <- length(assets)
1039
if(length(ptc) == 1) ptc <- rep(ptc, nassets)
1040
if(length(ptc) != nassets) stop("length of ptc must be equal to number of assets")
1041
Constraint <- constraint_v2(type, enabled=enabled, constrclass="transaction_cost_constraint", ...)
1042
Constraint$ptc <- ptc
1043
return(Constraint)
1044
}
1045
1046
#' constructor for leverage_exposure_constraint
1047
#'
1048
#' The leverage_exposure constraint specifies a maximum leverage where
1049
#' leverage is defined as the sum of the absolute value of the weights.
1050
#' Leverage exposure is computed as the sum of the absolute value of the
1051
#' weights, \code{sum(abs(weights))}.
1052
#'
1053
#'
1054
#' This should be used for constructing, for example, 130/30 portfolios or
1055
#' dollar neutral portfolios with 2:1 leverage. For the ROI solvers, this is
1056
#' implemented as a MILP problem and is not supported for problems formulated
1057
#' as a quadratic programming problem. This may change in the future if a MIQP
1058
#' solver is added.
1059
#'
1060
#' This function is called by add.constraint when type="leverage_exposure"
1061
#' is specified, see \code{\link{add.constraint}}.
1062
#'
1063
#' @param type character type of the constraint
1064
#' @param leverage maximum leverage value
1065
#' @param enabled TRUE/FALSE
1066
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
1067
#' @param \dots any other passthru parameters to specify diversification constraint
1068
#' an object of class 'diversification_constraint'
1069
#' @author Ross Bennett
1070
#' @seealso \code{\link{add.constraint}}
1071
#' @examples
1072
#' data(edhec)
1073
#' ret <- edhec[, 1:4]
1074
#'
1075
#' pspec <- portfolio.spec(assets=colnames(ret))
1076
#'
1077
#' pspec <- add.constraint(portfolio=pspec, type="leverage_exposure", leverage=1.6)
1078
#' @export
1079
leverage_exposure_constraint <- function(type="leverage_exposure", leverage=NULL, enabled=TRUE, message=FALSE, ...){
1080
Constraint <- constraint_v2(type, enabled=enabled, constrclass="leverage_exposure_constraint", ...)
1081
Constraint$leverage <- leverage
1082
return(Constraint)
1083
}
1084
1085
#' function for updating constrints, not well tested, may be broken
1086
#'
1087
#' can we use the generic update.default function?
1088
#' @param object object of type \code{\link{constraint}} to update
1089
#' @param ... any other passthru parameters, used to call \code{\link{constraint}}
1090
#' @author bpeterson
1091
#' @method update constraint
1092
1093
#' @export
1094
update.constraint <- function(object, ...){
1095
constraints <- object
1096
if (is.null(constraints) | !is.constraint(constraints)){
1097
stop("you must pass in an object of class constraints to modify")
1098
}
1099
call <- object$call
1100
if (is.null(call))
1101
stop("need an object with call component")
1102
extras <- match.call(expand.dots = FALSE)$...
1103
# if (!missing(formula.))
1104
# call$formula <- update.formula(formula(object), formula.)
1105
if (length(extras)) {
1106
existing <- !is.na(match(names(extras), names(call)))
1107
for (a in names(extras)[existing]) call[[a]] <- extras[[a]]
1108
if (any(!existing)) {
1109
call <- c(as.list(call), extras[!existing])
1110
call <- as.call(call)
1111
}
1112
}
1113
# if (hasArg(nassets)){
1114
# warning("changing number of assets may modify other constraints")
1115
# constraints$nassets<-nassets
1116
# }
1117
# if(hasArg(min)) {
1118
# if (is.vector(min) & length(min)!=nassets){
1119
# warning(paste("length of min !=",nassets))
1120
# if (length(min)<nassets) {stop("length of min must be equal to lor longer than nassets")}
1121
# constraints$min<-min[1:nassets]
1122
# }
1123
# }
1124
# if(hasArg(max)) {
1125
# if (is.vector(max) & length(max)!=nassets){
1126
# warning(paste("length of max !=",nassets))
1127
# if (length(max)<nassets) {stop("length of max must be equal to lor longer than nassets")}
1128
# constraints$max<-max[1:nassets]
1129
# }
1130
# }
1131
# if(hasArg(min_mult)){constrains$min_mult=min_mult}
1132
# if(hasArg(max_mult)){constrains$max_mult=max_mult}
1133
return(constraints)
1134
}
1135
1136
#' Insert a list of constraints into the constraints slot of a portfolio object
1137
#'
1138
#' This is a helper function primarily for backwards compatibility to insert
1139
#' constraints from a 'v1_constraint' object into the v2 'portfolio' object.
1140
#'
1141
#' @param portfolio object of class 'portfolio'
1142
#' @param constraints list of constraint objects
1143
#' @author Ross Bennett
1144
insert_constraints <- function(portfolio, constraints){
1145
# Check portfolio object
1146
if (is.null(portfolio) | !is.portfolio(portfolio)){
1147
stop("you must pass in an object of class portfolio")
1148
}
1149
1150
# Check that constraints is a list
1151
if(!is.list(constraints)) stop("constraints must be passed in as a list")
1152
1153
# Check that all objects in the list are of class constraint
1154
for(i in 1:length(constraints)){
1155
if(!is.constraint(constraints[[i]]))
1156
stop("constraints must be passed in as a list and all objects in constraints must be of class 'constraint'")
1157
}
1158
1159
portfolio$constraints <- constraints
1160
return(portfolio)
1161
}
1162
1163
#' Helper function to update v1_constraint objects to v2 specification in the portfolio object
1164
#'
1165
#' The function takes the constraints and objectives specified in the v1_constraint
1166
#' object and updates the portfolio object with those constraints and objectives. This
1167
#' function is used inside optimize.portfolio to maintain backwards compatibility
1168
#' if the user passes in a v1_constraint object for the constraint arg in
1169
#' optimize.portfolio.
1170
#'
1171
#' @param portfolio portfolio object passed into optimize.portfolio
1172
#' @param v1_constraint object of type v1_constraint passed into optimize.portfolio
1173
#' @return portfolio object containing constraints and objectives from v1_constraint
1174
#' @author Ross Bennett
1175
#' @seealso \code{\link{portfolio.spec}}, \code{\link{add.constraint}}
1176
#' @export
1177
update_constraint_v1tov2 <- function(portfolio, v1_constraint){
1178
if(!is.portfolio(portfolio)) stop("portfolio object must be of class 'portfolio'")
1179
if(!inherits(v1_constraint, "v1_constraint")) stop("v1_constraint object must be of class 'v1_constraint'")
1180
# Put the assets and weight_seq into slots in portfolio object
1181
portfolio$assets <- v1_constraint$assets
1182
portfolio$weight_seq <- v1_constraint$weight_seq
1183
1184
# The v1_constraint object supported 3 constraint types (weight_sum, box, and group)
1185
# Add weight_sum/leverage constraints from v1_constraint to portfolio
1186
if(!is.null(v1_constraint$min_sum) & !is.null(v1_constraint$max_sum)){
1187
portfolio <- add.constraint(portfolio=portfolio, type='weight_sum', min_sum=v1_constraint$min_sum, max_sum=v1_constraint$max_sum)
1188
}
1189
# Add box constraints from v1_constraint to portfolio
1190
if(!is.null(v1_constraint$min) & !is.null(v1_constraint$max)){
1191
portfolio <- add.constraint(portfolio=portfolio, type='box', min=v1_constraint$min, max=v1_constraint$max)
1192
}
1193
# Add group constraints from v1_constraint to portfolio
1194
if(!is.null(v1_constraint$groups) & !is.null(v1_constraint$cLO) & !is.null(v1_constraint$cUP)){
1195
portfolio <- add.constraint(portfolio=portfolio, type='group', groups=v1_constraint$groups, group_min=v1_constraint$cLO, group_max=v1_constraint$cUP)
1196
}
1197
1198
# Put the objectives from v1_constraint into the objectives slot in the portfolio
1199
# object. This overwrites what might already be in portfolio$objectives assuming
1200
# the user is using the v1_constraint object to specify the objectives
1201
portfolio$objectives <- v1_constraint$objectives
1202
return(portfolio)
1203
}
1204
1205
#' check if a set of weights satisfies the constraints
1206
#'
1207
#' This function checks if a set of weights satisfies all constraints. This is
1208
#' used as a helper function for random portfolios created with \code{rp_simplex}
1209
#' and \code{rp_grid} to eliminate portfolios that do not satisfy the constraints.
1210
#'
1211
#' @param weights vector of weights
1212
#' @param portfolio object of class 'portfolio'
1213
#' @return TRUE if all constraints are satisfied, FALSE if any constraint is violated
1214
#' @author Ross Bennett
1215
check_constraints <- function(weights, portfolio){
1216
1217
# get the constraints to check
1218
# We will check leverage, box, group, and position limit constraints
1219
constraints <- get_constraints(portfolio)
1220
min_sum <- constraints$min_sum
1221
max_sum <- constraints$max_sum
1222
min <- constraints$min
1223
max <- constraints$max
1224
groups <- constraints$groups
1225
cLO <- constraints$cLO
1226
cUP <- constraints$cUP
1227
group_pos <- constraints$group_pos
1228
div_target <- constraints$div_target
1229
turnover_target <- constraints$turnover_target
1230
turnover_penalty <- constraints$turnover_penalty
1231
weight_initial <- constraints$weight_initial
1232
max_pos <- constraints$max_pos
1233
max_pos_long <- constraints$max_pos_long
1234
max_pos_short <- constraints$max_pos_short
1235
leverage_exposure <- constraints$leverage
1236
tolerance <- .Machine$double.eps^0.5
1237
1238
log_vec <- c()
1239
# check leverage constraints
1240
if(!is.null(min_sum) & !is.null(max_sum)){
1241
# TRUE if constraints are satisfied
1242
log_vec <- c(log_vec, ((sum(weights) >= min_sum) & (sum(weights) <= max_sum)))
1243
}
1244
1245
# check box constraints
1246
if(!is.null(min) & !is.null(max)){
1247
# TRUE if constraints are satisfied
1248
log_vec <- c(log_vec, (all(weights >= min) & all(weights <= max)))
1249
}
1250
1251
# check group constraints
1252
if(!is.null(groups) & !is.null(cLO) & !is.null(cUP)){
1253
log_vec <- c(log_vec, all(!group_fail(weights, groups, cLO, cUP, group_pos)))
1254
}
1255
1256
# check position limit constraints
1257
if(!is.null(max_pos) | !is.null(max_pos_long) | !is.null(max_pos_short)){
1258
log_vec <- c(log_vec, !pos_limit_fail(weights, max_pos, max_pos_long, max_pos_short))
1259
}
1260
1261
# check leverage exposure constraints
1262
if(!is.null(leverage_exposure)){
1263
log_vec <- c(log_vec, sum(abs(weights)) <= leverage_exposure)
1264
}
1265
# return TRUE if all constraints are satisfied, FALSE if any constraint is violated
1266
return(all(log_vec))
1267
}
1268
1269
# #' constructor for class constraint_ROI
1270
# #'
1271
# #' @param assets number of assets, or optionally a named vector of assets specifying seed weights
1272
# #' @param op.problem an object of type "OP" (optimization problem, of \code{ROI}) specifying the complete optimization problem, see ROI help pages for proper construction of OP object.
1273
# #' @param solver string argument for what solver package to use, must have ROI plugin installed for that solver. Currently support is for \code{glpk} and \code{quadprog}.
1274
# #' @param weight_seq seed sequence of weights, see \code{\link{generatesequence}}
1275
# #' @author Hezky Varon
1276
# #' @export
1277
# constraint_ROI <- function(assets, op.problem, solver=c("glpk", "quadprog"), weight_seq=NULL)
1278
# {
1279
# if(op.problem == NULL || inherits(op.problem, "OP")) {
1280
# stop("Need to pass in optimiztion problem of ROI:::OP type.")
1281
# if() stop("Need to be ROI:::OP")
1282
# return(structure(
1283
# list(
1284
# assets = assets,
1285
# constrainted_objective = op.problem,
1286
# solver = solver[1],
1287
# weight_seq = weight_seq,
1288
# objectives = list(),
1289
# call = match.call()
1290
# ),
1291
# class=c("constraint_ROI","constraint")
1292
# ))
1293
# }
1294
1295
1296