1#' Asset Ranking2#'3#' Compute the first moment from a single complete sort4#'5#' This function computes the estimated centroid vector from a single complete6#' sort using the analytical approximation as described in R. Almgren and7#' N. Chriss, "Portfolios from Sorts". The centroid is estimated and then8#' scaled such that it is on a scale similar to the asset returns. By default,9#' the centroid vector is scaled according to the median of the asset mean10#' returns.11#'12#' @param R xts object of asset returns13#' @param order a vector of indexes of the relative ranking of expected asset14#' returns in ascending order. For example, \code{order = c(2, 3, 1, 4)} means15#' that the expected returns of \code{R[,2] < R[,3], < R[,1] < R[,4]}.16#' @param \dots any other passthrough parameters17#'18#' @return The estimated first moments based on ranking views19#'20#' @references21#' R. Almgren and N. Chriss, "Portfolios from Sorts"22#' \url{https://papers.ssrn.com/sol3/papers.cfm?abstract_id=720041}23#'24#' @examples25#' data(edhec)26#' R <- edhec[,1:4]27#' ac.ranking(R, c(2, 3, 1, 4))28#' @seealso \code{\link{centroid.complete.mc}} \code{\link{centroid.sectors}}29#' \code{\link{centroid.sign}} \code{\link{centroid.buckets}}30#' @export31ac.ranking <- function(R, order, ...){32if(length(order) != ncol(R)) stop("The length of the order vector must equal the number of assets")33nassets <- ncol(R)34if(hasArg(max.value)) {35max.value <- match.call(expand.dots=TRUE)$max.value36} else {37max.value <- median(colMeans(R))38}39# Compute the scaled centroid40c_hat <- scale_range(centroid(nassets), max.value)4142# Here we reorder the vector such that the highest centroid value is assigned43# to the asset index with the highest expected return and so on and so forth44# until the smallest centroid value is assigned to the asset index with the45# lowest expected return. The asset index with the lowest expected return46# is order[1]47out <- vector("numeric", nassets)48out[rev(order)] <- c_hat49return(out)50}5152# compute the centroid for a single complete sort53centroid <- function(n){54# Analytical solution to the centroid for single complete sort55# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=72004156A <- 0.442457B <- 0.118558beta <- 0.2159alpha <- A - B * n^(-beta)60j <- seq(from=1, to=n, by=1)61c_hat <- qnorm((n + 1 - j - alpha) / (n - 2 * alpha + 1))62c_hat63}6465# If we used the unscaled centroid vector in the optimization, the optimal66# portfolio would be correct but anything that uses moments$mu will not make67# sense6869# What is a valid value for max.value?70# - by default we use the median of the asset mean returns71scale_range <- function(x, max.value){72new.max <- 0.0573new.min <- -new.max74old.range <- max(x) - min(x)75new.range <- new.max - new.min76((x - min(x)) * new.range) / old.range + new.min77}7879# Numerically compute the centroid for different cases as described in80# the Almgren and Chriss paper.8182#' Complete Cases Centroid83#'84#' Numerical method to estimate complete cases centroid85#' @param order a vector of indexes of the relative ranking of expected asset86#' returns in ascending order. For example, \code{order = c(2, 3, 1, 4)}87#' expresses a view on the expected returns such that88#' R_2 < R_3 < R_1 < R_489#' @param simulations number of simulations90#' @return the centroid vector91#' @examples92#' # Express a view on the assets such that93#' # R_2 < R_1 < R_3 < R_494#' centroid.complete.mc(c(2, 1, 3, 4))95#' @author Ross Bennett96#' @export97centroid.complete.mc <- function(order, simulations=1000){98n <- length(order)99c_hat <- matrix(0, simulations, n)100for(i in 1:simulations){101c_hat[i,] <- sort(rnorm(n), decreasing=TRUE)102}103out <- vector("numeric", n)104out[rev(order)] <- colMeans(c_hat)105return(out)106}107108#' Multiple Sectors Centroid109#'110#' Compute the centroid for expressing views on the relative ranking of assets111#' within sectors.112#'113#' @param sectors a list where each list element contains the order of each114#' asset in the given sector115#' @param simulations number of simulations116#' @return the centroid vector117#' @examples118#' # Express a view on the assets in two sectors119#' # Sector 1 View: R_2 < R_1 < R_3120#' # Sector 2 View: R_5 < R_4121#' x <- list()122#' x[[1]] <- c(2, 1, 3)123#' x[[2]] <- c(5, 4)124#' centroid.sectors(x)125#' @author Ross Bennett126#' @export127centroid.sectors <- function(sectors, simulations=1000){128if(!is.list(sectors)) stop("sectors must be a list")129130# Get the number of assets and number of sectors131nassets <- length(unlist(sectors))132nsectors <- length(sectors)133134# Compute the centroid for each sector and combine at the end135sim.list <- vector("list", nsectors)136for(j in 1:nsectors){137# number of assets in sector j138n <- length(sectors[[j]])139out <- matrix(0, simulations, n)140for(i in 1:simulations){141out[i,] <- sort(rnorm(n), decreasing=TRUE)142}143sim.list[[j]] <- out144}145c_hat <- lapply(sim.list, colMeans)146out <- vector("numeric", nassets)147for(i in 1:length(c_hat)){148out[rev(sectors[[i]])] <- c_hat[[i]]149}150return(out)151}152153#' Positive and Negative View Centroid154#'155#' Compute the centroid for expressing a view on assets with positive or156#' negative expected returns157#'158#' @param positive a vector of the index of assets with positive expected159#' return in ascending order160#' @param negative a vector of the index of assets with negative expected161#' return in ascending order.162#' @param simulations number of simulations163#' @return the centroid vector164#' @examples165#' # Express a view that166#' # R_1 < R_2 < 0 < R_3 < R_4167#' centroid.sign(c(1, 2), c(4, 3))168#' @author Ross Bennett169#' @export170centroid.sign <- function(positive, negative, simulations=1000){171172# Number of positive and negative assets173pos <- length(positive)174neg <- length(negative)175nassets <- pos + neg176177c_hat <- matrix(0, simulations, nassets)178for(i in 1:simulations){179tmp <- rnorm(nassets)180# subset the positive and negative assets181tmp.pos <- tmp[1:pos]182tmp.neg <- tmp[(pos+1):(pos+neg)]183184# Sign correct the positive assets185idx <- which(tmp.pos < 0)186if(length(idx) != 0){187tmp.pos[idx] <- -1 * tmp.pos[idx]188}189190# Sign correct the negative assets191idx <- which(tmp.neg > 0)192if(length(idx) != 0){193tmp.neg[idx] <- -1 * tmp.neg[idx]194}195c_hat[i,] <- sort(c(tmp.pos, tmp.neg), decreasing=TRUE)196}197xx <- colMeans(c_hat)198out <- vector("numeric", nassets)199out[rev(positive)] <- xx[1:pos]200out[rev(negative)] <- xx[(1+pos):(pos+neg)]201return(out)202}203204#' Buckets Centroid205#'206#' Compute the centroid for buckets of assets207#'208#' A common use of buckets is to divide the assets into quartiles or deciles,209#' but is generalized here for an arbitrary number of buckets and arbitrary210#' number of assets in each bucket.211#'212#' @param buckets a list where each element contains the index of the assets in213#' the respective bucket. The assets within each bucket have no order.214#' The bucket elements are in ascending order such that215#' R_bucket_1 < ... < R_bucket_n216#' @param simulations number of simulations217#' @return the centroid vector218#' @author Ross Bennett219#' @export220centroid.buckets <- function(buckets, simulations=1000){221if(!is.list(buckets)) stop("buckets must be a list")222223# number of assets and buckets224nassets <- length(unlist(buckets))225nbuckets <- length(buckets)226227# Run simulations so we simulate n values for n buckets and then replicate228# that value for the number of assets in the given bucket229c_hat <- matrix(0, simulations, nbuckets)230for(i in 1:simulations){231c_hat[i,] <- sort(rnorm(nbuckets), decreasing=TRUE)232}233xx <- colMeans(c_hat)234out <- vector("numeric", nassets)235for(j in 1:nbuckets){236out[buckets[[j]]] <- xx[j]237}238return(out)239}240241242