###############################################################################1# R (https://r-project.org/) Numeric Methods for Optimization of Portfolios2#3# Copyright (c) 2004-2021 Brian G. Peterson, Peter Carl, Ross Bennett, Kris Boudt4#5# This library is distributed under the terms of the GNU Public License (GPL)6# for full details see the file COPYING7#8# $Id$9#10###############################################################################1112# functions to build portfolios for use by the optimizer1314# this code may be made obsolete by the advanced (non-linear, MIP) fPortfolio or roi optimizers, but for now, they are beta code at best1516# requireNamespace(LSPM) # for the un-exported .nPri functions17# generate all feasible portfolios18#LSPM:::.nPri(n=13,r=45,i=n^r,replace=TRUE)19# not likely to actually BE feasible for any portfolio of real size, but I'll write the grid generator anyway that will generate all the permutations, and kick out only the feasible portfolios202122# random portfolios232425#' create a sequence of possible weights for random or brute force portfolios26#'27#' This function creates the sequence of min<->max weights for use by28#' random or brute force optimization engines.29#'30#' The sequence created is not constrained by asset.31#'32#' @param min minimum value of the sequence33#' @param max maximum value of the sequence34#' @param by number to increment the sequence by35#' @param rounding integrer how many decimals should we round to36#' @author Peter Carl, Brian G. Peterson37#' @seealso \code{\link{constraint}}, \code{\link{objective}}38#' @export39generatesequence <- function (min=.01, max=1, by=min/max, rounding=3 )40{41# this creates the sequence of possible weights, not constrained by asset42ret <- seq(from = round(min,rounding), to = round(max,rounding), by = by)43return(ret)44}4546#' Random portfolio sample method47#'48#' This function generates random permutations of a portfolio seed meeting49#' leverage and box constraints. The final step is to run \code{\link{fn_map}}50#' on the random portfolio weights to transform the weights so they satisfy51#' other constraints such as group or position limit constraints. This is the52#' 'sample' method for random portfolios and is based on an idea by Pat Burns.53#'54#' @param rpconstraints an object of type "constraints" specifying the constraints for the optimization, see \code{\link{constraint}}55#' @param max_permutations integer: maximum number of iterations to try for a valid portfolio, default 20056#' @param rounding integer how many decimals should we round to57#' @return named weights vector58#' @author Peter Carl, Brian G. Peterson, (based on an idea by Pat Burns)59#' @export60randomize_portfolio_v1 <- function (rpconstraints, max_permutations=200, rounding=3)6162{ # @author: Peter Carl, Brian Peterson (based on an idea by Pat Burns)63# generate random permutations of a portfolio seed meeting your constraints on the weights of each asset64# set the portfolio to the seed65seed=rpconstraints$assets66nassets= length(seed)67min_mult=rpconstraints$min_mult68if(is.null(min_mult)) min_mult= rep(-Inf,nassets)69max_mult=rpconstraints$max_mult70if(is.null(max_mult)) max_mult= rep(Inf,nassets)71min_sum =rpconstraints$min_sum72max_sum =rpconstraints$max_sum73weight_seq=rpconstraints$weight_seq74portfolio=as.vector(seed)75max = rpconstraints$max76min = rpconstraints$min77rownames(portfolio)<-NULL78weight_seq=as.vector(weight_seq)79# initialize our loop80permutations=18182# create a temporary portfolio so we don't return a non-feasible portfolio83tportfolio=portfolio84# first randomly permute each element of the temporary portfolio85random_index <- sample(1:length(tportfolio),length(tportfolio))86for (i in 1:length(tportfolio)) {87cur_index<-random_index[i]88cur_val <- tportfolio[cur_index]89# randomly permute a random portfolio element90tportfolio[cur_index]<-sample(weight_seq[(weight_seq>=cur_val*min_mult[cur_index]) & (weight_seq<=cur_val*max_mult[cur_index]) & (weight_seq<=max[cur_index]) & (weight_seq>=min[cur_index])],1)91}9293#while portfolio is outside min/max sum and we have not reached max_permutations94while ((sum(tportfolio)<=min_sum | sum(tportfolio)>=max_sum) & permutations<=max_permutations) {95permutations=permutations+196# check our box constraints on total portfolio weight97# reduce(increase) total portfolio size till you get a match98# 1> check to see which bound you've failed on, brobably set this as a pair of while loops99# 2> randomly select a column and move only in the direction *towards the bound*, maybe call a function inside a function100# 3> check and repeat101random_index <- sample(1:length(tportfolio), length(tportfolio))102i = 1103while (sum(tportfolio)<=min_sum & i<=length(tportfolio)) {104# randomly permute and increase a random portfolio element105cur_index<-random_index[i]106cur_val <- tportfolio[cur_index]107if (length(weight_seq[(weight_seq>=cur_val)&(weight_seq<=max[cur_index])])>1)108{109# randomly sample one of the larger weights110tportfolio[cur_index]<-sample(weight_seq[(weight_seq>=cur_val)&(weight_seq<=max[cur_index])],1)111# print(paste("new val:",tportfolio[cur_index]))112} else {113if (length(weight_seq[(weight_seq>=cur_val)&(weight_seq<=max[cur_index])]) == 1) {114tportfolio[cur_index]<-weight_seq[(weight_seq>=cur_val)&(weight_seq<=max[cur_index])]115}116}117i=i+1 # increment our counter118} # end increase loop119while (sum(tportfolio)>=max_sum & i<=length(tportfolio)) {120# randomly permute and decrease a random portfolio element121cur_index<-random_index[i]122cur_val <- tportfolio[cur_index]123if (length(weight_seq[(weight_seq<=cur_val) & (weight_seq>=min[cur_index])] )>1) {124tportfolio[cur_index]<-sample(weight_seq[(weight_seq<=cur_val) & (weight_seq>=min[cur_index] )],1)125} else {126if (length(weight_seq[(weight_seq<=cur_val) & (weight_seq>=min[cur_index])] )==1) {127tportfolio[cur_index]<-weight_seq[(weight_seq<=cur_val) & (weight_seq>=min[cur_index])]128}129}130i=i+1 # increment our counter131} # end decrease loop132} # end final walk towards the edges133134portfolio<-tportfolio135136colnames(portfolio)<-colnames(seed)137if (sum(portfolio)<=min_sum | sum(tportfolio)>=max_sum){138portfolio <- seed139warning("Infeasible portfolio created, defaulting to seed, perhaps increase max_permutations.")140}141if(isTRUE(all.equal(seed,portfolio))) {142if (sum(seed)>=min_sum & sum(seed)<=max_sum) {143warning("Unable to generate a feasible portfolio different from seed, perhaps adjust your parameters.")144return(seed)145} else {146warning("Unable to generate a feasible portfolio, perhaps adjust your parameters.")147return(NULL)148}149}150return(portfolio)151}152153#' deprecated random portfolios wrapper until we write a random trades function154#'155#'156#' @param ... any other passthru parameters157#' @author bpeterson158#' @export159random_walk_portfolios <-function(...) {160# wrapper function protect older code for now?161random_portfolios(...=...)162}163164#' generate an arbitary number of constrained random portfolios165#'166#' repeatedly calls \code{\link{randomize_portfolio}} to generate an167#' arbitrary number of constrained random portfolios.168#'169#' @param rpconstraints an object of type "constraints" specifying the constraints for the optimization, see \code{\link{constraint}}170#' @param permutations integer: number of unique constrained random portfolios to generate171#' @param \dots any other passthru parameters172#' @return matrix of random portfolio weights173#' @seealso \code{\link{constraint}}, \code{\link{objective}}, \code{\link{randomize_portfolio}}174#' @author Peter Carl, Brian G. Peterson, (based on an idea by Pat Burns)175#' @examples176#' rpconstraint<-constraint_v1(assets=10,177#' min_mult=-Inf,178#' max_mult=Inf,179#' min_sum=.99,180#' max_sum=1.01,181#' min=.01,182#' max=.4,183#' weight_seq=generatesequence())184#'185#' rp<- random_portfolios_v1(rpconstraints=rpconstraint,permutations=1000)186#' head(rp)187#' @export188random_portfolios_v1 <- function (rpconstraints,permutations=100,...)189{ #190# this function generates a series of portfolios that are a "random walk" from the current portfolio191seed=rpconstraints$assets192result <- matrix(nrow=permutations, ncol=length(seed))193result[1,]<-seed194result[2,]<-rep(1/length(seed),length(seed))195# rownames(result)[1]<-"seed.portfolio"196# rownames(result)[2]<-"equal.weight"197i <- 3198while (i<=permutations) {199result[i,] <- as.matrix(randomize_portfolio_v1(rpconstraints=rpconstraints, ...))200if(i==permutations) {201result = unique(result)202i = nrow(result)203result = rbind(result, matrix(nrow=(permutations-i),ncol=length(seed)))204}205i<-i+1206}207colnames(result)<-names(seed)208return(result)209}210211#' version 2 generate random permutations of a portfolio seed meeting your constraints on the weights of each asset212#'213#' @param portfolio an object of type "portfolio" specifying the constraints for the optimization, see \code{\link{portfolio.spec}}214#' @param max_permutations integer: maximum number of iterations to try for a valid portfolio, default 200215#' @return named weighting vector216#' @author Peter Carl, Brian G. Peterson, (based on an idea by Pat Burns)217#' @aliases randomize_portfolio randomize_portfolio_v2218#' @rdname randomize_portfolio219#' @export randomize_portfolio220#' @export randomize_portfolio_v2221randomize_portfolio <- randomize_portfolio_v2 <- function (portfolio, max_permutations=200) {222# generate random permutations of a portfolio seed meeting your constraints on the weights of each asset223# set the portfolio to the seed224seed <- portfolio$assets225nassets <- length(seed)226227# get the constraints from the portfolio object228constraints <- get_constraints(portfolio)229230min_mult <- constraints$min_mult231if(is.null(min_mult)) min_mult <- rep(-Inf,nassets)232max_mult <- constraints$max_mult233if(is.null(max_mult)) max_mult <- rep(Inf,nassets)234min_sum <- constraints$min_sum235max_sum <- constraints$max_sum236# randomize_portfolio will rarely find a feasible portfolio if there is not some237# 'wiggle room' between min_sum and max_sum238if((max_sum - min_sum) < 0.02){239min_sum <- min_sum - 0.01240max_sum <- max_sum + 0.01241}242weight_seq <- portfolio$weight_seq243if(is.null(weight_seq)){244weight_seq <- generatesequence(min=min(constraints$min), max=max(constraints$max), by=0.002)245}246weight_seq <- as.vector(weight_seq)247248# box constraints249max <- constraints$max250min <- constraints$min251252# If any of the constraints below do not exist in the constraints object,253# then they are NULL values which rp_transform can handle in its checks.254255# group constraints256groups <- constraints$groups257cLO <- constraints$cLO258cUP <- constraints$cUP259group_pos <- constraints$group_pos260261# position limit constraints262max_pos <- constraints$max_pos263max_pos_long <- constraints$max_pos_long264max_pos_short <- constraints$max_pos_short265266# leverage constraint267leverage <- constraints$leverage268269# initial portfolio270iportfolio <- as.vector(seed)271rownames(iportfolio) <- NULL272273# initialize our loop274permutations <- 1275276# create a temporary portfolio so we don't return a non-feasible portfolio277tportfolio <- iportfolio278# first randomly permute each element of the temporary portfolio279random_index <- sample(1:length(tportfolio), length(tportfolio))280for (i in 1:length(tportfolio)) {281cur_index <- random_index[i]282cur_val <- tportfolio[cur_index]283# randomly permute a random portfolio element284tportfolio[cur_index] <- sample(weight_seq[(weight_seq >= cur_val * min_mult[cur_index]) & (weight_seq <= cur_val * max_mult[cur_index]) & (weight_seq <= max[cur_index]) & (weight_seq >= min[cur_index])], 1)285}286287# random portfolios algorithm designed to handle multiple constraint types288fportfolio <- rp_transform(w=tportfolio,289min_sum=min_sum,290max_sum=max_sum,291min_box=min,292max_box=max,293groups=groups,294cLO=cLO,295cUP=cUP,296max_pos=max_pos,297group_pos=group_pos,298max_pos_long=max_pos_long,299max_pos_short=max_pos_short,300leverage=leverage,301weight_seq=weight_seq,302max_permutations=max_permutations)303304# #while portfolio is outside min/max sum and we have not reached max_permutations305# while ((sum(tportfolio) <= min_sum | sum(tportfolio) >= max_sum) & permutations <= max_permutations) {306# permutations <- permutations+1307# # check our box constraints on total portfolio weight308# # reduce(increase) total portfolio size till you get a match309# # 1> check to see which bound you've failed on, brobably set this as a pair of while loops310# # 2> randomly select a column and move only in the direction *towards the bound*, maybe call a function inside a function311# # 3> check and repeat312# random_index <- sample(1:length(tportfolio), length(tportfolio))313# i <- 1314# while (sum(tportfolio) <= min_sum & i <= length(tportfolio)) {315# # randomly permute and increase a random portfolio element316# cur_index <- random_index[i]317# cur_val <- tportfolio[cur_index]318# tmp_seq <- weight_seq[(weight_seq >= cur_val) & (weight_seq <= max[cur_index])]319# n_tmp_seq <- length(tmp_seq)320# if(n_tmp_seq > 1){321# # randomly sample one of the larger weights322# tportfolio[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]323# # print(paste("new val:",tportfolio[cur_index]))324# } else {325# if(n_tmp_seq == 1){326# tportfolio[cur_index] <- tmp_seq327# }328# }329# i <- i + 1 # increment our counter330# } # end increase loop331# while (sum(tportfolio) >= max_sum & i <= length(tportfolio)) {332# # randomly permute and decrease a random portfolio element333# cur_index <- random_index[i]334# cur_val <- tportfolio[cur_index]335# tmp_seq <- weight_seq[(weight_seq <= cur_val) & (weight_seq >= min[cur_index])]336# n_tmp_seq <- length(tmp_seq)337# if(n_tmp_seq > 1) {338# # randomly sample one of the smaller weights339# tportfolio[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]340# } else {341# if(n_tmp_seq == 1){342# tportfolio[cur_index] <- tmp_seq343# }344# }345# i <- i + 1 # increment our counter346# } # end decrease loop347# } # end final walk towards the edges348# # final portfolio349# fportfolio <- fn_map(weights=tportfolio, portfolio=portfolio, relax=FALSE)$weights350351colnames(fportfolio) <- colnames(seed)352if (sum(fportfolio) < min_sum | sum(fportfolio) > max_sum){353fportfolio <- seed354warning("Infeasible portfolio created, defaulting to seed, perhaps increase max_permutations.")355}356if(isTRUE(all.equal(seed, fportfolio))) {357if (sum(seed) >= min_sum & sum(seed) <= max_sum) {358warning("Unable to generate a feasible portfolio different from seed, perhaps adjust your parameters.")359return(seed)360} else {361warning("Unable to generate a feasible portfolio, perhaps adjust your parameters.")362return(NULL)363}364}365return(fportfolio)366}367368#' version 2 generate an arbitary number of constrained random portfolios369#'370#' Generate random portfolios using the 'sample', 'simplex', or 'grid' method.371#' See details.372#'373#' @details374#' Random portfolios can be generate using one of three methods.375#' \describe{376#' \item{sample: }{The 'sample' method to generate random portfolios is based377#' on an idea pioneerd by Pat Burns. This is the most flexible method, but378#' also the slowest, and can generate portfolios to satisfy leverage, box,379#' group, position limit, and leverage exposure constraints.}380#' \item{simplex: }{The 'simplex' method to generate random portfolios is381#' based on a paper by W. T. Shaw. The simplex method is useful to generate382#' random portfolios with the full investment constraint, where the sum of the383#' weights is equal to 1, and min box constraints. Values for \code{min_sum}384#' and \code{max_sum} of the leverage constraint will be ignored, the sum of385#' weights will equal 1. All other constraints such as group and position386#' limit constraints will be handled by elimination. If the constraints are387#' very restrictive, this may result in very few feasible portfolios remaining.}388#' \item{grid: }{The 'grid' method to generate random portfolios is based on389#' the \code{gridSearch} function in package 'NMOF'. The grid search method390#' only satisfies the \code{min} and \code{max} box constraints. The391#' \code{min_sum} and \code{max_sum} leverage constraints will likely be392#' violated and the weights in the random portfolios should be normalized.393#' Normalization may cause the box constraints to be violated and will be394#' penalized in \code{constrained_objective}.}395#' }396#'397#' The constraint types checked are leverage, box, group, position limit, and398#' leverage exposure. Any399#' portfolio that does not satisfy all these constraints will be eliminated. This400#' function is particularly sensitive to \code{min_sum} and \code{max_sum}401#' leverage constraints. For the sample method, there should be some402#' "wiggle room" between \code{min_sum} and \code{max_sum} in order to generate403#' a sufficient number of feasible portfolios. For example, \code{min_sum=0.99}404#' and \code{max_sum=1.01} is recommended instead of \code{min_sum=1}405#' and \code{max_sum=1}. If \code{min_sum=1} and \code{max_sum=1}, the number of406#' feasible portfolios may be 1/3 or less depending on the other constraints.407#'408#'409#' @param portfolio an object of class 'portfolio' specifying the constraints for the optimization, see \code{\link{portfolio.spec}}410#' @param permutations integer: number of unique constrained random portfolios to generate411#' @param \dots any other passthru parameters412#' @param rp_method method to generate random portfolios. Currently "sample", "simplex", or "grid". See Details.413#' @param eliminate TRUE/FALSE, eliminate portfolios that do not satisfy constraints414#' @return matrix of random portfolio weights415#' @seealso \code{\link{portfolio.spec}},416#' \code{\link{objective}},417#' \code{\link{rp_sample}},418#' \code{\link{rp_simplex}},419#' \code{\link{rp_grid}}420#' @author Peter Carl, Brian G. Peterson, Ross Bennett421#' @aliases random_portfolios random_portfolios_v2422#' @rdname random_portfolios423#' @export random_portfolios424#' @export random_portfolios_v2425random_portfolios <- random_portfolios_v2 <- function( portfolio, permutations=100, rp_method="sample", eliminate=TRUE, ...){426if(hasArg(fev)) fev=match.call(expand.dots=TRUE)$fev else fev=0:5427if(hasArg(normalize)) normalize=match.call(expand.dots=TRUE)$normalize else normalize=TRUE428switch(rp_method,429sample = {rp <- rp_sample(portfolio, permutations)430},431simplex = {rp <- rp_simplex(portfolio, permutations, fev)432},433grid = {rp <- rp_grid(portfolio, permutations, normalize)434}435)436if(eliminate){437# eliminate portfolios that do not satisfy constraints438check <- vector("numeric", nrow(rp))439for(i in 1:nrow(rp)){440check[i] <- check_constraints(weights=rp[i,], portfolio=portfolio)441}442# We probably don't need or want to do this part in parallel. It could443# also interfere with optimize.portfolio.parallel since this function444# will likely be called. Not sure how foreach handles nested loops445# in parallel so it is best to avoid that altogether.446#stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly = TRUE))447#check <- foreach(i=1:nrow(rp), .combine=c) %dopar% {448# # check_constraint returns TRUE if all constraints are satisfied449# check_constraints(weights=rp[i,], portfolio=portfolio)450#}451rp <- rp[which(check==TRUE),]452}453return(rp)454}455456#' Generate random portfolios using the sample method457#'458#' This function generates random portfolios based on an idea by Pat Burns.459#'460#' @details461#' The 'sample' method to generate random portfolios is based462#' on an idea pioneerd by Pat Burns. This is the most flexible method, but also463#' the slowest, and can generate portfolios to satisfy leverage, box, group,464#' and position limit constraints.465#' @param portfolio an object of type "portfolio" specifying the constraints for the optimization, see \code{\link{portfolio.spec}}466#' @param permutations integer: number of unique constrained random portfolios to generate467#' @param max_permutations integer: maximum number of iterations to try for a valid portfolio, default 200468#' @return a matrix of random portfolio weights469#' @export470rp_sample <- function(portfolio, permutations, max_permutations=200){471# this function generates a series of portfolios that are a "random walk" from the current portfolio472seed <- portfolio$assets473result <- matrix(nrow=permutations, ncol=length(seed))474result[1,] <- seed475result[2,] <- rep(1/length(seed),length(seed))476# rownames(result)[1]<-"seed.portfolio"477# rownames(result)[2]<-"equal.weight"478for(i in 3:permutations) {479#result[i,] <- as.matrix(randomize_portfolio_v2(portfolio=portfolio, ...))480result[i,] <- randomize_portfolio_v2(portfolio=portfolio, max_permutations=max_permutations)481}482result <- unique(result)483# i <- nrow(result)484# result <- rbind(result, matrix(nrow=(permutations-i), ncol=length(seed)))485colnames(result) <- names(seed)486return(result)487}488489#' Generate random portfolios using the simplex method490#'491#' This function generates random portfolios based on the method outlined in the492#' Shaw paper. Need to add reference.493#'494#' @details495#' The simplex method is useful to generate random portfolios with the full496#' investment constraint where the sum of the weights is equal to 1 and min497#' box constraints with no upper bound on max constraints. Values for min_sum498#' and max_sum will be ignored, the sum of weights will equal 1. All other499#' constraints such as group and position limit constraints will be handled by500#' elimination. If the constraints are very restrictive, this may result in501#' very few feasible portfolios remaining.502#'503#' The random portfolios are created by first generating a set of uniform504#' random numbers.505#' \deqn{U \sim [0, 1]}506#' The portfolio weights are then transformed to satisfy the min of the507#' box constraints.508#' \deqn{w_{i} = min_{i} + (1 - \sum_{j=1}^{N} min_{j}) \frac{log(U_{i}^{q}}{\sum_{k=1}^{N}log(U_{k}^{q}}}509#'510#' \code{fev} controls the Face-Edge-Vertex (FEV) biasing where \deqn{q=2^{fev}}511#' As \code{q} approaches infinity, the set of weights will be concentrated in a512#' single asset. To sample the interior and exterior, \code{fev} can be passed513#' in as a vector. The number of portfolios, \code{permutations}, and the514#' length of \code{fev} affect how the random portfolios are generated. For515#' example, if \code{permutations=10000} and \code{fev=0:4}, 2000 portfolios will516#' be generated for each value of \code{fev}.517#'518#' @param portfolio an object of class 'portfolio' specifying the constraints for the optimization, see \code{\link{portfolio.spec}}519#' @param permutations integer: number of unique constrained random portfolios to generate520#' @param fev scalar or vector for FEV biasing521#' @return a matrix of random portfolio weights522#' @export523rp_simplex <- function(portfolio, permutations, fev=0:5){524# get the assets from the portfolio525assets <- portfolio$assets526nassets <- length(assets)527528# get the constraints529# the simplex method for generating random portfolios requires that the sum of weights is equal to 1530# ignore the min_sum and max_sum constraints531constraints <- get_constraints(portfolio)532L <- constraints$min533534# number of portfolios for each fev to generate535k <- ceiling(permutations / length(fev))536537# generate uniform[0, 1] random numbers538U <- runif(n=k*nassets, 0, 1)539Umat <- matrix(data=U, nrow=k, ncol=nassets)540541# do the transformation to the set of weights to satisfy lower bounds542stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly = TRUE))543j <- 1544i <- 1545out <- foreach::foreach(j = 1:length(fev), .combine=c) %:% foreach::foreach(i=1:nrow(Umat)) %dopar% {546q <- 2^fev[j]547tmp <- L + (1 - sum(L)) * log(Umat[i,])^q / sum(log(Umat[i,])^q)548tmp549}550# the foreach loop returns a list of each random portfolio551out <- do.call(rbind, out)552return(out)553}554555#' Generate random portfolios based on grid search method556#'557#' This function generates random portfolios based on the \code{gridSearch}558#' function from the 'NMOF' package.559#'560#' @details561#' The number of levels is calculated based on permutations and number of assets.562#' The number of levels must be an integer and may not result in the exact number563#' of permutations. We round up to the nearest integer for the levels so the564#' number of portfolios generated will be greater than or equal to permutations.565#'566#' The grid search method only satisfies the \code{min} and \code{max} box567#' constraints. The \code{min_sum} and \code{max_sum} leverage constraints will568#' likely be violated and the weights in the random portfolios should be569#' normalized. Normalization may cause the box constraints to be violated and570#' will be penalized in \code{constrained_objective}.571#'572#' @param portfolio an object of class 'portfolio' specifying the constraints for the optimization, see \code{\link{portfolio.spec}}573#' @param permutations integer: number of unique constrained random portfolios to generate574#' @param normalize TRUE/FALSE to normalize the weghts to satisfy min_sum or max_sum575#' @return matrix of random portfolio weights576#' @export577rp_grid <- function(portfolio, permutations=2000, normalize=TRUE){578579# get the constraints from the portfolio580constraints <- get_constraints(portfolio)581582# box constraints to generate the grid583min <- constraints$min584max <- constraints$max585586# number of parameters and length.out levels to generate587npar <- length(min)588n <- ceiling(exp(log(permutations) / npar))589590levels <- vector("list", length = length(min))591for (i in seq_len(npar)){592levels[[i]] <- seq(min[[i]], max[[i]], length.out = max(n, 2L))593}594np <- length(levels)595res <- vector("list", np)596rep.fac <- 1L597nl <- sapply(levels, length)598nlp <- prod(nl)599600# create the grid601for (i in seq_len(np)) {602x <- levels[[i]]603nx <- length(x)604nlp <- nlp/nx605res[[i]] <- x[rep.int(rep.int(seq_len(nx), rep.int(rep.fac, nx)), nlp)]606rep.fac <- rep.fac * nx607}608609# create the random portfolios from the grid610nlp <- prod(nl)611lstLevels <- vector("list", length = nlp)612for (r in seq_len(nlp)) {613lstLevels[[r]] <- sapply(res, `[[`, r)614}615# lstLevels is a list of random portfolios, rbind into a matrix616rp <- do.call(rbind, lstLevels)617618# min_sum and max_sum will likely be violated619# Normalization will likely cause min and max to be violated. This can be620# handled by the penalty in constrained_objective.621if(normalize){622normalize_weights <- function(weights){623# normalize results if necessary624if(!is.null(constraints$min_sum) | !is.null(constraints$max_sum)){625# the user has passed in either min_sum or max_sum constraints for the portfolio, or both.626# we'll normalize the weights passed in to whichever boundary condition has been violated627# NOTE: this means that the weights produced by a numeric optimization algorithm like DEoptim628# might violate your constraints, so you'd need to renormalize them after optimizing629# we'll create functions for that so the user is less likely to mess it up.630631# NOTE: need to normalize in the optimization wrapper too before we return, since we've normalized in here632# In Kris' original function, this was manifested as a full investment constraint633if(!is.null(constraints$max_sum) & constraints$max_sum != Inf ) {634max_sum=constraints$max_sum635if(sum(weights)>max_sum) { weights<-(max_sum/sum(weights))*weights } # normalize to max_sum636}637638if(!is.null(constraints$min_sum) & constraints$min_sum != -Inf ) {639min_sum=constraints$min_sum640if(sum(weights)<min_sum) { weights<-(min_sum/sum(weights))*weights } # normalize to min_sum641}642643} # end min_sum and max_sum normalization644return(weights)645}646647stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly = TRUE))648out <- foreach::foreach(i=1:nrow(rp)) %dopar% {649tmp <- normalize_weights(weights=rp[i,])650tmp651}652out <- do.call(rbind, out)653out <- na.omit(out)654}655if(normalize) return(out) else return(rp)656}657658# function to generate a set of random portfolios for each portfolio and return the superset659# this is primarily for use in optimize.portfolio.rebalancing660rp.regime.portfolios <- function(regime, permutations=100, rp_method="sample", eliminate=TRUE, ...){661if(!inherits(regime, "regime.portfolios")) stop("regime must be an object of class 'regime.portfolios'")662portf <- regime$portfolio.list663nportf <- length(portf)664rp.list <- vector("list", nportf)665for(i in 1:nportf){666rp.list[[i]] <- random_portfolios(portf[[i]], permutations=permutations, rp_method=rp_method, eliminate=eliminate, ...=...)667}668# rbind the list of matrices together and remove any duplicates669out <- unique(do.call("rbind", rp.list))670return(out)671}672673# EXAMPLE: start_t<- Sys.time(); x=random_walk_portfolios(rep(1/5,5), generatesequence(min=0.01, max=0.30, by=0.01), max_permutations=500, permutations=5000, min_sum=.99, max_sum=1.01); end_t<-Sys.time(); end_t-start_t;674# > nrow(unique(x))675# [1] 4906676# > which(rowSums(x)<.99 | rowSums(x)>1.01)677# integer(0)678679# start_t <- Sys.time(); s<-foreach(seed=iter(weights, by='row'),.combine=rbind) %dopar% random_walk_portfolios(seed,xseq,permutations=10000); end_t <- Sys.time(); save.image(); start_t-end_t;680681# TODO: write a function for random trades that only makes n trades and increases/decreases other elements to compensate.682683684