bluebot
Version:
A bitcoin trading bot for auto trading at various exchanges
302 lines (247 loc) • 9.93 kB
JavaScript
var WEX = require('node-wex');
var moment = require('moment');
var util = require('../core/util');
var _ = require('lodash');
var log = require('../core/log')
var Trader = function(config) {
this.key = config.key;
this.secret = config.secret;
this.asset = config.asset;
this.currency = config.currency;
this.pair = [config.asset, config.currency].join('_').toLowerCase();
this.name = 'wex.nz';
_.bindAll(this);
this.wex = new WEX(this.key, this.secret);
_.bindAll(this.wex, ['trade', 'trades', 'getInfo', 'ticker', 'orderList']);
// see @link https://github.com/askmike/gekko/issues/302
this.wexHistorocial = new WEX(false, false, {public_url: 'https://wex.nz/api/3/'});
_.bindAll(this.wexHistorocial, 'trades');
}
Trader.prototype.round = function(amount) {
// Prevent "You incorrectly entered one of fields."
// because of more than 8 decimals.
amount *= 100000000;
amount = Math.floor(amount);
amount /= 100000000;
return amount;
}
Trader.prototype.buy = function(amount, price, callback) {
amount = this.round(amount);
var set = function(err, data) {
if(err || !data || !data.return)
return log.error('unable to buy:', err);
callback(null, data.return.order_id);
}.bind(this);
// workaround for nonce error
setTimeout(function() {
this.wex.trade(this.pair, 'buy', price, amount, _.bind(set, this));
}.bind(this), 1000);
}
Trader.prototype.sell = function(amount, price, callback) {
amount = this.round(amount);
var set = function(err, data) {
if(err || !data || !data.return)
return log.error('unable to sell:\n\n', err);
callback(null, data.return.order_id);
};
// workaround for nonce error
setTimeout(function() {
this.wex.trade(this.pair, 'sell', price, amount, _.bind(set, this));
}.bind(this), 1000);
}
// if the exchange errors we try the same call again after
// waiting 10 seconds
Trader.prototype.retry = function(method, args) {
var wait = +moment.duration(10, 'seconds');
log.debug(this.name, 'returned an error, retrying..');
var self = this;
// run the failed method again with the same
// arguments after wait
setTimeout(
function() { method.apply(self, args) },
wait
);
}
Trader.prototype.getPortfolio = function(callback) {
var args = _.toArray(arguments);
var calculate = function(err, data) {
if(err) {
if(err.message === 'invalid api key')
util.die('Your ' + this.name + ' API keys are invalid');
return this.retry(this.getPortfolio, args);
}
if(_.isEmpty(data))
err = 'no data';
if (err || !data.return || !data.return.funds)
return this.retry(this.getPortfolio, args);
var assetAmount = parseFloat( data.return.funds[this.asset.toLowerCase()] );
var currencyAmount = parseFloat( data.return.funds[this.currency.toLowerCase()] );
if(!_.isNumber(assetAmount) || _.isNaN(assetAmount)) {
log.error(`wex.nz did not return portfolio for ${this.asset}, assuming 0.`);
assetAmount = 0;
}
if(!_.isNumber(currencyAmount) || _.isNaN(currencyAmount)) {
log.error(`wex.nz did not return portfolio for ${this.currency}, assuming 0.`);
currencyAmount = 0;
}
var portfolio = [
{ name: this.asset, amount: assetAmount },
{ name: this.currency, amount: currencyAmount }
];
callback(err, portfolio);
}.bind(this);
this.wex.getInfo(calculate);
}
Trader.prototype.getTicker = function(callback) {
// wex.nz doesn't state asks and bids in its
// ticker
var set = function(err, data) {
if(err)
return this.retry(this.wex.ticker, [this.pair, set]);
var ticker = {
ask: data.ticker.buy,
bid: data.ticker.sell
};
callback(err, ticker);
}.bind(this);
this.wex.ticker(this.pair, set);
}
Trader.prototype.getFee = function(callback) {
// wex.nz doesn't have different fees based on orders
// at this moment it is always 0.2%
callback(false, 0.002);
}
Trader.prototype.checkOrder = function(order, callback) {
var check = function(err, result) {
// wex returns an error when you have no open trades
// right now we assume on every error that the order
// was filled.
//
// TODO: check whether the error states that there are no
// open trades or that there is something else.
if(err)
callback(false, true);
else
callback(err, !result[order]);
}.bind(this);
this.wex.orderList({}, check);
}
Trader.prototype.getOrder = function(orderId, callback) {
console.log('getOrder', orderId);
var args = _.toArray(arguments);
var check = function(err, result) {
if(err) {
log.error('error on getOrder', err);
return this.retry(this.getOrder, args);
}
var order = null;
_.each(result.return, o => {
if(o.order_id === +orderId)
order = o;
});
if(!order)
return log.error('wex.nz did not provide the order');
var price = parseFloat(order.rate);
var amount = parseFloat(order.amount);
var date = moment.unix(order.timestamp);
callback(undefined, {price, amount, date});
}.bind(this);
this.wex.tradeHistory({pair: this.pair}, check);
}
Trader.prototype.cancelOrder = function(order, callback) {
this.wex.cancelOrder(order, (err, result) => {
callback();
});
}
Trader.prototype.getTrades = function(since, callback, descending) {
var args = _.toArray(arguments);
var process = function(err, trades) {
if(err)
return this.retry(this.getTrades, args);
if(descending)
callback(false, trades);
else
callback(false, trades.reverse());
}.bind(this);
// see @link https://github.com/askmike/gekko/issues/302
if(since) {
this.wexHistorocial.makePublicApiRequest(
'trades',
this.pair + '?limit=2000',
this.processAPIv3Trades(process)
)
} else
this.wex.trades(this.pair, process);
}
Trader.prototype.processAPIv3Trades = function(cb) {
return function(err, data) {
var trades = _.map(data[this.pair], function(t) {
return {
price: t.price,
amount: t.amount,
tid: t.tid,
date: t.timestamp
}
})
cb(err, trades);
}.bind(this)
}
Trader.getCapabilities = function () {
return {
name: 'wex.nz',
slug: 'wex.nz',
currencies: ['USD', 'RUR', 'EUR', 'BTC', 'LTC', 'ETH', 'NMC', 'NMC', 'NVC', 'PPC', 'DSH', 'BCH'],
assets: [
'BTC', 'LTC', 'NMC', 'NVC', 'USD', 'EUR', 'PPC', 'DSH', 'ETH',
'USDET', 'RURET', 'EURET', 'BTCET', 'LTCET', 'ETHET', 'NMCET', 'NVCET', 'PPCET', 'DSHET', 'BCHET' // Token
],
markets: [
{ pair: ['USD', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
{ pair: ['RUR', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
{ pair: ['EUR', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
{ pair: ['BTC', 'LTC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['USD', 'LTC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['RUR', 'LTC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['EUR', 'LTC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['BTC', 'NMC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['USD', 'NMC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['BTC', 'NVC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['USD', 'NVC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['RUR', 'USD'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['USD', 'EUR'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['RUR', 'EUR'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['BTC', 'PPC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['USD', 'PPC'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['BTC', 'DSH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['USD', 'DSH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['RUR', 'DSH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['EUR', 'DSH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['LTC', 'DSH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['ETH', 'DSH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['BTC', 'ETH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['USD', 'ETH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['EUR', 'ETH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['LTC', 'ETH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['RUR', 'ETH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['USD', 'BCH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['BTC', 'BCH'], minimalOrder: { amount: 0.1, unit: 'asset' } },
// Token pairs
{ pair: ['USD', 'USDET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['RUR', 'RURET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['EUR', 'EURET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['BTC', 'BTCET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['LTC', 'LTCET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['ETH', 'ETHET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['NMC', 'NMCET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['NVC', 'NVCET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['PPC', 'PPCET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['DSH', 'DSHET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
{ pair: ['BCH', 'BCHET'], minimalOrder: { amount: 0.1, unit: 'asset' } },
],
requires: ['key', 'secret'],
providesHistory: false,
tid: 'tid',
tradable: true
};
}
module.exports = Trader;