240 lines
6.1 KiB
JavaScript
240 lines
6.1 KiB
JavaScript
const _ = require('lodash');
|
|
const moment = require('moment');
|
|
const retry = require('../exchangeUtils').retry;
|
|
|
|
|
|
const CryptoJS = require("crypto-js");
|
|
const querystring = require('querystring');
|
|
const request = require('request');
|
|
|
|
API_URL='https://api.exmo.com/v1/';
|
|
|
|
const marketData = require('./exmo-markets.json');
|
|
|
|
|
|
const Trader = function(config) {
|
|
_.bindAll(this);
|
|
this.key="";
|
|
this.secret="";
|
|
|
|
if(_.isObject(config)) {
|
|
if(_.isString(config.key)) this.key = config.key;
|
|
if(_.isString(config.secret)) this.secret = config.secret;
|
|
this.currency = config.currency;
|
|
this.asset = config.asset;
|
|
this.pair = this.asset + '_' + this.currency;
|
|
};
|
|
|
|
this.name = 'EXMO';
|
|
this.nonce = new Date() * 1000;
|
|
}
|
|
|
|
|
|
const recoverableErrors = [
|
|
'SOCKETTIMEDOUT',
|
|
'TIMEDOUT',
|
|
'CONNRESET',
|
|
'CONNREFUSED',
|
|
'NOTFOUND',
|
|
'EHOSTUNREACH',
|
|
];
|
|
|
|
const includes = (str, list) => {
|
|
if(!_.isString(str))
|
|
return false;
|
|
|
|
return _.some(list, item => str.includes(item));
|
|
}
|
|
|
|
|
|
Trader.prototype.api_query = function(method, params, callback){
|
|
params.nonce = this.nonce++;
|
|
var post_data = querystring.stringify(params);
|
|
|
|
var options = {
|
|
url: API_URL + method,
|
|
headers: {'Key': this.key,'Sign': CryptoJS.HmacSHA512(post_data, this.secret).toString(CryptoJS.enc.hex) },
|
|
form: params
|
|
};
|
|
|
|
request.post(options, function (error, response, body) {
|
|
if (!error && response.statusCode == 200) {
|
|
data=JSON.parse(body);
|
|
if(data.error) error = { message: data.error }
|
|
else if (data.result!=undefined && data.result==false) error = { message: '"result": false' } ;
|
|
callback(error, data);
|
|
} else {
|
|
console.log('cb request error');
|
|
console.log(body);
|
|
|
|
if(error) {
|
|
if(includes(error.message, recoverableErrors)) {
|
|
error.notFatal = true;
|
|
}
|
|
console.log(error);
|
|
callback(error);
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
Trader.prototype.getTrades = function(since, callback, descending) {
|
|
const processResponse = (error, data) => {
|
|
if (error) return callback(error);
|
|
|
|
data=data[this.pair];
|
|
|
|
var parsedTrades = _.map(data, function(trade) {
|
|
return {
|
|
tid: trade.trade_id,
|
|
date: trade.date,
|
|
price: +trade.price,
|
|
amount: +trade.quantity
|
|
};
|
|
});
|
|
|
|
if (descending) callback(undefined, parsedTrades);
|
|
else callback(undefined, parsedTrades.reverse());
|
|
};
|
|
|
|
const fetch = cb => this.api_query("trades", { pair: this.pair} , cb);
|
|
retry(null, fetch, processResponse);
|
|
|
|
}
|
|
|
|
Trader.prototype.getTicker = function(callback) {
|
|
const processResponse = (err, data) => {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
data=data[this.pair];
|
|
|
|
callback(undefined, {bid: +data.sell_price, ask: +data.buy_price});
|
|
};
|
|
|
|
const fetch = cb => this.api_query("ticker", { pair: this.pair} , cb);
|
|
retry(null, fetch, processResponse);
|
|
}
|
|
|
|
Trader.prototype.getFee = function(callback) {
|
|
callback(undefined, 0.002);
|
|
}
|
|
|
|
Trader.prototype.roundAmount = function(amount) {
|
|
return _.floor(amount, 8);
|
|
};
|
|
|
|
Trader.prototype.roundPrice = function(price) {
|
|
return price;
|
|
}
|
|
|
|
Trader.prototype.submitOrder = function(type, amount, price, callback) {
|
|
const processResponse = (err, data) => {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
callback(null, data.order_id);
|
|
}
|
|
|
|
const fetch = cb => this.api_query("order_create", { pair: this.pair, quantity: amount, price: price, type: type } , cb);
|
|
retry(null, fetch, processResponse);
|
|
//callback(null, 1177669837);
|
|
}
|
|
|
|
Trader.prototype.buy = function(amount, price, callback) {
|
|
this.submitOrder('buy', amount, price, callback);
|
|
}
|
|
|
|
Trader.prototype.sell = function(amount, price, callback) {
|
|
this.submitOrder('sell', amount, price, callback);
|
|
}
|
|
|
|
Trader.prototype.getOrder = function(order_id, callback) {
|
|
const processResponse = (err, data) => {
|
|
|
|
if(err)
|
|
return callback(err);
|
|
|
|
data=data[this.pair];
|
|
|
|
if(data==undefined) return callback(new Error('Orders not found'));
|
|
|
|
const order = _.find(data, function(o) { return o.order_id === +order_id });
|
|
|
|
if(!order) return callback(new Error('Order not found'));
|
|
|
|
callback(undefined, { price: order.price, amount: order.amount, date: moment.unix(order.date) });
|
|
}
|
|
|
|
const fetch = cb => this.api_query("user_trades", { pair: this.pair} , cb);
|
|
retry(null, fetch, processResponse);
|
|
}
|
|
|
|
Trader.prototype.checkOrder = function(order_id, callback) {
|
|
const processResponse = (err, data) => {
|
|
|
|
if(err)
|
|
return callback(err);
|
|
|
|
data=data[this.pair];
|
|
|
|
if(data==undefined) {
|
|
return callback(undefined, { executed: true, open: false });
|
|
}
|
|
|
|
const order = _.find(data, function(o) { return o.order_id === +order_id });
|
|
if(!order)
|
|
return callback(undefined, { executed: true, open: false });
|
|
|
|
callback(undefined, { executed: false, open: true /*, filledAmount: order.startingAmount - order.amount*/ });
|
|
}
|
|
|
|
const fetch = cb => this.api_query("user_open_orders", { } , cb);
|
|
retry(null, fetch, processResponse);
|
|
}
|
|
|
|
Trader.prototype.cancelOrder = function(order_id, callback) {
|
|
const processResponse = (err, data) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
return callback(undefined, false);
|
|
}
|
|
|
|
const fetch = cb => this.api_query("order_cancel", { order_id } , cb);
|
|
retry(null, fetch, processResponse);
|
|
}
|
|
|
|
Trader.prototype.getPortfolio = function(callback) {
|
|
const processResponse = (err, data) => {
|
|
if (err) return callback(err);
|
|
|
|
data=data["balances"];
|
|
const balances = _.map(data, function (v,k){ return {name: k, amount: +v};});
|
|
const portfolio = balances.filter(c => c.name===this.asset || c.name===this.currency)
|
|
|
|
callback(undefined, portfolio);
|
|
};
|
|
|
|
const fetch = cb => this.api_query("user_info", {}, cb);
|
|
retry(null, fetch, processResponse);
|
|
}
|
|
|
|
Trader.getCapabilities = function () {
|
|
return {
|
|
name: 'EXMO',
|
|
slug: 'exmo',
|
|
currencies: marketData.currencies,
|
|
assets: marketData.assets,
|
|
markets: marketData.markets,
|
|
requires: ['key', 'secret'],
|
|
providesHistory: false,
|
|
tid: 'tid',
|
|
tradable: true,
|
|
gekkoBroker: 0.6
|
|
};
|
|
}
|
|
|
|
module.exports = Trader;
|