Skip to content

Commit 8ab602f

Browse files
committed
turnover constraints
1 parent a227827 commit 8ab602f

File tree

2 files changed

+60
-13
lines changed

2 files changed

+60
-13
lines changed

R/constraints.R

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ get_constraints <- function(portfolio){
759759
}
760760
if(inherits(constraint, "turnover_constraint")){
761761
out$turnover_target <- constraint$turnover_target
762+
out$turnover_penalty <- constraint$turnover_penalty
762763
out$weight_initial <- constraint$weight_initial
763764
}
764765
if(inherits(constraint, "diversification_constraint")){
@@ -837,10 +838,11 @@ get_constraints <- function(portfolio){
837838
#'
838839
#' pspec <- add.constraint(portfolio=pspec, type="turnover", turnover_target=0.6)
839840
#' @export
840-
turnover_constraint <- function(type="turnover", turnover_target, weight_initial=NULL, enabled=TRUE, message=FALSE, ...){
841+
turnover_constraint <- function(type="turnover", turnover_target, turnover_penalty=NULL, weight_initial=NULL, enabled=TRUE, message=FALSE, ...){
841842
Constraint <- constraint_v2(type, enabled=enabled, constrclass="turnover_constraint", ...)
842843
Constraint$turnover_target <- turnover_target
843844
Constraint$weight_initial <- weight_initial
845+
Constraint$turnover_penalty <- turnover_penalty
844846
return(Constraint)
845847
}
846848

@@ -1225,6 +1227,7 @@ check_constraints <- function(weights, portfolio){
12251227
group_pos <- constraints$group_pos
12261228
div_target <- constraints$div_target
12271229
turnover_target <- constraints$turnover_target
1230+
turnover_penalty <- constraints$turnover_penalty
12281231
weight_initial <- constraints$weight_initial
12291232
max_pos <- constraints$max_pos
12301233
max_pos_long <- constraints$max_pos_long

R/optimize.portfolio.R

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2988,11 +2988,21 @@ optimize.portfolio <- optimize.portfolio_v2 <- function(
29882988

29892989
## turnover constraint
29902990
if(!is.null(constraints$turnover_target)){
2991+
# set weight initial
29912992
if(is.null(constraints$weight_initial)){
29922993
weight_initial <- rep(1/N, N)
29932994
} else {
29942995
weight_initial <- constraints$weight_initial
29952996
}
2997+
# penalty for minvar
2998+
if(tmpname == "StdDev"){
2999+
if(is.null(constraints$turnover_penalty)){
3000+
sigma_value_penalty = nearPD(sigma_value)$mat
3001+
} else {
3002+
sigma_value_penalty = sigma_value + diag(constraints$turnover_penalty, N)
3003+
}
3004+
obj <- CVXR::quad_form(wts - weight_initial, sigma_value_penalty) + 2 * t(wts - weight_initial) %*% sigma_value %*% weight_initial + t(weight_initial) %*% sigma_value %*% weight_initial
3005+
}
29963006
constraints_cvxr = append(constraints_cvxr, sum(abs(wts - weight_initial)) <= constraints$turnover_target)
29973007
}
29983008

@@ -3349,22 +3359,56 @@ optimize.portfolio.rebalancing <- function(R, portfolio=NULL, constraints=NULL,
33493359
training_period <- rolling_window
33503360

33513361
if(is.null(training_period)) {if(nrow(R)<36) training_period=nrow(R) else training_period=36}
3352-
if (is.null(rolling_window)){
3353-
# define the index endpoints of our periods
3354-
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
3355-
# now apply optimize.portfolio to the periods, in parallel if available
3356-
ep <- ep.i[1]
3357-
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
3358-
optimize.portfolio(R[1:ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
3362+
3363+
# turnover weight_initial is previous time point optimal weight
3364+
turnover_idx <- which(sapply(portfolio$constraints, function(x) x$type == "turnover"))
3365+
if(length(turnover_idx) > 0){
3366+
turnover_idx <- turnover_idx[1]
3367+
# original weight_initial
3368+
if(is.null(portfolio$constraints[[turnover_idx]]$weight_initial)){
3369+
portfolio$constraints[[turnover_idx]]$weight_initial <- rep(1/length(portfolio$assets), length(portfolio$assets))
3370+
}
3371+
3372+
# rebalancing
3373+
if (is.null(rolling_window)){
3374+
# define the index endpoints of our periods
3375+
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
3376+
# now apply optimize.portfolio to the periods, in parallel if available
3377+
ep <- ep.i[1]
3378+
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
3379+
opt <- optimize.portfolio(R[1:ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
3380+
portfolio$constraints[[turnover_idx]]$weight_initial <- opt$weights
3381+
opt
3382+
}
3383+
} else {
3384+
# define the index endpoints of our periods
3385+
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
3386+
# now apply optimize.portfolio to the periods, in parallel if available
3387+
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
3388+
opt <- optimize.portfolio(R[(ifelse(ep-rolling_window>=1,ep-rolling_window,1)):ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
3389+
portfolio$constraints[[turnover_idx]]$weight_initial <- opt$weights
3390+
opt
3391+
}
33593392
}
33603393
} else {
3361-
# define the index endpoints of our periods
3362-
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
3363-
# now apply optimize.portfolio to the periods, in parallel if available
3364-
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
3365-
optimize.portfolio(R[(ifelse(ep-rolling_window>=1,ep-rolling_window,1)):ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
3394+
if (is.null(rolling_window)){
3395+
# define the index endpoints of our periods
3396+
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
3397+
# now apply optimize.portfolio to the periods, in parallel if available
3398+
ep <- ep.i[1]
3399+
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
3400+
optimize.portfolio(R[1:ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
3401+
}
3402+
} else {
3403+
# define the index endpoints of our periods
3404+
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
3405+
# now apply optimize.portfolio to the periods, in parallel if available
3406+
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
3407+
optimize.portfolio(R[(ifelse(ep-rolling_window>=1,ep-rolling_window,1)):ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
3408+
}
33663409
}
33673410
}
3411+
33683412
# out_list is a list where each element is an optimize.portfolio object
33693413
# at each rebalance date
33703414
names(out_list)<-index(R[ep.i])

0 commit comments

Comments
 (0)