Run Channel in Rcpp Update
08 Apr 2015To install Systematic Investor Toolbox (SIT) please visit About page.
Following is just a quick update to the Run Channel in Rcpp post. Paul noticed that I had a problem in the lower channel computation algo. I.e. Rank of Price was in the wrong order for the lower channel. Please see below the updated version.
#include <Rcpp.h>
using namespace Rcpp;
using namespace std;
// [[Rcpp::plugins(cpp11)]]
// quantile setup
struct quantile {
int lo, hi, n;
double hlo, hhi;
};
quantile make_quantile(int n, double prob, bool high) {
quantile res;
double index = (n - 1) * prob;
res.lo = floor(index);
res.hi = res.lo + 1;
res.hhi = index - res.lo;
res.hlo = 1 - res.hhi;
res.n = high ? n - res.hi : res.hi;
return res;
}
// index vector
vector<int> make_seq(int n) {
vector<int> id(n);
iota(id.begin(), id.end(), 0);
return id;
}
// compute weighted average
double avg_quantile(vector<int> id, vector<int> id1, quantile q,
NumericVector x, int n, bool high
) {
double temp, total; int j, k;
// sort id
for(k = 0; k < q.n; k++)
id1[k] = k;
int start = high ? q.hi : 0;
int end = high ? n : q.n;
if(high)
sort(id1.begin(), id1.end(),
[&](int a, int b) { return id[a + start] < id[b + start]; });
else // sort decreasing
sort(id1.begin(), id1.end(),
[&](int a, int b) { return id[a + start] > id[b + start]; });
for(j = start, k =0, temp=0.0, total=0.0; j < end; j++, k++) {
temp += x[id[j]] * (id1[k]+1) * (j - start + 1);
total += (id1[k]+1) * (j - start + 1);
}
return temp / total;
}
// [[Rcpp::export]]
NumericVector run_quantile_weight(NumericVector x, int n, double low_prob, double high_prob) {
auto sz = x.size();
NumericMatrix res(sz, 2);
colnames(res) = CharacterVector::create("low", "high");
// quantile setup
auto qlo = make_quantile(n, low_prob, false);
auto qhi = make_quantile(n, high_prob, true);
// index vector
auto id = make_seq(n);
auto idlo = make_seq(qlo.n);
auto idhi = make_seq(qhi.n);
for(int i = 0; i < (sz-n+1); i++) {
if(i == 0)
sort(id.begin(), id.end(),
[&](int a, int b) { return x[a] < x[b]; });
else {
// remove index (i-1)
id.erase(find(id.begin(), id.end(), i-1));
// insert keeping sorted order
id.insert(lower_bound(id.begin(), id.end(), i+n-1,
[&](int a, int b) { return x[a] < x[b]; }), i+n-1);
}
res(i+n-1,0) = avg_quantile(id, idlo, qlo, x, n, false);
res(i+n-1,1) = avg_quantile(id, idhi, qhi, x, n, true);
}
// pad the first n-1 elements with NA
for (int i = 0; i < (n-1); i++)
res(i,0) = res(i,1) = NA_REAL;
return res;
}
Please save above code in the channel1.cpp
file or download channel1.cpp.
Next let’s check for correctness and speed.
#*****************************************************************
# Rcpp Run Quantile
#*****************************************************************
# make sure to install Rtools on windows
# http://cran.r-project.org/bin/windows/Rtools/
library(SIT)
load.packages('quantmod')
load.packages('Rcpp')
# load run quantile functions
sourceCpp('channel1.cpp')
#*****************************************************************
# Inefficient R implementation
#*****************************************************************
run_quantile_weightR = function(x, n, low.prob, high.prob) {
hi = floor((n-1) * high.prob) + 1 + 1
hi.index = (hi:n) - (hi - 1)
lo = floor((n-1) * low.prob)+ 1
lo.index = 1:lo
out = cbind( matrix(NA,nr=3,nc=n-1), sapply(n:len(x), function(i) {
temp = x[(i- (n-1) ):i]
index = sort.list(temp)
# high
index1 = sort.list(index[hi:n])
h = sum(temp[index[hi:n]] * hi.index * index1) / sum(hi.index * index1)
# low
index1 = sort.list(index[1:lo])
l = sum(temp[index[1:lo]] * lo.index * index1) / sum(lo.index * index1)
# low new, position in time is same, but rank of price is decreasing
index1 = sort.list(index[1:lo], decreasing=T)
l1 = sum(temp[index[1:lo]] * lo.index * index1) / sum(lo.index * index1)
c(l1,l,h)
}
))
out = t(out)
colnames(out) = spl('low.new,low.old,high')
out
}
#*****************************************************************
# Test David's example
#*****************************************************************
print.helper = function(stats) {
print(stats[nrow(stats),,drop=F])
}
x = c(201:215,117,115,119,118,121)
n = len(x)
low.prob = 0.25
high.prob = 0.75
stats = run_quantile_weightR(x, n, low.prob, high.prob)
print.helper(stats)
low.new | low.old | high |
---|---|---|
118.3514 | 119.4906 | 214.0909 |
stats = run_quantile_weight(x, n, low.prob, high.prob)
print.helper(stats)
low | high |
---|---|
118.3514 | 214.0909 |
x = c(1:15,117,115,119,118,121)
stats = run_quantile_weightR(x, n, low.prob, high.prob)
print.helper(stats)
low.new | low.old | high |
---|---|---|
3 | 4.090909 | 119.4906 |
stats = run_quantile_weight(x, n, low.prob, high.prob)
print.helper(stats)
low | high |
---|---|
3 | 119.4906 |
#*****************************************************************
# Benchmark
#*****************************************************************
load.packages('microbenchmark')
n = 10
low.prob = 0.25
high.prob = 0.75
x = runif(100)
test1 = run_quantile_weightR(x, n, low.prob, high.prob)[,c(1,3)]
test2 = run_quantile_weight(x, n, low.prob, high.prob)
print(all.equal(test1, test2))
Attributes: < Component “dimnames”: Component 2: 1 string mismatch >
print(to.nice(summary(microbenchmark(
run_quantile_weightR(x, n, low.prob, high.prob)[,c(1,3)],
run_quantile_weight(x, n, low.prob, high.prob),
times = 10
)),0))
expr | min | lq | mean | median | uq | max | neval | |
---|---|---|---|---|---|---|---|---|
1 | run_quantile_weightR(x, n, low.prob, high.prob)[, c(1, 3)] | 14,757 | 14,979 | 15,643 | 15,555 | 16,335 | 16,631 | 10 |
2 | run_quantile_weight(x, n, low.prob, high.prob) | 103 | 106 | 118 | 115 | 124 | 145 | 10 |
(this report was produced on: 2015-04-13)