UNPKG

ccxt-look

Version:

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

1,188 lines (1,146 loc) 52.1 kB
'use strict'; // ---------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ExchangeError, AuthenticationError, ArgumentsRequired, OrderNotFound, BadRequest, PermissionDenied, RateLimitExceeded } = require ('./base/errors'); const { TICK_SIZE } = require ('./base/functions/number'); const Precise = require ('./base/Precise'); // ---------------------------------------------------------------------------- module.exports = class fairdesk extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'fairdesk', 'name': 'Fairdesk', 'countries': [ 'CN' ], // China 'rateLimit': 100, 'version': 'v1', 'certified': false, 'pro': true, 'hostname': 'api.fairdesk.com', 'has': { 'CORS': undefined, 'spot': false, 'margin': false, 'swap': undefined, // has but not fully implemented 'future': false, 'option': false, 'cancelAllOrders': true, 'cancelOrder': true, 'createOrder': true, 'editOrder': false, 'fetchBalance': true, 'fetchBorrowRate': false, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchBorrowRates': false, 'fetchBorrowRatesPerSymbol': false, 'fetchClosedOrders': true, 'fetchCurrencies': false, 'fetchDepositAddress': true, 'fetchDeposits': true, 'fetchIndexOHLCV': false, 'fetchLeverageTiers': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': true, 'fetchPositions': true, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': false, 'fetchWithdrawals': false, 'setLeverage': true, 'transfer': false, 'withdraw': false, }, 'urls': { 'logo': 'https://static.fairdesk.com/font/fairdesklogo.png', 'test': { 'public': 'https://api-testnet.fairdesk.com/api/v1/public', 'private': 'https://api-testnet.fairdesk.com/api/v1/private', }, 'api': { 'public': 'https://{hostname}/api/v1/public', 'private': 'https://{hostname}/api/v1/private', }, 'www': 'https://www.fairdesk.com', 'doc': 'https://github.com/fairdesk/fairdesk-api-docs', 'fees': 'https://www.fairdesk.com/fees', 'referral': 'https://www.fairdesk.com/signup?ref=URIJD5NI', }, 'timeframes': { '1m': '60', '3m': '180', '5m': '300', '15m': '900', '30m': '1800', '1h': '3600', '2h': '7200', '3h': '10800', '4h': '14400', '6h': '21600', '12h': '43200', '1d': '86400', '1w': '604800', '1M': '2592000', }, 'api': { 'public': { 'get': [ 'products', // contracts only 'md/orderbook', // ?symbol=BTCUSDT 'md/ticker24h', // ?symbol=BTCUSDT 'md/trade-recent', // ?symbol=BTCUSDT 'md/trade-history', // ?symbol=BTCUSDT&from=1651382628000 'md/kline', // ?symbol=BTCUSDT&interval=5m&from=1651382628000&to=1651469028000 ], }, 'private': { 'get': [ // swap 'account/order-detail', // orderId=571643709 'account/order-histories', // ?symbol=&type=&pageIndex=1&pageSize=1000 'account/balance', 'account/symbol-config', 'account/open-orders', 'account/positions', 'account/trade-histories', // wallet 'wallet/deposit-address', // ?currency=ETH 'wallet/deposit-records', // ?startTime=0&currency=USDT&pageIndex=1&pageSize=20 ], 'post': [ // swap 'trade/place-order', 'trade/cancel-all-order', ], 'put': [ // swap 'account/config/adjust-leverage', ], 'delete': [ // swap 'trade/cancel-order', ], }, }, 'precisionMode': TICK_SIZE, 'fees': { 'trading': { 'tierBased': false, 'percentage': true, 'taker': this.parseNumber ('0.001'), 'maker': this.parseNumber ('0.001'), }, }, 'requiredCredentials': { 'apiKey': true, 'secret': true, }, 'exceptions': { 'exact': { // not documented '412': BadRequest, // {"code":412,"msg":"Missing parameter - resolution","data":null} '6001': BadRequest, // {"error":{"code":6001,"message":"invalid argument"},"id":null,"result":null} // documented '19999': BadRequest, // REQUEST_IS_DUPLICATED Duplicated request ID // not documented '30000': BadRequest, // {"code":30000,"msg":"Please double check input arguments","data":null} '39995': RateLimitExceeded, // {"code": "39995","msg": "Too many requests."} '39996': PermissionDenied, // {"code": "39996","msg": "Access denied."} }, 'broad': { '401 Insufficient privilege': PermissionDenied, // {"code": "401","msg": "401 Insufficient privilege."} '401 Request IP mismatch': PermissionDenied, // {"code": "401","msg": "401 Request IP mismatch."} 'Failed to find api-key': AuthenticationError, // {"msg":"Failed to find api-key 1c5ec63fd-660d-43ea-847a-0d3ba69e106e","code":10500} 'Missing required parameter': BadRequest, // {"msg":"Missing required parameter","code":10500} 'API Signature verification failed': AuthenticationError, // {"msg":"API Signature verification failed.","code":10500} 'Api key not found': AuthenticationError, // {"msg":"Api key not found 698dc9e3-6faa-4910-9476-12857e79e198","code":"10500"} }, }, 'options': { 'x-fairdesk-request-expiry': 60, // in seconds 'createOrderByQuoteRequiresPrice': true, 'networks': { 'ERC20': 'ETH', 'TRC20': 'TRX', }, 'defaultNetworks': { 'USDT': 'ETH', }, 'defaultSubType': 'linear', 'accountsByType': { 'future': 'future', }, }, }); } parseSafeNumber (value = undefined) { if (value === undefined) { return value; } let parts = value.split (','); value = parts.join (''); parts = value.split (' '); return this.safeNumber (parts, 0); } parseMaxLevel (symbolName) { const hh = [ 'BTCUSDT' ]; const h = [ 'ETHUSDT' ]; const m = [ 'ADAUSDT', 'BNBUSDT', 'DOTUSDT' ]; const l = [ 'XRPUSDT', 'UNIUSDT', 'DOGEUSDT' ]; symbolName = symbolName.toLocaleUpperCase (); const hhLeverages = [ 50000, 125, 100, 80, 60, 40, 20, 10, 5 ]; const hLeverages = [ 30000, 80, 60, 40, 20, 10, 5 ]; const mLeverages = [ 20000, 50, 30, 20, 10, 5, 4 ]; const lLeverages = [ 10000, 25, 20, 15, 10, 5, 4 ]; if (hh.indexOf (symbolName) >= 0) { return hhLeverages; } if (h.indexOf (symbolName) >= 0) { return hLeverages; } if (m.indexOf (symbolName) >= 0) { return mLeverages; } if (l.indexOf (symbolName) >= 0) { return lLeverages; } return lLeverages; } parseSwapMarket (market) { // { // amountDecimal: 3 // baseCurrency: "BTC" // defaultLeverage: 20 // displayName: "BTC/USDT" // fundingInterval: "Every 8 hours" // limitPriceDiffRate: 0.1 // liquidationFeeRate: 0.01 // logUrl: "https://sgtnstatic-1306519353.file.myqcloud.com/currency/BTC.png" // makerFeeRate: "0.0001" // marketMaxQty: "0.000000" // marketPriceDiffRate: 0.1 // maxOrderQty: "100.000000" // maxPrice: "500000.000000" // minOrderQty: "0.000000" // minPrice: "0.500000" // priceDecimal: 1 // priceScale: 6 // productType: "Perpetual" // quoteCurrency: "USDT" // ratioScale: 8 // settleCurrency: "USDT" // status: "TRADING" // stepSize: "0.001000" // strategyPriceDiffRate: 0.075 // symbol: "btcusdt" // symbolId: 1211 // takerFeeRate: "0.00015" // tickSize: "0.500000" // type: "FUTURE_PERPETUAL" // valueScale: 6 // } const id = this.safeString (market, 'symbol'); const baseId = this.safeString (market, 'baseCurrency'); const quoteId = this.safeString (market, 'quoteCurrency'); const settleId = this.safeString (market, 'settleCurrency'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const settle = this.safeCurrencyCode (settleId); let inverse = false; if (settleId !== quoteId) { inverse = true; } const priceScale = this.safeInteger (market, 'priceScale'); const ratioScale = this.safeInteger (market, 'ratioScale'); const minPriceEp = this.safeString (market, 'minPrice'); const maxPriceEp = this.safeString (market, 'maxPrice'); const makerFeeRateEr = this.safeString (market, 'makerFeeRate'); const takerFeeRateEr = this.safeString (market, 'takerFeeRate'); const status = this.safeString (market, 'status'); // the size of one contract, only used if `contract` is true const contractSize = this.parseNumber (this.safeString (market, 'stepSize')); const maxLevel = this.parseMaxLevel (id)[1]; return { 'id': id, 'symbol': base + '/' + quote, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': 'swap', 'spot': false, 'margin': false, 'swap': true, 'future': false, 'option': false, 'active': status === 'TRADING', 'contract': true, 'linear': !inverse, 'inverse': inverse, 'taker': this.parseNumber (takerFeeRateEr), 'maker': this.parseNumber (makerFeeRateEr), 'contractSize': contractSize, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'priceScale': priceScale, 'ratioScale': ratioScale, 'precision': { 'amount': this.safeNumber (market, 'stepSize'), 'price': this.safeNumber (market, 'tickSize'), }, 'limits': { 'leverage': { 'min': this.parseNumber ('1'), 'max': this.parseNumber (maxLevel), }, 'amount': { 'min': undefined, 'max': undefined, }, 'price': { 'min': this.parseNumber (minPriceEp), 'max': this.parseNumber (maxPriceEp), }, 'cost': { 'min': this.parseNumber (this.safeString (market, 'minOrderQty')), 'max': this.parseNumber (this.safeString (market, 'maxOrderQty')), }, }, 'info': market, }; } async fetchMarkets (params = {}) { const res = await this.publicGetProducts (params); const products = res.data; const result = []; for (let i = 0; i < products.length; i++) { let market = products[i]; market = this.parseSwapMarket (market); result.push (market); } return result; } async fetchCurrencies (params = {}) { const response = await this.userGetPublicInstrumentChainCurrency (params); const currencies = this.safeValue (response, 'data', {}); const result = {}; for (let i = 0; i < currencies.length; i++) { const currency = currencies[i]; const id = this.safeString (currency, 'currency'); const name = this.safeString (currency, 'currency'); const code = this.safeCurrencyCode (id); const valueScaleString = this.safeString (currency, 'valueScale'); const valueScale = parseInt (valueScaleString); const minValueEv = this.safeString (currency, 'minValueEv'); const maxValueEv = this.safeString (currency, 'maxValueEv'); let minAmount = undefined; let maxAmount = undefined; let precision = undefined; if (valueScale !== undefined) { const precisionString = this.parsePrecision (valueScaleString); precision = this.parseNumber (precisionString); minAmount = this.parseNumber (Precise.stringMul (minValueEv, precisionString)); maxAmount = this.parseNumber (Precise.stringMul (maxValueEv, precisionString)); } result[code] = { 'id': id, 'info': currency, 'code': code, 'name': name, 'active': undefined, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'precision': precision, 'limits': { 'amount': { 'min': minAmount, 'max': maxAmount, }, 'withdraw': { 'min': undefined, 'max': undefined, }, }, 'valueScale': valueScale, }; } return result; } parseBidAsk (bidask, priceKey = 0, amountKey = 1, market = undefined) { if (market === undefined) { throw new ArgumentsRequired (this.id + ' parseBidAsk() requires a market argument'); } const amount = this.safeString (bidask, amountKey); return [ this.parseNumber (this.safeString (bidask, priceKey)), this.parseNumber (amount), ]; } parseOrderBook (orderbook, symbol, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1, market = undefined) { const result = { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'nonce': undefined, }; const sides = [ bidsKey, asksKey ]; for (let i = 0; i < sides.length; i++) { const side = sides[i]; const orders = []; const bidasks = this.safeValue (orderbook, side); for (let k = 0; k < bidasks.length; k++) { orders.push (this.parseBidAsk (bidasks[k], priceKey, amountKey, market)); } result[side] = orders; } result[bidsKey] = this.sortBy (result[bidsKey], 0, true); result[asksKey] = this.sortBy (result[asksKey], 0); return result; } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); const symbolId = this.parseSymbol (symbol); const market = this.market (symbolId); const request = { 'symbol': market['id'], }; const response = await this.publicGetMdOrderbook (this.extend (request, params)); const result = this.safeValue (response, 'data', {}); const timestamp = this.safeIntegerProduct (result, 'timestamp', 0.000001); const orderbook = this.parseOrderBook (result, symbol, timestamp, 'bids', 'asks', 0, 1, market); orderbook['nonce'] = this.safeInteger (result, 'sequence'); return orderbook; } parseOHLCV (ohlcv, market = undefined) { return [ this.parseNumber (this.safeString (ohlcv, 'closeTime')), this.parseNumber (this.safeString (ohlcv, 'open')), this.parseNumber (this.safeString (ohlcv, 'high')), this.parseNumber (this.safeString (ohlcv, 'low')), this.parseNumber (this.safeString (ohlcv, 'close')), this.parseNumber (this.safeString (ohlcv, 'volume')), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { const request = { 'interval': timeframe, }; let startTime = 0; let endTime = 0; const duration = this.parseTimeframe (timeframe); const now = this.seconds (); if (since !== undefined) { if (limit === undefined) { limit = 1000; // max 1000 } since = parseInt (since / 1000); startTime = since; // time ranges ending in the future are not accepted // https://github.com/ccxt/ccxt/issues/8050 endTime = Math.min (now, this.sum (since, duration * limit)); } else if (limit !== undefined) { limit = Math.min (limit, 1000); startTime = now - duration * this.sum (limit, 1); endTime = now; } else { throw new ArgumentsRequired (this.id + ' fetchOHLCV() requires a since argument, or a limit argument, or both'); } request['from'] = startTime * 1000; request['to'] = endTime * 1000; await this.loadMarkets (); const symbolId = this.parseSymbol (symbol); const market = this.market (symbolId); request['symbol'] = market['id']; const response = await this.publicGetMdKline (this.extend (request, params)); const rows = this.safeValue (response, 'data', {}); return this.parseOHLCVs (rows, market, timeframe, since, limit); } parseTicker (ticker, market = undefined) { const marketId = this.safeString (ticker, 'symbol'); market = this.safeMarket (marketId, market); const symbol = market['symbol']; const timestamp = this.safeIntegerProduct (ticker, 'timestamp', 0.000001); const last = this.safeString (ticker, 'close'); const open = this.safeString (ticker, 'open'); return this.safeTicker ({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeString (ticker, 'high'), 'low': this.safeString (ticker, 'low'), 'bid': this.safeString (ticker, 'bid'), 'bidVolume': undefined, 'ask': this.safeString (ticker, 'askEp'), 'askVolume': undefined, 'vwap': undefined, 'open': open, 'close': last, 'last': last, 'previousClose': undefined, // previous day close 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': this.safeString (ticker, 'baseVolume'), 'quoteVolume': this.safeString (ticker, 'quoteVolume'), 'info': ticker, }, market, false); } parseSymbol (symbol) { const str = symbol.replace ('/', ''); const res = str.toLowerCase (); return res; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const symbolId = this.parseSymbol (symbol); const market = this.market (symbolId); const request = { 'symbol': market['id'], }; const method = 'publicGetMdTicker24h'; const response = await this[method] (this.extend (request, params)); const result = this.safeValue (response, 'data', {}); return this.parseTicker (result, market); } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { const symbolId = this.parseSymbol (symbol); await this.loadMarkets (); const market = this.market (symbolId); const request = { 'symbol': market['id'], }; let method = 'publicGetMdTradeRecent'; if (since) { method = 'publicGetMdTradeHistory'; request['from'] = since; } const response = await this[method] (this.extend (request, params)); const result = this.safeValue (response, 'data', {}); const trades = this.safeValue (result, 'trades', []); return this.parseTrades (trades, market, since, limit); } parseTrade (trade, market = undefined) { let priceString = undefined; let amountString = undefined; let timestamp = undefined; let id = undefined; let side = undefined; let costString = undefined; let type = undefined; let fee = undefined; const marketId = this.safeString (trade, 'symbol'); market = this.safeMarket (marketId, market); const symbol = market['symbol']; let orderId = undefined; let takerOrMaker = undefined; if (Array.isArray (trade)) { const tradeLength = trade.length; timestamp = this.safeIntegerProduct (trade, 0, 0.001); if (tradeLength > 4) { id = this.safeString (trade, tradeLength - 4); } side = this.safeStringLower (trade, tradeLength - 3); priceString = this.safeString (trade, tradeLength - 2); amountString = this.safeString (trade, tradeLength - 1); } else { timestamp = this.safeNumber (trade, 'timestamp'); id = this.safeString2 (trade, 'execId', 'execID'); orderId = this.safeString (trade, 'orderID'); side = this.safeStringLower (trade, 'side'); type = this.parseOrderType (this.safeString (trade, 'ordType')); const execStatus = this.safeString (trade, 'execStatus'); if (execStatus === 'MakerFill') { takerOrMaker = 'maker'; } priceString = this.safeString (trade, 'price'); amountString = this.safeString (trade, 'qty'); costString = this.safeString2 (trade, 'price', 'qty'); const feeCostString = this.safeString (trade, 'execFeeEv'); if (feeCostString !== undefined) { const feeRateString = this.safeString (trade, 'feeRateEr'); let feeCurrencyCode = undefined; if (market['spot']) { feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; } else { const info = this.safeValue (market, 'info'); if (info !== undefined) { const settlementCurrencyId = this.safeString (info, 'settlementCurrency'); feeCurrencyCode = this.safeCurrencyCode (settlementCurrencyId); } } fee = { 'cost': this.parseNumber (feeCostString), 'rate': this.parseNumber (feeRateString), 'currency': feeCurrencyCode, }; } } return this.safeTrade ({ 'info': trade, 'id': id, 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'order': orderId, 'type': type, 'side': side, 'takerOrMaker': takerOrMaker, 'price': priceString, 'amount': amountString, 'cost': costString, 'fee': fee, }, market); } parseSwapBalance (response) { const result = { 'info': response }; const data = this.safeValue (response, 'data', {}); const balance = this.safeValue (data, 'accounts', {})[0]; const currencyId = this.safeString (balance, 'currency'); const code = this.safeCurrencyCode (currencyId); const account = this.account (); const accountBalanceEv = this.safeString (balance, 'accountBalance'); const totalUsedBalanceEv = this.safeString (balance, 'availBalance'); account['total'] = accountBalanceEv; account['used'] = totalUsedBalanceEv; result[code] = account; return this.safeBalance (result); } async fetchBalance (params = {}) { await this.loadMarkets (); const method = 'privateGetAccountBalance'; const request = {}; params = this.omit (params, 'type'); const response = await this[method] (this.extend (request, params)); const result = this.parseSwapBalance (response); return result; } parseOrderStatus (status) { const statuses = { 'Created': 'open', 'Untriggered': 'open', 'Deactivated': 'closed', 'Triggered': 'open', 'Rejected': 'rejected', 'New': 'open', 'PartiallyFilled': 'open', 'Filled': 'closed', 'Canceled': 'canceled', }; return this.safeString (statuses, status, status); } parseOrderType (type) { const types = { 'Limit': 'limit', 'Market': 'market', }; return this.safeString (types, type, type); } parseTimeInForce (timeInForce) { const timeInForces = { 'GoodTillCancel': 'GTC', 'PostOnly': 'PO', 'ImmediateOrCancel': 'IOC', 'FillOrKill': 'FOK', }; return this.safeString (timeInForces, timeInForce, timeInForce); } parseSwapOrder (order, market = undefined) { const id = this.safeString (order, 'orderId'); const state = () => { let value = ''; if (this.safeString (order, 'status') === 'FILLED') { const displayStr = `${this.safeString (order, 'side')}_${this.safeString (order, 'positionSide')}`; switch (displayStr) { case 'BUY_LONG': value = 'open'; break; case 'BUY_SHORT': value = 'closed'; break; case 'SELL_SHORT': value = 'open'; break; case 'SELL_LONG': value = 'closed'; break; default: break; } return value; } if (this.safeString (order, 'status') === 'CANCELED') { value = 'canceled'; return value; } if (this.safeString (order, 'status') === 'EXPIRED') { value = 'expired'; return value; } return value; }; let clientOrderId = this.safeString (order, 'clientOrderId'); if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { clientOrderId = undefined; } const marketId = this.safeString (order, 'symbol'); const symbol = this.safeSymbol (marketId, market); const status = state (); // 'open', 'closed', 'canceled', 'expired', 'rejected' const side = (this.safeStringLower (order, 'side')).toLowerCase (); const type = (this.parseOrderType (this.safeString (order, 'type'))).toLowerCase (); const price = this.parseNumber (this.safeString (order, 'price') === '--' ? '' : this.safeString (order, 'price')); const amount = this.safeNumber (order, 'origQty'); const filled = this.safeNumber (order, 'origQty') - this.safeNumber (order, 'executedQty'); // const remaining = this.safeNumber (order, 'leavesQty'); const remaining = undefined; const timestamp = this.safeNumber (order, 'transactTime'); // const cost = this.safeNumber (order, 'cumValue'); const cost = undefined; let lastTradeTimestamp = this.safeNumber (order, 'transactTimeNs'); if (lastTradeTimestamp === 0) { lastTradeTimestamp = undefined; } const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce')); const stopPrice = this.safeNumber (order, 'avlPrice'); const postOnly = (timeInForce === 'POST_ONLY'); return { 'info': order, 'id': id, 'clientOrderId': clientOrderId, 'datetime': this.iso8601 (timestamp), 'timestamp': timestamp, 'lastTradeTimestamp': lastTradeTimestamp, 'symbol': symbol, 'type': type, 'timeInForce': timeInForce, 'postOnly': postOnly, 'side': side, 'price': price, 'stopPrice': stopPrice, 'amount': amount, 'filled': filled, 'remaining': remaining, 'cost': cost, 'average': undefined, 'status': status, 'fee': undefined, 'trades': undefined, }; } parseOrder (order, market = undefined) { return this.parseSwapOrder (order, market); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); const symbolId = this.parseSymbol (symbol); const market = this.market (symbolId); side = this.capitalize (side); type = this.capitalize (type); const request = { // common 'symbol': market['id'], 'side': side.toLocaleUpperCase (), // Sell, Buy 'type': type.toLocaleUpperCase (), // Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched or Pegged for swap orders // swap 'clientOrderId': `CCXT_${this.uuid ()}`, 'orderRespType': 'ACK', 'positionSide': side === 'Buy' ? 'LONG' : 'SHORT', 'isolated': params.isolated || false, }; const stopPrice = this.safeString2 (params, 'stopPx', 'stopPrice'); if (stopPrice !== undefined) { request['stopPrice'] = stopPrice; } params = this.omit (params, [ 'stopPx', 'stopPrice' ]); if (market['swap']) { request['quantity'] = amount; if (stopPrice !== undefined) { const triggerType = this.safeString (params, 'triggerType', 'ByMarkPrice'); request['triggerType'] = triggerType; } } if ((type === 'Limit') || (type === 'StopLimit') || (type === 'LimitIfTouched')) { const priceString = price.toString (); request['timeInForce'] = 'GTC'; request['price'] = priceString; } const takeProfitPrice = this.safeString (params, 'takeProfitPrice'); if (takeProfitPrice !== undefined) { request['tpPrice'] = takeProfitPrice; params = this.omit (params, 'tpPrice'); } const stopLossPrice = this.safeString (params, 'stopLossPrice'); if (stopLossPrice !== undefined) { request['slPrice'] = stopLossPrice; params = this.omit (params, 'slPrice'); } const method = market['spot'] ? 'privatePostSpotOrders' : 'privatePostTradePlaceOrder'; const response = await this[method] (this.extend (request, params)); const data = this.safeValue (response, 'data', {}); return this.parseOrder (data, market); } async cancelOrder (id, symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument'); } const symbolId = this.parseSymbol (symbol); await this.loadMarkets (); const market = this.market (symbolId); const request = { 'symbol': market['id'], 'orderId': id, }; const method = 'privateDeleteTradeCancelOrder'; const response = await this[method] (this.extend (request, params)); if (response.status !== 0) { return response; } const data = this.safeValue (response, 'data', {}); return this.parseOrder (data, market); } async cancelAllOrders (symbol = undefined, params = {}) { await this.loadMarkets (); const request = { 'settleCcy': 'USDT', }; const method = 'privatePostTradeCancelAllOrder'; return await this[method] (this.extend (request, params)); } async fetchOrder (id, symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const method = market['spot'] ? 'privateGetSpotOrdersActive' : 'privateGetAccountOrderDetail'; const request = { 'symbol': market['id'], }; const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdID'); params = this.omit (params, [ 'clientOrderId', 'clOrdID' ]); if (clientOrderId !== undefined) { request['clOrdID'] = clientOrderId; } else { request['orderID'] = id; } const response = await this[method] (this.extend (request, params)); const data = this.safeValue (response, 'data', {}); let order = data; if (Array.isArray (data)) { const numOrders = data.length; if (numOrders < 1) { if (clientOrderId !== undefined) { throw new OrderNotFound (this.id + ' fetchOrder ' + symbol + ' order with clientOrderId ' + clientOrderId + ' not found'); } else { throw new OrderNotFound (this.id + ' fetchOrder ' + symbol + ' order with id ' + id + ' not found'); } } order = this.safeValue (data, 0, {}); } return this.parseOrder (order, market); } async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrders() requires a symbol argument'); } const symbolId = this.parseSymbol (symbol); await this.loadMarkets (); const market = this.market (symbolId); const method = market['spot'] ? 'privateGetSpotOrders' : 'privateGetAccountOrderHistories'; const request = { 'symbol': market['id'], }; if (since !== undefined) { request['start'] = since; } if (limit !== undefined) { request['limit'] = limit; } const response = await this[method] (this.extend (request, params)); const data = this.safeValue (response, 'data', {}); const rows = this.safeValue (data, 'rows', []); return this.parseOrders (rows, market, since, limit); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); } await this.loadMarkets (); const symbolId = this.parseSymbol (symbol); const market = this.market (symbolId); const method = market['spot'] ? 'privateGetSpotOrders' : 'privateGetAccountOpenOrders'; const request = { 'symbol': market['id'], }; let response = undefined; try { response = await this[method] (this.extend (request, params)); } catch (e) { if (e instanceof OrderNotFound) { return []; } } const data = this.safeValue (response, 'data', {}); if (Array.isArray (data)) { return this.parseOrders (data, market, since, limit); } else { const rows = this.safeValue (data, 'rows', []); return this.parseOrders (rows, market, since, limit); } } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchClosedOrders() requires a symbol argument'); } const symbolId = this.parseSymbol (symbol); await this.loadMarkets (); const market = this.market (symbolId); const method = market['spot'] ? 'privateGetExchangeSpotOrder' : 'privateGetAccountOrderHistories'; const request = { 'symbol': market['id'], 'status': 'CANCELED', }; if (since !== undefined) { request['start'] = since; } if (limit !== undefined) { request['limit'] = limit; } const response = await this[method] (this.extend (request, params)); const data = this.safeValue (response, 'data', {}); if (Array.isArray (data)) { return this.parseOrders (data, market, since, limit); } else { const rows = this.safeValue (data, 'rows', []); return this.parseOrders (rows, market, since, limit); } } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument'); } await this.loadMarkets (); const symbolId = this.parseSymbol (symbol); const market = this.market (symbolId); const method = 'privateGetAccountTradeHistories'; const request = { 'symbol': market['id'], }; if (since !== undefined) { request['start'] = since; } if (market['swap'] && (limit !== undefined)) { request['limit'] = limit; } const response = await this[method] (this.extend (request, params)); const data = this.safeValue (response, 'data', {}); const rows = this.safeValue (data, 'rows', []); return this.parseTrades (rows, market, since, limit); } async fetchDepositAddress (code, params = {}) { await this.loadMarkets (); const currency = this.currency (code); const request = { 'currency': currency['id'], }; const defaultNetworks = this.safeValue (this.options, 'defaultNetworks'); const defaultNetwork = this.safeStringUpper (defaultNetworks, code); const networks = this.safeValue (this.options, 'networks', {}); let network = this.safeStringUpper (params, 'network', defaultNetwork); network = this.safeString (networks, network, network); if (network === undefined) { request['chainName'] = currency['id']; } else { request['chainName'] = network; params = this.omit (params, 'network'); } const response = await this.privateGetWalletDepositAddress (this.extend (request, params)); const data = this.safeValue (response, 'data', {})[0]; const address = this.safeString (data, 'address'); const tag = this.safeString (data, 'chain'); this.checkAddress (address); return { 'currency': code, 'address': address, 'tag': tag, 'network': undefined, 'info': data, }; } async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let currency = undefined; if (code !== undefined) { currency = this.currency (code); } const response = await this.privateGetWalletDepositRecords (params); const data = this.safeValue (response, 'data', {}); const rows = this.safeValue (data, 'rows', {}); return this.parseTransactions (rows, currency, since, limit); } parseTransactionStatus (status) { const statuses = { 'Success': 'ok', 'Succeed': 'ok', }; return this.safeString (statuses, status, status); } parseTransaction (transaction, currency = undefined) { const id = this.safeString (transaction, 'id'); const address = this.safeString (transaction, 'address'); const tag = undefined; const txid = this.safeString (transaction, 'txId'); const currencyId = this.safeString (transaction, 'currency'); currency = this.safeCurrency (currencyId, currency); const code = currency['code']; const timestamp = this.safeInteger (transaction, 'time'); let type = this.safeStringLower (transaction, 'type'); const feeCost = this.parseNumber (this.safeString (transaction, 'fee')); let fee = undefined; if (feeCost !== undefined) { type = 'withdrawal'; fee = { 'cost': feeCost, 'currency': code, }; } const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); const amount = this.parseNumber (this.safeString (transaction, 'amount')); return { 'info': transaction, 'id': id, 'txid': txid, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'network': this.safeString (transaction, 'chain'), 'address': address, 'addressTo': address, 'addressFrom': undefined, 'tag': tag, 'tagTo': tag, 'tagFrom': undefined, 'type': type, 'amount': amount, 'currency': code, 'status': status, 'updated': undefined, 'fee': fee, }; } async fetchPositions (symbols = undefined, params = {}) { await this.loadMarkets (); const request = {}; const response = await this.privateGetAccountPositions (this.extend (request, params)); const positions = this.safeValue (response, 'data', {}); const result = []; for (let i = 0; i < positions.length; i++) { const position = positions[i]; result.push (this.parsePosition (position)); } return this.filterByArray (result, 'symbol', symbols, false); } parsePosition (position, market = undefined) { const marketId = this.safeString (position, 'symbol'); market = this.safeMarket (marketId, market); const symbol = market['symbol']; const collateral = this.safeString (position, 'positionMargin'); const notionalString = this.safeString (position, 'value'); const maintenanceMarginPercentageString = this.safeString (position, 'maintMarginReq'); const maintenanceMarginString = Precise.stringMul (notionalString, maintenanceMarginPercentageString); const initialMarginString = this.safeString (position, 'assignedPosBalance'); const initialMarginPercentageString = Precise.stringDiv (initialMarginString, notionalString); const liquidationPrice = this.safeNumber (position, 'liquidationPrice'); const markPriceString = this.safeString (position, 'markPrice'); const contracts = this.safeString (position, 'size'); const contractSize = this.safeValue (market, 'contractSize'); const contractSizeString = this.numberToString (contractSize); const leverage = this.safeNumber (position, 'leverage'); const entryPriceString = this.safeString (position, 'avgEntryPrice'); const rawSide = this.safeString (position, 'positionSide'); const side = (rawSide === 'Buy') ? 'long' : 'short'; let priceDiff = undefined; const currency = this.safeString (position, 'baseCurrency'); if (currency === 'USD') { if (side === 'long') { priceDiff = Precise.stringSub (markPriceString, entryPriceString); } else { priceDiff = Precise.stringSub (entryPriceString, markPriceString); } } else { // inverse if (side === 'long') { priceDiff = Precise.stringSub (Precise.stringDiv ('1', entryPriceString), Precise.stringDiv ('1', markPriceString)); } else { priceDiff = Precise.stringSub (Precise.stringDiv ('1', markPriceString), Precise.stringDiv ('1', entryPriceString)); } } const unrealizedPnl = Precise.stringMul (Precise.stringMul (priceDiff, contracts), contractSizeString); const percentage = Precise.stringMul (Precise.stringDiv (unrealizedPnl, initialMarginString), '100'); const marginRatio = Precise.stringDiv (maintenanceMarginString, collateral); return { 'info': position, 'symbol': symbol, 'contracts': this.parseNumber (contracts), 'contractSize': contractSize, 'unrealizedPnl': this.parseNumber (unrealizedPnl), 'leverage': leverage, 'liquidationPrice': liquidationPrice, 'collateral': this.parseNumber (collateral), 'notional': this.parseNumber (notionalString), 'markPrice': this.parseNumber (markPriceString), // markPrice lags a bit ¯\_(ツ)_/¯ 'entryPrice': this.parseNumber (entryPriceString), 'timestamp': undefined, 'initialMargin': this.parseNumber (initialMarginString), 'initialMarginPercentage': this.parseNumber (initialMarginPercentageString), 'maintenanceMargin': this.parseNumber (maintenanceMarginString), 'maintenanceMarginPercentage': this.parseNumber (maintenanceMarginPercentageString), 'marginRatio': this.parseNumber (marginRatio), 'datetime': undefined, 'marginType': undefined, 'side': side, 'hedged': false, 'percentage': this.parseNumber (percentage), }; } sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { const query = this.omit (params, this.extractParams (path)); const requestPath = '/' + this.implodeParams (path, params); let url = requestPath; let queryString = ''; if (method === 'GET') { if (Object.keys (query).length) { queryString = this.urlencodeWithArrayRepeat (query); url += '?' + queryString; } } if (api === 'private') { this.checkRequiredCredentials (); const timestamp = this.milliseconds (); const xFairdeskRequestExpiry = this.safeInteger (this.options, 'x-fairdesk-request-expiry', 60); const expiry = this.sum (timestamp, xFairdeskRequestExpiry * 1000); const expiryString = expiry.toString (); headers = { 'x-fairdesk-access-key': this.apiKey, 'x-fairdesk-request-expiry': expiryString, }; let payload = ''; if (method === 'POST' || method === 'PUT' || method === 'DELETE') { payload = this.json (params); body = payload; headers['Content-Type'] = 'application/json'; } const auth = '/api/v1/private' + requestPath + queryString + expiryString + payload; headers['x-fairdesk-request-signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); } url = this.implodeHostname (this.urls['api'][api]) + url;