crypto-nodes
Version:
482 lines (326 loc) • 12.5 kB
JavaScript
const BitMEXClient = require('bitmex-realtime-api');
var request = require('request');
var crypto = require('crypto');
var per_second = 0;
var interval;
// TESTNET API ACCESS::
var exchange = 'bitmex';
var bitmex_obj = {
fees: {},
interval: false,
poloniex: false,
connected: false,
exchange_symbols: [],
configured_symbols: [],
enabled_symbols: [],
get_exchange_symbols: function(callback) {
request(this.server_api + '/api/v1/instrument/active', function (error, response, body) {
if(response && response.statusCode && response.statusCode == 200) {
var out = {};
try {
var all_symbols = JSON.parse(body);
for(x in all_symbols) {
var sym = all_symbols[x];
out[sym.symbol] = [sym.rootSymbol, sym.quoteCurrency];
}
} catch(e) {
}
callback(out);
}
});
},
get_enabled_symbols: function() {
var out = {};
for(x in this.exchange_symbols) {
var pass = false;
var exc_tmp = this.exchange_symbols[x];
for(y in this.configured_symbols) {
var cfg_tmp = this.configured_symbols[y].split('/');
// If its matching or inverted variant matches its enabled symbol
if((exc_tmp[0] == cfg_tmp[0] && exc_tmp[1] == cfg_tmp[1]) ||
(exc_tmp[0] == cfg_tmp[1] && exc_tmp[1] == cfg_tmp[0]) && x == exc_tmp[0] + cfg_tmp[1]
) {
pass = true;
}
}
if(pass) {
out[x] = this.exchange_symbols[x];
}
}
return out;
},
init_ws: function(node, symbols) {
var that = this;
for(x in symbols) {
try {
function add_stream(code, sym_from, sym_to) {
/*
that.client.addStream(code, 'orderBook10', function (data, symbol, tableName) {
per_second++;
// console.log(data);
node.send({ payload: {
'exchange' : 'bitmex',
fees: that.fees,
sym_from: sym_from,
sym_to: sym_to,
data: data
}});
});
*/
that.client.addStream(code, 'orderBookL2', function (data, symbol, tableName) {
per_second++;
console.log(code + " " + data.length);
node.send({ payload: {
'exchange' : 'bitmex',
fees: that.fees,
sym_from: sym_from,
sym_to: sym_to,
data: data
}});
});
}
add_stream(x, symbols[x][0], symbols[x][1]);
} catch (e) {
console.log(e);
}
}
},
init: function(config, node, symbols, callback) {
var that = this;
try { this.fees = JSON.parse(config.fees); } catch (e) {}
this.configured_symbols = symbols;
this.server_api = config.server_api || 'https://www.bitmex.com';
//console.log('error:', error); // Print the error if one occurred
//console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
//console.log('body:', body); // Print the HTML for the Google homepage.
if(!this.interval) {
this.interval = setInterval(function () {
var color = per_second > 0 ? 'green' : 'red';
// console.log('BITMEX :: ' + per_second + ' requests per second');
// console.log('BITMEX :: LATEST ' + JSON.stringify(latest));
node.status({ fill: color,shape:'ring', text: per_second + ' requests per second' });
per_second = 0;
}, 1000);
}
var conf = {
testnet: true, // config.test || false,
maxTableLen: 10000,
}
if(config.api_key && config.api_secret) {
conf.apiKeyID = config.api_key || 'ZviWaX2HMC8oCIQTX5Dn9q3n';
conf.apiKeySecret = config.api_secret || 'rgNrGvhxyG-UPU_eEfM23CKolTgwg11B-SXRrk95FkD3dp2z';
}
// console.log(config);
// console.log(conf);
// 'ZviWaX2HMC8oCIQTX5Dn9q3n';
// '';
try {
this.client = new BitMEXClient(conf);
this.client.on('error', function(e) { console.error('BITMEX :: Received error:', e); });
this.client.on('end', function(e) {
console.error('BITMEX :: Received end:');
console.log(e);
/*
setTimeout(function () {
that.init(config, node, symbols, callback);
},5000);
*/
});
this.client.on('open', () => console.log('BITMEX :: Connection opened.'));
this.client.on('close', function(e) {
console.log('BITMEX :: Connection closed.');
console.log(e);
setTimeout(function () {
that.init(config, node, symbols, callback);
},5000);
});
this.client.on('initialize', function () {
that.get_exchange_symbols(function (exchange_symbols) {
that.exchange_symbols = exchange_symbols;
var enabled_symbols = that.get_enabled_symbols();
//console.log(exchange_symbols);
//console.log(enabled_symbols);
that.init_ws(node, enabled_symbols);
});
});
} catch(e) {
that.init(config, node, symbols, callback);
}
},
}
module.exports = function(RED) {
function bitmexConnector(config) {
RED.nodes.createNode(this,config);
var node = this;
node.on('input', function(msg) {
if(config.disabled) {
// Disable UI Updates
if(bitmex_obj.interval) {
clearInterval(bitmex_obj.interval);
}
// Set node status
node.status({ fill: 'red',shape:'ring', text: 'Disabled' });
// Emit flush orderbook
node.send({payload: { exchange: 'bitmex', op: 'flush' }});
return;
}
if(msg.payload && msg.payload.op == 'subscribe') {
bitmex_obj.init(config, node, msg.payload.symbols);
}
});
node.on('close', function() {
// tidy up any async code here - shutdown connections and so on.
node.status({ fill: 'red',shape:'ring', text: 'Offline' });
node.send({payload: { exchange: 'bitmex', op: 'flush' }});
});
}
function bitmexOrder(config) {
RED.nodes.createNode(this,config);
var node = this;
// DUE TO CURRENT PARSING METHODOLOGY
// CURRENT IMPLEMENTATION DOES NOT PLAY WELL WITH * SYMBOL
client.addStream('XBTUSD', 'execution', function (data, symbol, tableName) {
console.log(tableName);
console.log(symbol);
console.log(data);
});
client.addStream('XBTUSD', 'order', function (data, symbol, tableName) {
console.log(tableName);
console.log(symbol);
console.log(data);
});
/*
client.addStream('XBTUSD', 'position', function (data, symbol, tableName) {
console.log(tableName);
console.log(symbol);
console.log(data);
});
*/
node.on('input', function(msg) {
// Basic validate its an exchange order for this exchange
if(msg.payload &&
msg.payload.op &&
msg.payload.op == 'exchange_order' &&
msg.payload.exchange == exchange &&
msg.payload.tx) {
console.log('BITMEX SENDING EXCHANGE ORDER ::');
console.log(msg.payload.tx);
var verb = 'POST';
var path = '/api/v1/order';
var expires = new Date().getTime() + (60 * 1000);
// Order Data
var data = {
symbol: msg.payload.tx.symbol.replace('/',''),
orderQty:msg.payload.tx.qty,
price: msg.payload.tx.price,
ordType:"Limit",
timeInForce: "ImmediateOrCancel",
side: msg.payload.tx.is_buy ? 'Buy' : 'Sell',
clOrdID: msg.payload.tx.acc,
};
// Pre-compute the postBody so we can be sure that we're using *exactly* the same body in the request
// and in the signature. If you don't do this, you might get differently-sorted keys and blow the signature.
var postBody = JSON.stringify(data);
var signature = crypto.createHmac('sha256', apiKeySecret).update(verb + path + expires + postBody).digest('hex');
var headers = {
'content-type' : 'application/json',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
// This example uses the 'expires' scheme. You can also use the 'nonce' scheme. See
// https://www.bitmex.com/app/apiKeysUsage for more details.
'api-expires': expires,
'api-key': apiKeyID,
'api-signature': signature
};
const requestOptions = {
headers: headers,
url:'https://testnet.bitmex.com'+path,
method: verb,
body: postBody
};
request(requestOptions, function(error, response, body) {
if (error) { console.log(error); }
try {
var data = JSON.parse(body);
console.log('TRADE RESULT ::');
// {"orderID":"5a15357f-0253-4e19-7d82-c088dc0b0625","clOrdID":"","clOrdLinkID":"","account":73630,"symbol":"XBTUSD","side":"Buy","simpleOrderQty":null,"orderQty":1432,"price":9673,"displayQty":null,"stopPx":null,"pegOffsetValue":null,"pegPriceType":"","currency":"USD","settlCurrency":"XBt","ordType":"Limit","timeInForce":"ImmediateOrCancel","execInst":"","contingencyType":"","exDestination":"XBME","ordStatus":"Filled","triggered":"","workingIndicator":false,"ordRejReason":"","simpleLeavesQty":0,"leavesQty":0,"simpleCumQty":0.16518438,"cumQty":1432,"avgPx":8669.2674,"multiLegReportingType":"SingleSecurity","text":"Submitted via API.","transactTime":"2018-05-13T17:27:16.128Z","timestamp":"2018-05-13T17:27:16.128Z"}
console.log(data);
var res = {
op: 'exchange_order',
remote_id: data.orderID,
local_id: data.clOrdID,
filled: data.ordStatus == 'Filled'
}
var color = res.filled ? 'green' : 'red';
// console.log('BITMEX :: ' + per_second + ' requests per second');
// console.log('BITMEX :: LATEST ' + JSON.stringify(latest));
node.status({ fill: color,shape:'ring', text: res.local_id + " " + data.ordStatus });
node.send({ payload: res });
} catch(e) {
}
});
}
});
}
function bitmexBalance(config) {
RED.nodes.createNode(this,config);
var node = this;
return;
// Available tables are announcement,connected,chat,publicNotifications,instrument,settlement,funding,insurance,liquidation,orderBookL2,orderBookL2_25,orderBook10,quote,trade,quoteBin1m,quoteBin5m,quoteBin1h,quoteBin1d,tradeBin1m,tradeBin5m,tradeBin1h,tradeBin1d,privateNotifications,account,wallet,affiliate,margin,position,transact,order,execution.
bitmex_obj.client.addStream(false, 'wallet', function (data, symbol, tableName) {
//console.log('WALLET');
//console.log(data);
if(tableName == 'wallet' && data) {
var out = {};
for(x in data) {
out[data[x].currency.toUpperCase()] = { balance: parseFloat(data[x].amount) };
var text = 'Received balance data ' + data[x].currency.toUpperCase() + ' = ' + data[x].amount;
node.status({ fill: 'green',shape:'ring', text: text });
}
node.send({ payload: { exchange: exchange, op: 'get_balance', balances: out } });
}
});
node.on('input', function(msg) {
//console.log('BITMEX INPUT ::');
//console.log(msg);
if(msg.payload && msg.payload.op && msg.payload.op == 'get_balance') {
if(msg.payload.exchange && (msg.payload.exchange == 'all' || msg.payload.exchange == exchange)) {
var verb = 'GET';
var path = '/api/v1/user/wallet';
var expires = new Date().getTime() + (60 * 1000);
var signature = crypto.createHmac('sha256', apiKeySecret).update(verb + path + expires).digest('hex');
var headers = {
'content-type' : 'application/json',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'api-expires': expires,
'api-key': apiKeyID,
'api-signature': signature
};
const requestOptions = { headers: headers, url: bitmex_obj.requestUrl + path, method: verb };
request(requestOptions, function(error, response, body) {
var out = {};
if (error) {
console.log(error);
} else {
try {
var data = JSON.parse(body);
if(data.currency && data.amount) {
out[data.currency.toUpperCase()] = { balance: parseFloat(data.amount) };
var text = 'Received balance data ' + data.currency.toUpperCase() + ' = ' + data.amount;
node.status({ fill: 'green',shape:'ring', text: text });
}
} catch(e) {
console.log(e);
}
}
node.send({ payload: { exchange: exchange, op: 'get_balance', balances: out } });
});
}
}
});
}
RED.nodes.registerType("bitmexBalance", bitmexBalance);
RED.nodes.registerType("bitmexOrder", bitmexOrder);
RED.nodes.registerType("bitmexConnector", bitmexConnector);
}