302 lines
9.7 KiB
Plaintext
302 lines
9.7 KiB
Plaintext
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;
|