Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/sandbox/rp_transform2.R
1433 views
1
2
3
rp_transform2 <- function(weights,
4
min_sum,
5
max_sum,
6
min_box,
7
max_box,
8
groups=NULL,
9
cLO=NULL,
10
cUP=NULL,
11
max_pos=NULL,
12
group_pos=NULL,
13
max_pos_long=NULL,
14
max_pos_short=NULL,
15
leverage=NULL,
16
weight_seq=NULL,
17
max_permutations=200){
18
tmp_w <- weights
19
20
# Set some reasonable default values
21
# Maybe I should leave these as NULL values and incorporate that into the
22
# checks
23
#if(is.null(min_sum)) min_sum <- 0.99
24
#if(is.null(max_sum)) max_sum <- 1.01
25
#if(is.null(min_box)) min_box <- rep(-Inf, length(tmp_w))
26
#if(is.null(max_box)) max_box <- rep(Inf, length(tmp_w))
27
if(is.null(max_pos)) max_pos <- length(tmp_w)
28
#if(is.null(max_poslong)) max_pos_long <- length(tmp_w)
29
#if(is.null(max_pos_short)) max_pos_short <- length(tmp_w)
30
#if(is.null(leverage)) leverage <- Inf
31
32
# Generate a weight sequence, we should check for portfolio$weight_seq
33
if(is.null(weight_seq))
34
weight_seq <- generatesequence(min=min(min_box), max=max(max_box), by=0.002)
35
36
# make sure there is a 0 in weight_seq if we have a position limit constraint
37
if((!is.null(max_pos) | !is.null(group_pos) | !is.null(max_pos_long) | !is.null(max_pos_short)) & !is.element(0, weight_seq)) weight_seq <- c(0, weight_seq)
38
39
# Tolerance for "non-zero" definition for position limit constraints
40
tolerance <- .Machine$double.eps^0.5
41
42
# initialize the outer while loop
43
permutations <- 1
44
45
# while we have not reached max_permutations and the following constraints
46
# are violated:
47
# - min_sum
48
# - max_sum
49
# - leverage
50
# - max_pos, max_pos_long, max_pos_short
51
# - group
52
53
# Do we want to check all constraints in here?
54
# Box constraints should be satisfied by construction so we should not need
55
# to check those here
56
while (( min_sum_fail(tmp_w, min_sum) |
57
max_sum_fail(tmp_w, max_sum) |
58
leverage_fail(tmp_w, leverage) |
59
pos_limit_fail(tmp_w, max_pos, max_pos_long, max_pos_short) |
60
any(group_fail(tmp_w, groups, cLO, cUP)) ) &
61
(permutations < max_permutations)) {
62
63
# cat("permutation #:", permutations, "\n")
64
permutations <- permutations+1
65
66
# Reset tmp_w to original weights vector
67
# I'm not sure we want to do this here because it puts us back to where we
68
# started, but it seems to help with the position limit constraint
69
# tmp_w <- weights
70
71
# Reset the random index based on the maximum position constraint
72
# This basically allows us to generate a portfolio of max_pos assets
73
# with the given constraints and then add assets with zero weight
74
random_index <- sample(1:length(tmp_w), max_pos)
75
76
# Get the index values that are not in random_index and set them equal to 0
77
full_index <- 1:length(tmp_w)
78
not_index <- setdiff(full_index, random_index)
79
tmp_w[not_index] <- 0
80
81
# min_sum violation
82
if(min_sum_fail(tmp_w, min_sum)){
83
tmp_w <- rp_increase(weights=tmp_w,
84
min_sum=min_sum,
85
max_box=max_box,
86
weight_seq=weight_seq)
87
}
88
89
# max_sum violation
90
if(max_sum_fail(tmp_w, max_sum)){
91
tmp_w <- rp_decrease(weights=tmp_w,
92
max_sum=max_sum,
93
min_box=min_box,
94
weight_seq=weight_seq)
95
}
96
97
98
# leverage violation
99
if(leverage_fail(tmp_w, leverage)){
100
tmp_w <- rp_decrease_leverage(weights=tmp_w,
101
max_box=max_box,
102
min_box=min_box,
103
leverage=leverage,
104
weight_seq=weight_seq)
105
}
106
107
# position limit violation
108
if(pos_limit_fail(tmp_w, max_pos, max_pos_long, max_pos_short)){
109
tmp_w <- rp_position_limit(weights=tmp_w,
110
min_box=min_box,
111
max_box=max_box,
112
max_pos=max_pos,
113
max_pos_long=max_pos_long,
114
max_pos_short=max_pos_short,
115
weight_seq=weight_seq)
116
}
117
118
# group violation
119
if(any(group_fail(tmp_w, groups, cLO, cUP, group_pos))){
120
n_groups <- length(groups)
121
for(j in 1:n_groups){
122
# index of the weights vector belonging to the jth group
123
j_idx <- groups[[j]]
124
# weights of the jth group
125
tmp_group_w <- tmp_w[j_idx]
126
127
# May be easier to just make a recursive call and treat each group
128
# as a portfolio of weight vectors
129
tmp_w[j_idx] <- rp_transform2(weights=tmp_group_w,
130
min_sum=cLO[j],
131
max_sum=cUP[j],
132
min_box=min_box[j_idx],
133
max_box=max_box[j_idx],
134
group_pos=group_pos[j])
135
136
# treat this as if min_sum were violated
137
# if(sum(tmp_group_w) < cLO[j]){
138
# tmp_w[j_idx] <- rp_increase(weights=tmp_group_w,
139
# min_sum=cLO[j],
140
# max_box=max_box[j_idx],
141
# weight_seq=weight_seq)
142
# }
143
144
# treat this as if max_sum were violated
145
# if(sum(tmp_group_w) > cUP[j]){
146
# tmp_w[j_idx] <- rp_decrease(weights=tmp_group_w,
147
# max_sum=cUP[j],
148
# min_box=min_box[j_idx],
149
# weight_seq=weight_seq)
150
# }
151
}
152
} # end group violation loop
153
} # end final walk towards the edges
154
portfolio <- tmp_w
155
156
colnames(portfolio) <- colnames(weights)
157
158
# checks for infeasible portfolio
159
# Stop execution and return an error if an infeasible portfolio is created
160
# This will be useful in fn_map so that we can catch the error and take
161
# action (try again with more permutations, relax constraints, different
162
# method to normalize, etc.)
163
if (sum(portfolio) < min_sum | sum(portfolio) > max_sum){
164
portfolio <- weights
165
stop("Infeasible portfolio created, perhaps increase max_permutations and/or adjust your parameters.")
166
}
167
return(portfolio)
168
}
169
170
rp_increase <- function(weights, min_sum, max_box, weight_seq){
171
# randomly permute and increase a random portfolio element if the sum of
172
# the weights is less than min_sum while respecting box constraints
173
174
if(sum(weights) >= min_sum) return(weights)
175
176
tmp_w <- weights
177
n_weights <- length(weights)
178
# random_index <- sample(1:length(weights), max_pos)
179
random_index <- sample(1:n_weights, n_weights)
180
i <- 1
181
while (sum(tmp_w) < min_sum & i <= n_weights) {
182
# print("min_sum violation loop")
183
184
cur_index <- random_index[i]
185
cur_val <- tmp_w[cur_index]
186
tmp_seq <- weight_seq[(weight_seq > cur_val) & (weight_seq <= max_box[cur_index])]
187
n_tmp_seq <- length(tmp_seq)
188
if(n_tmp_seq > 1){
189
# randomly sample one of the larger weights
190
tmp_w[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]
191
} else if(n_tmp_seq == 1){
192
tmp_w[cur_index] <- tmp_seq
193
}
194
i <- i + 1 # increment our counter
195
} # end increase loop
196
return(tmp_w)
197
}
198
199
rp_decrease <- function(weights, max_sum, min_box, weight_seq){
200
# randomly permute and decrease a random portfolio element if the sum of
201
# the weights is greater than max_sum while respecting box constraints
202
203
if(sum(weights) <= max_sum) return(weights)
204
205
tmp_w <- weights
206
n_weights <- length(weights)
207
# random_index <- sample(1:length(weights), max_pos)
208
random_index <- sample(1:n_weights, n_weights)
209
210
i <- 1
211
while (sum(tmp_w) > max_sum & i <= n_weights) {
212
# print("max_sum violation loop")
213
214
cur_index <- random_index[i]
215
cur_val <- tmp_w[cur_index]
216
tmp_seq <- weight_seq[(weight_seq < cur_val) & (weight_seq >= min_box[cur_index])]
217
n_tmp_seq <- length(tmp_seq)
218
if(n_tmp_seq > 1){
219
tmp_w[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]
220
} else if(n_tmp_seq == 1){
221
tmp_w[cur_index] <- tmp_seq
222
}
223
i <- i + 1 # increment our counter
224
} # end decrease loop
225
return(tmp_w)
226
}
227
228
rp_decrease_leverage <- function(weights, max_box, min_box, leverage, weight_seq){
229
# randomly permute and increae decrease a random portfolio element
230
# according to leverage exposure while respecting box constraints
231
232
tmp_w <- weights
233
n_weights <- length(weights)
234
# random_index <- sample(1:length(weights), max_pos)
235
random_index <- sample(1:n_weights, n_weights)
236
237
# set counter to 1 for leverage violation loop
238
i <- 1
239
while (sum(abs(tmp_w)) > leverage & i <= length(tmp_w)) {
240
#print("leverage violation loop")
241
242
cur_index <- random_index[i]
243
cur_val <- tmp_w[cur_index]
244
245
tmp_seq <- NULL
246
# check the sign of the current value
247
if(cur_val < 0){
248
# if the current value is negative, we want to increase to lower
249
# sum(abs(weights)) while respecting uppper bound box constraint
250
tmp_seq <- weight_seq[(weight_seq > cur_val) & (weight_seq <= max_box[cur_index])]
251
} else if(cur_val > 0){
252
# if the current value is positive, we want to decrease to lower
253
# sum(abs(weights)) while respecting lower bound box constraint
254
tmp_seq <- weight_seq[(weight_seq < cur_val) & (weight_seq >= min_box[cur_index])]
255
}
256
# tmp_seq can be NULL if cur_val is zero
257
if(!is.null(tmp_seq)){
258
n_tmp_seq <- length(tmp_seq)
259
260
if(n_tmp_seq > 1) {
261
# randomly sample one of the weights
262
tmp_w[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]
263
} else if(n_tmp_seq == 1){
264
tmp_w[cur_index] <- tmp_seq
265
}
266
}
267
i <- i + 1 # increment our counter
268
} # end leverage violation loop
269
return(tmp_w)
270
}
271
272
rp_position_limit <- function(weights, max_pos=NULL, max_pos_long=NULL, max_pos_short=NULL, min_box, max_box, weight_seq){
273
tmp_w <- weights
274
n_weights <- length(weights)
275
# random_index <- sample(1:length(weights), max_pos)
276
random_index <- sample(1:n_weights, n_weights)
277
278
tolerance <- .Machine$double.eps^0.5
279
280
# set counter to 1 for position limit violation loop
281
i <- 1
282
while ( pos_limit_fail(tmp_w, max_pos, max_pos_long, max_pos_short) & i <= length(tmp_w)) {
283
#print("position limit violation loop")
284
285
cur_index <- random_index[i]
286
cur_val <- tmp_w[cur_index]
287
288
if(!is.null(max_pos_long)){
289
# Check if max_pos_long is violated
290
# If max_pos_long is violated, we we grab a positive weight and set it
291
# to be between min_box and 0
292
if(sum(tmp_w > tolerance) > max_pos_long){
293
if(cur_val > tolerance){
294
# subset such that min_box_i <= weight_i <= 0
295
tmp_seq <- weight_seq[(weight_seq <= 0) & (weight_seq >= min_box[cur_index])]
296
n_tmp_seq <- length(tmp_seq)
297
if(n_tmp_seq > 1){
298
tmp_w[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]
299
} else if(n_tmp_seq == 1){
300
tmp_w[cur_index] <- tmp_seq
301
}
302
}
303
} # end max_pos_long violation loop
304
}
305
306
if(!is.null(max_pos_short)){
307
# Check if max_pos_short is violated
308
# If max_pos_short is violated, we grab a negative weight and set it
309
# to be between 0 and max_box
310
if(sum(tmp_w < tolerance) > max_pos_short){
311
if(cur_val < tolerance){
312
# subset such that 0 <= weight_i <= max_box_i
313
tmp_seq <- weight_seq[(weight_seq >= 0) & (weight_seq <= max_box[cur_index])]
314
n_tmp_seq <- length(tmp_seq)
315
if(n_tmp_seq > 1){
316
tmp_w[cur_index] <- tmp_seq[sample.int(n=n_tmp_seq, size=1L, replace=FALSE, prob=NULL)]
317
} else if(n_tmp_seq == 1){
318
tmp_w[cur_index] <- tmp_seq
319
}
320
}
321
} # end max_pos_short violation loop
322
}
323
i <- i + 1 # increment our counter
324
} # end position limit violation loop
325
return(tmp_w)
326
}
327
328
329