252 lines
7.5 KiB
Plaintext
252 lines
7.5 KiB
Plaintext
var QuadrigaCX = require('quadrigacx');
|
|
var moment = require('moment');
|
|
var util = require('../core/util');
|
|
var _ = require('lodash');
|
|
var log = require('../core/log');
|
|
|
|
|
|
var Trader = function(config) {
|
|
_.bindAll(this);
|
|
|
|
if(_.isObject(config)) {
|
|
this.key = config.key;
|
|
this.secret = config.secret;
|
|
this.clientId = config.username;
|
|
this.asset = config.asset;
|
|
this.currency = config.currency;
|
|
}
|
|
|
|
this.pair = this.asset.toLowerCase() + '_' + this.currency.toLowerCase();
|
|
this.name = 'quadriga';
|
|
this.since = null;
|
|
|
|
this.quadriga = new QuadrigaCX(
|
|
this.clientId ? this.clientId : "1",
|
|
this.key ? this.key : "",
|
|
this.secret ? this.secret : "",
|
|
);
|
|
}
|
|
|
|
Trader.prototype.retry = function(method, warn, args, error) {
|
|
var wait = +moment.duration(30, 'seconds');
|
|
if (error.code === 200) {
|
|
log.debug(`${this.name}: API rate limit exceeded! unable to call ${method}, will retry in 2 minutes`)
|
|
wait = +moment.duration(120, 'seconds');
|
|
}
|
|
else {
|
|
log.debug(JSON.stringify(error));
|
|
log.debug(`${this.name}: ${warn}, will retry in 30 seconds`);
|
|
}
|
|
|
|
var self = this;
|
|
|
|
// make sure the callback (and any other fn)
|
|
// is bound to Trader
|
|
_.each(args, function(arg, i) {
|
|
if(_.isFunction(arg))
|
|
args[i] = _.bind(arg, self);
|
|
});
|
|
|
|
// run the failed method again with the same
|
|
// arguments after wait
|
|
setTimeout(
|
|
function() { method.apply(self, args) },
|
|
wait
|
|
);
|
|
};
|
|
|
|
Trader.prototype.getTrades = function(since, callback, descending) {
|
|
var args = _.toArray(arguments);
|
|
var process = function(err, trades) {
|
|
if (trades && trades.error) return this.retry(this.getTrades, 'unable to get trades', args, trades.error);
|
|
if (err) return this.retry(this.getTrades, 'unable to get trades', args, err);
|
|
|
|
var parsedTrades = [];
|
|
_.each(trades, function(trade) {
|
|
parsedTrades.push({
|
|
tid: trade.tid,
|
|
date: trade.date,
|
|
price: parseFloat(trade.price),
|
|
amount: parseFloat(trade.amount)
|
|
});
|
|
}, this);
|
|
|
|
if(descending)
|
|
callback(null, parsedTrades);
|
|
else
|
|
callback(null, parsedTrades.reverse());
|
|
};
|
|
|
|
var reqData = {
|
|
book: this.pair,
|
|
time: 'hour'
|
|
};
|
|
|
|
this.quadriga.api('transactions', reqData, _.bind(process, this));
|
|
};
|
|
|
|
Trader.prototype.getPortfolio = function(callback) {
|
|
var args = _.toArray(arguments);
|
|
var set = function(err, data) {
|
|
|
|
if (data && data.error) return this.retry(this.getPortfolio, 'unable to get balance', args, data.error);
|
|
if (err) return this.retry(this.getPortfolio, 'unable to get balance', args, err);
|
|
|
|
var assetAmount = parseFloat( data[this.asset.toLowerCase() + '_available'] );
|
|
var currencyAmount = parseFloat( data[this.currency.toLowerCase() + '_available'] );
|
|
|
|
if(!_.isNumber(assetAmount) || _.isNaN(assetAmount)) {
|
|
log.error(`Quadriga did not return balance for ${this.asset.toLowerCase()}, assuming 0.`);
|
|
assetAmount = 0;
|
|
}
|
|
|
|
if(!_.isNumber(currencyAmount) || _.isNaN(currencyAmount)) {
|
|
log.error(`Quadriga did not return balance for ${this.currency.toLowerCase()}, assuming 0.`);
|
|
currencyAmount = 0;
|
|
}
|
|
|
|
var portfolio = [
|
|
{ name: this.asset, amount: assetAmount },
|
|
{ name: this.currency, amount: currencyAmount }
|
|
];
|
|
callback(err, portfolio);
|
|
};
|
|
|
|
this.quadriga.api('balance', _.bind(set, this));
|
|
};
|
|
|
|
Trader.prototype.getFee = function(callback) {
|
|
callback(false, 0.005);
|
|
};
|
|
|
|
Trader.prototype.getTicker = function(callback) {
|
|
var set = function(err, data) {
|
|
|
|
if (data && data.error) return this.retry(this.getTicker, 'unable to get quote', args, data.error);
|
|
if (err) return this.retry(this.getTicker, 'unable to get quote', args, err);
|
|
|
|
var ticker = {
|
|
ask: data.ask,
|
|
bid: data.bid
|
|
};
|
|
callback(err, ticker);
|
|
};
|
|
|
|
this.quadriga.api('ticker', {book: this.pair}, _.bind(set, this));
|
|
};
|
|
|
|
Trader.prototype.roundAmount = function(amount) {
|
|
var precision = 100000000;
|
|
var market = Trader.getCapabilities().markets.find(function(market){ return market.pair[0] === this.currency && market.pair[1] === this.asset });
|
|
|
|
if(Number.isInteger(market.precision))
|
|
precision = 10 * market.precision;
|
|
|
|
amount *= precision;
|
|
amount = Math.floor(amount);
|
|
amount /= precision;
|
|
return amount;
|
|
};
|
|
|
|
Trader.prototype.addOrder = function(tradeType, amount, price, callback) {
|
|
var args = _.toArray(arguments);
|
|
|
|
amount = this.roundAmount(amount);
|
|
log.debug(tradeType.toUpperCase(), amount, this.asset, '@', price, this.currency);
|
|
|
|
var set = function(err, data) {
|
|
|
|
if (data && data.error) return this.retry(this.addOrder, 'unable to place order', args, data.error);
|
|
if (err) return this.retry(this.addOrder, 'unable to place order', args, err);
|
|
|
|
var txid = data.id;
|
|
log.debug('added order with txid:', txid);
|
|
|
|
callback(undefined, txid);
|
|
};
|
|
|
|
this.quadriga.api(tradeType, {
|
|
book: this.pair,
|
|
price: price,
|
|
amount: amount
|
|
}, _.bind(set, this));
|
|
};
|
|
|
|
|
|
Trader.prototype.getOrder = function(order, callback) {
|
|
var args = _.toArray(arguments);
|
|
|
|
var get = function(err, data) {
|
|
if (data && data.error) return this.retry(this.getOrder, 'unable to get order', args, data.error);
|
|
if (err) return this.retry(this.getOrder, 'unable to get order', args, err);
|
|
|
|
var price = parseFloat( data[0].price );
|
|
var amount = parseFloat( data[0].amount );
|
|
var date = (data[0].updated) ? moment.unix( data[0].updated ) : false;
|
|
|
|
callback(undefined, {price, amount, date});
|
|
}.bind(this);
|
|
|
|
this.quadriga.api('lookup_oder', {id: order}, get);
|
|
}
|
|
|
|
Trader.prototype.buy = function(amount, price, callback) {
|
|
this.addOrder('buy', amount, price, callback);
|
|
};
|
|
|
|
Trader.prototype.sell = function(amount, price, callback) {
|
|
this.addOrder('sell', amount, price, callback);
|
|
};
|
|
|
|
Trader.prototype.checkOrder = function(order, callback) {
|
|
var args = _.toArray(arguments);
|
|
|
|
var check = function(err, data) {
|
|
|
|
if (data && data.error) return this.retry(this.checkOrder, 'unable to get order', args, data.error);
|
|
if (err) return this.retry(this.checkOrder, 'unable to get order', args, err);
|
|
|
|
var result = data[0];
|
|
var stillThere = result.status === 0 || result.status === 1;
|
|
callback(err, !stillThere);
|
|
};
|
|
|
|
this.quadriga.api('lookup_order', {id: order}, _.bind(check, this));
|
|
};
|
|
|
|
Trader.prototype.cancelOrder = function(order, callback) {
|
|
var args = _.toArray(arguments);
|
|
var cancel = function(err, data) {
|
|
|
|
if (data && data.error) return this.retry(this.cancelOrder, 'unable to cancel order', args, data.error);
|
|
if (err) return this.retry(this.cancelOrder, 'unable to cancel order', args, err);
|
|
|
|
callback();
|
|
};
|
|
|
|
this.quadriga.api('cancel_order', {id: order}, _.bind(cancel, this));
|
|
};
|
|
|
|
Trader.getCapabilities = function () {
|
|
return {
|
|
name: 'Quadriga',
|
|
slug: 'quadriga',
|
|
currencies: ['CAD', 'USD', 'BTC'],
|
|
assets: ['BTC', 'ETH', 'LTC', 'BCH'],
|
|
markets: [
|
|
{ pair: ['BTC', 'ETH'], minimalOrder: { amount: 0.00001, unit: 'asset' }, precision: 8 },
|
|
{ pair: ['CAD', 'ETH'], minimalOrder: { amount: 0.00001, unit: 'asset' }, precision: 8 },
|
|
{ pair: ['USD', 'BTC'], minimalOrder: { amount: 0.00001, unit: 'asset' }, precision: 8 },
|
|
{ pair: ['CAD', 'BTC'], minimalOrder: { amount: 0.00001, unit: 'asset' }, precision: 8 },
|
|
{ pair: ['CAD', 'LTC'], minimalOrder: { amount: 0.00001, unit: 'asset' }, precision: 8 },
|
|
{ pair: ['CAD', 'BCH'], minimalOrder: { amount: 0.00001, unit: 'asset' }, precision: 8 },
|
|
],
|
|
requires: ['key', 'secret', 'username'],
|
|
providesHistory: false,
|
|
tid: 'tid',
|
|
tradable: true
|
|
};
|
|
}
|
|
|
|
module.exports = Trader;
|