UNPKG

tlab-trading-toolkit

Version:

A trading toolkit for building advanced trading bots on the GDAX platform

292 lines (291 loc) 12.9 kB
"use strict"; /*************************************************************************************************************************** * @license * * Copyright 2017 Coinbase, Inc. * * * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * * with the License. You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * * License for the specific language governing permissions and limitations under the License. * ***************************************************************************************************************************/ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const ExchangeFeed_1 = require("../ExchangeFeed"); const Bittrex = require("node-bittrex-api"); const BittrexAPI_1 = require("./BittrexAPI"); const types_1 = require("../../lib/types"); var wait = function (time) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { setTimeout(resolve, time); }); }); }; var retryCount = process.env.RETRY_COUNT || 1; class BittrexFeed extends ExchangeFeed_1.ExchangeFeed { constructor(config) { super(config); this.erroredProducts = new Set(); const auth = config.auth || { key: 'APIKEY', secret: 'APISECRET' }; this.url = config.wsUrl || 'wss://socket.bittrex.com/signalr'; this.counters = {}; Bittrex.options({ websockets_baseurl: this.url, apikey: auth.key, apisecret: auth.secret, inverse_callback_arguments: true, stream: false, cleartext: false, verbose: true }); this.connect(); } get owner() { return 'Bittrex'; } subscribe(products) { return __awaiter(this, void 0, void 0, function* () { if (!this.connection) { return false; } let index = 1; console.log('Subscribe started @ ', new Date()); for (let product of products) { yield wait(300); this.log('info', `Subscribing product ${product} at ${index} of ${products.length}`); index++; yield new Promise((resolve, reject) => { this.client.call('CoreHub', 'SubscribeToExchangeDeltas', product).done((err, result) => { if (err) { this.erroredProducts.add(product); console.log('Error occured'); resolve(false); return console.error(err); } if (result === true) { this.log('info', `Subscribed to ${product} on ${this.owner}, requesting snaphsot.`); this.client.call('CoreHub', 'queryExchangeState', product).done((err, data) => { this.log('info', `Snapshot received for ${product} on ${this.owner}`); const snapshot = this.processSnapshot(product, data); if (snapshot !== null) { this.push(snapshot); } else { this.erroredProducts.add(product); console.warn('Null received for snapshot for product ', product, 'raw message', data); } resolve(true); }); } }); }); } if (this.erroredProducts.size > 0) { console.log(`${this.erroredProducts.size} products errored retrying ....`); if (retryCount > 0) { retryCount--; this.subscribe(Array.from(this.erroredProducts)); } else { console.log('No more retry available'); console.log('could not subscribe following products ', Array.from(this.erroredProducts)); } ; this.erroredProducts.clear(); } else { console.log('All products subscribed'); console.log('Subscribe completed @ ', new Date()); } return true; }); } connect() { return __awaiter(this, void 0, void 0, function* () { Bittrex.websockets.client((client) => { this.client = client; client.serviceHandlers.messageReceived = (msg) => this.handleMessage(msg); client.serviceHandlers.bound = () => this.onNewConnection(); client.serviceHandlers.disconnected = (code, reason) => this.onClose(code, reason); client.serviceHandlers.onerror = (err) => this.onError(err); client.serviceHandlers.connected = (connection) => { this.connection = connection; this.emit('websocket-connection'); }; client.serviceHandlers.connectFailed = function (error) { console.log("Websocket connectFailed: ", error); }; client.serviceHandlers.bindingError = function (error) { console.log("Websocket bindingError: ", error); }; client.serviceHandlers.connectionLost = function (error) { console.log("Connection Lost: ", error); }; client.serviceHandlers.reconnecting = function (retry) { console.log("Websocket Retrying: ", retry); //return retry.count >= 3; // cancel retry true return true; }; }); }); } handleMessage(msg) { if (msg.type !== 'utf8' || !msg.utf8Data) { return; } let data; try { data = JSON.parse(msg.utf8Data); } catch (err) { this.log('debug', 'Error parsing feed message', msg.utf8Data); return; } if (!Array.isArray(data.M)) { return; } this.confirmAlive(); data.M.forEach((message) => { this.processMessage(message); }); } onOpen() { // no-op } onClose(code, reason) { console.log('Websocket connectFailed'); this.emit('websocket-closed'); this.connection = null; } close() { this.client.end(); } nextSequence(product) { let counter = this.counters[product]; if (!counter) { counter = this.counters[product] = { base: -1, offset: 0 }; } if (counter.base < 1) { console.warn(`Requesting next sequence without setting snapshot sequence for product ${product}, current counter offset ${counter.offset}`); return -1; } counter.offset += 1; return counter.base + counter.offset; } setSnapshotSequence(product, sequence) { let counter = this.counters[product]; if (!counter) { counter = this.counters[product] = { base: -1, offset: 0 }; } counter.base = sequence; } getSnapshotSequence(product) { const counter = this.counters[product]; return counter ? counter.base : -1; } processMessage(message) { switch (message.M) { case 'updateExchangeState': this.updateExchangeState(message.A); break; default: } } updateExchangeState(states) { const createUpdateMessage = (genericProduct, side, nonce, delta) => { const seq = this.nextSequence(genericProduct); const message = { type: 'level', time: new Date(), sequence: seq, sourceSequence: nonce, productId: genericProduct, side: side, price: delta.Rate, size: delta.Quantity, count: 1 }; return message; }; states.forEach((state) => { const product = state.MarketName; let genericProduct = BittrexAPI_1.BittrexAPI.genericProduct(product); const snaphotSeq = this.getSnapshotSequence(genericProduct); if (state.Nounce <= snaphotSeq) { return; } state.Buys.forEach((delta) => { const msg = createUpdateMessage(genericProduct, 'buy', state.Nounce, delta); this.push(msg); }); state.Sells.forEach((delta) => { const msg = createUpdateMessage(genericProduct, 'sell', state.Nounce, delta); this.push(msg); }); state.Fills.forEach((fill, index) => { const message = { type: 'trade', productId: genericProduct, time: new Date(fill.TimeStamp), tradeId: state.Nounce + '' + index, price: fill.Rate.toString(), size: fill.Quantity.toString(), side: fill.OrderType.toLowerCase() }; this.push(message); }); }); } processSnapshot(product, state) { try { if (state) { let genericProduct = BittrexAPI_1.BittrexAPI.genericProduct(product); const orders = {}; const snapshotMessage = { type: 'snapshot', time: new Date(), productId: genericProduct, sequence: state.Nounce, asks: [], bids: [], orderPool: orders }; state.Buys.forEach((order) => { addOrder(order, 'buy', snapshotMessage.bids); }); state.Sells.forEach((order) => { addOrder(order, 'sell', snapshotMessage.asks); }); this.setSnapshotSequence(genericProduct, state.Nounce); return snapshotMessage; function addOrder(order, side, levelArray) { const size = types_1.Big(order.Quantity); const newOrder = { id: String(order.Rate), price: types_1.Big(order.Rate), size: size, side: side }; const newLevel = { price: newOrder.price, totalSize: size, orders: [newOrder] }; levelArray.push(newLevel); } } return null; } catch (err) { console.log('Failed to process snapshot for ', product); console.error(err); return null; } } } exports.BittrexFeed = BittrexFeed;