tlab-trading-toolkit
Version:
A trading toolkit for building advanced trading bots on the GDAX platform
292 lines (291 loc) • 12.9 kB
JavaScript
;
/***************************************************************************************************************************
* @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;