UNPKG

ccxt-look

Version:

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

1,135 lines (1,117 loc) 119 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { TICK_SIZE } = require ('./base/functions/number'); const { ExchangeError, InvalidOrder, BadRequest, InsufficientFunds, OrderNotFound, AuthenticationError, RateLimitExceeded, ExchangeNotAvailable, CancelPending, ArgumentsRequired, PermissionDenied, BadSymbol, DuplicateOrderId, BadResponse } = require ('./base/errors'); const Precise = require ('./base/Precise'); // --------------------------------------------------------------------------- module.exports = class ftx extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'ftx', 'name': 'FTX', 'countries': [ 'BS' ], // Bahamas // hard limit of 7 requests per 200ms => 35 requests per 1000ms => 1000ms / 35 = 28.5714 ms between requests // 10 withdrawal requests per 30 seconds = (1000ms / rateLimit) / (1/3) = 90.1 // cancels do not count towards rateLimit // only 'order-making' requests count towards ratelimit 'rateLimit': 28.57, 'certified': true, 'pro': true, 'hostname': 'ftx.com', // or ftx.us 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg', 'www': 'https://ftx.com', 'api': { 'public': 'https://{hostname}', 'private': 'https://{hostname}', }, 'doc': 'https://github.com/ftexchange/ftx', 'fees': 'https://ftexchange.zendesk.com/hc/en-us/articles/360024479432-Fees', 'referral': { 'url': 'https://ftx.com/#a=ccxt', 'discount': 0.05, }, }, 'has': { 'CORS': undefined, 'spot': true, 'margin': true, 'swap': true, 'future': true, 'option': false, 'cancelAllOrders': true, 'cancelOrder': true, 'createOrder': true, 'createReduceOnlyOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'editOrder': true, 'fetchBalance': true, 'fetchBorrowInterest': true, 'fetchBorrowRate': true, 'fetchBorrowRateHistories': true, 'fetchBorrowRateHistory': true, 'fetchBorrowRates': true, 'fetchClosedOrders': undefined, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDeposits': true, 'fetchFundingFees': undefined, 'fetchFundingHistory': true, 'fetchFundingRate': true, 'fetchFundingRateHistory': true, 'fetchFundingRates': false, 'fetchIndexOHLCV': true, 'fetchLeverageTiers': false, 'fetchMarketLeverageTiers': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': true, 'fetchOrderTrades': true, 'fetchPosition': false, 'fetchPositions': true, 'fetchPositionsRisk': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': false, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransfer': undefined, 'fetchTransfers': undefined, 'fetchWithdrawals': true, 'reduceMargin': false, 'setLeverage': true, 'setMarginMode': false, // FTX only supports cross margin 'setPositionMode': false, 'transfer': true, 'withdraw': true, }, 'timeframes': { '15s': '15', '1m': '60', '5m': '300', '15m': '900', '1h': '3600', '4h': '14400', '1d': '86400', '3d': '259200', '1w': '604800', '2w': '1209600', // the exchange does not align candles to the start of the month // it can only fetch candles in fixed intervals of multiples of whole days // that works for all timeframes, except the monthly timeframe // because months have varying numbers of days '1M': '2592000', }, 'api': { 'public': { 'get': { 'coins': 1, // markets 'markets': 1, 'markets/{market_name}': 1, 'markets/{market_name}/orderbook': 1, // ?depth={depth} 'markets/{market_name}/trades': 1, // ?limit={limit}&start_time={start_time}&end_time={end_time} 'markets/{market_name}/candles': 1, // ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time} // futures 'futures': 1, 'futures/{future_name}': 1, 'futures/{future_name}/stats': 1, 'funding_rates': 1, 'indexes/{index_name}/weights': 1, 'expired_futures': 1, 'indexes/{market_name}/candles': 1, // ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time} // wallet 'wallet/coins': 1, // leverage tokens 'lt/tokens': 1, 'lt/{token_name}': 1, // etfs 'etfs/rebalance_info': 1, // options 'options/requests': 1, 'options/trades': 1, 'options/historical_volumes/BTC': 1, 'stats/24h_options_volume': 1, 'options/open_interest/BTC': 1, 'options/historical_open_interest/BTC': 1, // spot margin 'spot_margin/history': 1, 'spot_margin/borrow_summary': 1, // nfts 'nft/nfts': 1, 'nft/{nft_id}': 1, 'nft/{nft_id}/trades': 1, 'nft/all_trades': 1, 'nft/{nft_id}/account_info': 1, 'nft/collections': 1, // ftx pay 'ftxpay/apps/{user_specific_id}/details': 1, }, 'post': { 'ftxpay/apps/{user_specific_id}/orders': 1, }, }, 'private': { 'get': { // subaccounts 'subaccounts': 1, 'subaccounts/{nickname}/balances': 1, // account 'account': 1, 'positions': 1, // wallet 'wallet/balances': 1, 'wallet/all_balances': 1, 'wallet/deposit_address/{coin}': 1, // ?method={method} 'wallet/deposits': 1, 'wallet/withdrawals': 1, 'wallet/airdrops': 1, 'wallet/withdrawal_fee': 1, 'wallet/saved_addresses': 1, // orders 'orders': 1, // ?market={market} 'orders/history': 1, // ?market={market} 'orders/{order_id}': 1, 'orders/by_client_id/{client_order_id}': 1, // conditional orders 'conditional_orders': 1, // ?market={market} 'conditional_orders/{conditional_order_id}/triggers': 1, 'conditional_orders/history': 1, // ?market={market} 'fills': 1, // ?market={market} 'funding_payments': 1, // leverage tokens 'lt/balances': 1, 'lt/creations': 1, 'lt/redemptions': 1, // options 'options/my_requests': 1, 'options/requests/{request_id}/quotes': 1, 'options/my_quotes': 1, 'options/account_info': 1, 'options/positions': 1, 'options/fills': 1, // staking 'staking/stakes': 1, 'staking/unstake_requests': 1, 'staking/balances': 1, 'staking/staking_rewards': 1, // otc 'otc/quotes/{quoteId}': 1, // spot margin 'spot_margin/borrow_rates': 1, 'spot_margin/lending_rates': 1, 'spot_margin/market_info': 1, // ?market={market} 'spot_margin/borrow_history': 1, 'spot_margin/lending_history': 1, 'spot_margin/offers': 1, 'spot_margin/lending_info': 1, // nfts 'nft/balances': 1, 'nft/bids': 1, 'nft/deposits': 1, 'nft/withdrawals': 1, 'nft/fills': 1, 'nft/gallery/{gallery_id}': 1, 'nft/gallery_settings': 1, // latency statistics 'stats/latency_stats': 1, // pnl 'pnl/historical_changes': 1, }, 'post': { // subaccounts 'subaccounts': 1, 'subaccounts/update_name': 1, 'subaccounts/transfer': 1, // account 'account/leverage': 1, // wallet 'wallet/deposit_address/list': 1, 'wallet/withdrawals': 90, 'wallet/saved_addresses': 1, // orders 'orders': 1, 'conditional_orders': 1, 'orders/{order_id}/modify': 1, 'orders/by_client_id/{client_order_id}/modify': 1, 'conditional_orders/{order_id}/modify': 1, // leverage tokens 'lt/{token_name}/create': 1, 'lt/{token_name}/redeem': 1, // options 'options/requests': 1, 'options/requests/{request_id}/quotes': 1, 'options/quotes/{quote_id}/accept': 1, // staking 'staking/unstake_requests': 1, 'srm_stakes/stakes': 1, // otc 'otc/quotes/{quote_id}/accept': 1, 'otc/quotes': 1, // spot margin 'spot_margin/offers': 1, // nfts 'nft/offer': 1, 'nft/buy': 1, 'nft/auction': 1, 'nft/edit_auction': 1, 'nft/cancel_auction': 1, 'nft/bids': 1, 'nft/redeem': 1, 'nft/gallery_settings': 1, // ftx pay 'ftxpay/apps/{user_specific_id}/orders': 1, }, 'delete': { // subaccounts 'subaccounts': 1, // wallet 'wallet/saved_addresses/{saved_address_id}': 1, // orders 'orders/{order_id}': 1, 'orders/by_client_id/{client_order_id}': 1, 'orders': 1, 'conditional_orders/{order_id}': 1, // options 'options/requests/{request_id}': 1, 'options/quotes/{quote_id}': 1, // staking 'staking/unstake_requests/{request_id}': 1, }, }, }, 'fees': { 'trading': { 'tierBased': true, 'percentage': true, 'maker': this.parseNumber ('0.0002'), 'taker': this.parseNumber ('0.0007'), 'tiers': { 'taker': [ [ this.parseNumber ('0'), this.parseNumber ('0.0007') ], [ this.parseNumber ('2000000'), this.parseNumber ('0.0006') ], [ this.parseNumber ('5000000'), this.parseNumber ('0.00055') ], [ this.parseNumber ('10000000'), this.parseNumber ('0.0005') ], [ this.parseNumber ('25000000'), this.parseNumber ('0.0045') ], [ this.parseNumber ('50000000'), this.parseNumber ('0.0004') ], ], 'maker': [ [ this.parseNumber ('0'), this.parseNumber ('0.0002') ], [ this.parseNumber ('2000000'), this.parseNumber ('0.00015') ], [ this.parseNumber ('5000000'), this.parseNumber ('0.0001') ], [ this.parseNumber ('10000000'), this.parseNumber ('0.00005') ], [ this.parseNumber ('25000000'), this.parseNumber ('0') ], [ this.parseNumber ('50000000'), this.parseNumber ('0') ], ], }, }, 'funding': { 'withdraw': {}, }, }, 'exceptions': { 'exact': { 'Slow down': RateLimitExceeded, // {"error":"Slow down","success":false} 'Size too small for provide': InvalidOrder, // {"error":"Size too small for provide","success":false} 'Not enough balances': InsufficientFunds, // {"error":"Not enough balances","success":false} 'InvalidPrice': InvalidOrder, // {"error":"Invalid price","success":false} 'Size too small': InvalidOrder, // {"error":"Size too small","success":false} 'Size too large': InvalidOrder, // {"error":"Size too large","success":false} 'Invalid price': InvalidOrder, // {"success":false,"error":"Invalid price"} 'Missing parameter price': InvalidOrder, // {"error":"Missing parameter price","success":false} 'Order not found': OrderNotFound, // {"error":"Order not found","success":false} 'Order already closed': InvalidOrder, // {"error":"Order already closed","success":false} 'Trigger price too high': InvalidOrder, // {"error":"Trigger price too high","success":false} 'Trigger price too low': InvalidOrder, // {"error":"Trigger price too low","success":false} 'Order already queued for cancellation': CancelPending, // {"error":"Order already queued for cancellation","success":false} 'Duplicate client order ID': DuplicateOrderId, // {"error":"Duplicate client order ID","success":false} 'Spot orders cannot be reduce-only': InvalidOrder, // {"error":"Spot orders cannot be reduce-only","success":false} 'Invalid reduce-only order': InvalidOrder, // {"error":"Invalid reduce-only order","success":false} 'Account does not have enough balances': InsufficientFunds, // {"success":false,"error":"Account does not have enough balances"} 'Not authorized for subaccount-specific access': PermissionDenied, // {"success":false,"error":"Not authorized for subaccount-specific access"} 'Not approved to trade this product': PermissionDenied, // {"success":false,"error":"Not approved to trade this product"} }, 'broad': { // {"error":"Not logged in","success":false} // {"error":"Not logged in: Invalid API key","success":false} 'Not logged in': AuthenticationError, 'Account does not have enough margin for order': InsufficientFunds, 'Invalid parameter': BadRequest, // {"error":"Invalid parameter start_time","success":false} 'The requested URL was not found on the server': BadRequest, 'No such coin': BadRequest, 'No such subaccount': AuthenticationError, 'No such future': BadSymbol, 'No such market': BadSymbol, 'Do not send more than': RateLimitExceeded, 'An unexpected error occurred': ExchangeNotAvailable, // {"error":"An unexpected error occurred, please try again later (58BC21C795).","success":false} 'Please retry request': ExchangeNotAvailable, // {"error":"Please retry request","success":false} 'Please try again': ExchangeNotAvailable, // {"error":"Please try again","success":false} 'Try again': ExchangeNotAvailable, // {"error":"Try again","success":false} 'Only have permissions for subaccount': PermissionDenied, // {"success":false,"error":"Only have permissions for subaccount *sub_name*"} }, }, 'precisionMode': TICK_SIZE, 'options': { // support for canceling conditional orders // https://github.com/ccxt/ccxt/issues/6669 'fetchMarkets': { // the expiry datetime may be undefined for expiring futures, https://github.com/ccxt/ccxt/pull/12692 'throwOnUndefinedExpiry': false, }, 'cancelOrder': { 'method': 'privateDeleteOrdersOrderId', // privateDeleteConditionalOrdersOrderId }, 'fetchOpenOrders': { 'method': 'privateGetOrders', // privateGetConditionalOrders }, 'fetchOrders': { 'method': 'privateGetOrdersHistory', // privateGetConditionalOrdersHistory }, 'sign': { 'ftx.com': 'FTX', 'ftx.us': 'FTXUS', }, 'networks': { 'SOL': 'sol', 'SPL': 'sol', 'TRX': 'trx', 'TRC20': 'trx', 'ETH': 'erc20', 'ERC20': 'erc20', 'OMNI': 'omni', 'BEP2': 'bep2', 'BNB': 'bep2', 'BEP20': 'bsc', 'BSC': 'bsc', }, }, 'commonCurrencies': { 'AMC': 'AMC Entertainment Holdings', 'STARS': 'StarLaunch', }, }); } async fetchCurrencies (params = {}) { const response = await this.publicGetCoins (params); const currencies = this.safeValue (response, 'result', []); // // { // "success":true, // "result": [ // {"id":"BTC","name":"Bitcoin"}, // {"id":"ETH","name":"Ethereum"}, // {"id":"ETHMOON","name":"10X Long Ethereum Token","underlying":"ETH"}, // {"id":"EOSBULL","name":"3X Long EOS Token","underlying":"EOS"}, // ], // } // const result = {}; for (let i = 0; i < currencies.length; i++) { const currency = currencies[i]; const id = this.safeString (currency, 'id'); const code = this.safeCurrencyCode (id); const name = this.safeString (currency, 'name'); result[code] = { 'id': id, 'code': code, 'info': currency, 'type': undefined, 'name': name, 'active': undefined, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'precision': undefined, 'limits': { 'withdraw': { 'min': undefined, 'max': undefined }, 'amount': { 'min': undefined, 'max': undefined }, }, }; } return result; } async fetchMarkets (params = {}) { const response = await this.publicGetMarkets (params); // // { // 'success': true, // "result": [ // { // "ask":170.37, // "baseCurrency":null, // "bid":170.31, // "change1h":-0.019001554672655036, // "change24h":-0.024841165359738997, // "changeBod":-0.03816406029469881, // "enabled":true, // "last":170.37, // "name":"ETH-PERP", // "price":170.37, // "priceIncrement":0.01, // "quoteCurrency":null, // "quoteVolume24h":7742164.59889, // "sizeIncrement":0.001, // "type":"future", // "underlying":"ETH", // "volumeUsd24h":7742164.59889 // }, // { // "ask":170.44, // "baseCurrency":"ETH", // "bid":170.41, // "change1h":-0.018485459257126403, // "change24h":-0.023825887743413515, // "changeBod":-0.037605872388481086, // "enabled":true, // "last":172.72, // "name":"ETH/USD", // "price":170.44, // "priceIncrement":0.01, // "quoteCurrency":"USD", // "quoteVolume24h":382802.0252, // "sizeIncrement":0.001, // "type":"spot", // "underlying":null, // "volumeUsd24h":382802.0252 // }, // ], // } // // { // name: "BTC-PERP", // enabled: true, // postOnly: false, // priceIncrement: "1.0", // sizeIncrement: "0.0001", // minProvideSize: "0.001", // last: "60397.0", // bid: "60387.0", // ask: "60388.0", // price: "60388.0", // type: "future", // baseCurrency: null, // quoteCurrency: null, // underlying: "BTC", // restricted: false, // highLeverageFeeExempt: true, // change1h: "-0.0036463231533270636", // change24h: "-0.01844838515677064", // changeBod: "-0.010130151132675475", // quoteVolume24h: "2892083192.6099", // volumeUsd24h: "2892083192.6099" // } // let allFuturesResponse = undefined; if (this.has['future'] && (this.hostname !== 'ftx.us')) { allFuturesResponse = await this.publicGetFutures (); } // // { // success: true, // result: [ // { // name: "1INCH-PERP", // underlying: "1INCH", // description: "1INCH Token Perpetual Futures", // type: "perpetual", // expiry: null, // perpetual: true, // expired: false, // enabled: true, // postOnly: false, // priceIncrement: "0.0001", // sizeIncrement: "1.0", // last: "2.5556", // bid: "2.5555", // ask: "2.5563", // index: "2.5612449804010833", // mark: "2.5587", // imfFactor: "0.0005", // lowerBound: "2.4315", // upperBound: "2.6893", // underlyingDescription: "1INCH Token", // expiryDescription: "Perpetual", // moveStart: null, // marginPrice: "2.5587", // positionLimitWeight: "20.0", // group: "perpetual", // change1h: "0.00799716356760164", // change24h: "0.004909276569004792", // changeBod: "0.008394419484511705", // volumeUsd24h: "17834492.0818", // volume: "7224898.0", // openInterest: "5597917.0", // openInterestUsd: "14323390.2279", // }, // ... // ], // } // const result = []; const markets = this.safeValue (response, 'result', []); const allFutures = this.safeValue (allFuturesResponse, 'result', []); const allFuturesDict = this.indexBy (allFutures, 'name'); for (let i = 0; i < markets.length; i++) { const market = markets[i]; const id = this.safeString (market, 'name'); const future = this.safeValue (allFuturesDict, id); const marketType = this.safeString (market, 'type'); const contract = (marketType === 'future'); const baseId = this.safeString2 (market, 'baseCurrency', 'underlying'); const quoteId = this.safeString (market, 'quoteCurrency', 'USD'); const settleId = contract ? 'USD' : undefined; let base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const settle = this.safeCurrencyCode (settleId); const spot = !contract; const margin = !contract; const perpetual = this.safeValue (future, 'perpetual', false); const swap = perpetual; const option = false; const isFuture = contract && !swap; let expiry = undefined; const expiryDatetime = this.safeString (future, 'expiry'); let type = 'spot'; let symbol = base + '/' + quote; if (swap) { type = 'swap'; symbol = base + '/' + quote + ':' + settle; } else if (isFuture) { type = 'future'; expiry = this.parse8601 (expiryDatetime); if (expiry === undefined) { // it is likely a future that is expiring in this moment const options = this.safeValue (this.options, 'fetchMarkets', {}); const throwOnUndefinedExpiry = this.safeValue (options, 'throwOnUndefinedExpiry', false); if (throwOnUndefinedExpiry) { throw new BadResponse (this.id + " symbol '" + id + "' is a future contract with an invalid expiry datetime."); } else { continue; } } const parsedId = id.split ('-'); const length = parsedId.length; if (length > 2) { // handling for MOVE contracts // BTC-MOVE-2022Q1 // BTC-MOVE-0106 // BTC-MOVE-WK-0121 parsedId.pop (); // remove expiry // [ 'BTC', 'MOVE' ] // [ 'BTC', 'MOVE' ] // [ 'BTC', 'MOVE', 'WK' ] base = parsedId.join ('-'); } symbol = base + '/' + quote + ':' + settle + '-' + this.yymmdd (expiry, ''); } // check if a market is a spot or future market const sizeIncrement = this.safeString (market, 'sizeIncrement'); const minProvideSize = this.safeString (market, 'minProvideSize'); let minAmountString = sizeIncrement; if (minProvideSize !== undefined) { minAmountString = Precise.stringGt (minProvideSize, sizeIncrement) ? sizeIncrement : minProvideSize; } result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': type, 'spot': spot, 'margin': margin, 'swap': swap, 'future': isFuture, 'option': option, 'active': this.safeValue (market, 'enabled'), 'contract': contract, 'linear': contract ? true : undefined, 'inverse': contract ? false : undefined, 'contractSize': this.parseNumber ('1'), 'expiry': expiry, 'expiryDatetime': this.iso8601 (expiry), 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': this.parseNumber (sizeIncrement), 'price': this.safeNumber (market, 'priceIncrement'), }, 'limits': { 'leverage': { 'min': this.parseNumber ('1'), 'max': this.parseNumber ('20'), }, 'amount': { 'min': this.parseNumber (minAmountString), 'max': undefined, }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'info': market, }); } return result; } parseTicker (ticker, market = undefined) { // // { // "ask":171.29, // "baseCurrency":null, // base currency for spot markets // "bid":171.24, // "change1h":-0.0012244897959183673, // "change24h":-0.031603346901854366, // "changeBod":-0.03297013492914808, // "enabled":true, // "last":171.44, // "name":"ETH-PERP", // "price":171.29, // "priceIncrement":0.01, // "quoteCurrency":null, // quote currency for spot markets // "quoteVolume24h":8570651.12113, // "sizeIncrement":0.001, // "type":"future", // "underlying":"ETH", // null for spot markets // "volumeUsd24h":8570651.12113, // } // const marketId = this.safeString (ticker, 'name'); if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; } const symbol = this.safeSymbol (marketId, market); const last = this.safeString (ticker, 'last'); const timestamp = this.safeTimestamp (ticker, 'time', this.milliseconds ()); let percentage = this.safeString (ticker, 'change24h'); if (percentage !== undefined) { percentage = Precise.stringMul (percentage, '100'); } 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': this.safeString (ticker, 'bidSize'), 'ask': this.safeString (ticker, 'ask'), 'askVolume': this.safeString (ticker, 'askSize'), 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': percentage, 'average': undefined, 'baseVolume': undefined, 'quoteVolume': this.safeString (ticker, 'quoteVolume24h'), 'info': ticker, }, market, false); } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'market_name': market['id'], }; const response = await this.publicGetMarketsMarketName (this.extend (request, params)); // // { // "success":true, // "result":{ // "ask":171.29, // "baseCurrency":null, // base currency for spot markets // "bid":171.24, // "change1h":-0.0012244897959183673, // "change24h":-0.031603346901854366, // "changeBod":-0.03297013492914808, // "enabled":true, // "last":171.44, // "name":"ETH-PERP", // "price":171.29, // "priceIncrement":0.01, // "quoteCurrency":null, // quote currency for spot markets // "quoteVolume24h":8570651.12113, // "sizeIncrement":0.001, // "type":"future", // "underlying":"ETH", // null for spot markets // "volumeUsd24h":8570651.12113, // } // } // const result = this.safeValue (response, 'result', {}); return this.parseTicker (result, market); } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); const response = await this.publicGetMarkets (params); // // { // 'success': true, // "result": [ // { // "ask":170.44, // "baseCurrency":"ETH", // "bid":170.41, // "change1h":-0.018485459257126403, // "change24h":-0.023825887743413515, // "changeBod":-0.037605872388481086, // "enabled":true, // "last":172.72, // "name":"ETH/USD", // "price":170.44, // "priceIncrement":0.01, // "quoteCurrency":"USD", // "quoteVolume24h":382802.0252, // "sizeIncrement":0.001, // "type":"spot", // "underlying":null, // "volumeUsd24h":382802.0252 // }, // ], // } // const tickers = this.safeValue (response, 'result', []); return this.parseTickers (tickers, symbols); } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'market_name': market['id'], }; if (limit !== undefined) { request['depth'] = limit; // max 100, default 20 } const response = await this.publicGetMarketsMarketNameOrderbook (this.extend (request, params)); // // { // "success":true, // "result":{ // "asks":[ // [171.95,279.865], // [171.98,102.42], // [171.99,124.11], // ], // "bids":[ // [171.93,69.749], // [171.9,288.325], // [171.88,87.47], // ], // } // } // const result = this.safeValue (response, 'result', {}); return this.parseOrderBook (result, symbol); } parseOHLCV (ohlcv, market = undefined) { // // { // "close":177.23, // "high":177.45, // "low":177.2, // "open":177.43, // "startTime":"2019-10-17T13:27:00+00:00", // "time":1571318820000.0, // "volume":0.0 // } // return [ this.safeInteger (ohlcv, 'time'), this.safeNumber (ohlcv, 'open'), this.safeNumber (ohlcv, 'high'), this.safeNumber (ohlcv, 'low'), this.safeNumber (ohlcv, 'close'), this.safeNumber (ohlcv, 'volume'), ]; } getMarketId (symbol, key, params = {}) { const parts = this.getMarketParams (symbol, key, params); return this.safeString (parts, 1, symbol); } getMarketParams (symbol, key, params = {}) { let market = undefined; let marketId = undefined; if (symbol in this.markets) { market = this.market (symbol); marketId = market['id']; } else { marketId = this.safeString (params, key, symbol); } return [ market, marketId ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const [ market, marketId ] = this.getMarketParams (symbol, 'market_name', params); // max 1501 candles, including the current candle when since is not specified const maxLimit = 5000; const defaultLimit = 1500; limit = (limit === undefined) ? defaultLimit : Math.min (limit, maxLimit); const request = { 'resolution': this.timeframes[timeframe], 'market_name': marketId, // 'start_time': parseInt (since / 1000), // 'end_time': this.seconds (), 'limit': limit, }; const price = this.safeString (params, 'price'); params = this.omit (params, 'price'); if (since !== undefined) { const startTime = parseInt (since / 1000); request['start_time'] = startTime; const duration = this.parseTimeframe (timeframe); const endTime = this.sum (startTime, limit * duration); request['end_time'] = Math.min (endTime, this.seconds ()); if (duration > 86400) { const wholeDaysInTimeframe = parseInt (duration / 86400); request['limit'] = Math.min (limit * wholeDaysInTimeframe, maxLimit); } } let method = 'publicGetMarketsMarketNameCandles'; if (price === 'index') { if (symbol in this.markets) { request['market_name'] = market['baseId']; } method = 'publicGetIndexesMarketNameCandles'; } const response = await this[method] (this.extend (request, params)); // // { // "success": true, // "result":[ // { // "close":177.23, // "high":177.45, // "low":177.2, // "open":177.43, // "startTime":"2019-10-17T13:27:00+00:00", // "time":1571318820000.0, // "volume":0.0 // }, // { // "close":177.26, // "high":177.33, // "low":177.23, // "open":177.23, // "startTime":"2019-10-17T13:28:00+00:00", // "time":1571318880000.0, // "volume":0.0 // }, // ], // } // const result = this.safeValue (response, 'result', []); return this.parseOHLCVs (result, market, timeframe, since, limit); } async fetchIndexOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { const request = { 'price': 'index', }; return await this.fetchOHLCV (symbol, timeframe, since, limit, this.extend (request, params)); } parseTrade (trade, market = undefined) { // // fetchTrades (public) // // { // "id":1715826, // "liquidation":false, // "price":171.62, // "side":"buy", // "size":2.095, // "time":"2019-10-18T12:59:54.288166+00:00" // } // // fetchMyTrades (private) // // { // "fee": 20.1374935, // "feeRate": 0.0005, // "feeCurrency": "USD", // "future": "EOS-0329", // "id": 11215, // "liquidity": "taker", // "market": "EOS-0329", // "baseCurrency": null, // "quoteCurrency": null, // "orderId": 8436981, // "price": 4.201, // "side": "buy", // "size": 9587, // "time": "2019-03-27T19:15:10.204619+00:00", // "type": "order" // } // // { // "baseCurrency": "BTC", // "fee": 0, // "feeCurrency": "USD", // "feeRate": 0, // "future": null, // "id": 664079556, // "liquidity": "taker", // "market": null, // "orderId": null, // "price": 34830.61359, // "quoteCurrency": "USD", // "side": "sell", // "size": 0.0005996, // "time": "2021-01-15T16:05:29.246135+00:00", // "tradeId": null, // "type": "otc" // } // // with -ve fee // { // "id": 1171258927, // "fee": -0.0000713875, // "side": "sell", // "size": 1, // "time": "2021-03-11T13:34:35.523627+00:00", // "type": "order", // "price": 14.2775, // "future": null, // "market": "SOL/USD", // "feeRate": -0.000005, // "orderId": 33182929044, // "tradeId": 582936801, // "liquidity": "maker", // "feeCurrency": "USD", // "baseCurrency": "SOL", // "quoteCurrency": "USD" // } // // // from OTC order // { // "id": 1172129651, // "fee": 0, // "side": "sell", // "size": 1.47568846, // "time": "2021-03-11T15:04:46.893383+00:00", // "type": "otc", // "price": 14.60932598, // "future": null, // "market": null, // "feeRate": 0, // "orderId": null, // "tradeId": null, // "liquidity": "taker", // "feeCurrency": "USD", // "baseCurrency": "BCHA", // "quoteCurrency": "USD" // } // const id = this.safeString (trade, 'id'); const takerOrMaker = this.safeString (trade, 'liquidity'); // a workaround for the OTC trades, they don't have a symbol const baseId = this.safeString (trade, 'baseCurrency'); const quoteId = this.safeString (trade, 'quoteCurrency'); let defaultMarketId = undefined; if ((baseId !== undefined) && (quoteId !== undefined)) { defaultMarketId = baseId + '/' + quoteId; } const marketId = this.safeString (trade, 'market', defaultMarketId); market = this.safeMarket (marketId, market, '/'); const symbol = market['symbol']; const timestamp = this.parse8601 (this.safeString (trade, 'time')); const priceString = this.safeString (trade, 'price'); const amountString = this.safeString (trade, 'size'); const side = this.safeString (trade, 'side'); let fee = undefined; const feeCostString = this.safeString (trade, 'fee'); if (feeCostString !== undefined) { const feeCurrencyId = this.safeString (trade, 'feeCurrency'); const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); fee = { 'cost': feeCostString, 'currency': feeCurrencyCode, 'rate': this.safeString (trade, 'feeRate'), }; } const orderId = this.safeString (trade, 'orderId'); return this.safeTrade ({ 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'id': id, 'order': orderId, 'type': undefined, 'takerOrMaker': takerOrMaker, 'side': side, 'price': priceString, 'amount': amountString, 'cost': undefined, 'fee': fee, }, market); } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const [ market, marketId ] = this.getMarketParams (symbol, 'market_name', params); const request = { 'market_name': marketId, }; if (since !== undefined) { // the exchange aligns results to end_time returning 5000 trades max // the user must set the end_time (in seconds) close enough to start_time // for a proper pagination, fetch the most recent trades first // then set the end_time parameter to the timestamp of the last trade // start_time and end_time must be in seconds, divided by a thousand request['start_time'] = parseInt (since / 1000); // start_time doesn't work without end_time request['end_time'] = this.seconds (); } if (limit !== undefined) { request['limit'] = limit; } const response = await this.publicGetMarketsMarketNameTrades (this.extend (request, params)); // // { // "success":true, // "result":[ // { // "id":1715826, // "liquidation":false, // "price":171.62, // "side":"buy", // "size":2.095, // "time":"2019-10-18T12:59:54.288166+00:00" // }, // { // "id":1715763, // "liquidation":false, // "price":171.89, // "side":"sell", // "size":1.477, // "time":"2019-10-18T12:58:38.443734+00:00" // }, // ], // } // const result = this.safeValue (response, 'result', []); return this.parseTrades (result, market, since, limit); } async fetchTradingFees (params = {}) { await this.loadMarkets (); co