timeseries-transform
Version:
timeseries-transform
1,419 lines (1,187 loc) • 30.1 kB
JavaScript
if (module && module.exports) {
var _ = require('underscore');
}
function timeseriesTransform(data) {
this.data = data;
if (!this.data) {
this.data = [];
}
this.loadIndicators();
this.loadStats();
}
timeseriesTransform.prototype.C2D = function() {
this.data = _.map(this.data, function(item) {
return {
v: item
};
});
}
timeseriesTransform.prototype.transform = function(name, options) {
if (this.indicators.hasOwnProperty(name)) {
var data = this.indicators[name](this.data, options);
// Post-processing Transform
/*switch (options.transform) {
default:
case "none":
return data;
break;
case "fisher":
data = _.map(data, function(datapoint) {
var i;
for (i in datapoint) {
if (i != 'd') {
datapoint[i] = 0.5*Math.
}
}
return datapoint;
});
return data;
break;
}*/
return data;
} else {
return false;
}
}
timeseriesTransform.prototype.loadIndicators = function() {
var scope = this;
this.indicators = {
bollinger: function(data, options) {
options = _.extend({
period: 20,
stdev: 2,
value: 'c'
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var bands = [];
for (i=0;i<options.period;i++) {
bands[i] = {
d: data[i].d,
up: null,
mid: null,
down: null
}
}
for (i=options.period;i<l;i++) {
sum = 0;
for (j=options.period;j>0;j--) {
sum += data[i-j][options.value];
}
var avg = sum/options.period;
// Calculate the stdev
var stdev = scope.stats.stdev(data.slice(i-options.period, i), {value:options.value});
bands[i] = {
d: data[i].d,
up: avg+(stdev*options.stdev),
mid: avg,
down: avg-(stdev*options.stdev)
};
}
return bands;
},
ma: function(data, options) {
options = _.extend({
period: 20,
value: 'c'
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var ma = [];
for (i=0;i<options.period;i++) {
ma[i] = {
d: data[i].d,
v: null
}
}
for (i=options.period;i<l;i++) {
sum = 0;
for (j=options.period;j>0;j--) {
sum += data[i-j][options.value];
}
var avg = sum/options.period;
ma[i] = {
d: data[i].d,
v: avg
};
}
return ma;
},
// MA on more than one property
maGroup: function(data, options) {
options = _.extend({
period: 20,
values: ['c','o']
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sums = {};
// Leave the datapoints [0;period[ intact
var output = [];
for (i=0;i<options.period;i++) {
output[i] = {
d: data[i].d
}
_.each(options.values, function(value) {
output[i][value] = null;
});
}
for (i=options.period;i<l;i++) {
output[i] = {
d: data[i].d
};
_.each(options.values, function(value) {
sums[value] = 0;
for (j=options.period;j>0;j--) {
sums[value] += data[i-j][value];
}
output[i][value] = sums[value]/options.period
});
}
return output;
},
tr: function(data, options) {
options = _.extend({}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
// Leave the datapoints [0;period[ intact
var output = [];
for (i=0;i<l;i++) {
if (i==0) {
output[i] = {
d: data[i].d,
v: data[i].h-data[i].l
};
} else {
output[i] = {
d: data[i].d,
v: Math.max(data[i].h-data[i].l, Math.abs(data[i].h-data[i-1].c), Math.abs(data[i].l-data[i-1].c))
};
}
}
return output;
},
atr: function(data, options) {
options = _.extend({
period: 14
}, options);
options.period = parseInt(options.period);
var atr = scope.indicators.ma(scope.indicators.tr(data, options), {
period: options.period,
value: 'v'
});
// Now we create the band
return _.map(atr, function(item, p) {
item.up = data[p].o+item.v;
item.down = data[p].o-item.v;
return item;
});
},
range: function(data, options) {
options = _.extend({
period: 20,
mul: 5
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = {};
var avg = {};
var base = {
up: 0,
down: 0
};
// Leave the datapoints [0;period[ intact
var output = [];
for (i=0;i<options.period;i++) {
output[i] = {
d: data[i].d,
v: null,
up: null,
down: null
}
}
for (i=options.period;i<l;i++) {
sum = {
v: 0,
up: 0,
down: 0
};
for (j=options.period;j>0;j--) {
if (data[i-j].o>data[i-j].c) {
// down
sum.v += data[i-j].o-data[i-j].c;
sum.down += data[i-j].o-data[i-j].c;
} else {
// up
sum.v += data[i-j].c-data[i-j].o;
sum.up += data[i-j].c-data[i-j].o;
}
}
avg = {
v: sum.v/options.period,
up: sum.up/options.period,
down: sum.down/options.period
};
if (data[i].o>data[i].c) {
// down
base.up = data[i].o;
base.down = data[i].c;
} else {
// up
base.up = data[i].c;
base.down = data[i].o;
}
output[i] = {
d: data[i].d,
v: avg.v,
up: avg.up,
down: avg.down,
bandUp: data[i].o+avg.v*options.mul,
bandDown: data[i].o-avg.v*options.mul
};
}
return output;
},
momentum: function(data, options) {
options = _.extend({
period: 10,
stdevPeriod: 20,
meanPeriod: 12,
stdevMultiplier: 2
}, options);
options.period = parseInt(options.period);
options.stdevPeriod = parseInt(options.stdevPeriod);
options.stdevMultiplier = parseFloat(options.stdevMultiplier);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var main = [];
for (i=0;i<options.period;i++) {
main[i] = {
d: data[i].d,
v: null,
stdev: null,
up: null,
down: null
}
}
for (i=options.period;i<l;i++) {
sum = 0;
div = 0;
for (j=options.period;j>0;j--) {
sum += (data[i-j].c-data[i-j].o)*(options.period-j+1);
div += (options.period-j+1);
}
var avg = sum/div;
main[i] = {
d: data[i].d,
v: avg
};
}
for (i=options.period+Math.max(options.stdevPeriod, options.meanPeriod);i<main.length;i++) {
main[i].mean = scope.stats.mean(main.slice(i-options.meanPeriod, i), {value:'v'});
main[i].stdev = scope.stats.stdev(main.slice(i-options.stdevPeriod, i), {value:'v'});
main[i].up = main[i].mean + main[i].stdev*options.stdevMultiplier;
main[i].down = main[i].mean - main[i].stdev*options.stdevMultiplier;
}
return main;
},
momentum2: function(data, options) {
options = _.extend({
period: 10,
stdevPeriod: 20,
meanPeriod: 12,
stdevMultiplier: 2
}, options);
options.period = parseInt(options.period);
options.stdevPeriod = parseInt(options.stdevPeriod);
options.stdevMultiplier = parseFloat(options.stdevMultiplier);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var main = [];
for (i=0;i<options.period;i++) {
main[i] = {
d: data[i].d,
v: null,
stdev: null,
up: null,
down: null
}
}
for (i=options.period;i<l;i++) {
sum = 0;
div = 0;
for (j=options.period;j>0;j--) {
sum += Math.abs((data[i-j].c-data[i-j].o))*(options.period-j+1);
div += (options.period-j+1);
}
var avg = sum/div;
main[i] = {
d: data[i].d,
v: avg
};
}
for (i=options.period+Math.max(options.stdevPeriod, options.meanPeriod);i<main.length;i++) {
main[i].mean = scope.stats.mean(main.slice(i-options.meanPeriod, i), {value:'v'});
main[i].stdev = scope.stats.stdev(main.slice(i-options.stdevPeriod, i), {value:'v'});
main[i].up = main[i].mean + main[i].stdev*options.stdevMultiplier;
main[i].down = main[i].mean - main[i].stdev*options.stdevMultiplier;
}
return main;
},
avgChange: function(data, options) {
/*
Average Chnage
Average of price change from one bar to the next, averaged over a time period.
*/
options = _.extend({
period: 5,
value: 'c'
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var ma = [];
for (i=0;i<options.period+1;i++) {
ma[i] = {
d: data[i].d,
v: null
}
}
for (i=options.period+1;i<l;i++) {
sum = 0;
for (j=options.period;j>0;j--) {
sum += data[i-j][options.value]-data[i-j-1][options.value];
}
var avg = sum/options.period;
ma[i] = {
d: data[i].d,
v: avg
};
}
return ma;
},
crossOver: function(data, options) {
/*
MA CrossOver
*/
options = _.extend({
periodSlow: 50,
periodFast: 10,
value: 'c'
}, options);
options.periodSlow = parseInt(options.periodSlow);
options.periodFast = parseInt(options.periodFast);
// Calculate the slow and fast MAs
var slowMA = scope.indicators.ma(data, {
value: options.value,
period: options.periodSlow
});
var fastMA = scope.indicators.ma(data, {
value: options.value,
period: options.periodFast
});
/*
var slowMA = scope.transform('ma', {
value: options.value,
period: options.periodSlow
});
var fastMA = scope.transform('ma', {
value: options.value,
period: options.periodFast
});
*/
var i;
var j;
var l = data.length;
var dataset = [];
for (i=0;i<options.periodSlow;i++) {
dataset[i] = {
d: data[i].d,
v: null
}
}
for (i=options.periodSlow;i<l;i++) {
dataset[i] = {
d: data[i].d,
v: fastMA[i].v-slowMA[i].v
};
}
return dataset;
},
avgRange: function(data, options) {
/*
Average Range
Average of price range from low to high, expressed in % of the open price
*/
options = _.extend({
period: 5,
valueHigh: 'h',
valueLow: 'l',
valueOpen: 'o'
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var ma = [];
for (i=0;i<options.period;i++) {
ma[i] = {
d: data[i].d,
v: null
}
}
for (i=options.period;i<l;i++) {
sum = 0;
for (j=options.period;j>0;j--) {
sum += (data[i-j][options.valueHigh]-data[i-j][options.valueLow])/data[i-j][options.valueOpen]*100;
}
var avg = sum/options.period;
ma[i] = {
d: data[i].d,
v: avg
};
}
return ma;
},
ema: function(data, options) {
options = _.extend({
period: 20,
value: 'c'
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var ema = [];
for (i=0;i<options.period;i++) {
ema[i] = {
d: data[i].d,
v: null
}
}
var m = 2/(options.period*1+1); // Multiplier
for (i=options.period;i<l;i++) {
if (i==options.period || data[i][options.value]==null || ema[i-1].v==null) {
ema[i] = {
d: data[i].d,
v: data[i][options.value]
};
} else {
ema[i] = {
d: data[i].d,
v: data[i][options.value]*m+ema[i-1].v*(1-m)
};
}
}
return ema;
},
rsi: function(data, options) {
options = _.extend({
period: 14,
value: 'c'
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var rsi = [];
//console.log("data length -> period: ",data.length, '\t', options.period);
for (i=0;i<options.period+1;i++) {
//console.log(">", i, '\t',data[i]);
//if (!data[i]) {
// console.log("data error!", i, options, data.length, data);
//}
rsi[i] = {
d: data[i].d,
v: null
}
}
for (i=options.period+1;i<l;i++) {
sumUp = 0;
sumDown = 0;
for (j=options.period;j>0;j--) {
var diff = data[i-j][options.value] - data[i-j-1][options.value];
if (diff>0) {
sumUp += diff;
} else {
sumDown -= diff;
}
}
var avgDown = sumDown/options.period;
var avgUp = sumUp/options.period;
rsi[i] = {
d: data[i].d,
v: (100-(100/(1+(avgUp/avgDown))))
};
}
return rsi;
},
stochastic: function(data, options) {
options = _.extend({
period: 14,
smoothing: 3,
valueC: 'c',
valueH: 'h',
valueL: 'l'
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var stoch = [];
for (i=0;i<options.period+1;i++) {
stoch[i] = {
d: data[i].d,
v: null
}
}
for (i=options.period+1;i<l;i++) {
var min = scope.stats.min(data.slice(i+1-options.period, i+1), {value:options.valueL});
var max = scope.stats.max(data.slice(i+1-options.period, i+1), {value:options.valueH});
//console.log(">"+i,100*((data[i][options.valueC]-min)/(max-min)));
//console.log(">",i, '\t', min, '\t', max, '\t', '100*(('+data[i][options.valueC]+'-'+min+')/('+max+'-'+min+')) -> \t', '100*('+(data[i][options.valueC]-min).toFixed(2)+'/'+(max-min).toFixed(2)+') = \t', 100*((data[i][options.valueC]-min)/(max-min)));
stoch[i] = {
d: data[i].d,
v: 100*((data[i][options.valueC]-min)/(max-min))
};
}
// Now we apply the MA
var smoothed = _.indexBy(scope.indicators.ma(stoch, {
period: options.smoothing,
value: 'v'
}), function(datapoint) {
return datapoint.d;
});
stoch = _.map(stoch, function(datapoint) {
datapoint.s = smoothed[datapoint.d].v;
return datapoint;
});
return stoch;
},
donchian: function(data, options) {
options = _.extend({
period: 14,
valueH: 'h',
valueL: 'l'
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var donchian = [];
for (i=0;i<options.period+1;i++) {
donchian[i] = {
d: data[i].d,
min: null,
max: null
}
}
for (i=options.period+1;i<l;i++) {
var min = scope.stats.min(data.slice(i-options.period, i), {value:options.valueL});
var max = scope.stats.max(data.slice(i-options.period, i), {value:options.valueH});
donchian[i] = {
d: data[i].d,
min: min,
max: max
};
}
return donchian;
},
highlow: function(data, options) {
options = _.extend({
period: 200,
valueH: 'h',
valueL: 'l',
ma: false
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
// Leave the datapoints [0;period[ intact
var highlow = [];
for (i=0;i<options.period+1;i++) {
highlow[i] = {
d: data[i].d,
lowest: null,
highest: null,
bounceUp: null,
bounceDown: null,
signalUp: null,
signalDown: null
}
}
for (i=options.period+1;i<l;i++) {
var foundLowest = false;
var foundHighest = false;
var lowest = 0;
var highest = 0;
for (j=i;j>i-options.period;j--) {
if (foundLowest && foundHighest) {
break;
}
if (data[j][options.valueL] < data[i][options.valueL]) {
foundLowest = true;
} else {
lowest++;
}
if (data[j][options.valueH] > data[i][options.valueH]) {
foundHighest = true;
} else {
highest++;
}
}
if (!foundLowest) {
lowest = options.period;
}
if (!foundHighest) {
highest = options.period;
}
highlow[i] = {
d: data[i].d,
lowest: lowest,
highest: highest,
bounceUp: highlow[i-1].lowest-lowest,
bounceDown: highlow[i-1].highest-highest
};
if (options.ma) {
var sums = {};
sums['lowest'] = 0;
sums['highest'] = 0;
for (j=0;j<options.ma;j++) {
if (highlow[i-j]['lowest']) {
sums['lowest'] += highlow[i-j]['lowest'];
}
if (highlow[i-j]['highest']) {
sums['highest'] += highlow[i-j]['highest'];
}
}
highlow[i].lowest = sums['lowest']/options.ma;
highlow[i].highest = sums['highest']/options.ma;
highlow[i].bounceUp = highlow[i-1].lowest-lowest;
highlow[i].bounceDown = highlow[i-1].highest-highest;
}
highlow[i].signalUp = highlow[i].bounceUp>highlow[i-1].bounceUp?highlow[i-1].lowest:0;
highlow[i].signalDown = highlow[i].bounceDown>highlow[i-1].bounceDown?highlow[i-1].highest:0;
}
return highlow;
},
roofing: function(data, options) {
options = _.extend({
rangeLow: 48,
rangeHigh: 10,
value: 'c'
}, options);
options.rangeLow = parseInt(options.rangeLow);
options.rangeHigh = parseInt(options.rangeHigh);
var alpha1 = (Math.cos(360/options.rangeLow)+Math.sin(360/options.rangeLow)-1)/Math.cos(360/options.rangeLow);
var a1 = Math.exp(-1.414*Math.PI/options.rangeHigh);
var b1 = 2*a1*Math.cos(1.414*Math.PI/options.rangeHigh);
var c2 = b1;
var c3 = -a1*a1;
var c1 = 1-c2-c3;
var i;
var j;
var l = data.length;
var filtered = [];
var HP = [];
var filt1 = [];
var filt2 = [];
for (i=0;i<2;i++) {
filtered[i] = {
d: data[i].d,
v: 0
}
HP[i] = 0;
filt1[i] = 0;
filt2[i] = 0;
}
for (i=2;i<l;i++) {
HP[i] = (1-alpha1/2)*(data[i][options.value]-data[i-1][options.value])+(1-alpha1)*HP[i-1];
filt1[i] = c1*(HP[i]+HP[i-1])/2+c2*filt1[i-1]+c3*filt1[i-2];
filt2[i] = (1-alpha1/2)*(filt1[i]-filt1[i-1])+(1+alpha1)*filt2[i-1];
filtered[i] = {
d: data[i].d,
v: filt1[i],
v2: filt2[i]
};
}
/*console.log("Roofing",{
alpha1: alpha1,
a1: a1,
b1: b1,
c2: c2,
c3: c3,
c1: c1,
HP: HP,
filt1: filt1,
filt2: filt2
});*/
//console.log("filtered",filtered);
return filtered;
},
bandpass: function(data, options) {
options = _.extend({
rangeLow: 48,
rangeHigh: 10,
value: 'c'
}, options);
options.rangeLow = parseInt(options.rangeLow);
options.rangeHigh = parseInt(options.rangeHigh);
var alpha1 = (Math.cos(0.707*360/options.rangeLow)+Math.sin(0.707*360/options.rangeLow)-1)/Math.cos(0.707*360/options.rangeLow);
console.log("alpha1",alpha1);
var i;
var j;
var l = data.length;
var filtered = [];
var HP = [];
var LP = [];
for (i=0;i<2;i++) {
filtered[i] = {
d: data[i].d,
v: data[i][options.value]
}
HP[i] = 0;
LP[i] = 0;
}
console.log("filtered",filtered);
for (i=2;i<l;i++) {
HP[i] = (1-alpha1/2)*(1-alpha1/2)*(data[i][options.value]-2*data[i-1][options.value]+data[i-2][options.value])+2*(1-alpha1)*HP[i-1]-(1-alpha1)*(1-alpha1)*HP[i-2];
filtered[i] = {
d: data[i].d,
v: HP[i]
};
}
console.log("filtered",filtered);
// At this point, we have filtered out the low frequencies
// Now we filter out the high frequencies
var bandpassed = scope.indicators.superSmoother(filtered, {value:'v',period:options.rangeHigh});
return bandpassed;
},
sinwave: function(data, options) {
/*
John Ehlers Sinwave
Detect trends
*/
options = _.extend({
length: 40,
value: 'c'
}, options);
options.length = parseInt(options.length);
var alpha1 = (1-Math.sin(360/options.length))/Math.cos(360/options.length);
var i;
var j;
var l = data.length;
var output = [];
var HP = [];
var wave = [];
for (i=0;i<1;i++) {
output[i] = {
d: data[i].d,
v: 0
}
HP[i] = 0;
wave[i] = {
d: data[i].d,
v: 0
}
}
for (i=1;i<l;i++) {
HP[i] = 0.5*(1+alpha1)*(data[i][options.value]-data[i-1][options.value])+alpha1*HP[i-1];
output[i] = {
d: data[i].d,
v: HP[i]
};
}
var smoothed = scope.indicators.superSmoother(output, {value:'v'});
var averaged = scope.indicators.ma(smoothed, {period:3,value:'v'});
var powered = scope.indicators.power(smoothed, {value:'v',period:3});
for (i=1;i<l;i++) {
wave[i] = {
d: data[i].d,
v: averaged[i].v/Math.sqrt(powered[i].v)
};
}
/*console.log("averaged",averaged);
console.log("powered",powered);
console.log("wave",wave);*/
//console.log("sinwave",averaged);
return wave;
},
superSmoother: function(data, options) {
/*
John Ehlers Super Smoother
Smooth the data with a minimum of lag
*/
options = _.extend({
value: 'c',
period: 10
}, options);
options.period = parseInt(options.period);
var a1 = Math.exp(-1.414*Math.PI/options.period);
var b1 = 2*a1*Math.cos(1.414*Math.PI/options.period);
var c2 = b1;
var c3 = -a1*a1;
var c1 = 1-c2-c3;
var i;
var j;
var l = data.length;
var output = [];
var filt = [];
for (i=0;i<2;i++) {
output[i] = {
d: data[i].d,
v: data[i][options.value]
}
filt[i] = data[i][options.value];
}
for (i=2;i<l;i++) {
filt[i] = c1*(data[i][options.value]+data[i-1][options.value])/2+c2*filt[i-1]+c3*filt[i-2]
output[i] = {
d: data[i].d,
v: filt[i]
};
}
//console.log("superSmoother",output);
return output;
},
power: function(data, options) {
/*
Power function
Increase the power of a wave, with a moving average smoothing
*/
options = _.extend({
value: 'c',
period: 3
}, options);
options.period = parseInt(options.period);
var i;
var j;
var l = data.length;
var sum = 0;
var ma = [];
for (i=0;i<options.period;i++) {
ma[i] = {
d: data[i].d,
v: null
}
}
for (i=options.period;i<l;i++) {
sum = 0;
for (j=options.period;j>0;j--) {
sum += data[i-j][options.value]*data[i-j][options.value];
}
var avg = sum/options.period;
ma[i] = {
d: data[i].d,
v: avg
};
}
return ma;
},
patternCorrelation: function(data, options) {
/*
Pattern Correlation
Search for similar patterns in the historic data
*/
options = _.extend({
candles: 2,
future: 10,
patternThreshold: 0.1,
changeThreshold: 0.1,
n: 5
}, options);
options.candles = parseInt(options.candles);
options.future = parseInt(options.future);
options.patternThreshold = parseFloat(options.patternThreshold);
options.changeThreshold = parseFloat(options.changeThreshold);
options.repeats = parseInt(options.repeats);
//console.log("length",data.length);
data = data.slice(0,4000);
// clean the data, because somehow it's with bad data
/*data = _.filter(data, function(item) {
return item.o > 0;
});*/
var i;
var j;
var k;
var p;
var n;
var l;
var proportions = function(candle) {
// Mesures in value
var m = {
a: 0,
b: 0,
c: 0,
t: candle.h - candle.l
};
// Mesures in ratio
var p = {
a: 0,
b: 0,
c: 0
};
if (candle.o > candle.c) {
// down h o c l
m.a = (candle.h - candle.o)*-1;
m.b = (candle.o - candle.c)*-1;
m.c = (candle.c - candle.l)*-1;
} else {
// up h c o l
m.a = candle.h - candle.c;
m.b = candle.c - candle.o;
m.c = candle.o - candle.l;
}
// Calculate the ratio
p.a = m.a/m.t;
p.b = m.b/m.t;
p.c = m.c/m.t;
return p;
}
var getPattern = function(input, i, n) {
var j;
pattern = [];
for (j=n;j>0;j--) {
k = i-j;
p = proportions(data[k]);
pattern.push(p.a);
pattern.push(p.b);
pattern.push(p.c);
}
return pattern;
}
var getChanges = function(input, i, n) {
var j;
pattern = [];
for (j=1;j<n;j++) {
k = i+j;
pattern.push( (input[k].c - input[i].c) / input[i].c );
}
return pattern;
}
var map = function(x,in_min,in_max,out_min,out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
};
var getCorrelation = function(a, b) {
if (a.length != b.length) {
return -1;
}
var i;
var sum = 0;
var max = -10000;
var min = 10000;
_.each([a,b], function(series) {
_.each(series, function(item) {
if (item > max) max = item;
if (item < min) min = item;
});
});
var xa = _.map(a, function(item) {
return map(item, min, max, 0, 100);
});
var xb = _.map(b, function(item) {
return map(item, min, max, 0, 100);
});
for (i=0;i<a.length;i++) {
sum += Math.abs(xa[i]-xb[i]);
}
return (sum/a.length);
}
/*
- Build the pattern data
*/
var patterns = [];
var changes = [];
var correlations = [];
var chartData = [];
for (i=0;i<options.candles;i++) {
patterns[i] = false;
changes[i] = false;
}
l = data.length - options.future;
// We pre-calculate the pattern and future changes at each position
var pattern;
for (i=options.candles;i<l;i++) {
patterns[i] = getPattern(data, i, options.candles);
changes[i] = getChanges(data, i, options.future);
}
// Now We calculate the correlations
var doneIndex = {};
var correlation;
var correlationBuffer;
var sumPattern;
var sumChange;
for (i=options.candles;i<l;i++) {
correlationBuffer = {
i: i,
pattern: patterns[i],
matches: [],
series: {
pattern: [],
change: []
}
};
// Now we push the matching correlations
for (j=i;j<l;j++) {
correlation = getCorrelation(patterns[i],patterns[j]);
if (correlation < options.patternThreshold) {
correlationBuffer.matches.push({
correlation: correlation,
changeCorrelation: getCorrelation(changes[i],changes[j]),
i: j
});
correlationBuffer.series.pattern.push(patterns[j]);
correlationBuffer.series.change.push(changes[j]);
}
}
// We calculate the average correlation
correlationBuffer.n = correlationBuffer.matches.length;
if (correlationBuffer.n > options.n) {
sumPattern = 0;
sumChange = 0;
_.each(correlationBuffer.matches, function(item) {
if (correlationBuffer.i != item.i) {
sumPattern += item.correlation;
sumChange += item.changeCorrelation;
}
});
correlationBuffer.avg = {
pattern: sumPattern/(correlationBuffer.n-1),
change: sumChange/(correlationBuffer.n-1),
};
if (correlationBuffer.avg.change < options.changeThreshold) {
correlations.push(correlationBuffer);
}
}
}
correlations = correlations.sort(function(a,b) {
return a.avg.pattern-b.avg.pattern;
});
return {
chartData: chartData,
correlations: correlations
//data: data,
//patterns: patterns,
//correlations: correlations
};
}
};
}
timeseriesTransform.prototype.loadStats = function() {
var scope = this;
this.stats = {
stdev: function(data, options) {
options = _.extend({
value: 'v'
}, options);
if (!data) {
data = scope.data;
}
var sum = 0;
var n = 0;
var mean = scope.stats.mean(data, {value:options.value});
_.each(data, function(datapoint) {
sum += (datapoint[options.value]-mean)*(datapoint[options.value]-mean);
n++;
});
return Math.sqrt(sum/n);
},
mean: function(data, options) {
options = _.extend({
value: 'v'
}, options);
if (!data) {
data = scope.data;
}
var sum = 0;
var n = 0;
_.each(data, function(datapoint) {
sum += datapoint[options.value];
n++;
});
return sum/n;
},
sum: function(data, options) {
options = _.extend({
value: 'v'
}, options);
if (!data) {
data = scope.data;
}
var sum = 0;
var n = 0;
_.each(data, function(datapoint) {
sum += datapoint[options.value];
});
return sum;
},
min: function(data, options) {
options = _.extend({
value: 'v'
}, options);
if (!data) {
data = scope.data;
}
var min = Number.POSITIVE_INFINITY;
_.each(data, function(datapoint) {
if (datapoint[options.value] < min) {
min = datapoint[options.value];
}
});
return min;
},
max: function(data, options) {
options = _.extend({
value: 'v'
}, options);
if (!data) {
data = scope.data;
}
var max = Number.NEGATIVE_INFINITY;
_.each(data, function(datapoint) {
if (datapoint[options.value] > max) {
max = datapoint[options.value];
}
});
return max;
}
};
}
if (module && module.exports) {
module.exports = timeseriesTransform;
}