Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/ac_ranking.R
1433 views
1
2
#' Asset Ranking
3
#'
4
#' Compute the first moment from a single complete sort
5
#'
6
#' This function computes the estimated centroid vector from a single complete
7
#' sort using the analytical approximation as described in R. Almgren and
8
#' N. Chriss, "Portfolios from Sorts". The centroid is estimated and then
9
#' scaled such that it is on a scale similar to the asset returns. By default,
10
#' the centroid vector is scaled according to the median of the asset mean
11
#' returns.
12
#'
13
#' @param R xts object of asset returns
14
#' @param order a vector of indexes of the relative ranking of expected asset
15
#' returns in ascending order. For example, \code{order = c(2, 3, 1, 4)} means
16
#' that the expected returns of \code{R[,2] < R[,3], < R[,1] < R[,4]}.
17
#' @param \dots any other passthrough parameters
18
#'
19
#' @return The estimated first moments based on ranking views
20
#'
21
#' @references
22
#' R. Almgren and N. Chriss, "Portfolios from Sorts"
23
#' \url{https://papers.ssrn.com/sol3/papers.cfm?abstract_id=720041}
24
#'
25
#' @examples
26
#' data(edhec)
27
#' R <- edhec[,1:4]
28
#' ac.ranking(R, c(2, 3, 1, 4))
29
#' @seealso \code{\link{centroid.complete.mc}} \code{\link{centroid.sectors}}
30
#' \code{\link{centroid.sign}} \code{\link{centroid.buckets}}
31
#' @export
32
ac.ranking <- function(R, order, ...){
33
if(length(order) != ncol(R)) stop("The length of the order vector must equal the number of assets")
34
nassets <- ncol(R)
35
if(hasArg(max.value)) {
36
max.value <- match.call(expand.dots=TRUE)$max.value
37
} else {
38
max.value <- median(colMeans(R))
39
}
40
# Compute the scaled centroid
41
c_hat <- scale_range(centroid(nassets), max.value)
42
43
# Here we reorder the vector such that the highest centroid value is assigned
44
# to the asset index with the highest expected return and so on and so forth
45
# until the smallest centroid value is assigned to the asset index with the
46
# lowest expected return. The asset index with the lowest expected return
47
# is order[1]
48
out <- vector("numeric", nassets)
49
out[rev(order)] <- c_hat
50
return(out)
51
}
52
53
# compute the centroid for a single complete sort
54
centroid <- function(n){
55
# Analytical solution to the centroid for single complete sort
56
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=720041
57
A <- 0.4424
58
B <- 0.1185
59
beta <- 0.21
60
alpha <- A - B * n^(-beta)
61
j <- seq(from=1, to=n, by=1)
62
c_hat <- qnorm((n + 1 - j - alpha) / (n - 2 * alpha + 1))
63
c_hat
64
}
65
66
# If we used the unscaled centroid vector in the optimization, the optimal
67
# portfolio would be correct but anything that uses moments$mu will not make
68
# sense
69
70
# What is a valid value for max.value?
71
# - by default we use the median of the asset mean returns
72
scale_range <- function(x, max.value){
73
new.max <- 0.05
74
new.min <- -new.max
75
old.range <- max(x) - min(x)
76
new.range <- new.max - new.min
77
((x - min(x)) * new.range) / old.range + new.min
78
}
79
80
# Numerically compute the centroid for different cases as described in
81
# the Almgren and Chriss paper.
82
83
#' Complete Cases Centroid
84
#'
85
#' Numerical method to estimate complete cases centroid
86
#' @param order a vector of indexes of the relative ranking of expected asset
87
#' returns in ascending order. For example, \code{order = c(2, 3, 1, 4)}
88
#' expresses a view on the expected returns such that
89
#' R_2 < R_3 < R_1 < R_4
90
#' @param simulations number of simulations
91
#' @return the centroid vector
92
#' @examples
93
#' # Express a view on the assets such that
94
#' # R_2 < R_1 < R_3 < R_4
95
#' centroid.complete.mc(c(2, 1, 3, 4))
96
#' @author Ross Bennett
97
#' @export
98
centroid.complete.mc <- function(order, simulations=1000){
99
n <- length(order)
100
c_hat <- matrix(0, simulations, n)
101
for(i in 1:simulations){
102
c_hat[i,] <- sort(rnorm(n), decreasing=TRUE)
103
}
104
out <- vector("numeric", n)
105
out[rev(order)] <- colMeans(c_hat)
106
return(out)
107
}
108
109
#' Multiple Sectors Centroid
110
#'
111
#' Compute the centroid for expressing views on the relative ranking of assets
112
#' within sectors.
113
#'
114
#' @param sectors a list where each list element contains the order of each
115
#' asset in the given sector
116
#' @param simulations number of simulations
117
#' @return the centroid vector
118
#' @examples
119
#' # Express a view on the assets in two sectors
120
#' # Sector 1 View: R_2 < R_1 < R_3
121
#' # Sector 2 View: R_5 < R_4
122
#' x <- list()
123
#' x[[1]] <- c(2, 1, 3)
124
#' x[[2]] <- c(5, 4)
125
#' centroid.sectors(x)
126
#' @author Ross Bennett
127
#' @export
128
centroid.sectors <- function(sectors, simulations=1000){
129
if(!is.list(sectors)) stop("sectors must be a list")
130
131
# Get the number of assets and number of sectors
132
nassets <- length(unlist(sectors))
133
nsectors <- length(sectors)
134
135
# Compute the centroid for each sector and combine at the end
136
sim.list <- vector("list", nsectors)
137
for(j in 1:nsectors){
138
# number of assets in sector j
139
n <- length(sectors[[j]])
140
out <- matrix(0, simulations, n)
141
for(i in 1:simulations){
142
out[i,] <- sort(rnorm(n), decreasing=TRUE)
143
}
144
sim.list[[j]] <- out
145
}
146
c_hat <- lapply(sim.list, colMeans)
147
out <- vector("numeric", nassets)
148
for(i in 1:length(c_hat)){
149
out[rev(sectors[[i]])] <- c_hat[[i]]
150
}
151
return(out)
152
}
153
154
#' Positive and Negative View Centroid
155
#'
156
#' Compute the centroid for expressing a view on assets with positive or
157
#' negative expected returns
158
#'
159
#' @param positive a vector of the index of assets with positive expected
160
#' return in ascending order
161
#' @param negative a vector of the index of assets with negative expected
162
#' return in ascending order.
163
#' @param simulations number of simulations
164
#' @return the centroid vector
165
#' @examples
166
#' # Express a view that
167
#' # R_1 < R_2 < 0 < R_3 < R_4
168
#' centroid.sign(c(1, 2), c(4, 3))
169
#' @author Ross Bennett
170
#' @export
171
centroid.sign <- function(positive, negative, simulations=1000){
172
173
# Number of positive and negative assets
174
pos <- length(positive)
175
neg <- length(negative)
176
nassets <- pos + neg
177
178
c_hat <- matrix(0, simulations, nassets)
179
for(i in 1:simulations){
180
tmp <- rnorm(nassets)
181
# subset the positive and negative assets
182
tmp.pos <- tmp[1:pos]
183
tmp.neg <- tmp[(pos+1):(pos+neg)]
184
185
# Sign correct the positive assets
186
idx <- which(tmp.pos < 0)
187
if(length(idx) != 0){
188
tmp.pos[idx] <- -1 * tmp.pos[idx]
189
}
190
191
# Sign correct the negative assets
192
idx <- which(tmp.neg > 0)
193
if(length(idx) != 0){
194
tmp.neg[idx] <- -1 * tmp.neg[idx]
195
}
196
c_hat[i,] <- sort(c(tmp.pos, tmp.neg), decreasing=TRUE)
197
}
198
xx <- colMeans(c_hat)
199
out <- vector("numeric", nassets)
200
out[rev(positive)] <- xx[1:pos]
201
out[rev(negative)] <- xx[(1+pos):(pos+neg)]
202
return(out)
203
}
204
205
#' Buckets Centroid
206
#'
207
#' Compute the centroid for buckets of assets
208
#'
209
#' A common use of buckets is to divide the assets into quartiles or deciles,
210
#' but is generalized here for an arbitrary number of buckets and arbitrary
211
#' number of assets in each bucket.
212
#'
213
#' @param buckets a list where each element contains the index of the assets in
214
#' the respective bucket. The assets within each bucket have no order.
215
#' The bucket elements are in ascending order such that
216
#' R_bucket_1 < ... < R_bucket_n
217
#' @param simulations number of simulations
218
#' @return the centroid vector
219
#' @author Ross Bennett
220
#' @export
221
centroid.buckets <- function(buckets, simulations=1000){
222
if(!is.list(buckets)) stop("buckets must be a list")
223
224
# number of assets and buckets
225
nassets <- length(unlist(buckets))
226
nbuckets <- length(buckets)
227
228
# Run simulations so we simulate n values for n buckets and then replicate
229
# that value for the number of assets in the given bucket
230
c_hat <- matrix(0, simulations, nbuckets)
231
for(i in 1:simulations){
232
c_hat[i,] <- sort(rnorm(nbuckets), decreasing=TRUE)
233
}
234
xx <- colMeans(c_hat)
235
out <- vector("numeric", nassets)
236
for(j in 1:nbuckets){
237
out[buckets[[j]]] <- xx[j]
238
}
239
return(out)
240
}
241
242