cryptometric
Advanced tools
Comparing version 0.0.5 to 0.0.6
{ | ||
"name": "cryptometric", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"dependencies": { | ||
@@ -5,0 +5,0 @@ "eventemitter3": "^4.0.7", |
@@ -11,2 +11,3 @@ const EventEmitter = require('eventemitter3') | ||
this.reconnectionDelay = 1000 | ||
this.connectionTimeoutDelay = 6000 | ||
// this.valid = false | ||
@@ -41,3 +42,16 @@ } | ||
return true | ||
// Sometimes sockets will get stuck connecting which will prevent ready | ||
// events. Force closure if timeout is exceeded. | ||
this.connectionTimeout = setTimeout(() => { | ||
if (!this.connected) { | ||
if (this.socket) | ||
this.socket.close() | ||
// this.connect(true) | ||
// .catch(err => { | ||
// console.log(`[${this.id}] reconnection error`, err) | ||
// }) | ||
} | ||
}, this.connectionTimeoutDelay) | ||
return true //Promise.resolve() | ||
// } | ||
@@ -65,3 +79,4 @@ } | ||
if (this.connected) { | ||
return | ||
this.disconnect() | ||
// return | ||
} | ||
@@ -74,2 +89,5 @@ | ||
this.connect(true) | ||
.catch(err => { | ||
console.log(`[${this.id}] reconnection error`, err.message) | ||
}) | ||
} | ||
@@ -86,2 +104,4 @@ }, this.reconnectionDelay) | ||
emitOpen(event) { | ||
clearTimeout(this.connectionTimeout) | ||
this.connected = true | ||
@@ -99,3 +119,3 @@ this.error = false | ||
this.emit('error') | ||
this.emit('error', this.error) | ||
} | ||
@@ -102,0 +122,0 @@ |
@@ -1,6 +0,3 @@ | ||
// const EventEmitter = require('eventemitter3') | ||
const Exchanges = require('./exchanges') | ||
// const Config = require('../config') | ||
const QUEUE = {} | ||
@@ -19,40 +16,79 @@ const REFS = {} | ||
let sumsInterval = null | ||
// let activeExchanges = [] | ||
// extends EventEmitter | ||
class ExchangeManager { | ||
constructor(ticker, options) { | ||
// super() | ||
constructor(ticker, pair, options) { | ||
this.ticker = ticker | ||
this.instances = {} | ||
this.ready = false | ||
this.pair = pair | ||
this.options = Object.assign({ | ||
aggregateTrades: true, | ||
showSlippage: 'price', // false, 'price', 'bps' | ||
showSlippage: 'price', // false, 'price', 'bps' | ||
preferQuoteCurrencySize: true, | ||
}, options) | ||
this.instances = {} | ||
this.ready = false | ||
} | ||
async initialize(pair, options) { | ||
this.pair = pair | ||
initialize() { | ||
for (const name in Exchanges) { | ||
await this.initExchange(name, pair, options) | ||
this.initExchange(name) | ||
.catch(err => { | ||
console.log(`[exchange-manager] ${name} initialize error`, err.message) | ||
}) | ||
} | ||
setInterval(this.emitAggr.bind(this), 100) | ||
this.aggrInterval = setInterval(this.emitAggr.bind(this), 100) | ||
this.initHeartbeat() | ||
} | ||
async initExchange(name, pair, options) { | ||
shudown() { | ||
// FIXME: unbind preperly | ||
Object.keys(this.instances).map(x => x.shutdown()) | ||
clearInterval(this.heartbeatInterval) | ||
clearInterval(this.aggrInterval) | ||
} | ||
initHeartbeat() { | ||
const timeout = 60 | ||
this.heartbeatInterval = setInterval(() => { | ||
const now = new Date().getTime() / 1000 | ||
Object.values(this.instances).map(exchange => { | ||
if (exchange.alive && (now - exchange.alive) > timeout) { | ||
console.log('[exchange] heartbeat check failed', exchange.id, now - exchange.alive) | ||
if (!exchange.connecting()) { | ||
exchange.alive = null | ||
exchange.reconnect() | ||
} else { | ||
console.log('[exchange] heartbeat check failed: already connecting') | ||
} | ||
} | ||
}) | ||
}, 5 * 1000) | ||
} | ||
initExchange(name) { | ||
if (this.instances[name]) | ||
throw new Error(`${name} exchange already initialized`) | ||
const exchange = new Exchanges[name](options) | ||
exchange.pairs = [pair] | ||
exchange.connect() | ||
const exchange = new Exchanges[name](this.options) | ||
exchange.pairs = [this.ticker.pair] | ||
this.instances[name] = exchange | ||
exchange.on('trades', trades => { | ||
// console.log(`[exchange-manager] ${exchange.id} trades`, trades) | ||
// if (exchange.id === 'kraken' || exchange.id === 'hitbtc') { | ||
// | ||
// const now = new Date().getTime() / 1000 | ||
// console.log(`[exchange-manager] ${exchange.id} trades`, now - exchange.alive, trades) | ||
// } | ||
// [exchange] heartbeat check failed bitmex 12.329999923706055 || exchange.id === 'bitstamp' | ||
// [exchange] heartbeat check failed bitstamp 4.740999937057495 || | ||
//|| exchange.id === 'deribit' || exchange.id === 'bybit' | ||
// [exchange] heartbeat check failed hitbtc 6.608999967575073 | ||
// [exchange] heartbeat check failed deribit 4.352999925613403 | ||
// [exchange] heartbeat check failed bybit 0.37399983406066895 | ||
exchange.alive = new Date().getTime() / 1000 | ||
if (!trades || !trades.length) { | ||
@@ -152,18 +188,13 @@ return | ||
exchange.on('open-interest', value => { | ||
this.ticker.broadcast('exchange:open-interest', { | ||
exchange: exchange.id, | ||
value | ||
}) | ||
}) | ||
exchange.on('open', () => { | ||
console.log(`[exchange-manager] ${exchange.id} opened`) | ||
this.ticker.broadcast('exchange:connected', exchange.id) | ||
if (!this.ready) { | ||
this.ready = true | ||
Object.values(this.instances).forEach(x => { | ||
if (x.connecting()) { | ||
this.ready = false | ||
} | ||
}) | ||
if (this.ready) { | ||
console.log(`[exchange-manager] ready`) | ||
this.ticker.broadcast('exchange:ready') | ||
} | ||
} | ||
this.tryEmitReady() | ||
}) | ||
@@ -178,7 +209,7 @@ | ||
// exchange.on('match', pair => { | ||
// console.log(`[socket.exchange.on.match] ${exchange.id} matched ${pair}`) | ||
// exchange.on('match', this.ticker.pair => { | ||
// console.log(`[socket.exchange.on.match] ${exchange.id} matched ${this.ticker.pair}`) | ||
// // store.commit('settings/SET_EXCHANGE_MATCH', { | ||
// // exchange: exchange.id, | ||
// // match: pair | ||
// // match: this.ticker.pair | ||
// // }) | ||
@@ -190,5 +221,23 @@ // }) | ||
this.ticker.broadcast('exchange:error', error) | ||
this.tryEmitReady() | ||
}) | ||
return exchange.connect() | ||
} | ||
tryEmitReady() { | ||
if (!this.ready) { | ||
this.ready = true | ||
Object.values(this.instances).forEach(x => { | ||
if (x.connecting() && !x.error) { | ||
this.ready = false | ||
} | ||
}) | ||
if (this.ready) { | ||
console.log(`[exchange-manager] ready`) | ||
this.ticker.broadcast('exchange:ready') | ||
} | ||
} | ||
} | ||
emitAggr() { | ||
@@ -195,0 +244,0 @@ const now = Date.now() |
@@ -51,3 +51,2 @@ const Connection = require('./connection') | ||
return this._pairs[0] | ||
// return this._pair[0] | ||
} | ||
@@ -57,9 +56,6 @@ | ||
return this._pairs | ||
// return this._pair[0] | ||
} | ||
set pairs(values) { | ||
// console.log('PAIRS', values) | ||
this._pairs = values.map(x => this.formatPair(x)) | ||
// return this._pairs | ||
} | ||
@@ -97,3 +93,3 @@ | ||
queueTrades(trades) { | ||
emitTrades(trades) { | ||
if (!trades || !trades.length) { | ||
@@ -100,0 +96,0 @@ return |
@@ -26,4 +26,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -35,3 +35,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onopen = e => { | ||
@@ -43,6 +43,6 @@ this.emitOpen(e) | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -49,0 +49,0 @@ }) |
@@ -28,4 +28,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -37,3 +37,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onopen = e => { | ||
@@ -45,6 +45,6 @@ this.emitOpen(e) | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -51,0 +51,0 @@ }) |
@@ -25,4 +25,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -34,4 +34,4 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onopen = e => { | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onopen = event => { | ||
this.socket.send( | ||
@@ -45,12 +45,24 @@ JSON.stringify({ | ||
this.emitOpen(e) | ||
// https://docs.bitfinex.com/reference#ws-public-status | ||
// The WS API states that OPEN_INTEREST is available but always returns | ||
// a null placeholder | ||
// this.socket.send( | ||
// JSON.stringify({ | ||
// event: 'subscribe', | ||
// channel: 'status', | ||
// key: 'deriv:tBTCF0:USTF0' | ||
// }) | ||
// ) | ||
this.emitOpen(event) | ||
resolve() | ||
} | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
// this.socket.close() | ||
}) | ||
@@ -60,2 +72,9 @@ } | ||
formatLiveTrades(json) { | ||
// console.log('BITFINES', json) | ||
// if (json && Array.isArray(json[1])) { | ||
// // if (json[1][13]) | ||
// console.log('BITFINES', json[1][13], json[1]) | ||
// return | ||
// } | ||
if (!json || json[1] !== 'te') { | ||
@@ -62,0 +81,0 @@ return |
@@ -27,4 +27,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -38,3 +38,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onopen = e => { | ||
@@ -46,6 +46,6 @@ this.emitOpen(e) | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -55,3 +55,3 @@ }) | ||
/*queueTrades(trades) { | ||
/*emitTrades(trades) { | ||
if (!trades.length) { | ||
@@ -58,0 +58,0 @@ return |
@@ -35,4 +35,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -44,3 +44,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
@@ -70,6 +70,6 @@ this.socket.onopen = e => { | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -76,0 +76,0 @@ }) |
@@ -28,4 +28,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -37,4 +37,17 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => { | ||
const json = JSON.parse(event.data) | ||
if (json.topic === 'instrument_info.100ms.' + this.pair && json.data) { | ||
const info = json.data.update ? json.data.update[json.data.update.length - 1] : json.data | ||
if (info.open_interest) { | ||
// console.log(info) | ||
this.emit('open-interest', info.open_interest) | ||
} | ||
} | ||
if (json.topic === 'trade.' + this.pair && json.data) { | ||
this.emitTrades(this.formatLiveTrades(json)) | ||
} | ||
} | ||
this.socket.onopen = event => { | ||
@@ -46,3 +59,4 @@ this.skip = true | ||
op: 'subscribe', | ||
args: ['trade'] | ||
args: ['trade.' + this.pair , 'instrument_info.100ms.' + this.pair] | ||
// args: ['trade', 'instrument_info.100ms'] | ||
}) | ||
@@ -66,6 +80,6 @@ ) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -76,6 +90,2 @@ }) | ||
formatLiveTrades(json) { | ||
if (!json.data || json.topic !== 'trade.' + this.pair || !json.data.length) { | ||
return | ||
} | ||
return json.data.map(trade => { | ||
@@ -82,0 +92,0 @@ return { |
@@ -25,4 +25,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -34,3 +34,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
@@ -51,6 +51,6 @@ this.socket.onopen = e => { | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -62,3 +62,3 @@ }) | ||
if (json && json.size > 0) { | ||
this.queueTrades([ | ||
this.emitTrades([ | ||
{ | ||
@@ -65,0 +65,0 @@ exchange: this.id, |
@@ -26,4 +26,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -35,3 +35,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
@@ -54,7 +54,11 @@ this.socket.onopen = e => { | ||
this.keepalive = setInterval(() => { | ||
this.socket.send( | ||
JSON.stringify({ | ||
method: 'public/ping' | ||
}) | ||
) | ||
if (this.socket) { | ||
this.socket.send( | ||
JSON.stringify({ | ||
method: 'public/ping' | ||
}) | ||
) | ||
} else { | ||
clearInterval(this.keepalive) | ||
} | ||
}, 60000) | ||
@@ -72,6 +76,6 @@ | ||
} | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -78,0 +82,0 @@ }) |
@@ -51,4 +51,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -60,3 +60,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
@@ -71,7 +71,11 @@ this.socket.onopen = e => { | ||
this.keepalive = setInterval(() => { | ||
this.socket.send( | ||
JSON.stringify({ | ||
op: 'ping' | ||
}) | ||
) | ||
if (this.socket) { | ||
this.socket.send( | ||
JSON.stringify({ | ||
op: 'ping' | ||
}) | ||
) | ||
} else { | ||
clearInterval(this.keepalive) | ||
} | ||
}, 15000) | ||
@@ -89,6 +93,6 @@ | ||
} | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -95,0 +99,0 @@ }) |
@@ -25,4 +25,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -34,3 +34,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
@@ -53,6 +53,6 @@ this.socket.onopen = e => { | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -59,0 +59,0 @@ }) |
@@ -51,4 +51,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -58,9 +58,6 @@ else if (validation instanceof Promise) return validation | ||
return new Promise((resolve, reject) => { | ||
console.log('huobi', this.getUrl(this.pair), this.pair) | ||
this.socket = new WebSocket(this.getUrl(this.pair)) | ||
this.socket.binaryType = 'arraybuffer' | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(event.data)) | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(event.data)) | ||
this.socket.onopen = e => { | ||
@@ -82,6 +79,6 @@ for (let pair of this.pairs) { | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -88,0 +85,0 @@ }) |
@@ -27,4 +27,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -36,3 +36,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
@@ -56,7 +56,6 @@ this.socket.onopen = e => { | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = (e) => { | ||
console.log('KRAK!!! eee', e) | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -63,0 +62,0 @@ }) |
@@ -12,3 +12,3 @@ const Exchange = require('../exchange') | ||
connect() { | ||
connect(reconnection = false) { | ||
return false | ||
@@ -15,0 +15,0 @@ } |
@@ -41,4 +41,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -52,3 +52,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(event.data)) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(event.data)) | ||
@@ -64,3 +64,7 @@ this.socket.onopen = e => { | ||
this.keepalive = setInterval(() => { | ||
this.socket.send('ping') | ||
if (this.socket) { | ||
this.socket.send('ping') | ||
} else { | ||
clearInterval(this.keepalive) | ||
} | ||
}, 30000) | ||
@@ -79,6 +83,6 @@ | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -85,0 +89,0 @@ }) |
@@ -27,4 +27,4 @@ const Exchange = require('../exchange') | ||
connect() { | ||
const validation = super.connect() | ||
connect(reconnection = false) { | ||
const validation = super.connect(reconnection) | ||
if (!validation) return Promise.reject() | ||
@@ -36,3 +36,3 @@ else if (validation instanceof Promise) return validation | ||
this.socket.onmessage = event => this.queueTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
this.socket.onmessage = event => this.emitTrades(this.formatLiveTrades(JSON.parse(event.data))) | ||
@@ -53,6 +53,6 @@ this.socket.onopen = e => { | ||
this.socket.onclose = this.emitClose.bind(this) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.socket.onerror = err => { | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
reject() | ||
reject(err) | ||
} | ||
@@ -59,0 +59,0 @@ }) |
@@ -1,29 +0,49 @@ | ||
// const EventEmitter = require('eventemitter3') | ||
const OrderBooks = require('./order-books') | ||
const CombinedOrderBook = require('./combined-order-book') | ||
// const Config = require('../config') extends EventEmitter | ||
const OrderBookAnalyzer = require('./order-book-analyzer') | ||
class OrderBookManager { | ||
constructor(ticker) { | ||
// super() | ||
constructor(ticker, pair, options) { | ||
this.ticker = ticker | ||
this.options = Object.assign({ | ||
// pair: 'BTC-USD' | ||
}, options) | ||
this.ticker = ticker | ||
this.instances = {} | ||
this.combinedBook = new CombinedOrderBook() | ||
this.bookAnalyzer = new OrderBookAnalyzer(ticker, options) | ||
} | ||
async initialize(pair, options) { | ||
this.pair = pair | ||
this.options = options | ||
async initialize() { | ||
for (const name in OrderBooks) { | ||
await this.initOrderBook(name, pair, options) | ||
await this.initOrderBook(name) | ||
} | ||
this.bookAnalyzer.initialize() | ||
this.initHeartbeat() | ||
} | ||
async initOrderBook(name, pair, options) { | ||
shutdown() { | ||
clearInterval(this.heartbeatInterval) | ||
this.bookAnalyzer.shutdown() | ||
Object.keys(this.instances).map(x => x.stopOrderBook(x)) | ||
} | ||
initHeartbeat() { | ||
const timeout = 60 | ||
this.heartbeatInterval = setInterval(() => { | ||
const now = new Date().getTime() / 1000 | ||
Object.values(this.instances).map(book => { | ||
if (book.alive && now > book.alive + timeout) { | ||
console.log('[book] heartbeat check failed', book.id, now - book.alive) | ||
book.alive = null | ||
book.reconnect() | ||
} | ||
}) | ||
}, 5 * 1000) | ||
} | ||
async initOrderBook(name) { | ||
if (this.instances[name]) | ||
throw new Error(`${name} order book already initialized`) | ||
const book = new OrderBooks[name](pair) | ||
const book = new OrderBooks[name](this.ticker.pair) | ||
book.connect() | ||
@@ -40,4 +60,6 @@ this.instances[name] = book | ||
book.alive = new Date().getTime() / 1000 | ||
// this.emit('orders', orders) | ||
this.combinedBook.update(orders) | ||
this.bookAnalyzer.update(orders) | ||
this.ticker.broadcast('book:update', orders) | ||
@@ -76,3 +98,3 @@ }) | ||
snapshot() { | ||
return Object.values(this.instances).map(x => x.snapshot()) | ||
return Object.values(this.instances).map(x => book.alive ? x.snapshot() : null).filter(_ => _) | ||
} | ||
@@ -79,0 +101,0 @@ } |
@@ -25,3 +25,3 @@ const OrderBook = require('../order-book') | ||
connect() { | ||
connect(reconnection = false) { | ||
return new Promise((resolve, reject) => { | ||
@@ -43,3 +43,3 @@ this.socket = new WebSocket(`wss://fstream.binance.com/stream?streams=${this.formatPair(this.pair).toLowerCase()}@depth`) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
@@ -108,2 +108,3 @@ reject() | ||
const orders = { exchange: this.id, bids, asks, partial: true } | ||
// console.log('[order book] bimance', orders) | ||
this.emit('orders', orders) | ||
@@ -110,0 +111,0 @@ } |
@@ -25,3 +25,3 @@ const OrderBook = require('../order-book') | ||
connect() { | ||
connect(reconnection = false) { | ||
return new Promise((resolve, reject) => { | ||
@@ -43,3 +43,3 @@ this.socket = new WebSocket(`wss://stream.binance.com:9443/ws/${this.formatPair(this.pair).toLowerCase()}@depth`) | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
@@ -46,0 +46,0 @@ reject() |
@@ -15,3 +15,3 @@ const OrderBook = require('../order-book') | ||
connect() { | ||
connect(reconnection = false) { | ||
return new Promise((resolve, reject) => { | ||
@@ -25,12 +25,11 @@ this.socket = new WebSocket('wss://ws-feed.pro.coinbase.com') | ||
this.socket.onopen = e => { | ||
// this.socket.send( | ||
// JSON.stringify({ | ||
// type: 'subscribe', | ||
// product_ids: [this.pair], | ||
// channels: ['level2'] | ||
// }) | ||
// ) | ||
this.socket.send( | ||
JSON.stringify({ | ||
type: 'subscribe', | ||
product_ids: [this.pair], | ||
channels: ['level2'] | ||
}) | ||
) | ||
this.emitOpen(e) | ||
resolve() | ||
@@ -40,3 +39,3 @@ } | ||
this.socket.onerror = () => { | ||
this.emitError({ message: `${this.id} disconnected` }) | ||
this.emitError(err) //{ message: `${this.id} disconnected` }) | ||
@@ -43,0 +42,0 @@ reject() |
const BinanceOrderBook = require('./binance-order-book') | ||
const BinanceFuturesOrderBook = require('./binance-futures-order-book') | ||
const BybitOrderBook = require('./bybit-order-book') | ||
const CoinbaseProOrderBook = require('./coinbase-pro-order-book') | ||
@@ -8,3 +9,4 @@ | ||
BinanceFuturesOrderBook, | ||
BybitOrderBook, | ||
CoinbaseProOrderBook | ||
} |
@@ -8,18 +8,24 @@ const EventEmitter = require('eventemitter3') | ||
class Ticker extends EventEmitter { | ||
constructor() { | ||
constructor(pair) { | ||
super() | ||
this.pair = pair | ||
this.signals = {} | ||
} | ||
async initExchanges(pair, options) { | ||
this.exchanges = new ExchangeManager(this) | ||
await this.exchanges.initialize(pair, options) | ||
async initExchanges(options) { | ||
this.exchanges = new ExchangeManager(this, this.pair, options) | ||
await this.exchanges.initialize() | ||
} | ||
async initOrderBooks(pair, options) { | ||
this.orderBooks = new OrderBookManager(this) | ||
await this.orderBooks.initialize(pair, options) | ||
async initOrderBooks(options) { | ||
this.orderBooks = new OrderBookManager(this, this.pair, options) | ||
await this.orderBooks.initialize() | ||
} | ||
// async initOrderBooks(pair, options) { | ||
// this.orderBooks = new OrderBookManager(this) | ||
// await this.orderBooks.initialize(pair, options) | ||
// } | ||
async initWsServer(server) { | ||
@@ -26,0 +32,0 @@ this.wss = new WsServer(server) |
@@ -6,252 +6,172 @@ class Bar { | ||
this.open = this.close = this.high = this.low = Number.NaN | ||
// this.volume = Number.NaN | ||
} | ||
} | ||
// if (Array.isArray(data)) {, data | ||
// this.add(data) | ||
// } | ||
// else if (data) { | ||
// this.openTime = data.openTime | ||
// this.closeTime = data.openTime + interval | ||
// this.open = data.open | ||
// this.close = data.close | ||
// this.high = data.high | ||
// this.low = data.low | ||
// this.volume = data.volume | ||
// this.updateMeta(data.meta) | ||
// // if (data.meta) { | ||
// // Object.keys(data.meta).forEach(key => this[key] = data.meta[key]) | ||
// // } | ||
// } | ||
class CandleStream { | ||
constructor(interval, enableGaps) { | ||
this.interval = interval | ||
this.data = [] | ||
this.enableGaps = enableGaps !== false | ||
} | ||
tick(time, price, meta) { | ||
// if (!tick) return false | ||
// const t = tick[0], p = tick[1], v = tick[2], m = tick[3] | ||
if (time < this.openTime && time > this.closeTime) { | ||
console.log('[candlestick] mismatch:', time, 'open:', this.openTime, 'open:', this.closeTime) | ||
return false | ||
// Concentrate OHLC data into the current bars | ||
add(time, open, high, low, close, meta) { | ||
let bar = this.lastBar | ||
if (!bar || (time > bar.closeTime)) { | ||
bar = new Bar(this.interval) | ||
// this.push(bar) | ||
this.mergeBar(bar, time, open, high, low, close, meta) | ||
this.push(bar) | ||
} else { | ||
this.mergeBar(bar, time, open, high, low, close, meta) | ||
} | ||
} | ||
addData(data, meta) { | ||
this.add(data.time, data.open, data.high, data.low, data.close, meta) | ||
} | ||
if (isNaN(this.openTime)) { | ||
this.openTime = time | ||
this.closeTime = time + this.interval | ||
this.open = this.close = this.high = this.low = price | ||
// this.volume = volume | ||
this.updateMeta(meta) | ||
this.lastTickTime = time | ||
return true | ||
} | ||
addTick(time, price, meta) { | ||
let bar = this.lastBar | ||
if (time > this.openTime && time <= this.closeTime) { | ||
this.high = Math.max(this.high, price) | ||
this.low = Math.min(this.low, price) | ||
// this.volume += volume | ||
// if (this.high < price) { | ||
// this.high = price | ||
// } | ||
// else if (this.low > p) { | ||
// this.low = p | ||
// } | ||
this.close = price | ||
this.updateMeta(meta) | ||
this.lastTickTime = time | ||
return true | ||
if (!bar || (time > bar.closeTime)) { | ||
// console.log('NEW TICK BAR', interval, time, bar.closeTime, bar) | ||
bar = new Bar(this.interval) | ||
this.updateBar(bar, time, price, meta) | ||
this.push(bar) | ||
} else { | ||
this.updateBar(bar, time, price, meta) | ||
} | ||
} | ||
// add(openTime, open, high, low, close, meta) { | ||
ohlc(time, open, high, low, close, meta) { | ||
if (isNaN(this.openTime)) { | ||
this.openTime = time | ||
this.closeTime = time + this.interval | ||
this.open = open | ||
this.high = high | ||
this.low = low | ||
// this.volume = 0 | ||
updateBar(bar, time, price, meta) { | ||
if (time < bar.openTime && time > bar.closeTime) { | ||
console.log('[candlestick] mismatch:', time, 'open:', bar.openTime, 'close:', bar.closeTime) | ||
return false | ||
} | ||
// if (this.lastTickTime && time < this.lastTickTime) { | ||
// throw Error(`Cannot merge early bar`) | ||
// } | ||
const latest = !bar.lastBarTime || time > bar.lastBarTime | ||
bar.lastBarTime = time | ||
if (time < this.openTime && time > this.closeTime) { | ||
throw Error(`Cannot merge unsynchronized bar`) | ||
if (isNaN(bar.openTime)) { | ||
time = Math.round(time / this.interval) * this.interval | ||
bar.openTime = time | ||
bar.closeTime = time + this.interval | ||
bar.open = bar.close = bar.high = bar.low = price | ||
} else { | ||
bar.high = Math.max(bar.high, price) | ||
bar.low = Math.min(bar.low, price) | ||
bar.close = latest ? price : bar.close | ||
} | ||
const latest = !this.lastTickTime || time > this.lastTickTime | ||
this.close = latest ? close : this.close | ||
this.high = Math.max(this.high, high) | ||
this.low = Math.min(this.low, low) | ||
// this.volume += volume | ||
this.updateMeta(meta) | ||
this.lastTickTime = time | ||
// this.high = high | ||
// // this.low = low | ||
// if (isNaN(this.openTime)) { | ||
// this.openTime = time | ||
// this.closeTime = time + this.interval | ||
// this.open = open | ||
// this.close = close | ||
// this.high = high | ||
// this.low = low | ||
// this.volume = volume | ||
// this.updateMeta(meta) | ||
// | ||
// this.closeTime = Math.max(this.closeTime, bar.closeTime) | ||
// this.close = bar.close | ||
// this.volumeLow = Math.min(this.volumeLow, bar.volume) | ||
// this.volumeHigh = Math.max(this.volumeHigh, bar.volume) | ||
// | ||
// return true | ||
// } | ||
// | ||
// if (t > this.openTime && t <= this.closeTime) { | ||
// this.volume += v | ||
// | ||
// if (this.high < p) { | ||
// this.high = p | ||
// } | ||
// else if (this.low > p) { | ||
// this.low = p | ||
// } | ||
// this.close = p | ||
// | ||
// this.updateMeta(m) | ||
// return true | ||
// } | ||
return false | ||
this.updateMeta(bar, meta) | ||
return true | ||
} | ||
updateMeta(meta) { | ||
if (meta) { | ||
Object.keys(meta).forEach(key => { | ||
if (isNaN(this[key])) this[key] = 0 | ||
this[key] += meta[key] || 0 | ||
}) | ||
mergeBar(bar, time, open, high, low, close, meta) { | ||
if (time < bar.openTime && time > bar.closeTime) { | ||
console.log('[candlestick] mismatch:', time, 'open:', bar.openTime, 'close:', bar.closeTime) | ||
return false | ||
} | ||
} | ||
} | ||
class CandleStream { | ||
constructor(interval, enableGaps) { | ||
this.interval = interval | ||
this.data = [] | ||
this.openTime = this.closeTime = Number.NaN | ||
this.open = this.close = this.high = this.low = Number.NaN | ||
// this.volumeLow = this.volumeHigh = Number.NaN | ||
this.enableGaps = enableGaps !== false | ||
} | ||
const latest = !bar.lastBarTime || time > bar.lastBarTime | ||
bar.lastBarTime = time | ||
add(openTime, open, high, low, close, meta) { | ||
const bar = new Bar(this.interval) | ||
bar.ohlc(openTime, open, high, low, close, meta) | ||
this.push(bar) | ||
return bar | ||
} | ||
addTick(time, price, meta) { | ||
let bar = this.data[this.data.length - 1] | ||
if (this.data.length === 0 || time > bar.closeTime) { | ||
bar = new Bar(this.interval) | ||
bar.tick(time, price, meta) | ||
this.push(bar) | ||
if (isNaN(bar.openTime)) { | ||
time = Math.round(time / this.interval) * this.interval | ||
bar.openTime = time | ||
bar.closeTime = time + this.interval | ||
bar.open = open | ||
bar.high = high | ||
bar.low = low | ||
// bar.close = close | ||
} | ||
// else if (time > bar.closeTime) { | ||
// bar = new Bar(this.interval) | ||
// bar.tick(time, price, meta) | ||
// this.push(bar) | ||
// } | ||
else { | ||
if (bar.tick(time, price, meta)) { | ||
this.openTime = Math.min(this.openTime, bar.openTime) | ||
this.closeTime = Math.max(this.closeTime, bar.closeTime) | ||
this.high = Math.max(this.high, bar.high) | ||
this.low = Math.min(this.low, bar.low) | ||
this.close = bar.close | ||
// this.volumeLow = Math.min(this.volumeLow, bar.volume) | ||
// this.volumeHigh = Math.max(this.volumeHigh, bar.volume) | ||
// } | ||
// else { | ||
// bar = new Bar(this.interval) | ||
// bar.tick(time, price, meta) | ||
// this.push(bar) | ||
// } | ||
} | ||
} | ||
bar.high = Math.max(bar.high, high) | ||
bar.low = Math.min(bar.low, low) | ||
// bar.close = close | ||
bar.close = latest ? close : bar.close | ||
// bar.count += count | ||
// bar.closeTime = time // FIXME | ||
this.updateMeta(bar, meta) | ||
return true | ||
} | ||
// addTickStream(ticks) { | ||
// if (!ticks) { | ||
// return | ||
// ohlc(time, open, high, low, close, meta) { | ||
// if (isNaN(candle.openTime)) { | ||
// candle.openTime = time | ||
// candle.closeTime = time + candle.interval | ||
// candle.open = open | ||
// candle.high = high | ||
// candle.low = low | ||
// } | ||
// | ||
// if (ticks.data.length > 0) { | ||
// for (let i = 1; i < ticks.data.length; i++) { | ||
// this.addTick(ticks.data[i]) | ||
// } | ||
// // if (candle.lastBarTime && time < candle.lastBarTime) { | ||
// // throw Error(`Cannot merge early bar`) | ||
// // } | ||
// | ||
// if (time < candle.openTime && time > candle.closeTime) { | ||
// throw Error(`Cannot merge unsynchronized bar`) | ||
// } | ||
// | ||
// const latest = !candle.lastBarTime || time > candle.lastBarTime | ||
// | ||
// candle.close = latest ? close : candle.close | ||
// candle.high = Math.max(candle.high, high) | ||
// candle.low = Math.min(candle.low, low) | ||
// // candle.volume += volume | ||
// candle.updateMeta(meta) | ||
// | ||
// candle.lastBarTime = time | ||
// return false | ||
// } | ||
// Concentrate OHLC data into the current bars | ||
merge(time, open, high, low, close, meta) { | ||
let bar | ||
if (this.data.length === 0 || (!this.closeTime || time > this.closeTime)) { | ||
const openTime = Math.round(time / this.interval) * this.interval | ||
this.add(openTime, open, high, low, close, meta) | ||
updateMeta(bar, meta) { | ||
if (meta) { | ||
Object.keys(meta).forEach(key => { | ||
if (meta[key] === undefined) | ||
return | ||
if (!isNaN(meta[key])) { | ||
if (isNaN(bar[key])) bar[key] = 0 | ||
bar[key] += meta[key] || 0 | ||
} else { | ||
bar[key] = meta[key] | ||
} | ||
}) | ||
} | ||
else { | ||
const bar = this.data[this.data.length - 1] | ||
bar.ohlc(time, open, high, low, close, meta) | ||
this.openTime = Math.min(this.openTime, bar.openTime) | ||
this.closeTime = Math.max(this.closeTime, bar.closeTime) | ||
this.high = Math.max(this.high, bar.high) | ||
this.low = Math.min(this.low, bar.low) | ||
this.close = bar.close | ||
// this.volumeLow = Math.min(this.volumeLow, bar.volume) | ||
// this.volumeHigh = Math.max(this.volumeHigh, bar.volume) | ||
} | ||
} | ||
push(bar) { | ||
if (!this.enableGaps && this.data.length) { | ||
const lastBar = this.data[this.data.length - 1] | ||
bar.open = lastBar.close | ||
if (this.data.length) { | ||
this.lastBar.complete = true | ||
if (!this.enableGaps) { | ||
bar.open = this.lastBar.close | ||
} | ||
} | ||
// bar.complete = true | ||
if (isNaN(this.openTime)) { | ||
this.openTime = bar.openTime | ||
this.closeTime = bar.openTime + this.interval | ||
this.open = bar.open | ||
this.close = bar.close | ||
this.high = bar.high | ||
this.low = bar.low | ||
// this.volumeLow = bar.volume | ||
// this.volumeHigh = bar.volume | ||
} | ||
else { | ||
this.closeTime = Math.max(this.closeTime, bar.closeTime) | ||
this.high = Math.max(this.high, bar.high) | ||
this.low = Math.min(this.low, bar.low) | ||
this.close = bar.close | ||
// this.volumeLow = Math.min(this.volumeLow, bar.volume) | ||
// this.volumeHigh = Math.max(this.volumeHigh, bar.volume) | ||
} | ||
// if (isNaN(this.openTime)) { | ||
// this.openTime = bar.openTime | ||
// this.closeTime = bar.closeTime | ||
// this.open = bar.open | ||
// this.close = bar.close | ||
// this.high = bar.high | ||
// this.low = bar.low | ||
// // this.volumeLow = bar.volume | ||
// // this.volumeHigh = bar.volume | ||
// } | ||
// else { | ||
// this.closeTime = Math.max(this.closeTime, bar.closeTime) | ||
// this.high = Math.max(this.high, bar.high) | ||
// this.low = Math.min(this.low, bar.low) | ||
// this.close = bar.close | ||
// // this.volumeLow = Math.min(this.volumeLow, bar.volume) | ||
// // this.volumeHigh = Math.max(this.volumeHigh, bar.volume) | ||
// } | ||
this.data.push(bar) | ||
} | ||
clear() { | ||
reset() { | ||
this.data = [] | ||
this.openTime = this.closeTime = Number.NaN | ||
this.open = this.close = this.high = this.low = Number.NaN | ||
// this.openTime = this.closeTime = Number.NaN | ||
// this.open = this.close = this.high = this.low = Number.NaN | ||
// this.volumeLow = this.volumeHigh = Number.NaN | ||
@@ -279,4 +199,8 @@ } | ||
} | ||
get lastBar() { | ||
return this.data[this.data.length - 1] | ||
} | ||
} | ||
module.exports = CandleStream |
@@ -1,31 +0,2 @@ | ||
// import store from '../store' | ||
// | ||
// export function parseQueryString() { | ||
// let QUERY_STRING | ||
// | ||
// try { | ||
// QUERY_STRING = JSON.parse( | ||
// '{"' + | ||
// decodeURI(location.search.substring(1)) | ||
// .replace(/"/g, '\\"') | ||
// .replace(/&/g, '","') | ||
// .replace(/=/g, '":"') + | ||
// '"}' | ||
// ) | ||
// } catch (error) { | ||
// QUERY_STRING = {} | ||
// } | ||
// | ||
// for (let name in QUERY_STRING) { | ||
// try { | ||
// QUERY_STRING[name] = JSON.parse(QUERY_STRING[name]) | ||
// } catch (error) { | ||
// // empty | ||
// } | ||
// } | ||
// | ||
// return QUERY_STRING | ||
// } | ||
export function formatAmount(amount, decimals, precision) { | ||
function formatAmount(amount, decimals, precision) { | ||
const negative = amount < 0 | ||
@@ -44,3 +15,3 @@ | ||
} else { | ||
amount = +amount.toFixed(4) | ||
amount = +amount.toFixed(2) | ||
} | ||
@@ -55,34 +26,8 @@ | ||
export function countDecimals(value) { | ||
if (Math.floor(value) === value) return 0 | ||
return value.toString().split('.')[1].length || 0 | ||
} | ||
export function formatPrice(price, precision) { | ||
function formatPrice(price, precision) { | ||
price = +price || 0 | ||
// precision = precision || 2 | ||
return price.toFixed(precision || 2) | ||
// if (precision) { | ||
// return price.toFixed(store.state.settings.decimalPrecision) | ||
// // } else if (store.state.app.optimalDecimal) { | ||
// // return price.toFixed(store.state.app.optimalDecimal) | ||
// } else { | ||
// } | ||
return price.toFixed(typeof(precision) !== 'undefined' ? precision : 2) | ||
} | ||
export function padNumber(num, size) { | ||
var s = '000000000' + num | ||
return s.substr(s.length - size) | ||
} | ||
export function roundNearest(num, acc) { | ||
if (acc < 0) { | ||
return Math.round(num*acc) / acc | ||
} else { | ||
return Math.round(num/acc) * acc | ||
} | ||
} | ||
export function ago(timestamp) { | ||
function ago(timestamp) { | ||
const seconds = Math.floor((new Date() - timestamp) / 1000) | ||
@@ -101,3 +46,3 @@ let interval, output | ||
export function timeframeToMs(timeframe) { | ||
function timeframeToMs(timeframe) { | ||
const type = timeframe[timeframe.length - 1] | ||
@@ -114,3 +59,3 @@ const integer = parseInt(timeframe) | ||
export function getHms(timestamp, round) { | ||
function getHms(timestamp, round) { | ||
if (isNaN(timestamp) || timestamp === null) { | ||
@@ -139,3 +84,3 @@ return null | ||
export function uniqueName(name, names) { | ||
function uniqueName(name, names) { | ||
let base = name.substr() | ||
@@ -151,7 +96,7 @@ let variante = 1 | ||
export function movingAverage(accumulator, newValue, alpha) { | ||
return alpha * newValue + (1.0 - alpha) * accumulator | ||
} | ||
// function movingAverage(accumulator, newValue, alpha) { | ||
// return alpha * newValue + (1.0 - alpha) * accumulator | ||
// } | ||
export function formatTime(time) { | ||
function formatTime(time) { | ||
const date = new Date(time * 1000) | ||
@@ -162,3 +107,8 @@ | ||
export function camelToSentence(str) { | ||
function randomString(len) { | ||
const p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | ||
return [...Array(len)].reduce(a=>a+p[~~(Math.random()*p.length)],'') | ||
} | ||
function camelToSentence(str) { | ||
str = str.replace(/([A-Z])/g, ' $1') | ||
@@ -168,3 +118,3 @@ return str.charAt(0).toUpperCase() + str.slice(1) | ||
export function snakeToSentence(str) { | ||
function snakeToSentence(str) { | ||
str = str.replace(/_/g, ' ') | ||
@@ -174,2 +124,28 @@ return str.charAt(0).toUpperCase() + str.slice(1) | ||
function toTitleCase(str) { | ||
return str.replace (/^[-_]*(.)/, (_, c) => c.toUpperCase()) // Initial char (after -/_) | ||
.replace (/[-_]+(.)/g, (_, c) => ' ' + c.toUpperCase()) // First char after each -/_ | ||
} | ||
function trimArray(arr, maxLength) { | ||
const max = maxLength - (maxLength * 0.1) | ||
return arr.length > max ? arr.slice(Math.max(arr.length - max, 0)) : arr | ||
} | ||
module.exports = { | ||
formatAmount, | ||
formatPrice, | ||
ago, | ||
timeframeToMs, | ||
getHms, | ||
uniqueName, | ||
formatTime, | ||
randomString, | ||
camelToSentence, | ||
snakeToSentence, | ||
toTitleCase, | ||
trimArray, | ||
} | ||
// export const setValueByDotNotation = (object, path, value) => { | ||
@@ -176,0 +152,0 @@ // if (path.length === 1) object[path[0]] = value |
@@ -9,95 +9,5 @@ class Tick { | ||
addTick(time, price, meta) { | ||
if (this.count >= this.resolution) { | ||
console.log('[tick] exceeded update count') | ||
return false | ||
} | ||
if (isNaN(this.openTime)) { | ||
this.openTime = time | ||
this.open = this.close = this.high = this.low = price | ||
} | ||
this.high = Math.max(this.high, price) | ||
this.low = Math.min(this.low, price) | ||
this.close = price | ||
this.updateMeta(meta) | ||
this.count++ | ||
this.closeTime = time | ||
return true | ||
} | ||
add(count, time, open, high, low, close, meta) { | ||
// if (tick.resolution > this.resolution) { | ||
// console.log('[tick] cannot add greater resolution') | ||
// return false | ||
// } | ||
if (this.count + count > this.resolution) { | ||
console.log('[tick] exceeded add count') | ||
return false | ||
} | ||
if (isNaN(this.openTime)) { | ||
this.openTime = time | ||
// this.open = open | ||
// this.high = tick.high | ||
// this.low = tick.low | ||
this.open = this.close = this.high = this.low = close | ||
// this.close = tick.close | ||
// this.close = this.high = this.low = price | ||
} | ||
this.high = Math.max(this.high, high) | ||
this.low = Math.min(this.low, low) | ||
this.close = close | ||
this.updateMeta(meta) | ||
this.count += count | ||
this.closeTime = time // FIXME | ||
return true | ||
} | ||
// ohlc(time, open, high, low, close, meta) { | ||
// if (isNaN(this.openTime)) { | ||
// this.openTime = time | ||
// this.closeTime = time + this.resolution | ||
// this.open = open | ||
// this.high = high | ||
// this.low = low | ||
// // this.volume = 0 | ||
// } | ||
// | ||
// // if (this.closeTime && time < this.closeTime) { | ||
// // throw Error(`Cannot add early tick`) | ||
// // } | ||
// | ||
// if (time < this.openTime && time > this.closeTime) { | ||
// throw Error(`Cannot add unsynchronized tick`) | ||
// } | ||
// | ||
// const latest = !this.closeTime || time > this.closeTime | ||
// | ||
// this.close = latest ? close : this.close | ||
// this.high = Math.max(this.high, high) | ||
// this.low = Math.min(this.low, low) | ||
// // this.volume += volume | ||
// this.updateMeta(meta) | ||
// | ||
// this.closeTime = time | ||
// | ||
// return false | ||
// get complete() { | ||
// return this.count >= this.resolution | ||
// } | ||
get finished() { | ||
return this.count >= this.resolution | ||
} | ||
updateMeta(meta) { | ||
if (meta) { | ||
Object.keys(meta).forEach(key => { | ||
if (isNaN(this[key])) this[key] = 0 | ||
this[key] += meta[key] || 0 | ||
}) | ||
} | ||
} | ||
} | ||
@@ -109,4 +19,4 @@ | ||
this.data = [] | ||
this.openTime = this.closeTime = Number.NaN | ||
this.open = this.close = this.high = this.low = Number.NaN | ||
// this.openTime = this.closeTime = Number.NaN | ||
// this.open = this.close = this.high = this.low = Number.NaN | ||
this.enableGaps = enableGaps !== false | ||
@@ -117,13 +27,11 @@ } | ||
add(count, time, open, high, low, close, meta) { | ||
let tick = this.data[this.data.length - 1] | ||
let bar = this.lastBar | ||
if (!tick || tick.finished) { | ||
tick = new Tick(this.resolution) | ||
tick.add(count, time, open, high, low, close, meta) | ||
this.push(tick) | ||
if (!bar || bar.complete) { | ||
bar = new Tick(this.resolution) | ||
this.mergeBar(bar, count, time, open, high, low, close, meta) | ||
this.push(bar) | ||
} else { | ||
this.mergeBar(bar, count, time, open, high, low, close, meta) | ||
} | ||
else { | ||
tick.add(count, time, open, high, low, close, meta) | ||
} | ||
// if (this.data.length === 0 || (!this.closeTime || time > this.closeTime)) { | ||
@@ -141,3 +49,3 @@ // const openTime = Math.round(time / this.interval) * this.interval | ||
// else { | ||
// const bar = this.data[this.data.length - 1] | ||
// const bar = this.lastBar | ||
// bar.ohlc(time, open, high, low, close, meta) | ||
@@ -155,38 +63,121 @@ // | ||
addData(count, data, meta) { | ||
this.add(count, data.time, data.open, data.high, data.low, data.close, meta) | ||
// let bar = this.lastBar | ||
// | ||
// if (!bar|| bar.complete) { | ||
// bar = new Tick(this.resolution) | ||
// bar.addTick(time, price, meta) | ||
// this.push(bar) | ||
// } | ||
// else { | ||
// bar.addTick(time, price, meta) | ||
// } | ||
} | ||
addTick(time, price, meta) { | ||
let tick = this.data[this.data.length - 1] | ||
let bar = this.lastBar | ||
if (!tick|| tick.finished) { | ||
tick = new Tick(this.resolution) | ||
tick.addTick(time, price, meta) | ||
this.push(tick) | ||
if (!bar|| bar.complete) { | ||
bar = new Tick(this.resolution) | ||
this.updateBar(bar, time, price, meta) | ||
this.push(bar) | ||
} else { | ||
this.updateBar(bar, time, price, meta) | ||
} | ||
else { | ||
tick.addTick(time, price, meta) | ||
} | ||
updateBar(bar, time, price, meta) { | ||
if (bar.count >= bar.resolution) { | ||
console.log('[bar] exceeded update count') | ||
return false | ||
} | ||
if (isNaN(bar.openTime)) { | ||
bar.openTime = time | ||
bar.open = bar.close = bar.high = bar.low = price | ||
} | ||
bar.high = Math.max(bar.high, price) | ||
bar.low = Math.min(bar.low, price) | ||
bar.close = price | ||
bar.count++ | ||
bar.closeTime = time | ||
bar.complete = bar.count >= this.resolution | ||
this.updateMeta(bar, meta) | ||
return true | ||
} | ||
push(tick) { | ||
if (!this.enableGaps && this.data.length) { | ||
const lastTick = this.data[this.data.length - 1] | ||
tick.open = lastTick.close | ||
mergeBar(bar, count, time, open, high, low, close, meta) { | ||
// if (bar.resolution > this.resolution) { | ||
// console.log('[bar] cannot add greater resolution') | ||
// return false | ||
// } | ||
if (bar.count + count > this.resolution) { | ||
console.log('[bar] exceeded add count') | ||
return false | ||
} | ||
if (isNaN(this.openTime)) { | ||
this.openTime = tick.openTime | ||
this.open = this.close = this.high = this.low = tick.close | ||
if (isNaN(bar.openTime)) { | ||
bar.openTime = time | ||
bar.open = open | ||
bar.high = high | ||
bar.low = low | ||
bar.close = close | ||
} | ||
this.closeTime = Math.max(this.closeTime, tick.closeTime) | ||
this.high = Math.max(this.high, tick.high) | ||
this.low = Math.min(this.low, tick.low) | ||
this.close = tick.close | ||
bar.high = Math.max(bar.high, high) | ||
bar.low = Math.min(bar.low, low) | ||
bar.close = close | ||
bar.count += count | ||
bar.closeTime = time // FIXME | ||
bar.complete = bar.count >= this.resolution | ||
this.updateMeta(bar, meta) | ||
return true | ||
} | ||
this.data.push(tick) | ||
updateMeta(bar, meta) { | ||
if (meta) { | ||
Object.keys(meta).forEach(key => { | ||
if (meta[key] === undefined) | ||
return | ||
// console.log('updateMeta', key, meta[key], this[key], isNaN(meta[key])) | ||
if (!isNaN(meta[key])) { | ||
if (isNaN(bar[key])) bar[key] = 0 | ||
bar[key] += meta[key] || 0 | ||
} else { | ||
bar[key] = meta[key] | ||
} | ||
}) | ||
} | ||
} | ||
clear() { | ||
push(bar) { | ||
if (this.data.length) { | ||
this.lastBar.complete = true | ||
if (!this.enableGaps) { | ||
bar.open = this.lastBar.close | ||
} | ||
} | ||
// if (isNaN(this.openTime)) { | ||
// this.openTime = bar.openTime | ||
// this.open = this.close = this.high = this.low = bar.close | ||
// } | ||
// | ||
// this.closeTime = Math.max(this.closeTime, bar.closeTime) | ||
// this.high = Math.max(this.high, bar.high) | ||
// this.low = Math.min(this.low, bar.low) | ||
// this.close = bar.close | ||
this.data.push(bar) | ||
} | ||
reset() { | ||
this.data = [] | ||
this.openTime = this.closeTime = Number.NaN | ||
this.open = this.close = this.high = this.low = Number.NaN | ||
// this.openTime = this.closeTime = Number.NaN | ||
// this.open = this.close = this.high = this.low = Number.NaN | ||
} | ||
@@ -213,2 +204,6 @@ | ||
} | ||
get lastBar() { | ||
return this.data[this.data.length - 1] | ||
} | ||
} | ||
@@ -215,0 +210,0 @@ |
118354
43
3805