ccxt-compiled
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 90+ exchanges
645 lines (594 loc) • 25.3 kB
JavaScript
"use strict";
// ---------------------------------------------------------------------------
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const Exchange = require('./base/Exchange');
const { ExchangeError, InsufficientFunds, OrderNotFound, DDoSProtection } = require('./base/errors');
// ---------------------------------------------------------------------------
module.exports = class liqui extends Exchange {
describe() {
return this.deepExtend(super.describe(), {
'id': 'liqui',
'name': 'Liqui',
'countries': 'UA',
'rateLimit': 2500,
'version': '3',
'hasCORS': false,
// obsolete metainfo interface
'hasFetchOrder': true,
'hasFetchOrders': true,
'hasFetchOpenOrders': true,
'hasFetchClosedOrders': true,
'hasFetchTickers': true,
'hasFetchMyTrades': true,
'hasWithdraw': true,
// new metainfo interface
'has': {
'fetchOrder': true,
'fetchOrders': 'emulated',
'fetchOpenOrders': true,
'fetchClosedOrders': 'emulated',
'fetchTickers': true,
'fetchMyTrades': true,
'withdraw': true
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27982022-75aea828-63a0-11e7-9511-ca584a8edd74.jpg',
'api': {
'public': 'https://api.liqui.io/api',
'private': 'https://api.liqui.io/tapi'
},
'www': 'https://liqui.io',
'doc': 'https://liqui.io/api',
'fees': 'https://liqui.io/fee'
},
'api': {
'public': {
'get': ['info', 'ticker/{pair}', 'depth/{pair}', 'trades/{pair}']
},
'private': {
'post': ['getInfo', 'Trade', 'ActiveOrders', 'OrderInfo', 'CancelOrder', 'TradeHistory', 'TransHistory', 'CoinDepositAddress', 'WithdrawCoin', 'CreateCoupon', 'RedeemCoupon']
}
},
'fees': {
'trading': {
'maker': 0.001,
'taker': 0.0025
},
'funding': 0.0
}
});
}
calculateFee(symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) {
let market = this.markets[symbol];
let key = 'quote';
let rate = market[takerOrMaker];
let cost = parseFloat(this.costToPrecision(symbol, amount * rate));
if (side == 'sell') {
cost *= price;
} else {
key = 'base';
}
return {
'type': takerOrMaker,
'currency': market[key],
'rate': rate,
'cost': cost
};
}
commonCurrencyCode(currency) {
if (!this.substituteCommonCurrencyCodes) return currency;
if (currency == 'XBT') return 'BTC';
if (currency == 'BCC') return 'BCH';
if (currency == 'DRK') return 'DASH';
// they misspell DASH as dsh :/
if (currency == 'DSH') return 'DASH';
return currency;
}
getBaseQuoteFromMarketId(id) {
let uppercase = id.toUpperCase();
let [base, quote] = uppercase.split('_');
base = this.commonCurrencyCode(base);
quote = this.commonCurrencyCode(quote);
return [base, quote];
}
fetchMarkets() {
var _this = this;
return (0, _asyncToGenerator3.default)(function* () {
let response = yield _this.publicGetInfo();
let markets = response['pairs'];
let keys = (0, _keys2.default)(markets);
let result = [];
for (let p = 0; p < keys.length; p++) {
let id = keys[p];
let market = markets[id];
let [base, quote] = _this.getBaseQuoteFromMarketId(id);
let symbol = base + '/' + quote;
let precision = {
'amount': _this.safeInteger(market, 'decimal_places'),
'price': _this.safeInteger(market, 'decimal_places')
};
let amountLimits = {
'min': _this.safeFloat(market, 'min_amount'),
'max': _this.safeFloat(market, 'max_amount')
};
let priceLimits = {
'min': _this.safeFloat(market, 'min_price'),
'max': _this.safeFloat(market, 'max_price')
};
let costLimits = {
'min': _this.safeFloat(market, 'min_total')
};
let limits = {
'amount': amountLimits,
'price': priceLimits,
'cost': costLimits
};
let active = market['hidden'] == 0;
result.push(_this.extend(_this.fees['trading'], {
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'active': active,
'taker': market['fee'] / 100,
'lot': amountLimits['min'],
'precision': precision,
'limits': limits,
'info': market
}));
}
return result;
})();
}
fetchBalance(params = {}) {
var _this2 = this;
return (0, _asyncToGenerator3.default)(function* () {
yield _this2.loadMarkets();
let response = yield _this2.privatePostGetInfo();
let balances = response['return'];
let result = { 'info': balances };
let funds = balances['funds'];
let currencies = (0, _keys2.default)(funds);
for (let c = 0; c < currencies.length; c++) {
let currency = currencies[c];
let uppercase = currency.toUpperCase();
uppercase = _this2.commonCurrencyCode(uppercase);
let total = undefined;
let used = undefined;
if (balances['open_orders'] == 0) {
total = funds[currency];
used = 0.0;
}
let account = {
'free': funds[currency],
'used': used,
'total': total
};
result[uppercase] = account;
}
return _this2.parseBalance(result);
})();
}
fetchOrderBook(symbol, params = {}) {
var _this3 = this;
return (0, _asyncToGenerator3.default)(function* () {
yield _this3.loadMarkets();
let market = _this3.market(symbol);
let response = yield _this3.publicGetDepthPair(_this3.extend({
'pair': market['id']
}, params));
let market_id_in_reponse = market['id'] in response;
if (!market_id_in_reponse) throw new ExchangeError(_this3.id + ' ' + market['symbol'] + ' order book is empty or not available');
let orderbook = response[market['id']];
let result = _this3.parseOrderBook(orderbook);
result['bids'] = _this3.sortBy(result['bids'], 0, true);
result['asks'] = _this3.sortBy(result['asks'], 0);
return result;
})();
}
parseTicker(ticker, market = undefined) {
let timestamp = ticker['updated'] * 1000;
let symbol = undefined;
if (market) symbol = market['symbol'];
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'high': this.safeFloat(ticker, 'high'),
'low': this.safeFloat(ticker, 'low'),
'bid': this.safeFloat(ticker, 'buy'),
'ask': this.safeFloat(ticker, 'sell'),
'vwap': undefined,
'open': undefined,
'close': undefined,
'first': undefined,
'last': this.safeFloat(ticker, 'last'),
'change': undefined,
'percentage': undefined,
'average': this.safeFloat(ticker, 'avg'),
'baseVolume': this.safeFloat(ticker, 'vol_cur'),
'quoteVolume': this.safeFloat(ticker, 'vol'),
'info': ticker
};
}
fetchTickers(symbols = undefined, params = {}) {
var _this4 = this;
return (0, _asyncToGenerator3.default)(function* () {
yield _this4.loadMarkets();
let ids = undefined;
if (!symbols) {
// let numIds = this.ids.length;
// if (numIds > 256)
// throw new ExchangeError (this.id + ' fetchTickers() requires symbols argument');
ids = _this4.ids;
} else {
ids = _this4.marketIds(symbols);
}
let tickers = yield _this4.publicGetTickerPair(_this4.extend({
'pair': ids.join('-')
}, params));
let result = {};
let keys = (0, _keys2.default)(tickers);
for (let k = 0; k < keys.length; k++) {
let id = keys[k];
let ticker = tickers[id];
let market = _this4.markets_by_id[id];
let symbol = market['symbol'];
result[symbol] = _this4.parseTicker(ticker, market);
}
return result;
})();
}
fetchTicker(symbol, params = {}) {
var _this5 = this;
return (0, _asyncToGenerator3.default)(function* () {
let tickers = yield _this5.fetchTickers([symbol], params);
return tickers[symbol];
})();
}
parseTrade(trade, market = undefined) {
let timestamp = trade['timestamp'] * 1000;
let side = trade['type'];
if (side == 'ask') side = 'sell';
if (side == 'bid') side = 'buy';
let price = this.safeFloat(trade, 'price');
if ('rate' in trade) price = this.safeFloat(trade, 'rate');
let id = this.safeString(trade, 'tid');
if ('trade_id' in trade) id = this.safeString(trade, 'trade_id');
let order = this.safeString(trade, this.getOrderIdKey());
if ('pair' in trade) {
let marketId = trade['pair'];
market = this.markets_by_id[marketId];
}
let symbol = undefined;
if (market) symbol = market['symbol'];
let amount = trade['amount'];
let type = 'limit'; // all trades are still limit trades
let fee = undefined;
// this is filled by fetchMyTrades() only
// is_your_order is always false :\
// let isYourOrder = this.safeValue (trade, 'is_your_order');
// let takerOrMaker = 'taker';
// if (isYourOrder)
// takerOrMaker = 'maker';
// let fee = this.calculateFee (symbol, type, side, amount, price, takerOrMaker);
return {
'id': id,
'order': order,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'symbol': symbol,
'type': type,
'side': side,
'price': price,
'amount': amount,
'fee': fee,
'info': trade
};
}
fetchTrades(symbol, since = undefined, limit = undefined, params = {}) {
var _this6 = this;
return (0, _asyncToGenerator3.default)(function* () {
yield _this6.loadMarkets();
let market = _this6.market(symbol);
let request = {
'pair': market['id']
};
if (limit) request['limit'] = limit;
let response = yield _this6.publicGetTradesPair(_this6.extend(request, params));
return _this6.parseTrades(response[market['id']], market, since, limit);
})();
}
createOrder(symbol, type, side, amount, price = undefined, params = {}) {
var _this7 = this;
return (0, _asyncToGenerator3.default)(function* () {
if (type == 'market') throw new ExchangeError(_this7.id + ' allows limit orders only');
yield _this7.loadMarkets();
let market = _this7.market(symbol);
let request = {
'pair': market['id'],
'type': side,
'amount': _this7.amountToPrecision(symbol, amount),
'rate': _this7.priceToPrecision(symbol, price)
};
let response = yield _this7.privatePostTrade(_this7.extend(request, params));
let id = _this7.safeString(response['return'], _this7.getOrderIdKey());
if (!id) id = _this7.safeString(response['return'], 'init_order_id');
let timestamp = _this7.milliseconds();
price = parseFloat(price);
amount = parseFloat(amount);
let order = {
'id': id,
'timestamp': timestamp,
'datetime': _this7.iso8601(timestamp),
'status': 'open',
'symbol': symbol,
'type': type,
'side': side,
'price': price,
'cost': price * amount,
'amount': amount,
'remaining': amount,
'filled': 0.0,
'fee': undefined
// 'trades': this.parseTrades (order['trades'], market),
};
_this7.orders[id] = order;
return _this7.extend({ 'info': response }, order);
})();
}
getOrderIdKey() {
return 'order_id';
}
cancelOrder(id, symbol = undefined, params = {}) {
var _this8 = this;
return (0, _asyncToGenerator3.default)(function* () {
yield _this8.loadMarkets();
let response = undefined;
try {
let request = {};
let idKey = _this8.getOrderIdKey();
request[idKey] = id;
response = yield _this8.privatePostCancelOrder(_this8.extend(request, params));
if (id in _this8.orders) _this8.orders[id]['status'] = 'canceled';
} catch (e) {
if (_this8.last_json_response) {
let message = _this8.safeString(_this8.last_json_response, 'error');
if (message) {
if (message.indexOf('not found') >= 0) throw new OrderNotFound(_this8.id + ' cancelOrder() error: ' + _this8.last_http_response);
}
}
throw e;
}
return response;
})();
}
parseOrder(order, market = undefined) {
let id = order['id'].toString();
let status = order['status'];
if (status == 0) {
status = 'open';
} else if (status == 1) {
status = 'closed';
} else if (status == 2 || status == 3) {
status = 'canceled';
}
let timestamp = parseInt(order['timestamp_created']) * 1000;
let symbol = undefined;
if (!market) market = this.markets_by_id[order['pair']];
if (market) symbol = market['symbol'];
let remaining = this.safeFloat(order, 'amount');
let amount = this.safeFloat(order, 'start_amount', remaining);
if (typeof amount == 'undefined') {
if (id in this.orders) {
amount = this.safeFloat(this.orders[id], 'amount');
}
}
let price = this.safeFloat(order, 'rate');
let filled = undefined;
let cost = undefined;
if (typeof amount != 'undefined') {
if (typeof remaining != 'undefined') {
filled = amount - remaining;
cost = price * filled;
}
}
let fee = undefined;
let result = {
'info': order,
'id': id,
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'type': 'limit',
'side': order['type'],
'price': price,
'cost': cost,
'amount': amount,
'remaining': remaining,
'filled': filled,
'status': status,
'fee': fee
};
return result;
}
parseOrders(orders, market = undefined, since = undefined, limit = undefined) {
let ids = (0, _keys2.default)(orders);
let result = [];
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
let order = orders[id];
let extended = this.extend(order, { 'id': id });
result.push(this.parseOrder(extended, market));
}
return this.filterBySinceLimit(result, since, limit);
}
fetchOrder(id, symbol = undefined, params = {}) {
var _this9 = this;
return (0, _asyncToGenerator3.default)(function* () {
yield _this9.loadMarkets();
let response = yield _this9.privatePostOrderInfo(_this9.extend({
'order_id': parseInt(id)
}, params));
id = id.toString();
let newOrder = _this9.parseOrder(_this9.extend({ 'id': id }, response['return'][id]));
let oldOrder = id in _this9.orders ? _this9.orders[id] : {};
_this9.orders[id] = _this9.extend(oldOrder, newOrder);
return _this9.orders[id];
})();
}
fetchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
var _this10 = this;
return (0, _asyncToGenerator3.default)(function* () {
if (!symbol) throw new ExchangeError(_this10.id + ' fetchOrders requires a symbol');
yield _this10.loadMarkets();
let market = _this10.market(symbol);
let request = { 'pair': market['id'] };
let response = yield _this10.privatePostActiveOrders(_this10.extend(request, params));
let openOrders = [];
if ('return' in response) openOrders = _this10.parseOrders(response['return'], market);
for (let j = 0; j < openOrders.length; j++) {
_this10.orders[openOrders[j]['id']] = openOrders[j];
}
let openOrdersIndexedById = _this10.indexBy(openOrders, 'id');
let cachedOrderIds = (0, _keys2.default)(_this10.orders);
let result = [];
for (let k = 0; k < cachedOrderIds.length; k++) {
let id = cachedOrderIds[k];
if (id in openOrdersIndexedById) {
_this10.orders[id] = _this10.extend(_this10.orders[id], openOrdersIndexedById[id]);
} else {
let order = _this10.orders[id];
if (order['status'] == 'open') {
_this10.orders[id] = _this10.extend(order, {
'status': 'closed',
'cost': order['amount'] * order['price'],
'filled': order['amount'],
'remaining': 0.0
});
}
}
let order = _this10.orders[id];
if (order['symbol'] == symbol) result.push(order);
}
return _this10.filterBySinceLimit(result, since, limit);
})();
}
fetchOpenOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
var _this11 = this;
return (0, _asyncToGenerator3.default)(function* () {
let orders = yield _this11.fetchOrders(symbol, since, limit, params);
let result = [];
for (let i = 0; i < orders.length; i++) {
if (orders[i]['status'] == 'open') result.push(orders[i]);
}
return result;
})();
}
fetchClosedOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
var _this12 = this;
return (0, _asyncToGenerator3.default)(function* () {
let orders = yield _this12.fetchOrders(symbol, since, limit, params);
let result = [];
for (let i = 0; i < orders.length; i++) {
if (orders[i]['status'] == 'closed') result.push(orders[i]);
}
return result;
})();
}
fetchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
var _this13 = this;
return (0, _asyncToGenerator3.default)(function* () {
yield _this13.loadMarkets();
let market = undefined;
let request = {
// 'from': 123456789, // trade ID, from which the display starts numerical 0
// 'count': 1000, // the number of trades for display numerical, default = 1000
// 'from_id': trade ID, from which the display starts numerical 0
// 'end_id': trade ID on which the display ends numerical ∞
// 'order': 'ASC', // sorting, default = DESC
// 'since': 1234567890, // UTC start time, default = 0
// 'end': 1234567890, // UTC end time, default = ∞
// 'pair': 'eth_btc', // default = all markets
};
if (symbol) {
market = _this13.market(symbol);
request['pair'] = market['id'];
}
if (limit) request['count'] = parseInt(limit);
if (since) request['since'] = parseInt(since / 1000);
let response = yield _this13.privatePostTradeHistory(_this13.extend(request, params));
let trades = [];
if ('return' in response) trades = response['return'];
return _this13.parseTrades(trades, market, since, limit);
})();
}
withdraw(currency, amount, address, params = {}) {
var _this14 = this;
return (0, _asyncToGenerator3.default)(function* () {
yield _this14.loadMarkets();
let response = yield _this14.privatePostWithdrawCoin(_this14.extend({
'coinName': currency,
'amount': parseFloat(amount),
'address': address
}, params));
return {
'info': response,
'id': response['return']['tId']
};
})();
}
signBodyWithSecret(body) {
return this.hmac(this.encode(body), this.encode(this.secret), 'sha512');
}
getVersionString() {
return '/' + this.version;
}
sign(path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let url = this.urls['api'][api];
let query = this.omit(params, this.extractParams(path));
if (api == 'private') {
this.checkRequiredCredentials();
let nonce = this.nonce();
body = this.urlencode(this.extend({
'nonce': nonce,
'method': path
}, query));
let signature = this.signBodyWithSecret(body);
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Key': this.apiKey,
'Sign': signature
};
} else {
url += this.getVersionString() + '/' + this.implodeParams(path, params);
if ((0, _keys2.default)(query).length) url += '?' + this.urlencode(query);
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
request(path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
var _this15 = this;
return (0, _asyncToGenerator3.default)(function* () {
let response = yield _this15.fetch2(path, api, method, params, headers, body);
if ('success' in response) {
if (!response['success']) {
if (response['error'].indexOf('Not enougth') >= 0) {
// not enougTh is a typo inside Liqui's own API...
throw new InsufficientFunds(_this15.id + ' ' + _this15.json(response));
} else if (response['error'] == 'Requests too often') {
throw new DDoSProtection(_this15.id + ' ' + _this15.json(response));
} else if (response['error'] == 'not available' || response['error'] == 'external service unavailable') {
throw new DDoSProtection(_this15.id + ' ' + _this15.json(response));
} else {
throw new ExchangeError(_this15.id + ' ' + _this15.json(response));
}
}
}
return response;
})();
}
};