Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/constraints.R
1685 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 turnover_penalty optional penalty parameter for turnover constraint
828
#' @param weight_initial optional initial weights vector to compute turnover from
829
#' @param enabled TRUE/FALSE
830
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
831
#' @param \dots any other passthru parameters to specify box and/or group constraints
832
#' @return an object of class 'turnover_constraint'
833
#' @author Ross Bennett
834
#' @seealso \code{\link{add.constraint}}
835
#' @examples
836
#' data(edhec)
837
#' ret <- edhec[, 1:4]
838
#'
839
#' pspec <- portfolio.spec(assets=colnames(ret))
840
#'
841
#' pspec <- add.constraint(portfolio=pspec, type="turnover", turnover_target=0.6)
842
#' @export
843
turnover_constraint <- function(type="turnover", turnover_target, turnover_penalty=NULL, weight_initial=NULL, enabled=TRUE, message=FALSE, ...){
844
Constraint <- constraint_v2(type, enabled=enabled, constrclass="turnover_constraint", ...)
845
Constraint$turnover_target <- turnover_target
846
Constraint$weight_initial <- weight_initial
847
Constraint$turnover_penalty <- turnover_penalty
848
return(Constraint)
849
}
850
851
#' constructor for diversification_constraint
852
#'
853
#' The diversification constraint specifies a target diversification value.
854
#' This function is called by add.constraint when type="diversification" is
855
#' specified, see \code{\link{add.constraint}}. Diversification is computed
856
#' as \code{1 - sum(weights^2)}.
857
#'
858
#' @param type character type of the constraint
859
#' @param div_target diversification target value
860
#' @param enabled TRUE/FALSE
861
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
862
#' @param \dots any other passthru parameters to specify diversification constraint
863
#' an object of class 'diversification_constraint'
864
#' @author Ross Bennett
865
#' @seealso \code{\link{add.constraint}}
866
#' @examples
867
#' data(edhec)
868
#' ret <- edhec[, 1:4]
869
#'
870
#' pspec <- portfolio.spec(assets=colnames(ret))
871
#'
872
#' pspec <- add.constraint(portfolio=pspec, type="diversification", div_target=0.7)
873
#' @export
874
diversification_constraint <- function(type="diversification", div_target=NULL, enabled=TRUE, message=FALSE, ...){
875
Constraint <- constraint_v2(type, enabled=enabled, constrclass="diversification_constraint", ...)
876
Constraint$div_target <- div_target
877
return(Constraint)
878
}
879
880
#' constructor for return_constraint
881
#'
882
#' The return constraint specifes a target mean return value.
883
#' This function is called by add.constraint when type="return" is specified, \code{\link{add.constraint}}
884
#'
885
#' @param type character type of the constraint
886
#' @param return_target return target value
887
#' @param enabled TRUE/FALSE
888
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
889
#' @param \dots any other passthru parameters
890
#' @return an object of class 'return_constraint'
891
#' @author Ross Bennett
892
#' @seealso \code{\link{add.constraint}}
893
#' @examples
894
#' data(edhec)
895
#' ret <- edhec[, 1:4]
896
#'
897
#' pspec <- portfolio.spec(assets=colnames(ret))
898
#'
899
#' pspec <- add.constraint(portfolio=pspec, type="return", return_target=mean(colMeans(ret)))
900
#' @export
901
return_constraint <- function(type="return", return_target, enabled=TRUE, message=FALSE, ...){
902
Constraint <- constraint_v2(type, enabled=enabled, constrclass="return_constraint", ...)
903
Constraint$return_target <- return_target
904
return(Constraint)
905
}
906
907
#' constructor for filter_constraint
908
#'
909
#' This function is called by add.constraint when type="filter" is specified, \code{\link{add.constraint}}
910
#'
911
#' Allows the user to specify a filter function which will take returns, weights,
912
#' and constraints as inputs, and can return a modified weights vector as output.
913
#'
914
#' Fundamentally, it could be used to filter out certain assets, or to ensure
915
#' that they must be long or short.
916
#'
917
#' Typically, filter functions will be called by the random portfolio simulation
918
#' function or via the fn_map function.
919
#'
920
#' @param type character type of the constraint
921
#' @param filter_name either a function to apply, or a name of a function to apply
922
#' @param enabled TRUE/FALSE
923
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
924
#' @param \dots any other passthru parameters to specify position limit constraints
925
#' @return an object of class 'position_limit_constraint'
926
#' @author Ross Bennett
927
#' @seealso \code{\link{add.constraint}}
928
#' @examples
929
#' data(edhec)
930
#' ret <- edhec[, 1:4]
931
#'
932
#' pspec <- portfolio.spec(assets=colnames(ret))
933
#'
934
#' pspec <- add.constraint(portfolio=pspec, type="position_limit", max_pos=3)
935
#' pspec <- add.constraint(portfolio=pspec, type="position_limit", max_pos_long=3, max_pos_short=1)
936
#' @export
937
position_limit_constraint <- function(type="position_limit", filter_name=NULL, enabled=TRUE, message=FALSE, ...){
938
939
# check that filter_name either is a function or describes a function
940
#
941
Constraint <- constraint_v2(type, enabled=enabled, constrclass="position_limit_constraint", ...)
942
Constraint$filter_name <- filter_name
943
return(Constraint)
944
}
945
946
#' Constructor for factor exposure constraint
947
#'
948
#' The factor exposure constraint sets upper and lower bounds on exposures to risk factors.
949
#' This function is called by add.constraint when type="factor_exposure" is specified, see \code{\link{add.constraint}}
950
#'
951
#' \code{B} can be either a vector or matrix of risk factor exposures (i.e. betas).
952
#' If \code{B} is a vector, the length of \code{B} must be equal to the number of
953
#' assets and lower and upper must be scalars. If \code{B} is passed in as a vector,
954
#' it will be converted to a matrix with one column.
955
#'
956
#' If \code{B} is a matrix, the number of rows must be equal to the number
957
#' of assets and the number of columns represent the number of factors. The length
958
#' of lower and upper must be equal to the number of factors. The \code{B} matrix should
959
#' have column names specifying the factors and row names specifying the assets.
960
#' Default column names and row names will be assigned if the user passes in a
961
#' \code{B} matrix without column names or row names.
962
#'
963
#' @param type character type of the constraint
964
#' @param assets named vector of assets specifying initial weights
965
#' @param B vector or matrix of risk factor exposures
966
#' @param lower vector of lower bounds of constraints for risk factor exposures
967
#' @param upper vector of upper bounds of constraints for risk factor exposures
968
#' @param enabled TRUE/FALSE
969
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
970
#' @param \dots any other passthru parameters to specify risk factor exposure constraints
971
#' @return an object of class 'factor_exposure_constraint'
972
#' @author Ross Bennett
973
#' @seealso \code{\link{add.constraint}}
974
#' @export
975
factor_exposure_constraint <- function(type="factor_exposure", assets, B, lower, upper, enabled=TRUE, message=FALSE, ...){
976
# Number of assets
977
nassets <- length(assets)
978
979
# Assume the user has passed in a vector of betas
980
if(is.vector(B)){
981
# The number of betas must be equal to the number of assets
982
if(length(B) != nassets) stop("length of B must be equal to number of assets")
983
# The user passed in a vector of betas, lower and upper must be scalars
984
if(length(lower) != 1) stop("lower must be a scalar")
985
if(length(upper) != 1) stop("upper must be a scalar")
986
bnames <- names(B)
987
B <- matrix(B, ncol=1, dimnames=list(bnames))
988
}
989
# The user has passed in a matrix for B
990
if(is.matrix(B)){
991
# The number of rows in B must be equal to the number of assets
992
if(nrow(B) != nassets) stop("number of rows of B must be equal to number of assets")
993
# The user passed in a matrix for B --> lower and upper must be equal to the number of columns in the beta matrix
994
if(length(lower) != ncol(B)) stop("length of lower must be equal to the number of columns in the B matrix")
995
if(length(upper) != ncol(B)) stop("length of upper must be equal to the number of columns in the B matrix")
996
if(is.null(colnames(B))){
997
# The user has passed in a B matrix without column names specifying factors
998
colnames(B) <- paste("factor", 1:ncol(B), sep="")
999
}
1000
if(is.null(rownames(B))){
1001
# The user has passed in a B matrix without row names specifying assets
1002
rownames(B) <- names(assets)
1003
}
1004
}
1005
1006
Constraint <- constraint_v2(type=type, enabled=enabled, constrclass="factor_exposure_constraint", ...)
1007
Constraint$B <- B
1008
Constraint$lower <- lower
1009
Constraint$upper <- upper
1010
return(Constraint)
1011
}
1012
1013
#' constructor for transaction_cost_constraint
1014
#'
1015
#' The transaction cost constraint specifies a proportional cost value.
1016
#' This function is called by add.constraint when type="transaction_cost" is specified, see \code{\link{add.constraint}}.
1017
#'
1018
#' Note that with the ROI solvers, proportional transaction cost constraint is
1019
#' currently only supported for the global minimum variance and quadratic
1020
#' utility problems with ROI quadprog plugin.
1021
#'
1022
#' @param type character type of the constraint
1023
#' @param assets number of assets, or optionally a named vector of assets specifying initial weights
1024
#' @param ptc proportional transaction cost value
1025
#' @param enabled TRUE/FALSE
1026
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
1027
#' @param \dots any other passthru parameters to specify box and/or group constraints
1028
#' @return an object of class 'transaction_cost_constraint'
1029
#' @author Ross Bennett
1030
#' @seealso \code{\link{add.constraint}}
1031
#' @examples
1032
#' data(edhec)
1033
#' ret <- edhec[, 1:4]
1034
#'
1035
#' pspec <- portfolio.spec(assets=colnames(ret))
1036
#'
1037
#' pspec <- add.constraint(portfolio=pspec, type="transaction_cost", ptc=0.01)
1038
#' @export
1039
transaction_cost_constraint <- function(type="transaction_cost", assets, ptc, enabled=TRUE, message=FALSE, ...){
1040
nassets <- length(assets)
1041
if(length(ptc) == 1) ptc <- rep(ptc, nassets)
1042
if(length(ptc) != nassets) stop("length of ptc must be equal to number of assets")
1043
Constraint <- constraint_v2(type, enabled=enabled, constrclass="transaction_cost_constraint", ...)
1044
Constraint$ptc <- ptc
1045
return(Constraint)
1046
}
1047
1048
#' constructor for leverage_exposure_constraint
1049
#'
1050
#' The leverage_exposure constraint specifies a maximum leverage where
1051
#' leverage is defined as the sum of the absolute value of the weights.
1052
#' Leverage exposure is computed as the sum of the absolute value of the
1053
#' weights, \code{sum(abs(weights))}.
1054
#'
1055
#'
1056
#' This should be used for constructing, for example, 130/30 portfolios or
1057
#' dollar neutral portfolios with 2:1 leverage. For the ROI solvers, this is
1058
#' implemented as a MILP problem and is not supported for problems formulated
1059
#' as a quadratic programming problem. This may change in the future if a MIQP
1060
#' solver is added.
1061
#'
1062
#' This function is called by add.constraint when type="leverage_exposure"
1063
#' is specified, see \code{\link{add.constraint}}.
1064
#'
1065
#' @param type character type of the constraint
1066
#' @param leverage maximum leverage value
1067
#' @param enabled TRUE/FALSE
1068
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
1069
#' @param \dots any other passthru parameters to specify diversification constraint
1070
#' an object of class 'diversification_constraint'
1071
#' @author Ross Bennett
1072
#' @seealso \code{\link{add.constraint}}
1073
#' @examples
1074
#' data(edhec)
1075
#' ret <- edhec[, 1:4]
1076
#'
1077
#' pspec <- portfolio.spec(assets=colnames(ret))
1078
#'
1079
#' pspec <- add.constraint(portfolio=pspec, type="leverage_exposure", leverage=1.6)
1080
#' @export
1081
leverage_exposure_constraint <- function(type="leverage_exposure", leverage=NULL, enabled=TRUE, message=FALSE, ...){
1082
Constraint <- constraint_v2(type, enabled=enabled, constrclass="leverage_exposure_constraint", ...)
1083
Constraint$leverage <- leverage
1084
return(Constraint)
1085
}
1086
1087
#' function for updating constrints, not well tested, may be broken
1088
#'
1089
#' can we use the generic update.default function?
1090
#' @param object object of type \code{\link{constraint}} to update
1091
#' @param ... any other passthru parameters, used to call \code{\link{constraint}}
1092
#' @author bpeterson
1093
#' @method update constraint
1094
1095
#' @export
1096
update.constraint <- function(object, ...){
1097
constraints <- object
1098
if (is.null(constraints) | !is.constraint(constraints)){
1099
stop("you must pass in an object of class constraints to modify")
1100
}
1101
call <- object$call
1102
if (is.null(call))
1103
stop("need an object with call component")
1104
extras <- match.call(expand.dots = FALSE)$...
1105
# if (!missing(formula.))
1106
# call$formula <- update.formula(formula(object), formula.)
1107
if (length(extras)) {
1108
existing <- !is.na(match(names(extras), names(call)))
1109
for (a in names(extras)[existing]) call[[a]] <- extras[[a]]
1110
if (any(!existing)) {
1111
call <- c(as.list(call), extras[!existing])
1112
call <- as.call(call)
1113
}
1114
}
1115
# if (hasArg(nassets)){
1116
# warning("changing number of assets may modify other constraints")
1117
# constraints$nassets<-nassets
1118
# }
1119
# if(hasArg(min)) {
1120
# if (is.vector(min) & length(min)!=nassets){
1121
# warning(paste("length of min !=",nassets))
1122
# if (length(min)<nassets) {stop("length of min must be equal to lor longer than nassets")}
1123
# constraints$min<-min[1:nassets]
1124
# }
1125
# }
1126
# if(hasArg(max)) {
1127
# if (is.vector(max) & length(max)!=nassets){
1128
# warning(paste("length of max !=",nassets))
1129
# if (length(max)<nassets) {stop("length of max must be equal to lor longer than nassets")}
1130
# constraints$max<-max[1:nassets]
1131
# }
1132
# }
1133
# if(hasArg(min_mult)){constrains$min_mult=min_mult}
1134
# if(hasArg(max_mult)){constrains$max_mult=max_mult}
1135
return(constraints)
1136
}
1137
1138
#' Insert a list of constraints into the constraints slot of a portfolio object
1139
#'
1140
#' This is a helper function primarily for backwards compatibility to insert
1141
#' constraints from a 'v1_constraint' object into the v2 'portfolio' object.
1142
#'
1143
#' @param portfolio object of class 'portfolio'
1144
#' @param constraints list of constraint objects
1145
#' @author Ross Bennett
1146
insert_constraints <- function(portfolio, constraints){
1147
# Check portfolio object
1148
if (is.null(portfolio) | !is.portfolio(portfolio)){
1149
stop("you must pass in an object of class portfolio")
1150
}
1151
1152
# Check that constraints is a list
1153
if(!is.list(constraints)) stop("constraints must be passed in as a list")
1154
1155
# Check that all objects in the list are of class constraint
1156
for(i in 1:length(constraints)){
1157
if(!is.constraint(constraints[[i]]))
1158
stop("constraints must be passed in as a list and all objects in constraints must be of class 'constraint'")
1159
}
1160
1161
portfolio$constraints <- constraints
1162
return(portfolio)
1163
}
1164
1165
#' Helper function to update v1_constraint objects to v2 specification in the portfolio object
1166
#'
1167
#' The function takes the constraints and objectives specified in the v1_constraint
1168
#' object and updates the portfolio object with those constraints and objectives. This
1169
#' function is used inside optimize.portfolio to maintain backwards compatibility
1170
#' if the user passes in a v1_constraint object for the constraint arg in
1171
#' optimize.portfolio.
1172
#'
1173
#' @param portfolio portfolio object passed into optimize.portfolio
1174
#' @param v1_constraint object of type v1_constraint passed into optimize.portfolio
1175
#' @return portfolio object containing constraints and objectives from v1_constraint
1176
#' @author Ross Bennett
1177
#' @seealso \code{\link{portfolio.spec}}, \code{\link{add.constraint}}
1178
#' @export
1179
update_constraint_v1tov2 <- function(portfolio, v1_constraint){
1180
if(!is.portfolio(portfolio)) stop("portfolio object must be of class 'portfolio'")
1181
if(!inherits(v1_constraint, "v1_constraint")) stop("v1_constraint object must be of class 'v1_constraint'")
1182
# Put the assets and weight_seq into slots in portfolio object
1183
portfolio$assets <- v1_constraint$assets
1184
portfolio$weight_seq <- v1_constraint$weight_seq
1185
1186
# The v1_constraint object supported 3 constraint types (weight_sum, box, and group)
1187
# Add weight_sum/leverage constraints from v1_constraint to portfolio
1188
if(!is.null(v1_constraint$min_sum) & !is.null(v1_constraint$max_sum)){
1189
portfolio <- add.constraint(portfolio=portfolio, type='weight_sum', min_sum=v1_constraint$min_sum, max_sum=v1_constraint$max_sum)
1190
}
1191
# Add box constraints from v1_constraint to portfolio
1192
if(!is.null(v1_constraint$min) & !is.null(v1_constraint$max)){
1193
portfolio <- add.constraint(portfolio=portfolio, type='box', min=v1_constraint$min, max=v1_constraint$max)
1194
}
1195
# Add group constraints from v1_constraint to portfolio
1196
if(!is.null(v1_constraint$groups) & !is.null(v1_constraint$cLO) & !is.null(v1_constraint$cUP)){
1197
portfolio <- add.constraint(portfolio=portfolio, type='group', groups=v1_constraint$groups, group_min=v1_constraint$cLO, group_max=v1_constraint$cUP)
1198
}
1199
1200
# Put the objectives from v1_constraint into the objectives slot in the portfolio
1201
# object. This overwrites what might already be in portfolio$objectives assuming
1202
# the user is using the v1_constraint object to specify the objectives
1203
portfolio$objectives <- v1_constraint$objectives
1204
return(portfolio)
1205
}
1206
1207
#' check if a set of weights satisfies the constraints
1208
#'
1209
#' This function checks if a set of weights satisfies all constraints. This is
1210
#' used as a helper function for random portfolios created with \code{rp_simplex}
1211
#' and \code{rp_grid} to eliminate portfolios that do not satisfy the constraints.
1212
#'
1213
#' @param weights vector of weights
1214
#' @param portfolio object of class 'portfolio'
1215
#' @return TRUE if all constraints are satisfied, FALSE if any constraint is violated
1216
#' @author Ross Bennett
1217
check_constraints <- function(weights, portfolio){
1218
1219
# get the constraints to check
1220
# We will check leverage, box, group, and position limit constraints
1221
constraints <- get_constraints(portfolio)
1222
min_sum <- constraints$min_sum
1223
max_sum <- constraints$max_sum
1224
min <- constraints$min
1225
max <- constraints$max
1226
groups <- constraints$groups
1227
cLO <- constraints$cLO
1228
cUP <- constraints$cUP
1229
group_pos <- constraints$group_pos
1230
div_target <- constraints$div_target
1231
turnover_target <- constraints$turnover_target
1232
turnover_penalty <- constraints$turnover_penalty
1233
weight_initial <- constraints$weight_initial
1234
max_pos <- constraints$max_pos
1235
max_pos_long <- constraints$max_pos_long
1236
max_pos_short <- constraints$max_pos_short
1237
leverage_exposure <- constraints$leverage
1238
tolerance <- .Machine$double.eps^0.5
1239
1240
log_vec <- c()
1241
# check leverage constraints
1242
if(!is.null(min_sum) & !is.null(max_sum)){
1243
# TRUE if constraints are satisfied
1244
log_vec <- c(log_vec, ((sum(weights) >= min_sum) & (sum(weights) <= max_sum)))
1245
}
1246
1247
# check box constraints
1248
if(!is.null(min) & !is.null(max)){
1249
# TRUE if constraints are satisfied
1250
log_vec <- c(log_vec, (all(weights >= min) & all(weights <= max)))
1251
}
1252
1253
# check group constraints
1254
if(!is.null(groups) & !is.null(cLO) & !is.null(cUP)){
1255
log_vec <- c(log_vec, all(!group_fail(weights, groups, cLO, cUP, group_pos)))
1256
}
1257
1258
# check position limit constraints
1259
if(!is.null(max_pos) | !is.null(max_pos_long) | !is.null(max_pos_short)){
1260
log_vec <- c(log_vec, !pos_limit_fail(weights, max_pos, max_pos_long, max_pos_short))
1261
}
1262
1263
# check leverage exposure constraints
1264
if(!is.null(leverage_exposure)){
1265
log_vec <- c(log_vec, sum(abs(weights)) <= leverage_exposure)
1266
}
1267
# return TRUE if all constraints are satisfied, FALSE if any constraint is violated
1268
return(all(log_vec))
1269
}
1270
1271
# #' constructor for class constraint_ROI
1272
# #'
1273
# #' @param assets number of assets, or optionally a named vector of assets specifying seed weights
1274
# #' @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.
1275
# #' @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}.
1276
# #' @param weight_seq seed sequence of weights, see \code{\link{generatesequence}}
1277
# #' @author Hezky Varon
1278
# #' @export
1279
# constraint_ROI <- function(assets, op.problem, solver=c("glpk", "quadprog"), weight_seq=NULL)
1280
# {
1281
# if(op.problem == NULL || inherits(op.problem, "OP")) {
1282
# stop("Need to pass in optimiztion problem of ROI:::OP type.")
1283
# if() stop("Need to be ROI:::OP")
1284
# return(structure(
1285
# list(
1286
# assets = assets,
1287
# constrainted_objective = op.problem,
1288
# solver = solver[1],
1289
# weight_seq = weight_seq,
1290
# objectives = list(),
1291
# call = match.call()
1292
# ),
1293
# class=c("constraint_ROI","constraint")
1294
# ))
1295
# }
1296
1297
1298