From e826948f19c8a39a2aa7ee594ece7e97f7580c86 Mon Sep 17 00:00:00 2001 From: defkev Date: Mon, 11 Jun 2018 22:30:25 +0200 Subject: [PATCH 1/5] CEXIO: Migrate over to WebSocket Fees and Backfill still using REST due to API limitation. --- extensions/exchanges/cexio/exchange.js | 650 ++++++++++++++++--------- 1 file changed, 415 insertions(+), 235 deletions(-) diff --git a/extensions/exchanges/cexio/exchange.js b/extensions/exchanges/cexio/exchange.js index a4d8db472e..f1471ab5c3 100644 --- a/extensions/exchanges/cexio/exchange.js +++ b/extensions/exchanges/cexio/exchange.js @@ -1,235 +1,415 @@ -const CEX = require('cexio-api-node') -var path = require('path') -var n = require('numbro') -var minimist = require('minimist') - -module.exports = function cexio (conf) { - var s = { - options: minimist(process.argv) - } - var so = s.options - - var public_client, authed_client - - function publicClient () { - if (!public_client) { - public_client = new CEX().rest - } - return public_client - } - - function authedClient () { - if (!authed_client) { - if (!conf.cexio || !conf.cexio.username || !conf.cexio.key || conf.cexio.key === 'YOUR-API-KEY') { - throw new Error('please configure your CEX.IO credentials in ' + path.resolve(__dirname, 'conf.js')) - } - authed_client = new CEX(conf.cexio.username, conf.cexio.key, conf.cexio.secret).rest - } - return authed_client - } - - function joinProduct (product_id) { - return product_id.split('-')[0] + '/' + product_id.split('-')[1] - } - - function retry (method, args) { - if (so.debug) { - console.error(('\nCEX.IO API is down! unable to call ' + method + ', retrying in 10s').red) - } - setTimeout(function () { - exchange[method].apply(exchange, args) - }, 10000) - } - - function refreshFees(args) { - var skew = 5000 // in ms - var now = new Date() - var nowUTC = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds()) - var midnightUTC = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds()).setHours(24,0,0,0) - var countdown = midnightUTC - nowUTC + skew - if (so.debug) { - var hours = parseInt((countdown/(1000*60*60))%24) - var minutes = parseInt((countdown/(1000*60))%60) - var seconds = parseInt((countdown/1000)%60) - console.log('\nRefreshing fees in ' + hours + ' hours ' + minutes + ' minutes ' + seconds + ' seconds') - } - setTimeout(function() { - exchange['setFees'].apply(exchange, args) - }, countdown) - } - - var orders = {} - var exchange = { - name: 'cexio', - historyScan: 'forward', - backfillRateLimit: 0, - makerFee: 0.16, - takerFee: 0.25, - dynamicFees: true, - makerBuy100Workaround: true, - - getProducts: function () { - return require('./products.json') - }, - - getTrades: function (opts, cb) { - var func_args = [].slice.call(arguments) - var args - if (typeof opts.from === 'undefined' && opts.product_id === 'BTC-USD') { - args = 2000000 - } else { - args = opts.from - } - var client = publicClient() - var pair = joinProduct(opts.product_id) - client.trade_history(pair, args, function (err, body) { - if (so.debug && typeof body === 'string' && body.match(/error/)) console.log(('\ngetTrades ' + body).red) - if (err || (typeof body === 'string' && body.match(/error/))) return cb(err) - var trades = body.map(function (trade) { - return { - trade_id: Number(trade.tid), - time: Number(trade.date) * 1000, - size: Number(trade.amount), - price: Number(trade.price), - side: trade.type - } - }) - cb(null, trades) - }) - }, - - getBalance: function (opts, cb) { - var func_args = [].slice.call(arguments) - var client = authedClient() - client.account_balance(function (err, body) { - if (so.debug && typeof body === 'string' && body.match(/error/)) console.log(('\ngetBalance ' + body).red) - if (err || (typeof body === 'string' && body.match(/error/))) return retry('getBalance', func_args) - var balance = { asset: 0, currency: 0 } - balance.currency = n(body[opts.currency].available).add(body[opts.currency].orders).format('0.00000000') - balance.currency_hold = n(body[opts.currency].orders).format('0.00000000') - balance.asset = n(body[opts.asset].available).add(body[opts.asset].orders).format('0.00000000') - balance.asset_hold = n(body[opts.asset].orders).format('0.00000000') - cb(null, balance) - }) - }, - - getQuote: function (opts, cb) { - var func_args = [].slice.call(arguments) - var client = publicClient() - var pair = joinProduct(opts.product_id) - client.ticker(pair, function (err, body) { - if (so.debug && typeof body === 'string' && body.match(/error/)) console.log(('\ngetQuote ' + body).red) - if (err || (typeof body === 'string' && body.match(/error/))) return retry('getQuote', func_args) - cb(null, { bid: String(body.bid), ask: String(body.ask) }) - }) - }, - - cancelOrder: function (opts, cb) { - var func_args = [].slice.call(arguments) - var client = authedClient() - client.cancel_order(opts.order_id, function (err, body) { - if (so.debug && typeof body === 'string' && body.match(/error/)) console.log(('\ncancelOrder ' + body).red) - if (err || (typeof body === 'string' && body.match(/error/) && body !== 'error: Error: Order not found')) return retry('cancelOrder', func_args) - cb() - }) - }, - - trade: function (action, opts, cb) { - var func_args = [].slice.call(arguments) - var client = authedClient() - var pair = joinProduct(opts.product_id) - if (opts.order_type === 'taker') { - delete opts.price - delete opts.post_only - if (action === 'buy') { - opts.size = n(opts.size).multiply(opts.orig_price).value() // CEXIO estimates asset size and uses free currency to performe margin buy - } - opts.type = 'market' - } - client.place_order(pair, action, opts.size, opts.price, opts.type, function (err, body) { - if (so.debug && typeof body === 'string' && body.match(/error/)) console.log(('\ntrade ' + body).red) - if (err || (typeof body === 'string' && body.match(/error/) && body !== 'error: Error: Place order error: Insufficient funds.')) return retry('trade', func_args) - if (body === 'error: Error: Place order error: Insufficient funds.') { - var order = { - status: 'rejected', - reject_reason: 'balance' - } - return cb(null, order) - } - if (err) return retry('trade', func_args, err) - order = { - id: body && (body.complete === false || body.message) ? body.id : null, - status: 'open', - price: opts.price, - size: opts.size, - post_only: !!opts.post_only, - created_at: new Date().getTime(), - filled_size: '0', - ordertype: opts.order_type - } - orders['~' + body.id] = order - cb(null, order) - }) - }, - - buy: function (opts, cb) { - exchange.trade('buy', opts, cb) - }, - - sell: function (opts, cb) { - exchange.trade('sell', opts, cb) - }, - - getOrder: function (opts, cb) { - var func_args = [].slice.call(arguments) - var order = orders['~' + opts.order_id] - var client = authedClient() - client.get_order_details(opts.order_id, function (err, body) { - if (so.debug && typeof body === 'string' && body.match(/error/)) console.log(('\ngetOrder ' + body).red) - if (err || (typeof body === 'string' && body.match(/error/))) return retry('getOrder', func_args) - if (body.status === 'c') { - order.status = 'rejected' - order.reject_reason = 'canceled' - } - if (body.status === 'd' || body.status === 'cd') { - order.status = 'done' - order.done_at = new Date().getTime() - order.filled_size = n(body.amount).subtract(body.remains).format('0.00000000') - } - cb(null, order) - }) - }, - - setFees: function(opts) { - var func_args = [].slice.call(arguments) - var client = authedClient() - client.get_my_fee(function (err, body) { - if (err || (typeof body === 'string' && body.match(/error/))) { - if (so.debug) { - console.log(('\nsetFees ' + body + ' - using fixed fees!').red) - } - return retry('setFees', func_args) - } else { - var pair = opts.asset + ':' + opts.currency - var makerFee = (parseFloat(body[pair].buyMaker) + parseFloat(body[pair].sellMaker)) / 2 - var takerFee = (parseFloat(body[pair].buy) + parseFloat(body[pair].sell)) / 2 - if (exchange.makerFee != makerFee) { - if (so.debug) console.log('\nMaker fee changed: ' + exchange.makerFee + '% -> ' + makerFee + '%') - exchange.makerFee = makerFee - } - if (exchange.takerFee != takerFee) { - if (so.debug) console.log('\nTaker fee changed: ' + exchange.takerFee + '% -> ' + takerFee + '%') - exchange.takerFee = takerFee - } - } - return refreshFees(func_args) - }) - }, - - // return the property used for range querying. - getCursor: function (trade) { - return trade.trade_id - } - } - return exchange -} +const CEX = require('cexio-api-node') +const path = require('path') +const n = require('numbro') +const minimist = require('minimist') +const _ = require('lodash') + +module.exports = function cexio (conf) { + let s = { + options: minimist(process.argv) + } + let so = s.options + + let public_client, authed_client, ws_client, ws_subscribed, amount_format + let ws_trades = [] + let orders = {} + + function publicClient () { + if (!public_client) { + public_client = new CEX().rest + } + return public_client + } + + function authedClient () { + if (!authed_client) { + if (!conf.cexio || !conf.cexio.username || !conf.cexio.key || conf.cexio.key === 'YOUR-API-KEY') { + throw new Error('please configure your CEX.IO credentials in ' + path.resolve(__dirname, 'conf.js')) + } + authed_client = new CEX(conf.cexio.username, conf.cexio.key, conf.cexio.secret).rest + } + return authed_client + } + + function joinProduct (product_id) { + return product_id.split('-')[0] + '/' + product_id.split('-')[1] + } + + function retry (method, args) { + if (so.debug) { + console.error(('\nCEX.IO API is down! unable to call ' + method + ', retrying in 10s').red) + } + setTimeout(function () { + exchange[method].apply(exchange, args) + }, 10000) + } + + function refreshFees(args) { + let skew = 5000 // in ms + let now = new Date() + let nowUTC = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds()) + let midnightUTC = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds()).setHours(24,0,0,0) + let countdown = midnightUTC - nowUTC + skew + if (so.debug) { + let hours = parseInt((countdown/(1000*60*60))%24) + let minutes = parseInt((countdown/(1000*60))%60) + let seconds = parseInt((countdown/1000)%60) + console.log('\nRefreshing fees in ' + hours + ' hours ' + minutes + ' minutes ' + seconds + ' seconds') + } + setTimeout(function() { + exchange['setFees'].apply(exchange, args) + }, countdown) + } + + function wsClient() { + return new Promise(function(resolve, reject) { + if (!ws_client) { + if (!conf.cexio || !conf.cexio.key || conf.cexio.key === 'YOUR-API-KEY') { + throw new Error('please configure your CEX.IO credentials in ' + path.resolve(__dirname, 'conf.js')) + } + ws_client = new CEX(conf.cexio.key, conf.cexio.secret).ws + ws_client.open() + ws_client.on('open', function() { + if (so.debug) console.log('WebSocket connected') + ws_client.auth() + ws_client.on('auth', function() { + if (so.debug) console.log('WebSocket authenticated') + resolve(ws_client) + }) + }) + ws_client.on('message', function(msg) { + switch (msg.e) { + case 'ping': + ws_client.send({ e: 'pong' }) // Heartbeat + break + case 'get-balance': + ws_client.emit('balance', msg.data) + break + case 'ticker': + ws_client.emit('ticker', msg.data) + break + case 'history': + ws_client.emit('history', msg.data) + break + case 'history-update': + msg.data.forEach(function(trade) { + ws_trades.push({ + trade_id: Number(trade[4]), + time: Number(trade[1]), + size: Number(n(trade[2]).divide(amount_format).format('0.00000000')), + price: Number(trade[3]), + side: trade[0] + }) + }) + case 'cancel-order': + ws_client.emit('cancelOrder') + break + case 'place-order': + ws_client.emit('placeOrder', msg.data) + break + case 'get-order': + ws_client.emit('getOrder', msg.data) + break + } + }) + ws_client.on('error', function(err) { + console.error('WebSocket error:', err) + }) + ws_client.on('close', function(err) { + ws_client = null + ws_subscribed = false + if (so.debug) console.log('WebSocket disconnected:', err ? err : ' No reason given') + }) + } else { + switch (ws_client.ws.readyState) { + case 0: + reject('WebSocket connecting') + break + case 1: + if (so.debug) console.log('WebSocket open') + resolve(ws_client) + break + case 2: + reject('WebSocket closing') + break + case 3: + reject('WebSocket closed') + break + } + } + }) + } + + function wsTrades(pair) { + return new Promise(function(resolve, reject) { + wsClient().then(function(client) { + client.send({ + e: 'subscribe', + rooms: [ 'pair-' + pair ] + }) + client.once('history', function(trades) { + resolve(trades) + }) + }) + }).catch(function(err) { + reject(err) + }) + } + + function wsBalance() { + return new Promise(function(resolve, reject) { + wsClient().then(function(client) { + client.getBalance() + client.once('balance', function(balance) { + resolve(balance) + }) + }).catch(function(err) { + reject(err) + }) + }) + } + + function wsQuote(pair) { + return new Promise(function(resolve, reject) { + wsClient().then(function(client) { + client.authTicker(pair) + client.once('ticker', function(quote) { + resolve(quote) + }) + }).catch(function(err) { + reject(err) + }) + }) + } + + function wsCancelOrder(order_id) { + return new Promise(function(resolve, reject) { + wsClient().then(function(client) { + client.cancelOrder(order_id) + client.once('cancelOrder', function() { + resolve() + }) + }).catch(function(err) { + reject(err) + }) + }) + } + + function wsTrade(order) { + return new Promise(function(resolve, reject) { + wsClient().then(function(client) { + client.placeOrder(order.type, order.pair, order.size, order.price) + client.once('placeOrder', function(order) { + resolve(order) + }) + }).catch(function(err) { + reject(err) + }) + }) + } + + function wsGetOrder(order_id) { + return new Promise(function(resolve, reject) { + wsClient().then(function(client) { + client.getOrder(order_id) + client.once('getOrder', function(order) { + resolve(order) + }) + }).catch(function(err) { + reject(err) + }) + }) + } + + let lastTID + let exchange = { + name: 'cexio', + historyScan: 'forward', + backfillRateLimit: 0, + makerFee: 0.16, + takerFee: 0.25, + dynamicFees: true, + makerBuy100Workaround: true, + + getProducts: function () { + return require('./products.json') + }, + + getTrades: function (opts, cb) { + let func_args = [].slice.call(arguments) + if (so._[2] === 'backfill') { // Backfill using REST + let client = publicClient() + let pair = joinProduct(opts.product_id) + client.trade_history(pair, opts.from, function (err, body) { + if (err || (typeof body === 'string' && body.match(/error/))) { + if (so.debug) console.log(('\ngetTrades ' + (err ? err : body)).red) + return retry('getTrades', func_args) + } + let trades = body.map(function (trade) { + return { + trade_id: Number(trade.tid), + time: Number(trade.date) * 1000, + size: Number(trade.amount), + price: Number(trade.price), + side: trade.type + } + }) + cb(null, trades) + }) + } else { // WebSocket once Live + if (!ws_subscribed) wsTrades(opts.product_id).then(function(data) { + ws_subscribed = true + amount_format = opts.product_id.split('-')[0] === 'ETH' ? 1000000 : 100000000 // trade amount is an unformatted integer + data.forEach(function(trade) { + let t = trade.split(':') + ws_trades.push({ + trade_id: Number(t[4]), + time: Number(t[1]), + size: Number(n(t[2]).divide(amount_format).format('0.00000000')), + price: Number(t[3]), + side: t[0] + }) + }) + }).catch(function(err) { + if (so.debug) console.log(('\ngetTrades ' + err).red) + return retry('getTrades', func_args) + }) + _.remove(ws_trades, function(t) { + return t.trade_id <= opts.from + }) + cb(null, ws_trades) + } + }, + + getBalance: function (opts, cb) { + let func_args = [].slice.call(arguments) + wsBalance().then(function(data) { + ws_balance = { + currency: n(data.balance[opts.currency]).format('0.00000000'), + asset: n(data.balance[opts.asset]).format('0.00000000'), + currency_hold: n(data.obalance[opts.currency]).format('0.00000000'), + asset_hold: n(data.obalance[opts.asset]).format('0.00000000') + } + cb(null, ws_balance) + }).catch(function(err) { + if (so.debug) console.log(('\ngetBalance ' + err).red) + return retry('getBalance', func_args) + }) + }, + + getQuote: function (opts, cb) { + let func_args = [].slice.call(arguments) + wsQuote(opts.product_id).then(function(data) { + ws_ticker = { + ask: data.ask, + bid: data.bid + } + cb(null, ws_ticker) + }).catch(function(err) { + if (so.debug) console.log(('\ngetQuote ' + err).red) + return retry('getQuote', func_args) + }) + }, + + cancelOrder: function (opts, cb) { + let func_args = [].slice.call(arguments) + wsCancelOrder(opts.order_id).then(function() { + cb() + }).catch(function(err) { + if (so.debug) console.log(('\ncancelOrder ' + err).red) + return retry('cancelOrder', func_args) + }) + }, + + trade: function (action, opts, cb) { + let func_args = [].slice.call(arguments) + if (opts.order_type === 'taker') { + delete opts.price + delete opts.post_only + if (action === 'buy') { + opts.size = n(opts.size).multiply(opts.orig_price).value() // CEXIO estimates asset size and uses free currency to performe margin buy + } + opts.type = 'market' + } + wsTrade({ + type: action, + pair: opts.product_id, + size: opts.size, + price: opts.price + }).then(function(data) { + let order = { + id: data.complete === false ? data.id : null, + status: 'open', + price: data.price, + size: opts.size, + post_only: !!opts.post_only, + created_at: data.time, + filled_size: data.amount - data.pending, + ordertype: opts.order_type + } + orders['~' + data.id] = order + cb(null, order) + }).catch(function(err) { + if (so.debug) console.log(('\ntrade ' + err).red) + return retry('trade', func_args, err) + }) + }, + + buy: function (opts, cb) { + exchange.trade('buy', opts, cb) + }, + + sell: function (opts, cb) { + exchange.trade('sell', opts, cb) + }, + + getOrder: function (opts, cb) { + let func_args = [].slice.call(arguments) + let order = orders['~' + opts.order_id] + wsGetOrder(opts.order_id).then(function(data) { + if (data.status === 'c') { + order.status = 'rejected' + order.reject_reason = 'canceled' + } else if ( data.status === 'd' || data.status === 'cd') { + order.status = 'done' + order.done_at = new Date().getTime() + order.filled_size = n(data.amount).substract(data.remains).format('0.00000000') + } + cb(null, order) + }).catch(function(err) { + if (so.debug) console.log(('\ngetOrder ' + err).red) + return retry('getOrder', func_args) + }) + }, + + setFees: function(opts) { + let func_args = [].slice.call(arguments) + let client = authedClient() + client.get_my_fee(function (err, body) { + if (err || (typeof body === 'string' && body.match(/error/))) { + if (so.debug) console.log(('\nsetFees ' + (err ? err : body) + ' - using fixed fees!').red) + return retry('setFees', func_args) + } else { + let pair = opts.asset + ':' + opts.currency + let makerFee = (parseFloat(body[pair].buyMaker) + parseFloat(body[pair].sellMaker)) / 2 + let takerFee = (parseFloat(body[pair].buy) + parseFloat(body[pair].sell)) / 2 + if (exchange.makerFee != makerFee) { + if (so.debug) console.log('\nMaker fee changed: ' + exchange.makerFee + '% -> ' + makerFee + '%') + exchange.makerFee = makerFee + } + if (exchange.takerFee != takerFee) { + if (so.debug) console.log('\nTaker fee changed: ' + exchange.takerFee + '% -> ' + takerFee + '%') + exchange.takerFee = takerFee + } + } + return refreshFees(func_args) + }) + }, + + // return the property used for range querying. + getCursor: function (trade) { + return trade.trade_id + } + } + return exchange +} \ No newline at end of file From e4561eb3484ab774eeaef0acade58dcc3edc06db Mon Sep 17 00:00:00 2001 From: defkev Date: Tue, 12 Jun 2018 23:33:30 +0200 Subject: [PATCH 2/5] Cleanup --- extensions/exchanges/cexio/exchange.js | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/exchanges/cexio/exchange.js b/extensions/exchanges/cexio/exchange.js index f1471ab5c3..cf008c61e7 100644 --- a/extensions/exchanges/cexio/exchange.js +++ b/extensions/exchanges/cexio/exchange.js @@ -221,7 +221,6 @@ module.exports = function cexio (conf) { }) } - let lastTID let exchange = { name: 'cexio', historyScan: 'forward', From 16b8964a3ed28833688a76c3e3e6d0c3ba2f460d Mon Sep 17 00:00:00 2001 From: defkev Date: Thu, 14 Jun 2018 19:30:52 +0200 Subject: [PATCH 3/5] CEXIO: WebSocket additions * wsTrades catch reject was out-of-scope *duh* * Disconnect message formating --- extensions/exchanges/cexio/exchange.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/exchanges/cexio/exchange.js b/extensions/exchanges/cexio/exchange.js index cf008c61e7..3b4cc26bc5 100644 --- a/extensions/exchanges/cexio/exchange.js +++ b/extensions/exchanges/cexio/exchange.js @@ -118,7 +118,7 @@ module.exports = function cexio (conf) { ws_client.on('close', function(err) { ws_client = null ws_subscribed = false - if (so.debug) console.log('WebSocket disconnected:', err ? err : ' No reason given') + if (so.debug) console.log('WebSocket disconnected:', err ? err : 'No reason given') }) } else { switch (ws_client.ws.readyState) { @@ -150,9 +150,9 @@ module.exports = function cexio (conf) { client.once('history', function(trades) { resolve(trades) }) + }).catch(function(err) { + reject(err) }) - }).catch(function(err) { - reject(err) }) } From 6b22fbe2153dbfeba6d453d506d87ac2cc9765ce Mon Sep 17 00:00:00 2001 From: defkev Date: Sun, 17 Jun 2018 19:01:15 +0200 Subject: [PATCH 4/5] CEXIO: WebSocket * Improve error handling * Reject client requests until authenticated --- extensions/exchanges/cexio/exchange.js | 58 +++++++++++++++++++------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/extensions/exchanges/cexio/exchange.js b/extensions/exchanges/cexio/exchange.js index 3b4cc26bc5..76f04d8bd8 100644 --- a/extensions/exchanges/cexio/exchange.js +++ b/extensions/exchanges/cexio/exchange.js @@ -10,7 +10,7 @@ module.exports = function cexio (conf) { } let so = s.options - let public_client, authed_client, ws_client, ws_subscribed, amount_format + let public_client, authed_client, ws_client, ws_authed, ws_subscribed, amount_format let ws_trades = [] let orders = {} @@ -74,11 +74,15 @@ module.exports = function cexio (conf) { ws_client.auth() ws_client.on('auth', function() { if (so.debug) console.log('WebSocket authenticated') + ws_authed = true resolve(ws_client) }) }) ws_client.on('message', function(msg) { switch (msg.e) { + case 'disconnecting': + if (so.debug) console.log('WebSocket disconnecting:', msg.reason) + break case 'ping': ws_client.send({ e: 'pong' }) // Heartbeat break @@ -101,8 +105,9 @@ module.exports = function cexio (conf) { side: trade[0] }) }) + break case 'cancel-order': - ws_client.emit('cancelOrder') + ws_client.emit('cancelOrder', msg.data) break case 'place-order': ws_client.emit('placeOrder', msg.data) @@ -113,12 +118,13 @@ module.exports = function cexio (conf) { } }) ws_client.on('error', function(err) { - console.error('WebSocket error:', err) + console.error('WebSocket error:', err.Error) }) - ws_client.on('close', function(err) { + ws_client.on('close', function() { ws_client = null + ws_authed = false ws_subscribed = false - if (so.debug) console.log('WebSocket disconnected:', err ? err : 'No reason given') + if (so.debug) console.log('WebSocket disconnected') }) } else { switch (ws_client.ws.readyState) { @@ -126,8 +132,12 @@ module.exports = function cexio (conf) { reject('WebSocket connecting') break case 1: - if (so.debug) console.log('WebSocket open') - resolve(ws_client) + if (ws_authed) { + if (so.debug) console.log('WebSocket open') + resolve(ws_client) + } else { + reject('WebSocket auth pending') + } break case 2: reject('WebSocket closing') @@ -161,7 +171,11 @@ module.exports = function cexio (conf) { wsClient().then(function(client) { client.getBalance() client.once('balance', function(balance) { - resolve(balance) + if (balance.error) { + reject(balance.error) + } else { + resolve(balance) + } }) }).catch(function(err) { reject(err) @@ -174,7 +188,11 @@ module.exports = function cexio (conf) { wsClient().then(function(client) { client.authTicker(pair) client.once('ticker', function(quote) { - resolve(quote) + if (quote.error) { + reject(quote.error) + } else { + resolve(quote) + } }) }).catch(function(err) { reject(err) @@ -186,8 +204,12 @@ module.exports = function cexio (conf) { return new Promise(function(resolve, reject) { wsClient().then(function(client) { client.cancelOrder(order_id) - client.once('cancelOrder', function() { - resolve() + client.once('cancelOrder', function(order) { + if (order.error) { + reject(order.error) + } else { + resolve() + } }) }).catch(function(err) { reject(err) @@ -200,7 +222,11 @@ module.exports = function cexio (conf) { wsClient().then(function(client) { client.placeOrder(order.type, order.pair, order.size, order.price) client.once('placeOrder', function(order) { - resolve(order) + if (order.error) { + reject(order.error) + } else { + resolve(order) + } }) }).catch(function(err) { reject(err) @@ -213,7 +239,11 @@ module.exports = function cexio (conf) { wsClient().then(function(client) { client.getOrder(order_id) client.once('getOrder', function(order) { - resolve(order) + if (order.error) { + reject(order.error) + } else { + resolve(order) + } }) }).catch(function(err) { reject(err) @@ -316,7 +346,7 @@ module.exports = function cexio (conf) { cb() }).catch(function(err) { if (so.debug) console.log(('\ncancelOrder ' + err).red) - return retry('cancelOrder', func_args) + if (err !== 'Error: Order not found') return retry('cancelOrder', func_args) }) }, From 3fc52d72130d315df5f7465dcd640a281c30f69e Mon Sep 17 00:00:00 2001 From: defkev Date: Sun, 17 Jun 2018 23:28:07 +0200 Subject: [PATCH 5/5] CEXIO: WebSocket trade * Use REST for Taker/Market orders, looks like this isn't supported (yet?) :disappointed: --- extensions/exchanges/cexio/exchange.js | 78 +++++++++++++++++--------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/extensions/exchanges/cexio/exchange.js b/extensions/exchanges/cexio/exchange.js index 76f04d8bd8..19c96257b1 100644 --- a/extensions/exchanges/cexio/exchange.js +++ b/extensions/exchanges/cexio/exchange.js @@ -352,36 +352,64 @@ module.exports = function cexio (conf) { trade: function (action, opts, cb) { let func_args = [].slice.call(arguments) - if (opts.order_type === 'taker') { + if (opts.order_type === 'taker') { // Looks like WebSocket doesn't support taker/market orders (yet?) delete opts.price delete opts.post_only if (action === 'buy') { opts.size = n(opts.size).multiply(opts.orig_price).value() // CEXIO estimates asset size and uses free currency to performe margin buy } - opts.type = 'market' - } - wsTrade({ - type: action, - pair: opts.product_id, - size: opts.size, - price: opts.price - }).then(function(data) { - let order = { - id: data.complete === false ? data.id : null, - status: 'open', - price: data.price, + let client = authedClient() + client.place_order(joinProduct(opts.product_id), action, opts.size, opts.price, 'market', function (err, body) { + if (err || (typeof body === 'string' && body.match(/error/))) { + if (so.debug) console.log(('\ntrade ' + (err ? err : body)).red) + if (body === 'error: Error: Place order error: Insufficient funds.') { + let order = { + status: 'rejected', + reject_reason: 'balance' + } + return cb(null, order) + } else { + return retry('trade', func_args) + } + } else { + let order = { + id: body.id, + status: 'open', + price: opts.price, + size: opts.size, + post_only: !!opts.post_only, + created_at: body.time, + filled_size: '0', + ordertype: 'taker' + } + orders['~' + body.id] = order + cb(null, order) + } + }) + } else { + wsTrade({ + type: action, + pair: opts.product_id, size: opts.size, - post_only: !!opts.post_only, - created_at: data.time, - filled_size: data.amount - data.pending, - ordertype: opts.order_type - } - orders['~' + data.id] = order - cb(null, order) - }).catch(function(err) { - if (so.debug) console.log(('\ntrade ' + err).red) - return retry('trade', func_args, err) - }) + price: opts.price + }).then(function(data) { + let order = { + id: data.id, + status: 'open', + price: data.price, + size: data.amount, + post_only: !!opts.post_only, + created_at: data.time, + filled_size: data.amount - data.pending, + ordertype: 'maker' + } + orders['~' + data.id] = order + cb(null, order) + }).catch(function(err) { + if (so.debug) console.log(('\ntrade ' + err).red) + return retry('trade', func_args, err) + }) + } }, buy: function (opts, cb) { @@ -402,7 +430,7 @@ module.exports = function cexio (conf) { } else if ( data.status === 'd' || data.status === 'cd') { order.status = 'done' order.done_at = new Date().getTime() - order.filled_size = n(data.amount).substract(data.remains).format('0.00000000') + order.filled_size = n(data.amount).subtract(data.remains).format('0.00000000') } cb(null, order) }).catch(function(err) {