UNPKG

@madnai/ccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

1,272 lines (1,234 loc) 49.3 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InvalidNonce, BadRequest, InsufficientFunds, PermissionDenied, DDoSProtection, InvalidOrder, AuthenticationError } = require ('./base/errors'); // --------------------------------------------------------------------------- module.exports = class dsx extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'dsx', 'name': 'DSX', 'countries': [ 'UK' ], 'rateLimit': 1500, 'version': 'v3', 'has': { 'cancelOrder': true, 'CORS': false, 'createDepositAddress': true, 'createMarketOrder': false, 'createOrder': true, 'fetchBalance': true, 'fetchClosedOrders': false, 'fetchDepositAddress': true, 'fetchMarkets': true, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrderBooks': true, 'fetchOrders': true, 'fetchTicker': true, 'fetchTickers': true, 'fetchTransactions': true, 'fetchTrades': true, 'withdraw': true, }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/51840849/76909626-cb2bb100-68bc-11ea-99e0-28ba54f04792.jpg', 'api': { 'public': 'https://dsxglobal.com/mapi', // market data 'private': 'https://dsxglobal.com/tapi', // trading 'dwapi': 'https://dsxglobal.com/dwapi', // deposit/withdraw }, 'www': 'https://dsxglobal.com', 'doc': [ 'https://dsxglobal.com/developers/publicApi', ], }, 'fees': { 'trading': { 'tierBased': true, 'percentage': true, 'maker': 0.15 / 100, 'taker': 0.25 / 100, }, }, 'timeframes': { '1m': 'm', '1h': 'h', '1d': 'd', }, 'api': { // market data (public) 'public': { 'get': [ 'barsFromMoment/{pair}/{period}/{start}', 'depth/{pair}', 'info', 'lastBars/{pair}/{period}/{amount}', // period is 'm', 'h' or 'd' 'periodBars/{pair}/{period}/{start}/{end}', 'ticker/{pair}', 'trades/{pair}', ], }, // trading (private) 'private': { 'post': [ 'info/account', 'history/transactions', 'history/trades', 'history/orders', 'orders', 'order/cancel', // 'order/cancel/all', 'order/status', 'order/new', 'volume', 'fees', // trading fee schedule ], }, // deposit / withdraw (private) 'dwapi': { 'post': [ 'deposit/cryptoaddress', 'withdraw/crypto', 'withdraw/fiat', 'withdraw/submit', // 'withdraw/cancel', 'transaction/status', // see 'history/transactions' in private tapi above ], }, }, 'exceptions': { 'exact': { 'Sign is invalid': AuthenticationError, // {"success":0,"error":"Sign is invalid"} 'Order was rejected. Incorrect price.': InvalidOrder, // {"success":0,"error":"Order was rejected. Incorrect price."} "Order was rejected. You don't have enough money.": InsufficientFunds, // {"success":0,"error":"Order was rejected. You don't have enough money."} 'This method is blocked for your pair of keys': PermissionDenied, // {"success":0,"error":"This method is blocked for your pair of keys"} }, 'broad': { 'INVALID_PARAMETER': BadRequest, 'Invalid pair name': ExchangeError, // {"success":0,"error":"Invalid pair name: btc_eth"} 'invalid api key': AuthenticationError, 'invalid sign': AuthenticationError, 'api key dont have trade permission': AuthenticationError, 'invalid parameter': InvalidOrder, 'invalid order': InvalidOrder, 'Requests too often': DDoSProtection, 'not available': ExchangeNotAvailable, 'data unavailable': ExchangeNotAvailable, 'external service unavailable': ExchangeNotAvailable, 'nonce is invalid': InvalidNonce, // {"success":0,"error":"Parameter: nonce is invalid"} 'Incorrect volume': InvalidOrder, // {"success": 0,"error":"Order was rejected. Incorrect volume."} }, }, 'options': { 'fetchTickersMaxLength': 250, }, 'commonCurrencies': { 'DSH': 'DASH', }, }); } async fetchMarkets (params = {}) { const response = await this.publicGetInfo (params); // // { // "server_time": 1522057909, // "pairs": { // "ethusd": { // "decimal_places": 5, // "min_price": 100, // "max_price": 1500, // "min_amount": 0.01, // "hidden": 0, // "fee": 0, // "amount_decimal_places": 4, // "quoted_currency": "USD", // "base_currency": "ETH" // } // } // } // const markets = this.safeValue (response, 'pairs'); const keys = Object.keys (markets); const result = []; for (let i = 0; i < keys.length; i++) { const id = keys[i]; const market = markets[id]; const baseId = this.safeString (market, 'base_currency'); const quoteId = this.safeString (market, 'quoted_currency'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const symbol = base + '/' + quote; const precision = { 'amount': this.safeInteger (market, 'decimal_places'), 'price': this.safeInteger (market, 'decimal_places'), }; const amountLimits = { 'min': this.safeFloat (market, 'min_amount'), 'max': this.safeFloat (market, 'max_amount'), }; const priceLimits = { 'min': this.safeFloat (market, 'min_price'), 'max': this.safeFloat (market, 'max_price'), }; const costLimits = { 'min': this.safeFloat (market, 'min_total'), }; const limits = { 'amount': amountLimits, 'price': priceLimits, 'cost': costLimits, }; const hidden = this.safeInteger (market, 'hidden'); const active = (hidden === 0); // see parseMarket below // https://github.com/ccxt/ccxt/pull/5786 const otherId = base.toLowerCase () + quote.toLowerCase (); result.push ({ 'id': id, 'otherId': otherId, // https://github.com/ccxt/ccxt/pull/5786 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'active': active, 'precision': precision, 'limits': limits, 'info': market, }); } return result; } async fetchBalance (params = {}) { await this.loadMarkets (); const response = await this.privatePostInfoAccount (); // // { // "success" : 1, // "return" : { // "funds" : { // "BTC" : { // "total" : 0, // "available" : 0 // }, // "USD" : { // "total" : 0, // "available" : 0 // }, // "USDT" : { // "total" : 0, // "available" : 0 // } // }, // "rights" : { // "info" : 1, // "trade" : 1 // }, // "transactionCount" : 0, // "openOrders" : 0, // "serverTime" : 1537451465 // } // } // const balances = this.safeValue (response, 'return'); const result = { 'info': response }; const funds = this.safeValue (balances, 'funds'); const currencyIds = Object.keys (funds); for (let i = 0; i < currencyIds.length; i++) { const currencyId = currencyIds[i]; const code = this.safeCurrencyCode (currencyId); const balance = this.safeValue (funds, currencyId, {}); const account = this.account (); account['free'] = this.safeFloat (balance, 'available'); account['total'] = this.safeFloat (balance, 'total'); result[code] = account; } return this.parseBalance (result); } parseTicker (ticker, market = undefined) { // // { high: 0.03492, // low: 0.03245, // avg: 29.46133, // vol: 500.8661, // vol_cur: 17.000797104, // last: 0.03364, // buy: 0.03362, // sell: 0.03381, // updated: 1537521993, // pair: "ethbtc" } // const timestamp = this.safeTimestamp (ticker, 'updated'); let symbol = undefined; const marketId = this.safeString (ticker, 'pair'); market = this.parseMarket (marketId); if (market !== undefined) { symbol = market['symbol']; } // dsx average is inverted, liqui average is not let average = this.safeFloat (ticker, 'avg'); if (average !== undefined) { if (average > 0) { average = 1 / average; } } const last = this.safeFloat (ticker, 'last'); return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeFloat (ticker, 'high'), 'low': this.safeFloat (ticker, 'low'), 'bid': this.safeFloat (ticker, 'buy'), 'bidVolume': undefined, 'ask': this.safeFloat (ticker, 'sell'), 'askVolume': undefined, 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': average, 'baseVolume': this.safeFloat (ticker, 'vol'), 'quoteVolume': this.safeFloat (ticker, 'vol_cur'), 'info': ticker, }; } parseTrade (trade, market = undefined) { // // fetchTrades (public) // // { // "amount" : 0.0128, // "price" : 6483.99000, // "timestamp" : 1540334614, // "tid" : 35684364, // "type" : "ask" // } // // fetchMyTrades (private) // // { // "number": "36635882", // <-- this is present if the trade has come from the '/order/status' call // "id": "36635882", // <-- this may have been artifically added by the parseTrades method // "pair": "btcusd", // "type": "buy", // "volume": 0.0595, // "rate": 9750, // "orderId": 77149299, // "timestamp": 1519612317, // "commission": 0.00020825, // "commissionCurrency": "btc" // } // const timestamp = this.safeTimestamp (trade, 'timestamp'); let side = this.safeString (trade, 'type'); if (side === 'ask') { side = 'sell'; } else if (side === 'bid') { side = 'buy'; } const price = this.safeFloat2 (trade, 'rate', 'price'); const id = this.safeString2 (trade, 'number', 'id'); const orderId = this.safeString (trade, 'orderId'); const marketId = this.safeString (trade, 'pair'); market = this.parseMarket (marketId); let symbol = undefined; if (market !== undefined) { symbol = market['symbol']; } const amount = this.safeFloat2 (trade, 'amount', 'volume'); const type = 'limit'; // all trades are still limit trades let takerOrMaker = undefined; let fee = undefined; const feeCost = this.safeFloat (trade, 'commission'); if (feeCost !== undefined) { const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); fee = { 'cost': feeCost, 'currency': feeCurrencyCode, }; } const isYourOrder = this.safeValue (trade, 'is_your_order'); if (isYourOrder !== undefined) { takerOrMaker = 'taker'; if (isYourOrder) { takerOrMaker = 'maker'; } if (fee === undefined) { fee = this.calculateFee (symbol, type, side, amount, price, takerOrMaker); } } let cost = undefined; if (price !== undefined) { if (amount !== undefined) { cost = price * amount; } } return { 'id': id, 'order': orderId, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'type': type, 'side': side, 'takerOrMaker': takerOrMaker, 'price': price, 'amount': amount, 'cost': cost, 'fee': fee, 'info': trade, }; } parseTrades (trades, market = undefined, since = undefined, limit = undefined, params = {}) { let result = []; if (Array.isArray (trades)) { for (let i = 0; i < trades.length; i++) { result.push (this.parseTrade (trades[i], market)); } } else { const ids = Object.keys (trades); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const trade = this.parseTrade (trades[id], market); result.push (this.extend (trade, { 'id': id }, params)); } } result = this.sortBy (result, 'timestamp'); const symbol = (market !== undefined) ? market['symbol'] : undefined; return this.filterBySymbolSinceLimit (result, symbol, since, limit); } calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { const market = this.markets[symbol]; let key = 'quote'; const 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, }; } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'pair': market['id'], }; if (limit !== undefined) { request['limit'] = limit; // default = 150, max = 2000 } const response = await this.publicGetDepthPair (this.extend (request, params)); const market_id_in_reponse = (market['id'] in response); if (!market_id_in_reponse) { throw new ExchangeError (this.id + ' ' + market['symbol'] + ' order book is empty or not available'); } const orderbook = response[market['id']]; return this.parseOrderBook (orderbook); } async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let ids = undefined; if (symbols === undefined) { ids = this.ids.join ('-'); // max URL length is 2083 symbols, including http schema, hostname, tld, etc... if (ids.length > 2048) { const numIds = this.ids.length; throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); } } else { ids = this.marketIds (symbols); ids = ids.join ('-'); } const request = { 'pair': ids, }; if (limit !== undefined) { request['limit'] = limit; // default = 150, max = 2000 } const response = await this.publicGetDepthPair (this.extend (request, params)); const result = {}; ids = Object.keys (response); for (let i = 0; i < ids.length; i++) { const id = ids[i]; let symbol = id; if (id in this.markets_by_id) { const market = this.markets_by_id[id]; symbol = market['symbol']; } result[symbol] = this.parseOrderBook (response[id]); } return result; } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); let ids = this.ids; if (symbols === undefined) { const numIds = ids.length; ids = ids.join ('-'); const maxLength = this.safeInteger (this.options, 'fetchTickersMaxLength', 2048); // max URL length is 2048 symbols, including http schema, hostname, tld, etc... if (ids.length > this.options['fetchTickersMaxLength']) { throw new ArgumentsRequired (this.id + ' has ' + numIds.toString () + ' markets exceeding max URL length for this endpoint (' + maxLength.toString () + ' characters), please, specify a list of symbols of interest in the first argument to fetchTickers'); } } else { ids = this.marketIds (symbols); ids = ids.join ('-'); } const request = { 'pair': ids, }; const tickers = await this.publicGetTickerPair (this.extend (request, params)); // // { // "bchbtc" : { // "high" : 0.02989, // "low" : 0.02736, // "avg" : 33.90585, // "vol" : 0.65982205, // "vol_cur" : 0.0194604180960, // "last" : 0.03000, // "buy" : 0.02980, // "sell" : 0.03001, // "updated" : 1568104614, // "pair" : "bchbtc" // }, // "ethbtc" : { // "high" : 0.01772, // "low" : 0.01742, // "avg" : 56.89082, // "vol" : 229.247115044, // "vol_cur" : 4.02959737298943, // "last" : 0.01769, // "buy" : 0.01768, // "sell" : 0.01776, // "updated" : 1568104614, // "pair" : "ethbtc" // } // } // const result = {}; const keys = Object.keys (tickers); for (let k = 0; k < keys.length; k++) { const id = keys[k]; const ticker = tickers[id]; let symbol = id; let market = undefined; if (id in this.markets_by_id) { market = this.markets_by_id[id]; symbol = market['symbol']; } result[symbol] = this.parseTicker (ticker, market); } return this.filterByArray (result, 'symbol', symbols); } async fetchTicker (symbol, params = {}) { const tickers = await this.fetchTickers ([ symbol ], params); return tickers[symbol]; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'pair': market['id'], }; if (limit !== undefined) { request['limit'] = limit; } const response = await this.publicGetTradesPair (this.extend (request, params)); if (Array.isArray (response)) { const numElements = response.length; if (numElements === 0) { return []; } } return this.parseTrades (response[market['id']], market, since, limit); } parseOHLCV (ohlcv, market = undefined) { // // { // "high" : 0.01955, // "open" : 0.01955, // "low" : 0.01955, // "close" : 0.01955, // "amount" : 2.5, // "timestamp" : 1565155740000 // } // return [ this.safeInteger (ohlcv, 'timestamp'), this.safeFloat (ohlcv, 'open'), this.safeFloat (ohlcv, 'high'), this.safeFloat (ohlcv, 'low'), this.safeFloat (ohlcv, 'close'), this.safeFloat (ohlcv, 'amount'), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'pair': market['id'], 'period': this.timeframes[timeframe], }; let method = 'publicGetLastBarsPairPeriodAmount'; if (since === undefined) { if (limit === undefined) { limit = 100; // required, max 2000 } request['amount'] = limit; } else { method = 'publicGetPeriodBarsPairPeriodStartEnd'; // in their docs they expect milliseconds // but it returns empty arrays with milliseconds // however, it does work properly with seconds request['start'] = parseInt (since / 1000); if (limit === undefined) { request['end'] = this.seconds (); } else { const duration = this.parseTimeframe (timeframe) * 1000; const end = this.sum (since, duration * limit); request['end'] = parseInt (end / 1000); } } const response = await this[method] (this.extend (request, params)); // // { // "ethbtc": [ // { // "high" : 0.01955, // "open" : 0.01955, // "low" : 0.01955, // "close" : 0.01955, // "amount" : 2.5, // "timestamp" : 1565155740000 // }, // { // "high" : 0.01967, // "open" : 0.01967, // "low" : 0.01967, // "close" : 0.01967, // "amount" : 0, // "timestamp" : 1565155680000 // } // ] // } // const candles = this.safeValue (response, market['id'], []); return this.parseOHLCVs (candles, market, timeframe, since, limit); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); if (type === 'market' && price === undefined) { throw new ArgumentsRequired (this.id + ' createOrder requires a price argument even for market orders, that is the worst price that you agree to fill your order for'); } const request = { 'pair': market['id'], 'type': side, 'volume': this.amountToPrecision (symbol, amount), 'rate': this.priceToPrecision (symbol, price), 'orderType': type, }; price = parseFloat (price); amount = parseFloat (amount); const response = await this.privatePostOrderNew (this.extend (request, params)); // // { // "success": 1, // "return": { // "received": 0, // "remains": 10, // "funds": { // "BTC": { // "total": 100, // "available": 95 // }, // "USD": { // "total": 10000, // "available": 9995 // }, // "EUR": { // "total": 1000, // "available": 995 // }, // "LTC": { // "total": 1000, // "available": 995 // } // }, // "orderId": 0, // https://github.com/ccxt/ccxt/issues/3677 // } // } // let status = 'open'; let filled = 0.0; let remaining = amount; const responseReturn = this.safeValue (response, 'return'); let id = this.safeString2 (responseReturn, 'orderId', 'order_id'); if (id === '0') { id = this.safeString (responseReturn, 'initOrderId', 'init_order_id'); status = 'closed'; } filled = this.safeFloat (responseReturn, 'received', 0.0); remaining = this.safeFloat (responseReturn, 'remains', amount); const timestamp = this.milliseconds (); return { 'info': response, 'id': id, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': undefined, 'status': status, 'symbol': symbol, 'type': type, 'side': side, 'price': price, 'cost': price * filled, 'amount': amount, 'remaining': remaining, 'filled': filled, 'fee': undefined, // 'trades': this.parseTrades (order['trades'], market), }; } parseOrderStatus (status) { const statuses = { '0': 'open', // Active '1': 'closed', // Filled '2': 'canceled', // Killed '3': 'canceling', // Killing '7': 'canceled', // Rejected }; return this.safeString (statuses, status, status); } parseMarket (id) { if (id in this.markets_by_id) { return this.markets_by_id[id]; } else { // the following is a fix for // https://github.com/ccxt/ccxt/pull/5786 // https://github.com/ccxt/ccxt/issues/5770 let markets_by_other_id = this.safeValue (this.options, 'markets_by_other_id'); if (markets_by_other_id === undefined) { this.options['markets_by_other_id'] = this.indexBy (this.markets, 'otherId'); markets_by_other_id = this.options['markets_by_other_id']; } if (id in markets_by_other_id) { return markets_by_other_id[id]; } } return undefined; } parseOrder (order, market = undefined) { // // fetchOrder // // { // "number": 36635882, // "pair": "btcusd", // "type": "buy", // "remainingVolume": 10, // "volume": 10, // "rate": 1000.0, // "timestampCreated": 1496670, // "status": 0, // "orderType": "limit", // "deals": [ // { // "pair": "btcusd", // "type": "buy", // "amount": 1, // "rate": 1000.0, // "orderId": 1, // "timestamp": 1496672724, // "commission": 0.001, // "commissionCurrency": "btc" // } // ] // } // const id = this.safeString (order, 'id'); const status = this.parseOrderStatus (this.safeString (order, 'status')); const timestamp = this.safeTimestamp (order, 'timestampCreated'); const marketId = this.safeString (order, 'pair'); market = this.parseMarket (marketId); let symbol = undefined; if (market !== undefined) { symbol = market['symbol']; } const remaining = this.safeFloat (order, 'remainingVolume'); const amount = this.safeFloat (order, 'volume'); const price = this.safeFloat (order, 'rate'); let filled = undefined; let cost = undefined; if (amount !== undefined) { if (remaining !== undefined) { filled = amount - remaining; cost = price * filled; } } const orderType = this.safeString (order, 'orderType'); const side = this.safeString (order, 'type'); let fee = undefined; const deals = this.safeValue (order, 'deals', []); const numDeals = deals.length; let trades = undefined; let lastTradeTimestamp = undefined; if (numDeals > 0) { trades = this.parseTrades (deals); let feeCost = undefined; let feeCurrency = undefined; for (let i = 0; i < trades.length; i++) { const trade = trades[i]; if (feeCost === undefined) { feeCost = 0; } feeCost = this.sum (feeCost, trade['fee']['cost']); feeCurrency = trade['fee']['currency']; lastTradeTimestamp = trade['timestamp']; } if (feeCost !== undefined) { fee = { 'cost': feeCost, 'currency': feeCurrency, }; } } return { 'info': order, 'id': id, 'clientOrderId': undefined, 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': lastTradeTimestamp, 'type': orderType, 'side': side, 'price': price, 'cost': cost, 'amount': amount, 'remaining': remaining, 'filled': filled, 'status': status, 'fee': fee, 'trades': trades, }; } async fetchOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); const request = { 'orderId': parseInt (id), }; const response = await this.privatePostOrderStatus (this.extend (request, params)); // // { // "success": 1, // "return": { // "pair": "btcusd", // "type": "buy", // "remainingVolume": 10, // "volume": 10, // "rate": 1000.0, // "timestampCreated": 1496670, // "status": 0, // "orderType": "limit", // "deals": [ // { // "pair": "btcusd", // "type": "buy", // "amount": 1, // "rate": 1000.0, // "orderId": 1, // "timestamp": 1496672724, // "commission": 0.001, // "commissionCurrency": "btc" // } // ] // } // } // return this.parseOrder (this.extend ({ 'id': id, }, response['return'])); } parseOrdersById (orders, symbol = undefined, since = undefined, limit = undefined) { const ids = Object.keys (orders); const result = []; for (let i = 0; i < ids.length; i++) { const id = ids[i]; const order = this.parseOrder (this.extend ({ 'id': id.toString (), }, orders[id])); result.push (order); } return this.filterBySymbolSinceLimit (result, symbol, since, limit); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = { // 'count': 10, // Decimal, The maximum number of orders to return // 'fromId': 123, // Decimal, ID of the first order of the selection // 'endId': 321, // Decimal, ID of the last order of the selection // 'order': 'ASC', // String, Order in which orders shown. Possible values are "ASC" — from first to last, "DESC" — from last to first. }; const response = await this.privatePostOrders (this.extend (request, params)); // // { // "success": 1, // "return": { // "0": { // "pair": "btcusd", // "type": "buy", // "remainingVolume": 10, // "volume": 10, // "rate": 1000.0, // "timestampCreated": 1496670, // "status": 0, // "orderType": "limit" // } // } // } // return this.parseOrdersById (this.safeValue (response, 'return', {}), symbol, since, limit); } async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = { // 'count': 10, // Decimal, The maximum number of orders to return // 'fromId': 123, // Decimal, ID of the first order of the selection // 'endId': 321, // Decimal, ID of the last order of the selection // 'order': 'ASC', // String, Order in which orders shown. Possible values are "ASC" — from first to last, "DESC" — from last to first. }; if (limit !== undefined) { request['count'] = limit; } const response = await this.privatePostHistoryOrders (this.extend (request, params)); // // { // "success": 1, // "return": { // "0": { // "pair": "btcusd", // "type": "buy", // "remainingVolume": 10, // "volume": 10, // "rate": 1000.0, // "timestampCreated": 1496670, // "status": 0, // "orderType": "limit" // } // } // } // return this.parseOrdersById (this.safeValue (response, 'return', {}), symbol, since, limit); } async cancelOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); const request = { 'orderId': id, }; const response = await this.privatePostOrderCancel (this.extend (request, params)); return response; } parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) { const result = []; const ids = Object.keys (orders); let symbol = undefined; if (market !== undefined) { symbol = market['symbol']; } for (let i = 0; i < ids.length; i++) { const id = ids[i]; const order = this.extend ({ 'id': id }, orders[id]); result.push (this.extend (this.parseOrder (order, market), params)); } return this.filterBySymbolSinceLimit (result, symbol, since, limit); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { const orders = await this.fetchOrders (symbol, since, limit, params); return this.filterBy (orders, 'status', 'closed'); } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = undefined; // some derived classes use camelcase notation for request fields const request = { // 'from': 123456789, // trade ID, from which the display starts numerical 0 (test result: liqui ignores this field) // '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 (test result: liqui ignores this field, most recent trade always goes last) // 'since': 1234567890, // UTC start time, default = 0 (test result: liqui ignores this field) // 'end': 1234567890, // UTC end time, default = ∞ (test result: liqui ignores this field) // 'pair': 'eth_btc', // default = all markets }; if (symbol !== undefined) { market = this.market (symbol); request['pair'] = market['id']; } if (limit !== undefined) { request['count'] = parseInt (limit); } if (since !== undefined) { request['since'] = parseInt (since / 1000); } const response = await this.privatePostHistoryTrades (this.extend (request, params)); let trades = []; if ('return' in response) { trades = response['return']; } return this.parseTrades (trades, market, since, limit); } async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let currency = undefined; const request = {}; if (code !== undefined) { currency = this.currency (code); request['currency'] = currency['id']; } if (since !== undefined) { request['since'] = since; } if (limit !== undefined) { request['count'] = limit; } const response = await this.privatePostHistoryTransactions (this.extend (request, params)); // // { // "success": 1, // "return": [ // { // "id": 1, // "timestamp": 11, // "type": "Withdraw", // "amount": 1, // "currency": "btc", // "confirmationsCount": 6, // "address": "address", // "status": 2, // "commission": 0.0001 // } // ] // } // const transactions = this.safeValue (response, 'return', []); return this.parseTransactions (transactions, currency, since, limit); } parseTransactionStatus (status) { const statuses = { '1': 'failed', '2': 'ok', '3': 'pending', '4': 'failed', }; return this.safeString (statuses, status, status); } parseTransaction (transaction, currency = undefined) { // // { // "id": 1, // "timestamp": 11, // 11 in their docs ( // "type": "Withdraw", // "amount": 1, // "currency": "btc", // "confirmationsCount": 6, // "address": "address", // "status": 2, // "commission": 0.0001 // } // const timestamp = this.safeTimestamp (transaction, 'timestamp'); let type = this.safeString (transaction, 'type'); if (type !== undefined) { if (type === 'Incoming') { type = 'deposit'; } else if (type === 'Withdraw') { type = 'withdrawal'; } } const currencyId = this.safeString (transaction, 'currency'); const code = this.safeCurrencyCode (currencyId, currency); const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); return { 'id': this.safeString (transaction, 'id'), 'txid': this.safeString (transaction, 'txid'), 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'address': this.safeString (transaction, 'address'), 'type': type, 'amount': this.safeFloat (transaction, 'amount'), 'currency': code, 'status': status, 'fee': { 'currency': code, 'cost': this.safeFloat (transaction, 'commission'), 'rate': undefined, }, 'info': transaction, }; } async createDepositAddress (code, params = {}) { const request = { 'new': 1, }; const response = await this.fetchDepositAddress (code, this.extend (request, params)); return response; } async fetchDepositAddress (code, params = {}) { await this.loadMarkets (); const currency = this.currency (code); const request = { 'currency': currency['id'], }; const response = await this.dwapiPostDepositCryptoaddress (this.extend (request, params)); const result = this.safeValue (response, 'return', {}); const address = this.safeString (result, 'address'); this.checkAddress (address); return { 'currency': code, 'address': address, 'tag': undefined, // not documented in DSX API 'info': response, }; } async withdraw (code, amount, address, tag = undefined, params = {}) { this.checkAddress (address); await this.loadMarkets (); const currency = this.currency (code); const commission = this.safeValue (params, 'commission'); if (commission === undefined) { throw new ArgumentsRequired (this.id + ' withdraw() requires a `commission` (withdrawal fee) parameter (string)'); } params = this.omit (params, commission); const request = { 'currency': currency['id'], 'amount': parseFloat (amount), 'address': address, 'commission': commission, }; if (tag !== undefined) { request['address'] += ':' + tag; } const response = await this.dwapiPostWithdrawCrypto (this.extend (request, params)); // // [ // { // "success": 1, // "return": { // "transactionId": 2863073 // } // } // ] // const data = this.safeValue (response, 'return', {}); const id = this.safeString (data, 'transactionId'); return { 'info': response, 'id': id, }; } sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { let url = this.urls['api'][api]; const query = this.omit (params, this.extractParams (path)); if (api === 'private' || api === 'dwapi') { url += '/' + this.version + '/' + this.implodeParams (path, params); this.checkRequiredCredentials (); const nonce = this.nonce (); body = this.urlencode (this.extend ({ 'nonce': nonce, }, query)); const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512', 'base64'); headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Key': this.apiKey, 'Sign': signature, }; } else if (api === 'public') { url += '/' + this.implodeParams (path, params); if (Object.keys (query).length) { url += '?' + this.urlencode (query); } } else { url += '/' + this.implodeParams (path, params); if (method === 'GET') { if (Object.keys (query).length) { url += '?' + this.urlencode (query); } } else { if (Object.keys (query).length) { body = this.json (query); headers = { 'Content-Type': 'application/json', }; } } } return { 'url': url, 'method': method, 'body': body, 'headers': headers }; } handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { if (response === undefined) { return; // fallback to default error handler } if ('success' in response) { // // 1 - Liqui only returns the integer 'success' key from their private API // // { "success": 1, ... } httpCode === 200 // { "success": 0, ... } httpCode === 200 // // 2 - However, exchanges derived from Liqui, can return non-integers // // It can be a numeric string // { "sucesss": "1", ... } // { "sucesss": "0", ... }, httpCode >= 200 (can be 403, 502, etc) // // Or just a string // { "success": "true", ... } // { "success": "false", ... }, httpCode >= 200 // // Or a boolean // { "success": true, ... } // { "success": false, ... }, httpCode >= 200 // // 3 - Oversimplified, Python PEP8 forbids comparison operator (===) of different types // // 4 - We do not want to copy-paste and duplicate the code of this handler to other exchanges derived from Liqui // // To cover points 1, 2, 3 and 4 combined this handler should work like this: // let success = this.safeValue (response, 'success', false); if (typeof success === 'string') { if ((success === 'true') || (success === '1')) { success = true; } else { success = false; } } if (!success) { const code = this.safeString (response, 'code'); const message = this.safeString (response, 'error'); const feedback = this.id + ' ' + body; this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); throw new ExchangeError (feedback); // unknown message } } } };