UNPKG

timeseries-transform

Version:
1,419 lines (1,187 loc) 30.1 kB
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; }