如何矢量化依赖于 R 中先前计算的函数?

How to vectorize a function that depends on a previous calculation in R?

我有一些随机游走。我的目标是创建一个函数,向此 data.table 添加一列,根据其累积百分比收益和百分比缩减标记其所在的“区域”。

library(data.table)

set.seed(1)
# generate random returns with drift
df <- data.table(
    "date" = 1:50,
    "ret" = rnorm(50, mean = .002, sd = .01)
)
# calculate the value of the random-walk over-time
df[, val := cumprod(1 + ret)]
df[, draw_down := val / cummax(val) - 1]

第一个区域出现在第一行并一直上升,直到出现 5% cumulative gain2% drawdown

第二个区域在第一个区域结束后开始一行,一直持续到同样的情况再次发生,5% cumulative gain2% drawdown

重复此过程,直到这两种情况都没有发生,在这种情况下,区域会继续到最后一行。

这是一个可重现的例子:

# start with the first row and zone of 1
idx <- 1
count <- 1
res <- data.table()
while (idx <= nrow(df)) {

    # grab the start of the zone and all future rows
    tmp <- df[idx:.N]
    # calculate the necessary things
    tmp[, val := cumprod(1 + ret)]
    tmp[, draw_down := val / cummax(val) - 1]

    # find out if we crossed our drawdown threshold
    loss_idx <- which(
        tmp$draw_down == min(tmp$draw_down[tmp$draw_down <= -.02])
    )
    # find out if we crossed gain threshold
    gain_idx <- which(tmp$val == min(tmp$val[tmp$val >= 1.05]))
    # if we have no thresholds, label the rest of the zones
    # and exit
    if (length(loss_idx) == 0 & length(gain_idx) == 0) {
        tmp[, zone := count]
        res <- rbind(res, tmp)
        break
    }
    # mark the zone
    tmp[1:min(gain_idx, loss_idx), zone := count]
    # increment our index
    idx <- tmp[min(gain_idx, loss_idx)]$date + 1
    print(idx)
    # increment our zone
    count <- count + 1
    res <- rbind(res, tmp[!is.na(zone)])
}

我已经尝试获取这些区域点出现位置的索引。但是后来我 运行 遇到了需要根据最后一个区域的索引重新计算 valdrawdown 的问题。我想不出一种方法来对其进行矢量化。也许在这里使用 roll 函数会有效?

问题归结为知道按区域划分的回撤,但需要前一个区域才能计算回撤。与累积 return 类似。如果这个函数依赖于先前的值,是否可以对其进行矢量化?

在尝试实现以下所需输出时,我们将不胜感激任何方向的帮助。

所需的输出:

> res
date    ret val draw_down   zone
<int>   <dbl>   <dbl>   <dbl>   <dbl>
1   -0.0042645381   0.9957355   0.0000000000    1
2   0.0038364332    0.9995555   0.0000000000    1
3   -0.0063562861   0.9932021   -0.0063562861   1
4   0.0179528080    1.0110328   0.0000000000    1
5   0.0052950777    1.0163863   0.0000000000    1
6   -0.0062046838   1.0100800   -0.0062046838   1
7   0.0068742905    1.0170236   0.0000000000    1
8   0.0093832471    1.0265665   0.0000000000    1
9   0.0077578135    1.0345305   0.0000000000    1
10  -0.0010538839   1.0334402   -0.0010538839   1
11  0.0171178117    1.0511304   0.0000000000    1
12  0.0058984324    1.0058984   0.0000000000    2
13  -0.0042124058   1.0016612   -0.0042124058   2
14  -0.0201469989   0.9814807   -0.0242745373   2
15  0.0132493092    1.0132493   0.0000000000    3
16  0.0015506639    1.0148205   0.0000000000    3
17  0.0018380974    1.0166859   0.0000000000    3
18  0.0114383621    1.0283151   0.0000000000    3
19  0.0102122120    1.0388164   0.0000000000    3
20  0.0079390132    1.0470636   0.0000000000    3
21  0.0111897737    1.0587800   0.0000000000    3
22  0.0098213630    1.0691787   0.0000000000    3
23  0.0027456498    1.0721143   0.0000000000    3
24  -0.0178935170   1.0529304   -0.0178935170   3
25  0.0081982575    1.0615626   -0.0098419551   3
26  0.0014387126    1.0630899   -0.0084174023   3
27  0.0004420449    1.0635598   -0.0079790782   3
28  -0.0127075238   1.0500446   -0.0205852077   3
29  -0.0027815006   0.9972185   0.0000000000    4
30  0.0061794156    1.0033807   0.0000000000    4
31  0.0155867955    1.0190202   0.0000000000    4
32  0.0009721227    1.0200108   0.0000000000    4
33  0.0058767161    1.0260051   0.0000000000    4
34  0.0014619496    1.0275051   0.0000000000    4
35  -0.0117705956   1.0154108   -0.0117705956   4
36  -0.0021499456   1.0132277   -0.0138952351   4
37  -0.0019428995   1.0112591   -0.0158111376   4
38  0.0014068660    1.0126818   -0.0144265157   4
39  0.0130002537    1.0258469   -0.0016138103   4
40  0.0096317575    1.0357276   0.0000000000    4
41  0.0003547640    1.0360951   0.0000000000    4
42  -0.0005336168   1.0355422   -0.0005336168   4
43  0.0089696338    1.0448306   0.0000000000    4
44  0.0075666320    1.0527365   0.0000000000    4
45  -0.0048875569   0.9951124   0.0000000000    5
46  -0.0050749516   0.9900623   -0.0050749516   5
47  0.0056458196    0.9956520   0.0000000000    5
48  0.0096853292    1.0052952   0.0000000000    5
49  0.0008765379    1.0061764   0.0000000000    5
50  0.0108110773    1.0170543   0.0000000000    5

