crypto-nodes
Version:
563 lines (392 loc) • 14.7 kB
JavaScript
const GeminiAPI = require('gemini-api').default;
var gemeni_fix = {
uuid_map: {},
init: function (node, config) {
//console.log('LOADING GEMENI FIX');
this.node = node;
this.config = config;
// this.connect();
},
connect: function() {
var that = this;
if(!this.config.server_fix || !this.config.server_fix_port) {
that.node.status({ fill: 'red', shape:'ring', text: "Missing FIX Config" });
return;
}
if(!this.config.api_key || !this.config.api_secret || !this.config.api_pass) {
that.node.status({ fill: 'red', shape:'ring', text: "Missing FIX Auth" });
return;
}
this.stream = tls.connect({ host: 'fix-public.sandbox.gdax.com', port: '4198' }, function() {
that.node.status({ fill: 'green', shape:'ring', text: "FIX Connected" });
});
this.client = fix.createClient(this.stream);
this.session = this.client.session(this.config.api_key, 'Coinbase');
// Stream Events
// this.stream.on('data', function(data) { console.log("FIX DATA :: " + data.toString()); });
this.stream.on('error', function(err) { console.log("FIX ERR :: " + err); });
this.stream.on('close', function () { console.log("FIX DISCONNECT"); });
this.stream.on('end', function() { console.log('FIX STREAM ENDED'); });
// Session Events
this.session.on('send', function(msg) { /*console.log('sending message: %s', msg);*/ });
this.session.on('error', function(err) { console.error(err.stack); });
this.session.on('logout', function() { console.log('FIX SESSION :: Logged out'); });
this.session.on('end', function() { console.log('FIX SESSION :: Ended'); this.stream.end(); });
this.session.on('logon', function() {
that.node.status({ fill: 'green', shape:'ring', text: "FIX Authenticated" });
});
// this.session.on('Reject', function(msg, next) { console.log('reject: %s', msg); next(); });
// this.session.on('OrderCancelReject', function(msg, next) { console.log('order cancel reject: %s', msg); next(); });
// The rest of these are handlers for various FIX keywords
this.session.on('ExecutionReport', function(msg, next) {
that.node.status({ fill: 'green', shape:'ring', text: "FIX Order Report" });
// console.log('FIX ORDER DATA :: ' + msg.toString());
var fnk_map = {
'0': 'Added',
'1': 'Filled',
'3': 'Done',
'4': 'Canceled',
'7': 'Stopped',
'8': 'Rejected',
'D': 'Changed',
'I': 'Info'
};
// console.log(msg.ExecType);
switch(msg.ExecType) {
case '0':
// NEW
if(that.uuid_map[msg.ClOrdID]) {
// Create mapping between REMOTE ORDER ID and LOCAL ORDER ID
that.uuid_map[msg.OrderID] = that.uuid_map[msg.ClOrdID];
var order_log_obj = {
op: 'order_log',
order_id: that.uuid_map[msg.OrderID],
status: 'ADDED',
msg: 'Order Added',
data: { exchange: 'gdax', protocol: 'fix', data: msg.toString() }
}
that.node.send({ payload: order_log_obj });
}
break;
case '1':
// FILL
var order_log_obj = {
op: 'order_log',
order_id: that.uuid_map[msg.OrderID],
status: 'FILLED',
filled_amount: parseFloat(msg.LastShares),
msg: 'Order Filled',
data: { exchange: 'gdax', protocol: 'fix', data: msg.toString() }
}
that.node.send({ payload: order_log_obj });
break;
case '3':
// DONE
var order_log_obj = {
op: 'order_log',
order_id: that.uuid_map[msg.OrderID],
status: 'DONE',
msg: 'Order Done',
data: { exchange: 'gdax', protocol: 'fix', data: msg.toString() }
}
that.node.send({ payload: order_log_obj });
break;
case '4':
// CANCELED
var order_log_obj = {
op: 'order_log',
order_id: that.uuid_map[msg.OrderID],
status: 'CANCEL',
msg: 'Order Canceled',
data: { exchange: 'gdax', protocol: 'fix', data: msg.toString() }
}
that.node.send({ payload: order_log_obj });
break;
case '7':
// STOPPED
var order_log_obj = {
op: 'order_log',
order_id: that.uuid_map[msg.OrderID],
status: 'CANCEL',
msg: 'Order Stopped',
data: { exchange: 'gdax', protocol: 'fix', data: msg.toString() }
}
that.node.send({ payload: order_log_obj });
break;
case '8':
// REJECTED
var order_log_obj = {
op: 'order_log',
order_id: that.uuid_map[msg.OrderID],
status: 'ERROR',
msg: (msg.OrdRejReason && msg.OrdRejReason == 3 ? 'FUNDS': 'OTHER'),
data: { exchange: 'gdax', protocol: 'fix', data: msg.toString() }
}
that.node.send({ payload: order_log_obj });
break;
case 'D':
// CHANGED
break;
case 'I':
// STATUS
var order_log_obj = {
op: 'order_log',
order_id: that.uuid_map[msg.OrderID],
status: 'INFO',
msg: 'Order Info',
data: { exchange: 'gdax', protocol: 'fix', data: msg.toString() }
}
that.node.send({ payload: order_log_obj });
break;
}
//if(fnk_map[msg.ExecType]) {
if(that.uuid_map[msg.OrderID]) {
var text = 'Order ' + fnk_map[msg.ExecType] + '! Local ID ' + that.uuid_map[msg.OrderID] || ' NOT FOUND';
console.log(text);
that.node.status({ fill: 'green', shape:'ring', text: text });
}
//}
next();
});
// DO LOGIN ::
var logon = new fix.Msgs.Logon();
logon.SendingTime = new Date();
logon.HeartBtInt = 30;
logon.EncryptMethod = 0;
logon.set(554, this.config.api_pass); // FIX 4.4 Password tag
var presign = [
logon.SendingTime,
logon.MsgType,
this.session.outgoing_seq_num,
this.session.sender_comp_id,
this.session.target_comp_id,
this.config.api_pass
];
var what = presign.join('\x01');
logon.RawData = this.sign(what, this.config.api_secret);
// console.log(logon);
this.session.send(logon, true);
},
status: function () {
var orders = new fix.Msgs.OrderStatusRequest();
orders.OrderID = '*';
this.session.send(orders);
},
sign: function(what, secret) {
var key = Buffer(secret, 'base64');
var hmac = crypto.createHmac('sha256', key);
//console.log("presign: " + what);
var signature = hmac.update(what).digest('base64');
return signature;
},
sendOrder(symbol, price, amount, is_buy, id) {
var order = new fix.Msgs.NewOrderSingle();
var order_uuid = uuid();
// Map our numeric ID's to UUID to send to order
this.uuid_map[order_uuid] = id;
// Populate order object
order.Symbol = symbol;
order.ClOrdID = order_uuid;
order.Side = is_buy ? 1 : 2;
order.HandlInst = 1;
order.OrdType = 2; // 2=Limit
order.OrderQty = amount;
order.Price = price;
order.TimeInForce = '3'; // 1=GTC 3=IOC
order.TransactTime = new Date();
order.set(7928, 'D'); // STP
this.session.send(order);
},
cancelOrder: function(ClOrdID, OrderID, symbol) {
var cancel = new fix.Msgs.OrderCancelRequest();
cancel.Symbol = symbol;
cancel.OrigClOrdID = ClOrdID;
cancel.ClOrdID = 123456;
cancel.OrderID = OrderID;
this.session.send(cancel);
}
}
var gemini = {
fix: gemeni_fix,
order_id_map: {},
order: function(data, node) {
var that = this;
gemini.order_node = node;
// CCTX / FIX / WS
gemini.utils.ccxt_order('gemini', data, node, function (err, res) {
if(err) {
node.status({ fill: 'red', shape:'ring', text: res });
// Emit to order log..
} else {
node.status({ fill: 'green', shape:'ring', text: 'Sent order OK' });
console.log(res);
/*{ info:
{ order_id: '94785411',
id: '94785411',
symbol: 'ethbtc',
exchange: 'gemini',
avg_execution_price: '0.07653',
side: 'buy',
type: 'exchange limit',
timestamp: '1527511832',
timestampms: 1527511832456,
is_live: false,
is_cancelled: false,
is_hidden: false,
was_forced: false,
executed_amount: '0.01',
remaining_amount: '0',
client_order_id: '1527511832',
options: [],
price: '0.07653',
original_amount: '0.01' },*/
that.order_id_map[res.info.order_id] = data.tx.acc;
var order_log_obj = {
op: 'order_log',
order_id: data.tx.acc,
status: 'ADDED',
msg: 'Order added at exchange',
data: { exchange: 'gemini', protocol: 'api', data: res }
}
node.send({ payload: order_log_obj });
}
});
return;
},
data: {
setup: function(node, config) {
// Initialize object with node and config which might contain AUTH and TESTNET params
gemini.data.node = node;
gemini.data.config = config;
//console.log(gemini.ccxt);
var that = this;
//
//console.log('INIT CONNECETIONS');
//console.log(this.config);
that.websocketClient = new GeminiAPI.WebsocketClient({ key: this.config.api_key, secret: this.config.api_secret, sandbox: this.config.testnet });
that.websocketClient.openOrderSocket();
that.websocketClient.orderSocket.on('open', function (d) {
});
that.websocketClient.orderSocket.on('message', function (msg) {
try {
msg = JSON.parse(msg);
} catch(e) {
return;
}
if(msg && msg.length) {
for(x in msg) {
var row = msg[x];
console.log(row);
if(row.type) {
if(row.order_id) {
if(!gemini.order_id_map[row.order_id]) {
if(row.client_order_id) {
gemini.order_id_map[row.order_id] = row.client_order_id;
} else {
gemini.order_id_map[row.order_id] = ' unknown ';
}
}
}
switch(row.type) {
case 'initial':
// Not interesting..
// console.log('ORDER ' + gemini.order_id_map[row.order_id] + ' UPDATE ' + row.executed_amount + ' of ' + row.original_amount + ' filled ' );
break;
case 'accepted':
console.log('ORDER ' + gemini.order_id_map[row.order_id] + ' ACCEPTED');
// {"type":"accepted","order_id":"94785291","event_id":"94785292","client_order_id":"1527508720","api_session":"ggtwNc2VS01UTAAgYDy2","symbol":"ethbtc","side":"buy","order_type":"exchange limit","timestamp":"1527508720","timestampms":1527508720539,"is_live":true,"is_cancelled":false,"is_hidden":false,"original_amount":"0.01","price":"0.07653","socket_sequence":3}
var order_log_obj = {
op: 'order_log',
order_id: gemini.order_id_map[row.order_id],
status: 'ACCEPTED',
msg: 'Order Accepted at exchange',
data: { exchange: 'gemini', protocol: 'ws', data: msg.toString() }
}
if(gemini.order_node) {
//console.log(order_log_obj);
gemini.order_node.send({ payload: order_log_obj });
}
break;
case 'fill':
console.log('ORDER ' + gemini.order_id_map[row.order_id] + ' FILLED WITH ' + row.executed_amount);
// {"type":"fill","order_id":"94785291","client_order_id":"1527508720","api_session":"ggtwNc2VS01UTAAgYDy2","symbol":"ethbtc","side":"buy","order_type":"exchange limit","timestamp":"1527508720","timestampms":1527508720539,"is_live":false,"is_cancelled":false,"is_hidden":false,"avg_execution_price":"0.07653","executed_amount":"0.01","remaining_amount":"0","original_amount":"0.01","price":"0.07653","fill":{"trade_id":"94785293","liquidity":"Taker","price":"0.07653","amount":"0.01","fee":"0.00000191325","fee_currency":"BTC"},"socket_sequence":4}
var order_log_obj = {
op: 'order_log',
order_id: gemini.order_id_map[row.order_id],
status: 'FILLED',
filled_amount: parseFloat(row.executed_amount),
msg: 'Order Filled at exchange',
data: { exchange: 'gemini', protocol: 'ws', data: row }
}
if(gemini.order_node) {
//console.log(order_log_obj);
gemini.order_node.send({ payload: order_log_obj });
}
break;
case 'closed':
console.log('ORDER ' + gemini.order_id_map[row.order_id] + ' CLOSED ' + row.remaining_amount + ' REMAINING');
// {"type":"closed","order_id":"94785291","event_id":"94785295","client_order_id":"1527508720","api_session":"ggtwNc2VS01UTAAgYDy2","symbol":"ethbtc","side":"buy","order_type":"exchange limit","timestamp":"1527508720","timestampms":1527508720539,"is_live":false,"is_cancelled":false,"is_hidden":false,"avg_execution_price":"0.07653","executed_amount":"0.01","remaining_amount":"0","original_amount":"0.01","price":"0.07653","socket_sequence":5}
var order_log_obj = {
op: 'order_log',
order_id: gemini.order_id_map[row.order_id],
status: (row.remaining_amount == 0 ? 'DONE' : 'CANCEL'),
msg: 'Order Closed at exchange',
data: { exchange: 'gemini', protocol: 'ws', data: row }
}
if(gemini.order_node) {
//console.log(order_log_obj);
gemini.order_node.send({ payload: order_log_obj });
}
break;
}
}
}
}
});
return true;
},
close: function () {
console.log('CLOSE CONNECETIONS');
},
subscribe: function (symbols) {
var that = this;
// Either Pass it back to generic UTILS CCXT subscribe method
// Or do something with the data ..
gemini.utils.ccxt_subscribe('gemini', symbols, this.node);
return;
//console.log(that.websocketClient.marketSocket);
for(x in symbols) {
var symbol = symbols[x];
if(symbol in gemini.ccxt.markets) {
that.websocketClient.openMarketSocket(gemini.ccxt.markets[symbol].id, () => {
that.websocketClient.addMarketMessageListener(data => {
if(data.type == 'update') {
console.log('UP :: ' + data.events.length);
} else {
console.log(data);
}
})
});
}
}
that.websocketClient.marketSocket.on('message', function (d) { console.log('message!'); } );
// that.websocketClient.marketSocket.on('close', function () { console.log('CLOSED!'); } );
// that.websocketClient.marketSocket.on('error', function (e) { console.log('ERR!' + e); } );
// that.websocketClient.marketSocket.on('disconnect', function () { console.log('ERR!'); } );
},
init: function (obj, config, tickCB) {
this.config = config; // Config containing authentication for creating out CCXT object
this.tickCB = typeof(tickCB) == 'function' ? tickCB : false;
},
connect: function () {
// If this config.auth PRIVATE else PUBLIC else NOT SUPPORTED
},
tick: function(data) {
// CALLBACK FOR DATA
if(this.tickCB) {
this.tickCB(data);
}
}
}
}
module.exports = gemini;