crypto-nodes
Version:
527 lines (397 loc) • 11.3 kB
JavaScript
/*
GENERIC CCXT 3 TYPES OF RESPONCES REQUIRED TO HANDLE::
{ _msgid: '3d3003de.c0249c',
topic: '',
payload:
{ key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '120000',
price: 84.55,
symbol: 'ETH/USD',
is_buy: true } } }
{ _msgid: '3d3003de.c0249c',
topic: '',
payload:
{ key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '120000',
price: 84.55,
symbol: 'ETH/USD',
is_buy: true } },
key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '120000',
price: 84.55,
symbol: 'ETH/USD',
is_buy: true } }
ExchangeError
gemini POST https://api.sandbox.gemini.com/v1/order/new 406 Not Acceptable {"result":"error","reason":"InsufficientFunds","message":"Failed to place buy order on symbol 'ETHUSD' for price $84.55 and quantity 120,000 ETH due to insufficient funds"}
RESET ::
GDAX :: WebSocket connection disconnected
GDAX :: CONNECTED
Already have socket..
{ _msgid: '20f30417.9c7cfc',
topic: '',
payload:
{ key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '0.19',
price: 484.55,
symbol: 'ETH/USD',
is_buy: false } } }
{ _msgid: '20f30417.9c7cfc',
topic: '',
payload:
{ key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '0.19',
price: 484.55,
symbol: 'ETH/USD',
is_buy: false } },
key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '0.19',
price: 484.55,
symbol: 'ETH/USD',
is_buy: false } }
false
{ info:
{ order_id: '99068078',
id: '99068078',
symbol: 'ethusd',
exchange: 'gemini',
avg_execution_price: '0.00',
side: 'sell',
type: 'exchange limit',
timestamp: '1530023603',
timestampms: 1530023603284,
is_live: true,
is_cancelled: false,
is_hidden: false,
was_forced: false,
executed_amount: '0',
remaining_amount: '0.19',
client_order_id: '5b228e720ea83970b58648aa',
options: [],
price: '484.55',
original_amount: '0.19' },
id: '99068078' }
{ _msgid: '1fd361c3.2902ae',
topic: '',
payload:
{ key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '0.19',
price: 484.55,
symbol: 'ETH/USD',
is_buy: true } } }
{ _msgid: '1fd361c3.2902ae',
topic: '',
payload:
{ key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '0.19',
price: 484.55,
symbol: 'ETH/USD',
is_buy: true } },
key: 'fill',
data:
{ _id: '5b228e720ea83970b58648aa',
provider: 'gemini',
quantity: '0.19',
price: 484.55,
symbol: 'ETH/USD',
is_buy: true } }
false
{ info:
{ order_id: '99068081',
id: '99068081',
symbol: 'ethusd',
exchange: 'gemini',
avg_execution_price: '181.91',
side: 'buy',
type: 'exchange limit',
timestamp: '1530023613',
timestampms: 1530023613115,
is_live: false,
is_cancelled: false,
is_hidden: false,
was_forced: false,
executed_amount: '0.19',
remaining_amount: '0',
client_order_id: '5b228e720ea83970b58648aa',
options: [],
price: '484.55',
original_amount: '0.19' },
id: '99068081' }
*/
// var moment = require('moment');
var ccxt = require('ccxt');
const utils = require('./lib/utils');
var exchange_manager = {
process_order_result(node, msg, err, res, source) {
console.log(node);
console.log(msg);
console.log(err);
console.log(res);
console.log(source);
if(err) {
console.log(err);
var o_id = msg._id;
// Check presense of word to see if funds specific error
var f = err.toLowerCase().indexOf('insufficient') > -1 ? 'FUNDS' : err;
var order_log_obj = {
payload: {
order_id: o_id,
status: 'ERROR',
msg: f,
data: { response: res, tx: msg }
}
}
console.log(order_log_obj);
//this.emit(node, 'errors', order_log_obj);
this.emit(node, 'orderlog', order_log_obj);
return;
}
if(res) {
// We have a result, lets figure the status
// CANCEL = 0 fill
// FILL = some fill
// DONE = 100% filled
var o_ammount = parseFloat(res.info.original_amount);
var e_ammount = parseFloat(res.info.executed_amount);
// var o_id = res.info.client_order_id;
var o_id = msg._id;
var status = '';
if(!e_ammount) {
status = 'CANCEL';
} else {
if(o_ammount == e_ammount) {
status = 'DONE';
} else {
status = 'FILLED';
}
}
var order_log_obj = {
payload: {
order_id: o_id,
status: status,
filled_amount: e_ammount,
msg: 'OK',
data: { res: res, tx: msg }
}
}
console.log(order_log_obj);
this.emit(node, 'orderlog', order_log_obj);
return;
}
},
exchanges: {},
set_exchange(exchange_id, config) {
var opts = {
enableRateLimit: false,
urls: {}
};
// console.log(config);
// Set USER / PASS CONFIG
if(config.key) {
opts.apiKey = config.key;
}
if(config.secret) {
opts.secret = config.secret;
}
if(config.password) {
opts.password = config.password;
}
var tmp = new ccxt[exchange_id] (opts);
// Set TESTNET CONFIG
// console.log(exchange_id);
if(config.testnet && tmp.urls.test) {
opts.urls.api = tmp.urls.test;
}
if(exchange_id == 'gemini' && config.testnet) {
opts.urls.api = 'https://api.sandbox.gemini.com';
}
//console.log(opts);
this.exchanges[exchange_id] = new ccxt[exchange_id] (opts);
},
async create_order(data, callback) {
if(!this.exchanges[data.provider]) {
callback(true, 'exchange not found');
return false;
}
var ccxt_obj = this.exchanges[data.provider];
try {
var opt = {
'timeInForce': 'IOC',
'client_order_id': data._id.toString(),
};
// Exchange specific fix
if(data.provider == 'gemeni') {
opt.options = [ 'immediate-or-cancel' ];
}
const response = await ccxt_obj.createOrder(
data.symbol,
'limit',
(data.is_buy ? 'buy' : 'sell'),
data.quantity,
data.price,
opt
);
callback(false, response);
} catch (e) {
callback(e.constructor.name, e.message);
}
},
async get_balances(exchanges, callback) {
var out = {};
if(exchanges == 'all') {
exchanges = Object.keys(this.exchanges);
}
for(x in exchanges) {
if(!this.exchanges[exchanges[x]]) {
callback(true, 'Misconfigured Exchange :: ' + exchanges[x]);
}
}
for(x in exchanges) {
var ex = exchanges[x];
var cctx_obj = this.exchanges[ex];
let balances = await cctx_obj.fetchBalance().catch(function (e) {
out[ex] = { ERR: { balance: 0, err: e }};
//console.log(e);
});
if(!out[ex] && balances && balances.free) {
out[ex] = {};
for(x in balances.free) {
out[ex][x] = { balance: balances.free[x] };
}
}
}
//console.log(out);
callback(false, out);
},
emit(node, type, msg) {
var outputs = [ 'exchanges', 'balances', 'http', 'orderlog', 'errors' ];
var out = [];
for(x in outputs) {
if(type == outputs[x]) {
out.push(msg);
} else {
out.push(null);
}
}
node.send(out);
}
}
module.exports = function(RED) {
function exchangeManager(config) {
RED.nodes.createNode(this,config);
var node = this;
var conf = { exchanges: {} };
if(config.conf && config.conf != '') {
try {
conf = JSON.parse(config.conf);
} catch(e) {
//
}
}
// Set exchange in our helper object
for(ex_id in conf.exchanges) {
var ex_conf = conf.exchanges[ex_id];
if(ex_conf.balance) {
ex_conf.auth.testnet = ex_conf.testnet;
exchange_manager.set_exchange(ex_id, ex_conf.auth);
}
}
node.on('input', function(msg) {
console.log(msg);
if(msg.req) {
switch(req.route.path) {
case '/create_order':
msg.payload.key = 'fill';
msg.payload.data = msg.req.query;
break;
case '/balances':
default:
msg.payload.op = 'get_balance';
break;
}
}
// Handle OrderBook specific incoming messages
// Change mapping a bit
if(msg.key && msg.data) {
msg.payload.key = msg.key;
msg.payload.data = msg.data;
}
if(msg.payload.key) {
console.log(msg.payload);
switch(msg.payload.key) {
case 'fill':
exchange_manager.create_order(msg.payload.data, function(err, res) {
exchange_manager.process_order_result(node, msg.payload.data, err, res, 'orderbook');
});
break;
}
return;
}
// Nadda
if(!msg.payload || !msg.payload.op) {
// Make sure to release the request
if(msg.req) { exchange_manager.emit(node, 'http', msg); }
return false;
}
if(msg.payload.op == 'order_log') {
console.log(msg);
exchange_manager.emit(node, 'orderlog', { payload: msg.payload });
return;
}
if(msg.payload.op == 'get_balance') {
if(!msg.payload.exchanges) {
msg.payload.exchanges = 'all';
}
exchange_manager.get_balances(msg.payload.exchanges, function(err, data) {
var payload = { op: 'get_balance', exchanges: msg.payload.exchanges };
if(err) {
payload.err = data;
} else {
payload.data = data;
}
msg.payload = payload;
exchange_manager.emit(node, 'balances', msg);
if(msg.req) {
exchange_manager.emit(node, 'http', msg);
}
});
return;
}
if(msg.payload.op == 'create_order' && msg.payload.data) {
exchange_manager.create_order(msg.payload.data, function(err, res) {
exchange_manager.process_order_result(node, msg, err, res, 'rest');
});
return;
}
// Release hanging requests..
if(msg.req) {
exchange_manager.emit(node, 'http', msg);
}
console.log(msg);
});
}
RED.nodes.registerType("exchangeManager", exchangeManager);
}