我不认为滚动计算是正确的方法:通常他们已经固定 windows,而这更动态一些。类似地,累积运算(例如 cumsum)出于类似原因将不起作用。 (这并不是说我不能扭曲 zoo::rollapply 方法来执行此操作,但我认为它比推荐的方法效率低得多。)

这是一个简单的 while 循环,似乎可以提供您所要求的 zone

breaks <- integer(0)
rn <- 1L
while (rn <= nrow(df)) {
  theserows <- seq(rn, nrow(df))
  ratios <- df$val[theserows] / df$val[theserows][1]
  upordown <- which(ratios >= 1.05 | ratios <= 0.98)
  if (!length(upordown)) break
  breaks <- c(breaks, upordown[1] + rn)
  rn <- rn + upordown[1]
}
df[, zone := cumsum(seq_len(.N) %in% breaks)]
#      date           ret       val     draw_down  zone
#     <int>         <num>     <num>         <num> <int>
#  1:     1 -0.0042645381 0.9957355  0.0000000000     0
#  2:     2  0.0038364332 0.9995555  0.0000000000     0
#  3:     3 -0.0063562861 0.9932021 -0.0063562861     0
#  4:     4  0.0179528080 1.0110328  0.0000000000     0
#  5:     5  0.0052950777 1.0163863  0.0000000000     0
#  6:     6 -0.0062046838 1.0100800 -0.0062046838     0
#  7:     7  0.0068742905 1.0170236  0.0000000000     0
#  8:     8  0.0093832471 1.0265665  0.0000000000     0
#  9:     9  0.0077578135 1.0345305  0.0000000000     0
# 10:    10 -0.0010538839 1.0334402 -0.0010538839     0
# 11:    11  0.0171178117 1.0511304  0.0000000000     0
# 12:    12  0.0058984324 1.0573304  0.0000000000     1
# 13:    13 -0.0042124058 1.0528765 -0.0042124058     1
# 14:    14 -0.0201469989 1.0316642 -0.0242745373     1
# 15:    15  0.0132493092 1.0453331 -0.0113468490     2
# 16:    16  0.0015506639 1.0469540 -0.0098137803     2
# 17:    17  0.0018380974 1.0488784 -0.0079937216     2
# 18:    18  0.0114383621 1.0608759  0.0000000000     2
# 19:    19  0.0102122120 1.0717098  0.0000000000     2
# 20:    20  0.0079390132 1.0802181  0.0000000000     2
# 21:    21  0.0111897737 1.0923055  0.0000000000     2
# 22:    22  0.0098213630 1.1030334  0.0000000000     2
# 23:    23  0.0027456498 1.1060620  0.0000000000     3
# 24:    24 -0.0178935170 1.0862706 -0.0178935170     3
# 25:    25  0.0081982575 1.0951762 -0.0098419551     3
# 26:    26  0.0014387126 1.0967518 -0.0084174023     3
# 27:    27  0.0004420449 1.0972366 -0.0079790782     3
# 28:    28 -0.0127075238 1.0832934 -0.0205852077     3
# 29:    29 -0.0027815006 1.0802803 -0.0233094505     4
# 30:    30  0.0061794156 1.0869558 -0.0172740737     4
# 31:    31  0.0155867955 1.1038979 -0.0019565256     4
# 32:    32  0.0009721227 1.1049710 -0.0009863049     4
# 33:    33  0.0058767161 1.1114646  0.0000000000     4
# 34:    34  0.0014619496 1.1130896  0.0000000000     4
# 35:    35 -0.0117705956 1.0999878 -0.0117705956     4
# 36:    36 -0.0021499456 1.0976229 -0.0138952351     4
# 37:    37 -0.0019428995 1.0954903 -0.0158111376     4
# 38:    38  0.0014068660 1.0970316 -0.0144265157     4
# 39:    39  0.0130002537 1.1112932 -0.0016138103     4
# 40:    40  0.0096317575 1.1219969  0.0000000000     4
# 41:    41  0.0003547640 1.1223950  0.0000000000     4
# 42:    42 -0.0005336168 1.1217961 -0.0005336168     4
# 43:    43  0.0089696338 1.1318582  0.0000000000     4
# 44:    44  0.0075666320 1.1404225  0.0000000000     4
# 45:    45 -0.0048875569 1.1348486 -0.0048875569     5
# 46:    46 -0.0050749516 1.1290893 -0.0099377044     5
# 47:    47  0.0056458196 1.1354640 -0.0043479913     5
# 48:    48  0.0096853292 1.1464613  0.0000000000     5
# 49:    49  0.0008765379 1.1474662  0.0000000000     5
# 50:    50  0.0108110773 1.1598716  0.0000000000     5
#      date           ret       val     draw_down  zone

