UNPKG

crypto-nodes

Version:

482 lines (326 loc) 12.5 kB
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); }