var Coingi = require('coingi'); var moment = require('moment'); var _ = require('lodash'); var util = require('../core/util'); var Errors = require('../core/error'); var log = require('../core/log'); var Trader = function (config) { _.bindAll(this); if (_.isObject(config)) { this.key = config.key; this.secret = config.secret; this.currency = config.currency.toUpperCase() this.asset = config.asset.toUpperCase(); } this.pair = this.asset + "-" + this.currency; this.name = 'coingi'; this.since = null; this.coingi = new Coingi( this.key, this.secret, {timeout: +moment.duration(60, 'seconds')} ); } var retryCritical = { retries: 10, factor: 1.2, minTimeout: 1 * 1000, maxTimeout: 30 * 1000 }; var retryForever = { forever: true, factor: 1.2, minTimeout: 10, maxTimeout: 30 }; var recoverableErrors = new RegExp(/(SOCKETTIMEDOUT|TIMEDOUT|CONNRESET|CONNREFUSED|NOTFOUND|API:Invalid nonce|Service:Unavailable|Request timed out|Response code 520|Response code 504|Response code 502)/) Trader.prototype.processError = function (funcName, error) { if (!error) return undefined; if (!error.message.match(recoverableErrors)) { log.error(`[coingi.js] (${funcName}) returned an irrecoverable error: ${error.message}`); return new Errors.AbortError('[coingi.js] ' + error.message); } log.debug(`[coingi.js] (${funcName}) returned an error, retrying: ${error.message}`); return new Errors.RetryError('[coingi.js] ' + error.message); }; Trader.prototype.handleResponse = function (funcName, callback) { return (error, body) => { if (!error) { if (_.isEmpty(body)) error = new Error('NO DATA WAS RETURNED'); else if (!_.isEmpty(body.error)) error = new Error(body.error); } return callback(this.processError(funcName, error), body); } }; /* PORTFOLIO MANAGER */ Trader.prototype.getTicker = function (callback) { var setTicker = function (err, data) { if (err) return callback(err); var ticker = { ask: data.asks[0].price, bid: data.bids[0].price }; callback(undefined, ticker); }; let handler = (cb) => this.coingi.api('order-book', "/" + this.pair + "/1/1/1", this.handleResponse('getTicker', cb)); util.retryCustom(retryForever, _.bind(handler, this), _.bind(setTicker, this)); }; Trader.prototype.getTrades = function (since, callback, ascending) { var startTs = since ? moment(since).valueOf() : null; var processResults = function (err, trades) { if (err) { return callback(err); } var parsedTrades = []; _.each(trades, function (trade) { // Even when you supply 'since' you can still get more trades than you asked for, it needs to be filtered if (_.isNull(startTs) || startTs < moment(trade.timestamp).valueOf()) { parsedTrades.push({ type: trade.type === 0 ? "sell" : "buy", date: moment(trade.timestamp).unix(), amount: String(trade.amount), price: String(trade.price), tid: trade.timestamp }); } }, this); if(ascending) callback(undefined, parsedTrades); else callback(undefined, parsedTrades.reverse()); }; var optionalSince = ""; if (since) { optionalSince = "/" + startTs; } let handler = (cb) => this.coingi.api('transactions', "/" + this.pair + "/512" + optionalSince, this.handleResponse('getTrades', cb)); util.retryCustom(retryForever, _.bind(handler, this), _.bind(processResults, this)); }; Trader.prototype.getPortfolio = function (callback) { var setBalance = function (err, data) { if (err) return callback(err); log.debug('[coingi.js] entering "setBalance" callback after coingi-api call, data:', data); const portfolio = []; for (var i = 0; i < data.length; i++) { portfolio.push({ name: data[i].currency.name.toUpperCase(), amount: data[i].available }); } return callback(undefined, portfolio); }; let handler = (cb) => this.coingi.api('balance', {currencies: this.asset + "," + this.currency}, this.handleResponse('getPortfolio', cb)); util.retryCustom(retryForever, _.bind(handler, this), _.bind(setBalance, this)); }; // Base fee is 0.2% Trader.prototype.getFee = function (callback) { const fee = 0.2; callback(undefined, fee / 100); }; Trader.prototype.addOrder = function (tradeType, amount, price, callback) { log.debug('[coingi.js] (add-order)', tradeType.toUpperCase(), amount, this.asset, '@', price, this.currency); var setOrder = function (err, data) { if (err) return callback(err); var uuid = data.result; log.debug('[coingi.js] (addOrder) added order with uuid:', uuid); callback(undefined, uuid); }; let reqData = { currencyPair: this.pair, type: tradeType.toLowerCase() === "sell" ? 0 : 1, price: price, volume: amount.toString() }; let handler = (cb) => this.coingi.api('add-order', reqData, this.handleResponse('addOrder', cb)); util.retryCustom(retryCritical, _.bind(handler, this), _.bind(setOrder, this)); }; Trader.prototype.getOrder = function (orderId, callback) { var getOrder = function (err, order) { if (err) return callback(err); if (order !== null) { const price = parseFloat(order.price); const amount = parseFloat(order.baseAmount); const date = moment.unix(order.timestamp); callback(undefined, {price, amount, date}); } else { log.error("Error! Order ID '" + orderId + "' couldn't be found in the result of get order!"); } }; let reqData = {ordeId: orderId}; let handler = (cb) => this.coingi.api('get-order', reqData, this.handleResponse('getOrder', cb)); util.retryCustom(retryCritical, _.bind(handler, this), _.bind(getOrder, this)); } 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 (orderId, callback) { var check = function (err, order) { if (err) return callback(err); if (order === null) { log.error("Error! Order ID '" + orderId + "' couldn't be found in the result of get order!"); } // returns true when the order has been filled (processed in full), false otherwise callback(undefined, order !== null && order.status === 2); }; let reqData = {orderId: orderId}; let handler = (cb) => this.coingi.api('get-order', reqData, this.handleResponse('checkOrder', cb)); util.retryCustom(retryCritical, _.bind(handler, this), _.bind(check, this)); }; Trader.prototype.cancelOrder = function (order, callback) { let reqData = {orderId: order}; let handler = (cb) => this.coingi.api('cancel-order', reqData, this.handleResponse('cancelOrder', cb)); util.retryCustom(retryForever, _.bind(handler, this), callback); }; Trader.getCapabilities = function () { return { name: 'Coingi', slug: 'coingi', currencies: ['EUR', 'USD', 'BTC'], assets: ['BTC', 'DASH', 'DOGE', 'EUR', 'LTC', 'NMC', 'PPC', 'VTC'], markets: [ // Tradeable against BTC {pair: ['USD', 'BTC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, {pair: ['EUR', 'BTC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, // Tradeable against DASH {pair: ['BTC', 'DASH'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, // Tradeable against DOGE {pair: ['BTC', 'DOGE'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, {pair: ['USD', 'DOGE'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, // Tradeable against EUR {pair: ['USD', 'EUR'], minimalOrder: {amount: 0.01, unit: 'asset'}, precision: 2}, // Tradeable against LTC {pair: ['BTC', 'LTC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, {pair: ['EUR', 'LTC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, {pair: ['USD', 'LTC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, // Tradeable against NMC {pair: ['BTC', 'NMC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, // Tradeable against PPC {pair: ['USD', 'PPC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, {pair: ['EUR', 'PPC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, {pair: ['BTC', 'PPC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8}, // Tradeable against VTC {pair: ['BTC', 'VTC'], minimalOrder: {amount: 0.00001, unit: 'asset'}, precision: 8} ], requires: ['key', 'secret'], providesHistory: 'date', providesFullHistory: true, exchangeMaxHistoryAge: 30, tid: 'date', tradable: true }; } module.exports = Trader;