还有一个简单的函数来做同样的事情:

func <- function(x, up = 1.05, down = 0.98) {
  breaks <- integer(0)
  if (!length(x)) return(breaks)
  ind <- 1L
  while (ind <= length(x)) {
    theseind <- seq(ind, length(x))
    ratios <- x[theseind] / x[theseind][1]
    upordown <- which(ratios >= up | ratios <= down)
    if (!length(upordown)) break
    breaks <- c(breaks, upordown[1] + ind)
    ind <- ind + upordown[1]
  }
  return(cumsum(seq_along(x) %in% breaks))
}
df[, zone := func(val, 1.05, 0.98) ]

假设您正在探索矢量化以加速计算,这里是另一个使用 Rccp 加速计算的选项:

library(Rcpp)
cppFunction("IntegerVector zoning(NumericVector idx) {
    int zone = 1, n = idx.size();
    IntegerVector res = IntegerVector(n);
    double x0 = idx[0];


    for (int i = 1; i < n; i++) {
        res[i] = zone;
        if (idx[i]/x0 < 0.98 || idx[i]/x0 > 1.05) {
            if (i+1 < n) {
                x0 = idx[i+1];
            }
            zone++;
        }
    }

    return res;
}")

df[, zone := zoning(c(1, val))[-1L]]

输出:

    date           ret       val zone
 1:    1 -0.0042645381 0.9957355    1
 2:    2  0.0038364332 0.9995555    1
 3:    3 -0.0063562861 0.9932021    1
 4:    4  0.0179528080 1.0110328    1
 5:    5  0.0052950777 1.0163863    1
 6:    6 -0.0062046838 1.0100800    1
 7:    7  0.0068742905 1.0170236    1
 8:    8  0.0093832471 1.0265665    1
 9:    9  0.0077578135 1.0345305    1
10:   10 -0.0010538839 1.0334402    1
11:   11  0.0171178117 1.0511304    1
12:   12  0.0058984324 1.0573304    2
13:   13 -0.0042124058 1.0528765    2
14:   14 -0.0201469989 1.0316642    2
15:   15  0.0132493092 1.0453331    3
16:   16  0.0015506639 1.0469540    3
17:   17  0.0018380974 1.0488784    3
18:   18  0.0114383621 1.0608759    3
19:   19  0.0102122120 1.0717098    3
20:   20  0.0079390132 1.0802181    3
21:   21  0.0111897737 1.0923055    3
22:   22  0.0098213630 1.1030334    3
23:   23  0.0027456498 1.1060620    4
24:   24 -0.0178935170 1.0862706    4
25:   25  0.0081982575 1.0951762    4
26:   26  0.0014387126 1.0967518    4
27:   27  0.0004420449 1.0972366    4
28:   28 -0.0127075238 1.0832934    4
29:   29 -0.0027815006 1.0802803    5
30:   30  0.0061794156 1.0869558    5
31:   31  0.0155867955 1.1038979    5
32:   32  0.0009721227 1.1049710    5
33:   33  0.0058767161 1.1114646    5
34:   34  0.0014619496 1.1130896    5
35:   35 -0.0117705956 1.0999878    5
36:   36 -0.0021499456 1.0976229    5
37:   37 -0.0019428995 1.0954903    5
38:   38  0.0014068660 1.0970316    5
39:   39  0.0130002537 1.1112932    5
40:   40  0.0096317575 1.1219969    5
41:   41  0.0003547640 1.1223950    5
42:   42 -0.0005336168 1.1217961    5
43:   43  0.0089696338 1.1318582    5
44:   44  0.0075666320 1.1404225    5
45:   45 -0.0048875569 1.1348486    6
46:   46 -0.0050749516 1.1290893    6
47:   47  0.0056458196 1.1354640    6
48:   48  0.0096853292 1.1464613    6
49:   49  0.0008765379 1.1474662    6
50:   50  0.0108110773 1.1598716    6
    date           ret       val zone

感谢 https://rdrr.io/snippets/