UNPKG

bfx-hf-ext-plugin-bitfinex

Version:
263 lines (217 loc) 7.15 kB
'use strict' const Promise = require('bluebird') const _pick = require('lodash/pick') const _isEqual = require('lodash/isEqual') const _isObject = require('lodash/isObject') const _isString = require('lodash/isString') const _isEmpty = require('lodash/isEmpty') const { EventEmitter } = require('events') const debug = require('debug')('bfx:hf:ext-plugin:bitfinex:ao-adpater') const ManagedOB = require('bfx-api-node-plugin-managed-ob') const ManagedCandles = require('bfx-api-node-plugin-managed-candles') const Watchdog = require('bfx-api-node-plugin-wd') const { Order } = require('bfx-api-node-models') const { subscribe, unsubscribe, findChannelId, Manager, cancelOrder, submitOrder, send } = require('bfx-api-node-core') const HB_INTERVAL_MS = 2500 module.exports = class AOAdapter extends EventEmitter { static getTimeFrames () { return { '1 Minute': '1m', '5 Minutes': '5m', '15 Minutes': '15m', '30 Minutes': '30m', '1 Hour': '1h', '3 Hours': '3h', '6 Hours': '6h', '12 Hours': '12h', '1 Day': '1D', '7 Days': '7D', '14 Days': '14D', '1 Month': '1M' } } constructor ({ wsURL, restURL, apiKey, apiSecret, agent, dms, withHeartbeat, affiliateCode }) { super() this.pendingOrderSubmitCancelTimeouts = [] this.affiliateCode = affiliateCode this.hbEnabled = withHeartbeat this.hbInterval = null this.m = new Manager({ plugins: [ManagedOB(), ManagedCandles(), Watchdog({ packetWDDelay: 30 * 1000 })], transform: true, dms, apiSecret, apiKey, agent, wsURL, restURL }) this.m.on('ws2:error', this.propagateEvent.bind(this, 'meta:error')) this.m.on('ws2:ticker', this.propagateDataEvent.bind(this, 'ticker')) this.m.on('ws2:trades', this.propagateDataEvent.bind(this, 'trades')) this.m.on('ws2:candles', this.propagateDataEvent.bind(this, 'candles')) this.m.on('ws2:book', this.propagateDataEvent.bind(this, 'book')) this.m.on('ws2:managed:book', this.propagateDataEvent.bind(this, 'managed:book')) this.m.on('ws2:managed:candles', this.propagateDataEvent.bind(this, 'managed:candles')) this.m.on('ws2:notification', this.propagateDataEvent.bind(this, 'notification')) this.m.on('socket:updated', this.onSocketUpdate.bind(this)) this.m.on('ws2:event:info-server-restart', this.onServerRestart.bind(this)) this.m.on('ws2:reopen', this.onWSReopen.bind(this)) this.m.on('ws2:open', this.propagateEvent.bind(this, 'open')) this.m.on('ws2:event:auth:success', this.propagateEvent.bind(this, 'auth:success')) this.m.on('ws2:event:auth:error', this.propagateEvent.bind(this, 'auth:error')) this.m.on('ws2:auth:n', this.propagateEvent.bind(this, 'auth:n')) this.m.on('ws2:auth:os', this.propagateEvent.bind(this, 'order:snapshot')) this.m.on('ws2:auth:on', this.propagateEvent.bind(this, 'order:new')) this.m.on('ws2:auth:ou', this.propagateEvent.bind(this, 'order:update')) this.m.on('ws2:auth:oc', this.propagateEvent.bind(this, 'order:close')) this.m.on('ws2:data:trades', this.propagateEvent.bind(this, 'trades')) this.m.on('ws2:data:book', this.propagateEvent.bind(this, 'book')) } updateAuthArgs (args = {}) { this.m.updateAuthArgs(args) } reconnect () { if (this.hbInterval !== null) { clearInterval(this.hbInterval) this.hbInterval = null } this.m.reconnectAllSockets() if (this.hbEnabled) { this.hbInterval = setInterval(this.sendHB.bind(this), HB_INTERVAL_MS) } } connect () { this.m.openWS() if (this.hbEnabled) { this.hbInterval = setInterval(this.sendHB.bind(this), HB_INTERVAL_MS) } } /** * @return {Promise} p */ disconnect () { if (this.hbInterval !== null) { clearInterval(this.hbInterval) this.hbInterval = null } // Clean up pending actions this.pendingOrderSubmitCancelTimeouts.forEach(timeoutObject => { if (timeoutObject.t !== null) { clearTimeout(timeoutObject.t) timeoutObject.t = null } }) this.pendingOrderSubmitCancelTimeouts = [] return this.m.closeAllSockets() } sendHB () { this.m.withAuthSocket((ws) => { send(ws, [0, 'n', null, { mid: Date.now(), type: 'ucm-hb', info: {} }]) }) } getConnection () { const id = this.m.sampleWSI() const c = this.m.getWSByIndex(id) return { id, c } } propagateEvent (name, ...args) { this.emit(name, ...args) } propagateDataEvent (name, data, meta = {}) { this.emit(`data:${name}`, data, meta) } onSocketUpdate (i, state) { this.emit('meta:connection:update', i, state) } onServerRestart () { // Otherwise the DMS flag closes all orders, and the packets are received // after a server restart, before the connection drops. We cannot // differentiate between manual user cancellations and those packets. this._ignoreOrderEvents = true } onWSReopen () { this._ignoreOrderEvents = false // see onServerRestart this.emit('meta:reload') } subscribe (connection, channel, filter) { subscribe(connection.c, channel, filter) } unsubscribe (connection, channel, filter) { const cid = findChannelId(connection.c, (data) => { if (data.channel !== channel) { return false } const fv = _pick(data, Object.keys(filter)) return _isEqual(filter, fv) }) if (!cid) { return debug('error unsubscribing: unknown channel %s', channel) } unsubscribe(connection.c, cid) } orderEventsValid () { return !this._ignoreOrderEvents } async submitOrderWithDelay (connection, delay, order) { const { c } = connection if (_isString(this.affiliateCode) && !_isEmpty(this.affiliateCode)) { if (order instanceof Order) { order.affiliateCode = this.affiliateCode } else if (_isObject(order)) { if (!order.meta) { order.meta = {} } order.meta.aff_code = this.affiliateCode // eslint-disable-line } } return new Promise((resolve, reject) => { const t = setTimeout(() => { timeoutObject.t = null submitOrder(c, order) .then(resolve) .catch(reject) }, delay) const timeoutObject = { t } this.pendingOrderSubmitCancelTimeouts.push(timeoutObject) }) } async cancelOrderWithDelay (connection, delay, order) { const { c } = connection return new Promise((resolve, reject) => { const t = setTimeout(() => { timeoutObject.t = null cancelOrder(c, order) .then(resolve) .catch(reject) }, delay) const timeoutObject = { t } this.pendingOrderSubmitCancelTimeouts.push(timeoutObject) }) } sendWithAnyConnection (packet) { this.m.withAuthSocket((ws) => { send(ws, packet) }) } notify (ws, level, message) { send(ws, [0, 'n', null, { type: 'ucm-notify-ui', info: { level, message } }]) } }