consequunturatque
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges
615 lines (591 loc) • 23 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const Precise = require ('./base/Precise');
// ---------------------------------------------------------------------------
module.exports = class lykke extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'lykke',
'name': 'Lykke',
'countries': [ 'CH' ],
'version': 'v1',
'rateLimit': 200,
'has': {
'CORS': false,
'fetchOHLCV': false,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchTrades': true,
'fetchMyTrades': true,
'createOrder': true,
'cancelOrder': true,
'cancelAllOrders': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchOrderBook': true,
'fetchTicker': true,
},
'timeframes': {
'1m': 'Minute',
'5m': 'Min5',
'15m': 'Min15',
'30m': 'Min30',
'1h': 'Hour',
'4h': 'Hour4',
'6h': 'Hour6',
'12h': 'Hour12',
'1d': 'Day',
'1w': 'Week',
'1M': 'Month',
},
'requiredCredentials': {
'apiKey': true,
'secret': false,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/34487620-3139a7b0-efe6-11e7-90f5-e520cef74451.jpg',
'api': {
'mobile': 'https://public-api.lykke.com/api',
'public': 'https://hft-api.lykke.com/api',
'private': 'https://hft-api.lykke.com/api',
},
'test': {
'mobile': 'https://public-api.lykke.com/api',
'public': 'https://hft-service-dev.lykkex.net/api',
'private': 'https://hft-service-dev.lykkex.net/api',
},
'www': 'https://www.lykke.com',
'doc': [
'https://hft-api.lykke.com/swagger/ui/',
'https://www.lykke.com/lykke_api',
],
'fees': 'https://www.lykke.com/trading-conditions',
},
'api': {
'mobile': {
'get': [
'AssetPairs/rate',
'AssetPairs/rate/{assetPairId}',
'AssetPairs/dictionary/{market}',
'Assets/dictionary',
'Candles/history/{market}/available',
'Candles/history/{market}/{assetPair}/{period}/{type}/{from}/{to}',
'Company/ownershipStructure',
'Company/registrationsCount',
'IsAlive',
'Market',
'Market/{market}',
'Market/capitalization/{market}',
'OrderBook',
'OrderBook/{assetPairId}',
'Trades/{AssetPairId}',
'Trades/Last/{assetPair}/{n}',
],
'post': [
'AssetPairs/rate/history',
'AssetPairs/rate/history/{assetPairId}',
],
},
'public': {
'get': [
'AssetPairs',
'AssetPairs/{id}',
'IsAlive',
'OrderBooks',
'OrderBooks/{AssetPairId}',
],
},
'private': {
'get': [
'Orders',
'Orders/{id}',
'Wallets',
'History/trades',
],
'post': [
'Orders/limit',
'Orders/market',
'Orders/{id}/Cancel',
'Orders/v2/market',
'Orders/v2/limit',
'Orders/stoplimit',
'Orders/bulk',
],
'delete': [
'Orders',
'Orders/{id}',
],
},
},
'fees': {
'trading': {
'tierBased': false,
'percentage': true,
'maker': 0.0, // as of 7 Feb 2018, see https://github.com/ccxt/ccxt/issues/1863
'taker': 0.0, // https://www.lykke.com/cp/wallet-fees-and-limits
},
'funding': {
'tierBased': false,
'percentage': false,
'withdraw': {
'BTC': 0.001,
},
'deposit': {
'BTC': 0,
},
},
},
'commonCurrencies': {
'CAN': 'CanYaCoin',
'XPD': 'Lykke XPD',
},
});
}
parseTrade (trade, market) {
//
// public fetchTrades
//
// {
// "id": "d5983ab8-e9ec-48c9-bdd0-1b18f8e80a71",
// "assetPairId": "BTCUSD",
// "dateTime": "2019-05-15T06:52:02.147Z",
// "volume": 0.00019681,
// "index": 0,
// "price": 8023.333,
// "action": "Buy"
// }
//
// private fetchMyTrades
// {
// Id: '3500b83c-9963-4349-b3ee-b3e503073cea',
// OrderId: '83b50feb-8615-4dc6-b606-8a4168ecd708',
// DateTime: '2020-05-19T11:17:39.31+00:00',
// Timestamp: '2020-05-19T11:17:39.31+00:00',
// State: null,
// Amount: -0.004,
// BaseVolume: -0.004,
// QuotingVolume: 39.3898,
// Asset: 'BTC',
// BaseAssetId: 'BTC',
// QuotingAssetId: 'USD',
// AssetPair: 'BTCUSD',
// AssetPairId: 'BTCUSD',
// Price: 9847.427,
// Fee: { Amount: null, Type: 'Unknown', FeeAssetId: null }
// },
const marketId = this.safeString (trade, 'AssetPairId');
const symbol = this.safeSymbol (marketId, market);
const id = this.safeString2 (trade, 'id', 'Id');
const orderId = this.safeString (trade, 'OrderId');
const timestamp = this.parse8601 (this.safeString2 (trade, 'dateTime', 'DateTime'));
const priceString = this.safeString2 (trade, 'price', 'Price');
let amountString = this.safeString2 (trade, 'volume', 'Amount');
let side = this.safeStringLower (trade, 'action');
if (side === undefined) {
side = (amountString[0] === '-') ? 'sell' : 'buy';
}
amountString = Precise.stringAbs (amountString);
const price = this.parseNumber (priceString);
const amount = this.parseNumber (amountString);
const cost = this.parseNumber (Precise.stringMul (priceString, amountString));
const fee = {
'cost': 0, // There are no fees for trading. https://www.lykke.com/wallet-fees-and-limits/
'currency': market['quote'],
};
return {
'id': id,
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'symbol': symbol,
'type': undefined,
'order': orderId,
'side': side,
'takerOrMaker': undefined,
'price': price,
'amount': amount,
'cost': cost,
'fee': fee,
};
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
if (limit === undefined) {
limit = 100;
}
const request = {
'AssetPairId': market['id'],
'skip': 0,
'take': limit,
};
const response = await this.mobileGetTradesAssetPairId (this.extend (request, params));
return this.parseTrades (response, market, since, limit);
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
let market = undefined;
if (limit !== undefined) {
request['take'] = limit; // How many maximum items have to be returned, max 1000 default 100.
}
if (symbol !== undefined) {
market = this.market (symbol);
request['assetPairId'] = market['id'];
}
const response = await this.privateGetHistoryTrades (this.extend (request, params));
return this.parseTrades (response, market, since, limit);
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const response = await this.privateGetWallets (params);
const result = { 'info': response };
for (let i = 0; i < response.length; i++) {
const balance = response[i];
const currencyId = this.safeString (balance, 'AssetId');
const code = this.safeCurrencyCode (currencyId);
const account = this.account ();
account['total'] = this.safeString (balance, 'Balance');
account['used'] = this.safeString (balance, 'Reserved');
result[code] = account;
}
return this.parseBalance (result, false);
}
async cancelOrder (id, symbol = undefined, params = {}) {
const request = { 'id': id };
return await this.privateDeleteOrdersId (this.extend (request, params));
}
async cancelAllOrders (symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
request['assetPairId'] = market['id'];
}
return await this.privateDeleteOrders (this.extend (request, params));
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const query = {
'AssetPairId': market['id'],
'OrderAction': this.capitalize (side),
'Volume': amount,
'Asset': market['baseId'],
};
if (type === 'limit') {
query['Price'] = price;
}
const method = 'privatePostOrdersV2' + this.capitalize (type);
const result = await this[method] (this.extend (query, params));
//
// market
//
// {
// "Price": 0
// }
//
// limit
//
// {
// "Id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
// }
//
const id = this.safeString (result, 'Id');
price = this.safeNumber (result, 'Price');
return {
'id': id,
'info': result,
'clientOrderId': undefined,
'timestamp': undefined,
'datetime': undefined,
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'side': side,
'price': price,
'amount': amount,
'cost': undefined,
'average': undefined,
'filled': undefined,
'remaining': undefined,
'status': undefined,
'fee': undefined,
'trades': undefined,
};
}
async fetchMarkets (params = {}) {
const markets = await this.publicGetAssetPairs ();
//
// [ { Id: "AEBTC",
// Name: "AE/BTC",
// Accuracy: 6,
// InvertedAccuracy: 8,
// BaseAssetId: "6f75280b-a005-4016-a3d8-03dc644e8912",
// QuotingAssetId: "BTC",
// MinVolume: 0.4,
// MinInvertedVolume: 0.0001 },
// { Id: "AEETH",
// Name: "AE/ETH",
// Accuracy: 6,
// InvertedAccuracy: 8,
// BaseAssetId: "6f75280b-a005-4016-a3d8-03dc644e8912",
// QuotingAssetId: "ETH",
// MinVolume: 0.4,
// MinInvertedVolume: 0.001 } ]
//
const result = [];
for (let i = 0; i < markets.length; i++) {
const market = markets[i];
const id = this.safeString (market, 'Id');
const name = this.safeString (market, 'Name');
const [ baseId, quoteId ] = name.split ('/');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const symbol = base + '/' + quote;
const pricePrecision = this.safeString (market, 'Accuracy');
const priceLimit = this.parsePrecision (pricePrecision);
const precision = {
'price': parseInt (pricePrecision),
'amount': this.safeInteger (market, 'InvertedAccuracy'),
};
result.push ({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'active': true,
'info': market,
'precision': precision,
'limits': {
'amount': {
'min': this.safeNumber (market, 'MinVolume'),
'max': undefined,
},
'price': {
'min': this.parseNumber (priceLimit),
'max': undefined,
},
'cost': {
'min': this.safeNumber (market, 'MinInvertedVolume'),
'max': undefined,
},
},
'baseId': undefined,
'quoteId': undefined,
});
}
return result;
}
parseTicker (ticker, market = undefined) {
const timestamp = this.milliseconds ();
let symbol = undefined;
if (market) {
symbol = market['symbol'];
}
const close = this.safeNumber (ticker, 'lastPrice');
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': undefined,
'low': undefined,
'bid': this.safeNumber (ticker, 'bid'),
'bidVolume': undefined,
'ask': this.safeNumber (ticker, 'ask'),
'askVolume': undefined,
'vwap': undefined,
'open': undefined,
'close': close,
'last': close,
'previousClose': undefined,
'change': undefined,
'percentage': undefined,
'average': undefined,
'baseVolume': undefined,
'quoteVolume': this.safeNumber (ticker, 'volume24H'),
'info': ticker,
};
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'market': market['id'],
};
const ticker = await this.mobileGetMarketMarket (this.extend (request, params));
return this.parseTicker (ticker, market);
}
parseOrderStatus (status) {
const statuses = {
'Open': 'open',
'Pending': 'open',
'InOrderBook': 'open',
'Processing': 'open',
'Matched': 'closed',
'Cancelled': 'canceled',
'Rejected': 'rejected',
'Replaced': 'canceled',
'Placed': 'open',
};
return this.safeString (statuses, status, status);
}
parseOrder (order, market = undefined) {
//
// {
// "Id": "string",
// "Status": "Unknown",
// "AssetPairId": "string",
// "Volume": 0,
// "Price": 0,
// "RemainingVolume": 0,
// "LastMatchTime": "2020-03-26T20:58:50.710Z",
// "CreatedAt": "2020-03-26T20:58:50.710Z",
// "Type": "Unknown",
// "LowerLimitPrice": 0,
// "LowerPrice": 0,
// "UpperLimitPrice": 0,
// "UpperPrice": 0
// }
//
const status = this.parseOrderStatus (this.safeString (order, 'Status'));
const marketId = this.safeString (order, 'AssetPairId');
const symbol = this.safeSymbol (marketId, market);
const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'LastMatchTime'));
let timestamp = undefined;
if (('Registered' in order) && (order['Registered'])) {
timestamp = this.parse8601 (order['Registered']);
} else if (('CreatedAt' in order) && (order['CreatedAt'])) {
timestamp = this.parse8601 (order['CreatedAt']);
}
const price = this.safeNumber (order, 'Price');
let side = undefined;
let amount = this.safeNumber (order, 'Volume');
if (amount < 0) {
side = 'sell';
amount = Math.abs (amount);
} else {
side = 'buy';
}
const remaining = Math.abs (this.safeNumber (order, 'RemainingVolume'));
const id = this.safeString (order, 'Id');
return this.safeOrder ({
'info': order,
'id': id,
'clientOrderId': undefined,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': undefined,
'timeInForce': undefined,
'postOnly': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': undefined,
'average': undefined,
'amount': amount,
'filled': undefined,
'remaining': remaining,
'status': status,
'fee': undefined,
'trades': undefined,
});
}
async fetchOrder (id, symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = {
'id': id,
};
const response = await this.privateGetOrdersId (this.extend (request, params));
return this.parseOrder (response);
}
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.privateGetOrders (params);
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
}
return this.parseOrders (response, market, since, limit);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
const request = {
'status': 'InOrderBook',
};
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
}
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
const request = {
'status': 'Matched',
};
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.publicGetOrderBooksAssetPairId (this.extend ({
'AssetPairId': this.marketId (symbol),
}, params));
const orderbook = {
'timestamp': undefined,
'bids': [],
'asks': [],
};
let timestamp = undefined;
for (let i = 0; i < response.length; i++) {
const side = response[i];
if (side['IsBuy']) {
orderbook['bids'] = this.arrayConcat (orderbook['bids'], side['Prices']);
} else {
orderbook['asks'] = this.arrayConcat (orderbook['asks'], side['Prices']);
}
const sideTimestamp = this.parse8601 (side['Timestamp']);
timestamp = (timestamp === undefined) ? sideTimestamp : Math.max (timestamp, sideTimestamp);
}
return this.parseOrderBook (orderbook, symbol, timestamp, 'bids', 'asks', 'Price', 'Volume');
}
parseBidAsk (bidask, priceKey = 0, amountKey = 1) {
const price = this.safeNumber (bidask, priceKey);
let amount = this.safeNumber (bidask, amountKey);
if (amount < 0) {
amount = -amount;
}
return [ price, amount ];
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let url = this.urls['api'][api] + '/' + this.implodeParams (path, params);
const query = this.omit (params, this.extractParams (path));
if (api === 'mobile') {
if (Object.keys (query).length) {
url += '?' + this.urlencode (query);
}
} else if (api === 'public') {
if (Object.keys (query).length) {
url += '?' + this.urlencode (query);
}
} else if (api === 'private') {
if ((method === 'GET') || (method === 'DELETE')) {
if (Object.keys (query).length) {
url += '?' + this.urlencode (query);
}
}
this.checkRequiredCredentials ();
headers = {
'api-key': this.apiKey,
'Accept': 'application/json',
'Content-Type': 'application/json',
};
if (method === 'POST') {
if (Object.keys (params).length) {
body = this.json (params);
}
}
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
};