UNPKG

crypto-nodes

Version:

512 lines (349 loc) 10.8 kB
var redis = require("redis"); var async = require('async'); var orderCache = { prefix: 'crypto-order-', cache: {}, errors: {}, error_limit: 3, error_tick: 1000, register_error(exchange, error) { if(!this.errors[exchange]) { this.errors[exchange] = []; } this.errors[exchange].push(error); if(this.errors[exchange].length > error_limit) { // Need to disable exchange orderManagerEmit(this.node, 'orderbook_op', { payload: { op: "disable_exchange", exchange: exchange }}); } }, init: function (node) { client = redis.createClient('6379', process.env.REDIS_SERVER || 'node1.builder-apps.org', { password: process.env.REDIS_PASSWORD }); var that = this; this.node = node; if(this.error_timer) { clearInterval(this.error_timer); } // Remove 1 error every tick from each exchange this.error_timer = setInterval(function () { for(x in that.errors) { if(that.errors[x].length) { that.errors[x].shift(); } } }, this.error_tick); // Read all orders from redis function callback(orders) { console.log(orders); } client.keys('*', function (err, keys) { if (err) return console.log(err); if(keys) { var snapshot_keys = []; for(x in keys) { if(keys[x].substr(0, that.prefix.length) == that.prefix) { snapshot_keys.push(keys[x]); } else { console.log(keys[x]); } } async.map(snapshot_keys, function(key, cb) { client.get(key, function (error, value) { if (error) { return cb(null, false); } else { return cb(null, value); } }); }, function (error, results) { var out = []; if(!error && results.length) { for(x in results) { try { var order = JSON.parse(results[x]); out.push(order); } catch(e) { } } } callback(out); }); } }); }, process: function(order) { // 2. Save incoming orders in simple format under <XXX ID> // This should trigger in order ADD / ACCEPT and should modify redis data as being the most up to date var order_id = order.account_; client.set(this.prefix + order_id, JSON.stringify(order)); }, update: function(order_id, filled) { // 3. If we have order filled events, substract from redis crypto-order-<XXX ID> the filled QTY client.get(this.prefix + order_id, function(err, reply) { if(reply) { try { var order = JSON.parse(reply); order.order_qty_ = order.order_qty_ - filled; client.set(this.prefix + order_id, JSON.stringify(order)); } catch (e) {} } }); }, get: function(order_id, callback) { // 4. Read the current order state from redis, we need to add it again to the orderbook because: // A. It was partially processed and IOC triggered by exchanged // B. Exchange returned error either BALANCE or NETWORK or DDOS client.get(this.prefix + order_id, function(err, reply) { var order = false; if(reply) { try { order = JSON.parse(reply); } catch (e) {} } callback(order); }); } } module.exports = function(RED) { function orderManager(config) { RED.nodes.createNode(this,config); var node = this; orderCache.init(node); var processed = { add: 0, accept: 0, cancel: 0, replace: 0, fill: 0, error: 0 } console.log('Order Manager Online!'); console.log('Debug Data'); setTimeout(function () { orderManagerEmit(node, 'exchange_op', { payload: { exchange: 'all', op: 'get_balance' } }); }, 1000); setInterval(function () { node.status({fill:"green",shape:"dot", text: JSON.stringify(processed)}); }, 1000); node.on('close', function() { om_http.close(); }); node.on('input', function(msg) { // console.log(msg); // Internal functions if(msg && msg.payload && msg.payload.op) { if(msg.payload.op == 'order_log') { if(msg.payload.data && msg.payload.data.exchange) { console.log("Updating Balance"); orderManagerEmit(node, 'exchange_op', { payload: { exchange: msg.payload.data.exchange, op: 'get_balance' } }); } switch(msg.payload.status) { case 'ADDED': break; case 'DONE': // Order Completed break; case 'CANCEL': // Order was cancelled before fully filled // Return Order to orderbook break; case 'ERROR': // Order was cancelled before fully filled if(msg.payload.msg == 'BALANCE') { // Should be handled elsewhere } else { // Generic exchange error // Increment exchange error counter // If 3 errors in 10 seconds // Disable exchange for 1 minute (or similar) } break; } if(msg.payload.status == 'CANCEL' || msg.payload.status == 'ERROR') { // These 2 statuses require returning the order to orderbook } console.log('ORDERLOG ::') console.log(msg.payload); orderManagerEmit(node, 'order_status', { payload: msg.payload }); } if(msg.payload.op == 'exchange_order') { console.log(msg); } // We need to forward balances data to orderbook if(msg.payload.op == 'get_balance') { msg.payload.type = 'balances'; orderManagerEmit(node, 'orderbook_op', msg); } } else { // Check for orderbook event if(msg.key) { if(processed.hasOwnProperty(msg.key)) { processed[msg.key]++; } var tx = orderManagerProcessTX(msg); // console.log(tx); if(tx.op) { console.log('OP ::'); console.log(tx); // If its a fill on exchange, forward it to exchanges if(tx.op == 'exchange_order' && tx.exchange) { orderManagerEmit(node, 'exchange_op', { payload: { exchange: tx.exchange, op: tx.op, tx: tx } }); } // Inhouse order emits multiple events if(tx.op == 'inhouse_order') { for(x in tx.emits) { orderManagerEmit(node, 'order_status', { payload: tx.emits[x] }); } } else { // Emit single event orderManagerEmit(node, 'order_status', { payload: tx }); } } if(tx.status) { //console.log(tx); if(tx.data && tx.data.data && tx.data.data.order) { var order = tx.data.data.order; // We have original order information here console.log('STATUS ::'); console.log(order); // Lets store it in redis in case we need to return it to orderbook orderCache.process(order); orderManagerEmit(node, 'order_status', { payload: tx }); } } } else { // Not implemented? if(msg.payload.status == 'disconnected') { return; } if(msg.payload.status == 'connected') { return; } console.log('orderManager missing EVENT'); console.log(msg); } //console.log('OB EVENT::'); // Orderbook events like fill.. //console.log(msg); } }); } function orderManagerProcessTX(msg) { var tx = {}; console.log(msg); switch(msg.key) { case 'add': case 'added': tx.status = 'ADDED'; tx.order_id = msg.data.account_id; tx.data = msg; break; case 'accept': tx.status = 'ACCEPT'; tx.order_id = msg.data.order.account_; tx.data = msg; // Order added break; case 'fill': var o_inbound = msg.data.inbound_order; var o_matched = msg.data.matched_order; var i_house = o_inbound.exchange == 'internal'; var m_house = o_matched.exchange == 'internal'; tx = { op: 'exchange_order', symbol: msg.data.symbol, qty: msg.data.fill_qty, price: msg.data.fill_price, emits: [], } if(i_house && m_house) { tx.op = 'inhouse_order'; tx.emits.push({ status:'FILLED', order_id: o_inbound.account_, filled_amount: msg.data.fill_qty, data: msg.data }); tx.emits.push({ status:'FILLED', order_id: o_matched.account_, filled_amount: msg.data.fill_qty, data: msg.data }); if(o_inbound.open_qty_ == 0) { tx.emits.push({ status:'DONE', order_id: o_inbound.account_, data: msg.data }); } if(o_matched.open_qty_ == 0) { tx.emits.push({ status:'DONE', order_id: o_matched.account_, data: msg.data }); } // TRANSFER $$ BETWEEN ACCOUNTS } else if(!i_house && !m_house) { // TRANSFER BETWEEN 2 EXCHANGES, WE DONT SUPPORT THIS YET.. // OR .. TODO .. } else { // Inbound order is internal if(i_house) { tx.exchange = o_matched.exchange; tx.is_buy = !o_matched.is_buy_; tx.cost = o_matched.cost; tx.o_qty = o_matched.order_qty_; tx.acc = o_inbound.account_; tx.o_price = o_inbound.price_; tx.o_cost = o_inbound.cost; } // Matched order is internal if(m_house) { tx.exchange = o_inbound.exchange; tx.is_buy = !o_inbound.is_buy_; tx.cost = o_inbound.cost; tx.o_qty = o_inbound.order_qty_; tx.acc = o_matched.account_; tx.o_price = o_matched.price_; tx.o_cost = o_matched.cost; } } break; } //console.log(tx); return tx; } function orderManagerAddOrder(node, order) { /* var payload = { type: 'add', from: order.symbol.split('/')[0], to: order.symbol.split('/')[1], is_buy: priority: 10, } var order = { s: msg.payload.from + '/' + msg.payload.to , b: (msg.payload.is_buy ? true : false), p: msg.payload.price, q: msg.payload.quantity, a: msg.payload.account_id }; orderManagerEmit(node, 'orderbook_op', { payload: payload }); */ } function orderManagerEmit(node, type, msg) { var outputs = [ 'exchange_op', 'orderbook_op', 'order_status', 'error' ]; var out = []; for(x in outputs) { if(type == outputs[x]) { out.push(msg); } else { out.push(null); } } if(type == 'order_status') { // Check for errors, disable exchanges.. } //console.log('EMITTING ::'); //console.log(out); node.send(out); } RED.nodes.registerType("orderManager",orderManager); RED.nodes.registerType("redis_server",function () {}); }