-
Notifications
You must be signed in to change notification settings - Fork 9
/
Aggregate.js
130 lines (90 loc) · 4.14 KB
/
Aggregate.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
const EventEmitter = require('./EventEmitter');
const Tick = require('./Tick');
// Add your own resolutions here
const RESOLUTION = {
'1m': 1 * 1000 * 60,
'15m': 15 * 1000 * 60,
'5m': 5 * 1000 * 60,
'1h': 1 * 1000 * 60 * 60,
'4h': 4 * 1000 * 60 * 60,
'1d': 24 * 1000 * 60 * 60,
}
class Aggregate extends EventEmitter {
constructor( options ) {
super();
// this.sendticks = options.sendticks; // Redundant, just don't subscribe to `tick` even if not needed doh
this.tick = new Tick({ files: options.files, nozeroes: false });
this.tick.on('tick', this.newtick.bind(this) );
this.tick.on('eof', this.lasttick.bind(this) );
this.label = options.resolution;
this.resolution = RESOLUTION[ this.label ];
this.lastopen = -1;
this.agg = {
resolution: this.label,
timestamp: null,
open: 0,
high: 0,
low: 0,
close: 0,
volume: 0
};
this.clearedunstarted = false;
}
start( symbol ) {
this.tick.start( symbol );
}
lasttick( ) {
this.agg.timestamp = new Date( this.lastopen );
this.emit( 'bar', this.agg );
this.emit( 'eof' );
}
newtick( tick ) {
// Get millisecond timestamp of this tick
let timestamp = tick.timestamp.getTime();
// Using `this.resolution` as a reference, calculate the millisecond open time of the current bar (in the past)
let openms = timestamp - ( timestamp % this.resolution );
// Hack for first unstarted bar, this will produce invalid data for the first bar
if ( this.lastopen == -1 )
this.lastopen = openms;
// Trades have been sent out of order (after bar has closed)
if ( openms < this.lastopen ) {
console.error('ERROR: price data sent after close!');
}
// If the open time of the current tick inside a bar of interval `this.resolution` is not equal to previous
// open time, then this tick starts a new bar
if ( openms != this.lastopen ) {
// Emit the previous bar, now it has closed
this.agg.timestamp = new Date( this.lastopen );
// Skip the first bar as we cannot guarantee we have the first trade in this bar to calculate an accurate OHL(c)
// This is *slightly* problematic.
// You can make an assumption that the first tick of the file you process from BitMEX historical
// data is indeed the very first (i.e. there is no previous data we've missed). This isn't really
// the case in a live situation when consuming a stream. But for this offline use case you can
// probably remove this 'unstarted' logic from code and restore the missing bar.
if ( this.clearedunstarted )
this.emit( 'bar', this.agg );
this.clearedunstarted = true;
// Note that, on TradingView at least, the `open` price is always equal to the previous `close` price
// I wasn't sure if the open price was the actual price of the first trade in the new bar? That seemed like it might be correct but no.
// No idea if this is a matter of convention or a 'standard' but if you check on tv this is how they do it hey-ho.
let lc = this.agg.close;
this.agg = {
resolution: this.label,
timestamp: null,
open: lc,
high: Math.max( tick.price, lc ), // Bug fix 26th jan
low: Math.min( tick.price, lc ),
close: tick.price,
volume: tick.size
};
this.lastopen = openms;
} else {
this.agg.high = Math.max( this.agg.high, tick.price );
this.agg.low = Math.min( this.agg.low, tick.price );
this.agg.close = tick.price;
this.agg.volume += tick.size;
}
this.emit( 'tick', tick );
}
}
module.exports = Aggregate;