diff --git a/exchange/wrappers/luno.js b/exchange/wrappers/luno.js index b21e12d57..c3f697f94 100644 --- a/exchange/wrappers/luno.js +++ b/exchange/wrappers/luno.js @@ -1,12 +1,13 @@ const Luno = require("bitx"); const _ = require('lodash'); const moment = require('moment'); - +const node_util = require('util'); const Errors = require('../exchangeErrors'); const retry = require('../exchangeUtils').retry; - const name = 'Luno'; +var tradeAttempt = 0; + const Trader = function(config) { if (_.isObject(config)) { this.key = config.key; @@ -15,13 +16,29 @@ const Trader = function(config) { this.asset = config.asset; this.pair = config.asset + config.currency; } - this.luno = new Luno(this.key, this.secret, { pair: this.pair }); + this.luno = new Luno(this.key, this.secret, { + pair: this.pair + }); this.market = _.find(Trader.getCapabilities().markets, (market) => { return market.pair[0] === this.currency && market.pair[1] === this.asset; }); - this.interval = 3000; + this.interval = 2000; +} + +const log = function() { + const message = node_util.format.apply(null, _.toArray(arguments)); + console.log(moment().format('YYYY-MM-DD HH:mm:ss') + ' (DEBUG): ' + message); } +Trader.prototype.inspect = function(obj) { + return node_util.inspect(obj, { + showhidden: false, + depth: null, + breakLength: Infinity, + colors: true + }); +}; + const setPrecision = (amount, digits) => { let precision = 100000000; if (Number.isInteger(digits)) precision = Math.pow(10, digits); @@ -38,7 +55,6 @@ const round = (value, decimals = 14) => { const includes = (str, list) => { if (!_.isString(str)) return false; - let result = _.some(list, item => str.includes(item)); return result; } @@ -51,20 +67,45 @@ const recoverableErrors = [ 'NOTFOUND' ]; + const processResponse = function(funcName, callback) { return (error, body) => { + /* BitX Error Codes + Error: BitX error 400: invalid id + Error: BitX error 401: "error":"API key not found","error_code":"ErrAPIKeyNotFound","error_action": + Error: BitX error 429: -- API limit + Error: BitX error 500: Something went wrong, we're looking into it. + */ + if (!error && !body) { - error = new Error('Empty response'); + error = new Error('received empty response'); } if (error) { + log(name, funcName + '() received error -->', error.message); + if (includes(error.message, recoverableErrors)) { error.notFatal = true; } if (includes(error.message, ['error 429'])) { error.notFatal = true; - error.backoffDelay = 10000; + error.backoffDelay = 3000; + } + + if (includes(error.message, ['error 500'])) { + error.notFatal = true; + error.backoffDelay = 15000; + } + + if (includes(error.message, ['Insufficient balance'])) { + error.notFatal = true; + error.backoffDelay = 2000; + tradeAttempt++; + if (tradeAttempt >= 3) { + error.notFatal = false; + log(name, funcName + '() giving up after 3 failed attempts.'); + } } return callback(error, undefined); @@ -77,9 +118,11 @@ const processResponse = function(funcName, callback) { //------- Gekko Functions ---------// Trader.prototype.getTicker = function(callback) { + // log(name, 'getTicker()'); + const process = (err, data) => { if (err) { - console.log(name, 'Error: --> ', err); + log(name, 'Error: -->', err); return callback(err); } const ticker = { @@ -87,6 +130,7 @@ Trader.prototype.getTicker = function(callback) { bid: data.bid, spread: round(data.ask - data.bid) }; + log(name, 'getTicker() -->', this.inspect(ticker)); callback(undefined, ticker); }; @@ -95,58 +139,61 @@ Trader.prototype.getTicker = function(callback) { } Trader.prototype.getFee = function(callback) { - // const process = (err, data) => { - // if (err) { - // console.log(name, 'Error: --> ', err); - // return callback(err); - // } - // callback(undefined, data.taker_fee / 100); - // }; - // const handler = cb => this.luno.getFee(processResponse('getFee', cb)); - // retry(null, handler, process); + // log(name, 'getFee()'); if (this.pair === 'ETHXBT') - callback(undefined, 0.000025); + return callback(undefined, 0.000025); else if (this.pair === 'XBTIDR') - callback(undefined, 0.00002); + return callback(undefined, 0.00002); else - callback(undefined, 0.0001); + return callback(undefined, 0.0001); + + /* + const process = (err, data) => { + if (err) { + // log(name, 'Error: -->', err); + return callback(err); + } + // log(name, 'getFee() --> fee:', data.taker_fee / 100); + callback(undefined, data.taker_fee / 100); + }; + const handler = cb => this.luno.getFee(processResponse('getFee', cb)); + retry(null, handler, process); + */ } Trader.prototype.getPortfolio = function(callback) { + // log(name, 'getPortfolio()'); + const process = (err, data) => { if (err) { - console.log(name, 'Error: --> ', err); + log(name, 'Error: -->', err); return callback(err); } + const assetProfile = _.find(data.balance, a => a.asset === this.asset); + const currencyProfile = _.find(data.balance, a => a.asset === this.currency); + let assetAmount = round(assetProfile.balance - assetProfile.reserved); + let currencyAmount = round(currencyProfile.balance - currencyProfile.reserved); - let assetAmount = 0, - currencyAmount = 0, - assetHold = 0, - currencyHold = 0; - - _.forEach(data.balance, (t) => { - if (this.asset === t.asset) { - assetAmount = +t.balance; - assetHold = +t.reserved; - } else if (this.currency === t.asset) { - currencyAmount = +t.balance; - currencyHold = +t.reserved; - } - }); + if (!_.isNumber(assetAmount) || _.isNaN(assetAmount)) { + assetAmount = 0; + } - if (!_.isNumber(assetAmount) || _.isNaN(assetAmount) || - !_.isNumber(currencyAmount) || _.isNaN(currencyAmount) - ) { - return console.log(name, 'account balance error: Gekko is unable to trade with ', this.currency.toUpperCase(), ':', currencyAmount, ' or ', this.asset.toUpperCase(), ':', assetAmount); + if (!_.isNumber(currencyAmount) || _.isNaN(currencyAmount)) { + currencyAmount = 0; } - const portfolio = [ - { name: this.asset.toUpperCase(), amount: round(assetAmount - assetHold) }, - { name: this.currency.toUpperCase(), amount: round(currencyAmount - currencyHold) } + const portfolio = [{ + name: this.asset.toUpperCase(), + amount: assetAmount + }, + { + name: this.currency.toUpperCase(), + amount: currencyAmount + } ]; - - callback(err, portfolio); + log(name, 'getPortfolio() --> ' + this.inspect(portfolio)); + callback(undefined, portfolio); }; const handler = cb => this.luno.getBalance(processResponse('getPortfolio', cb)); @@ -154,14 +201,18 @@ Trader.prototype.getPortfolio = function(callback) { } Trader.prototype.buy = function(amount, price, callback) { + log(name, 'buy() amount:', amount, 'price:', price); + + tradeAttempt = 0; amount = round(amount); price = round(price); const process = (err, data) => { if (err) { - console.log(name, 'unable to buy:', err.message); + log(name, 'unable to buy:', err.message); return callback(err); } - callback(err, data.order_id); + log(name, 'buy() order id: -->', data.order_id); + callback(undefined, data.order_id); }; const handler = cb => this.luno.postBuyOrder(amount, price, processResponse('buy', cb)); @@ -169,14 +220,18 @@ Trader.prototype.buy = function(amount, price, callback) { } Trader.prototype.sell = function(amount, price, callback) { + log(name, 'sell() amount:', amount, 'price:', price); + + tradeAttempt = 0; amount = round(amount); price = round(price); const process = (err, data) => { if (err) { - console.log(name, 'unable to sell:', err.message); + log(name, 'unable to sell:', err.message); return callback(err); } - callback(err, data.order_id); + log(name, 'sell() order id: -->', data.order_id); + callback(undefined, data.order_id); }; const handler = cb => this.luno.postSellOrder(amount, price, processResponse('sell', cb)); @@ -193,25 +248,38 @@ Trader.prototype.roundPrice = function(price) { } Trader.prototype.getOrder = function(order, callback) { + // log(name, 'getOrder() order id:', order); + if (!order) { return callback('invalid order_id', false); } const process = (err, data) => { if (err) { - console.log(name, 'Error: --> ', err); + log(name, 'Error: -->', err); return callback(err); } - let price = 0; - let amount = 0; + const price = parseFloat(data.limit_price); + const amount = parseFloat(data.base); let date = moment(); - price = parseFloat(data.limit_price); - amount = parseFloat(data.base); + const fees = { + [this.asset]: +data.fee_base, + [this.currency]: +data.fee_counter + }; + const feePercent = round(data.fee_base / data.base * 100, 2); if (data.state === 'PENDING') { date = moment(data.creation_timestamp); } else { date = moment(data.completed_timestamp); } - callback(err, { price, amount, date }); + const result = { + price, + amount, + date, + fees, + feePercent + }; + log(name, 'getOrder() -->', this.inspect(result)); + callback(undefined, result); }; const handler = cb => this.luno.getOrder(order, processResponse('getOrder', cb)); @@ -219,22 +287,23 @@ Trader.prototype.getOrder = function(order, callback) { } Trader.prototype.checkOrder = function(order, callback) { + // log(name, 'checkOrder() order id:', order); + if (!order) { return callback('invalid order_id'); } const process = (err, data) => { if (err) { - console.log(name, 'Error: --> ', err); + log(name, 'Error: -->', err); return callback(err); } - const result = { open: data.state === 'PENDING', executed: data.limit_volume === data.base, filledAmount: +data.base, remaining: round(+data.limit_volume - +data.base) } - + log(name, 'checkOrder()', order, 'result:', this.inspect(result)); callback(undefined, result); }; @@ -243,44 +312,43 @@ Trader.prototype.checkOrder = function(order, callback) { } Trader.prototype.cancelOrder = function(order, callback) { + log(name, 'cancelOrder() order id:', order); + if (!order) { return callback('invalid order_id'); } const process = (err, data) => { if (err) { if (_.includes(err.message, 'Cannot stop unknown')) { - console.log(name, 'unable to cancel order:', order, '(' + err.message + ') assuming success...'); - // return callback(undefined, true); + log(name, 'unable to cancel order:', order, '(' + err.message + ') assuming success...'); } else { - console.log(name, 'unable to cancel order:', order, '(' + err.message + ') aborting...'); + log(name, 'unable to cancel order:', order, '(' + err.message + ') aborting...'); return callback(err); } } if (data && !data.success) { - console.log('cancelOrder() --> status:', data.success); + log('cancelOrder() --> status:', data.success); return callback(undefined, false); } this.checkOrder(order, (error, orderStatus) => { if (error) { - console.log(name, 'cancelOrder\'s checkOrder failed. What do i do here?'); + log(name, 'cancelOrder\'s checkOrder failed. What do i do here?'); return callback(error, false); } if (orderStatus.executed) { + log(name, 'cancelOrder() -->', order, 'was fulfilled before cancelOrder was completed.'); return callback(undefined, true); } - - // TODO: Fees? const remaining = { remaining: orderStatus.remaining, filled: orderStatus.filledAmount } - + log(name, 'cancelOrder() --> status: false remaining:', this.inspect(remaining)); return callback(undefined, false, remaining); }); - } const handler = cb => this.luno.stopOrder(order, processResponse('cancelOrder', cb)); @@ -288,9 +356,11 @@ Trader.prototype.cancelOrder = function(order, callback) { } Trader.prototype.getTrades = function(since, callback, descending) { + // log(name, 'getTrades() since:', since); + const process = (err, result) => { if (err) { - console.log(name, 'Error: --> ', err); + log(name, 'Error: -->', err); return callback(err); } let trades = _.map(result.trades, (t) => { @@ -301,7 +371,6 @@ Trader.prototype.getTrades = function(since, callback, descending) { tid: t.timestamp } }); - // Decending by default if (!descending) { trades = trades.reverse() } @@ -332,7 +401,7 @@ Trader.getCapabilities = function() { { pair: ['KES', 'XBT'], minimalOrder: { amount: 0.0005, unit: 'asset' }, precision: 6 }, { pair: ['NGN', 'XBT'], minimalOrder: { amount: 0.0005, unit: 'asset' }, precision: 6 }, { pair: ['ZAR', 'XBT'], minimalOrder: { amount: 0.0005, unit: 'asset' }, precision: 6 }, - ], + ], requires: ['key', 'secret'], providesFullHistory: true, providesHistory: 'date', diff --git a/strategies/DEBUG_toggle-advice.js b/strategies/DEBUG_toggle-advice.js index 1fabd2184..251585a57 100644 --- a/strategies/DEBUG_toggle-advice.js +++ b/strategies/DEBUG_toggle-advice.js @@ -23,7 +23,7 @@ var method = { return; log.info('iteration:', i); - + if(i % settings.each === 0) { log.debug('trigger SHORT'); this.advice('short'); @@ -42,4 +42,4 @@ var method = { } }; -module.exports = method; \ No newline at end of file +module.exports = method;