UNPKG

@proton/ccxt

Version:

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

1,136 lines (1,134 loc) 72.5 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import Exchange from './abstract/bitfinex.js'; import { NotSupported, RateLimitExceeded, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, ExchangeNotAvailable, InsufficientFunds, InvalidOrder, OrderNotFound, InvalidNonce, BadSymbol } from './base/errors.js'; import { Precise } from './base/Precise.js'; import { SIGNIFICANT_DIGITS, DECIMAL_PLACES, TRUNCATE, ROUND } from './base/functions/number.js'; import { sha384 } from './static_dependencies/noble-hashes/sha512.js'; // --------------------------------------------------------------------------- export default class bitfinex extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'bitfinex', 'name': 'Bitfinex', 'countries': ['VG'], 'version': 'v1', // cheapest is 90 requests a minute = 1.5 requests per second on average => ( 1000ms / 1.5) = 666.666 ms between requests on average 'rateLimit': 666.666, 'pro': true, // new metainfo interface 'has': { 'CORS': undefined, 'spot': true, 'margin': undefined, 'swap': undefined, 'future': undefined, 'option': undefined, 'cancelAllOrders': true, 'cancelOrder': true, 'createDepositAddress': true, 'createOrder': true, 'editOrder': true, 'fetchBalance': true, 'fetchClosedOrders': true, 'fetchDepositAddress': true, 'fetchDeposits': false, 'fetchDepositsWithdrawals': true, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': true, 'fetchIndexOHLCV': false, 'fetchLeverageTiers': false, 'fetchMarginMode': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchPositionMode': false, 'fetchPositions': true, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': false, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransactionFees': true, 'fetchTransactions': true, 'transfer': true, 'withdraw': true, }, 'timeframes': { '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '3h': '3h', '4h': '4h', '6h': '6h', '12h': '12h', '1d': '1D', '1w': '7D', '2w': '14D', '1M': '1M', }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg', 'api': { 'v2': 'https://api-pub.bitfinex.com', 'public': 'https://api.bitfinex.com', 'private': 'https://api.bitfinex.com', }, 'www': 'https://www.bitfinex.com', 'referral': 'https://www.bitfinex.com/?refcode=P61eYxFL', 'doc': [ 'https://docs.bitfinex.com/v1/docs', 'https://github.com/bitfinexcom/bitfinex-api-node', ], }, 'api': { // v2 symbol ids require a 't' prefix // just the public part of it (use bitfinex2 for everything else) 'v2': { 'get': { 'platform/status': 3, 'tickers': 1, 'ticker/{symbol}': 1, 'tickers/hist': 1, 'trades/{symbol}/hist': 1, 'book/{symbol}/{precision}': 0.375, 'book/{symbol}/P0': 0.375, 'book/{symbol}/P1': 0.375, 'book/{symbol}/P2': 0.375, 'book/{symbol}/P3': 0.375, 'book/{symbol}/R0': 0.375, 'stats1/{key}:{size}:{symbol}:{side}/{section}': 1, 'stats1/{key}:{size}:{symbol}/{section}': 1, 'stats1/{key}:{size}:{symbol}:long/last': 1, 'stats1/{key}:{size}:{symbol}:long/hist': 1, 'stats1/{key}:{size}:{symbol}:short/last': 1, 'stats1/{key}:{size}:{symbol}:short/hist': 1, 'candles/trade:{timeframe}:{symbol}/{section}': 1, 'candles/trade:{timeframe}:{symbol}/last': 1, 'candles/trade:{timeframe}:{symbol}/hist': 1, }, }, 'public': { 'get': { 'book/{symbol}': 1, // 'candles/{symbol}':0, 'lendbook/{currency}': 6, 'lends/{currency}': 3, 'pubticker/{symbol}': 3, 'stats/{symbol}': 6, 'symbols': 18, 'symbols_details': 18, 'tickers': 1, 'trades/{symbol}': 3, // 60 requests a minute = 1 request per second => (1000ms / rateLimit) / 1 = 1.5 ... but only works if set to 3 }, }, 'private': { 'post': { 'account_fees': 18, 'account_infos': 6, 'balances': 9.036, 'basket_manage': 6, 'credits': 6, 'deposit/new': 18, 'funding/close': 6, 'history': 6, 'history/movements': 6, 'key_info': 6, 'margin_infos': 3, 'mytrades': 3, 'mytrades_funding': 6, 'offer/cancel': 6, 'offer/new': 6, 'offer/status': 6, 'offers': 6, 'offers/hist': 90.03, 'order/cancel': 0.2, 'order/cancel/all': 0.2, 'order/cancel/multi': 0.2, 'order/cancel/replace': 0.2, 'order/new': 0.2, 'order/new/multi': 0.2, 'order/status': 0.2, 'orders': 0.2, 'orders/hist': 90.03, 'position/claim': 18, 'position/close': 18, 'positions': 18, 'summary': 18, 'taken_funds': 6, 'total_taken_funds': 6, 'transfer': 18, 'unused_taken_funds': 6, 'withdraw': 18, }, }, }, 'fees': { 'trading': { 'feeSide': 'get', 'tierBased': true, 'percentage': true, 'maker': this.parseNumber('0.001'), 'taker': this.parseNumber('0.002'), 'tiers': { 'taker': [ [this.parseNumber('0'), this.parseNumber('0.002')], [this.parseNumber('500000'), this.parseNumber('0.002')], [this.parseNumber('1000000'), this.parseNumber('0.002')], [this.parseNumber('2500000'), this.parseNumber('0.002')], [this.parseNumber('5000000'), this.parseNumber('0.002')], [this.parseNumber('7500000'), this.parseNumber('0.002')], [this.parseNumber('10000000'), this.parseNumber('0.0018')], [this.parseNumber('15000000'), this.parseNumber('0.0016')], [this.parseNumber('20000000'), this.parseNumber('0.0014')], [this.parseNumber('25000000'), this.parseNumber('0.0012')], [this.parseNumber('30000000'), this.parseNumber('0.001')], ], 'maker': [ [this.parseNumber('0'), this.parseNumber('0.001')], [this.parseNumber('500000'), this.parseNumber('0.0008')], [this.parseNumber('1000000'), this.parseNumber('0.0006')], [this.parseNumber('2500000'), this.parseNumber('0.0004')], [this.parseNumber('5000000'), this.parseNumber('0.0002')], [this.parseNumber('7500000'), this.parseNumber('0')], [this.parseNumber('10000000'), this.parseNumber('0')], [this.parseNumber('15000000'), this.parseNumber('0')], [this.parseNumber('20000000'), this.parseNumber('0')], [this.parseNumber('25000000'), this.parseNumber('0')], [this.parseNumber('30000000'), this.parseNumber('0')], ], }, }, 'funding': { 'tierBased': false, 'percentage': false, // Actually deposit fees are free for larger deposits (> $1000 USD equivalent) // these values below are deprecated, we should not hardcode fees and limits anymore // to be reimplemented with bitfinex funding fees from their API or web endpoints 'deposit': {}, 'withdraw': {}, }, }, // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method 'commonCurrencies': { 'ALG': 'ALGO', 'AMP': 'AMPL', 'ATO': 'ATOM', 'BCHABC': 'XEC', 'BCHN': 'BCH', 'DAT': 'DATA', 'DOG': 'MDOGE', 'DSH': 'DASH', // https://github.com/ccxt/ccxt/issues/7399 // https://coinmarketcap.com/currencies/pnetwork/ // https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/ 'EDO': 'PNT', 'EUS': 'EURS', 'EUT': 'EURT', 'IDX': 'ID', 'IOT': 'IOTA', 'IQX': 'IQ', 'LUNA': 'LUNC', 'LUNA2': 'LUNA', 'MNA': 'MANA', 'ORS': 'ORS Group', 'PAS': 'PASS', 'QSH': 'QASH', 'QTM': 'QTUM', 'RBT': 'RBTC', 'SNG': 'SNGLS', 'STJ': 'STORJ', 'TERRAUST': 'USTC', 'TSD': 'TUSD', 'YGG': 'YEED', 'YYW': 'YOYOW', 'UDC': 'USDC', 'UST': 'USDT', 'VSY': 'VSYS', 'WAX': 'WAXP', 'XCH': 'XCHF', 'ZBT': 'ZB', }, 'exceptions': { 'exact': { 'temporarily_unavailable': ExchangeNotAvailable, 'Order could not be cancelled.': OrderNotFound, 'No such order found.': OrderNotFound, 'Order price must be positive.': InvalidOrder, 'Could not find a key matching the given X-BFX-APIKEY.': AuthenticationError, 'Key price should be a decimal number, e.g. "123.456"': InvalidOrder, 'Key amount should be a decimal number, e.g. "123.456"': InvalidOrder, 'ERR_RATE_LIMIT': RateLimitExceeded, 'Ratelimit': RateLimitExceeded, 'Nonce is too small.': InvalidNonce, 'No summary found.': ExchangeError, 'Cannot evaluate your available balance, please try again': ExchangeNotAvailable, 'Unknown symbol': BadSymbol, 'Cannot complete transfer. Exchange balance insufficient.': InsufficientFunds, 'Momentary balance check. Please wait few seconds and try the transfer again.': ExchangeError, }, 'broad': { 'Invalid X-BFX-SIGNATURE': AuthenticationError, 'This API key does not have permission': PermissionDenied, 'not enough exchange balance for ': InsufficientFunds, 'minimum size for ': InvalidOrder, 'Invalid order': InvalidOrder, 'The available balance is only': InsufficientFunds, // {"status":"error","message":"Cannot withdraw 1.0027 ETH from your exchange wallet. The available balance is only 0.0 ETH. If you have limit orders, open positions, unused or active margin funding, this will decrease your available balance. To increase it, you can cancel limit orders or reduce/close your positions.","withdrawal_id":0,"fees":"0.0027"} }, }, 'precisionMode': SIGNIFICANT_DIGITS, 'options': { 'currencyNames': { 'AGI': 'agi', 'AID': 'aid', 'AIO': 'aio', 'ANT': 'ant', 'AVT': 'aventus', 'BAT': 'bat', // https://github.com/ccxt/ccxt/issues/5833 'BCH': 'bab', // 'BCH': 'bcash', // undocumented 'BCI': 'bci', 'BFT': 'bft', 'BSV': 'bsv', 'BTC': 'bitcoin', 'BTG': 'bgold', 'CFI': 'cfi', 'COMP': 'comp', 'DAI': 'dai', 'DADI': 'dad', 'DASH': 'dash', 'DATA': 'datacoin', 'DTH': 'dth', 'EDO': 'eidoo', 'ELF': 'elf', 'EOS': 'eos', 'ETC': 'ethereumc', 'ETH': 'ethereum', 'ETP': 'metaverse', 'FUN': 'fun', 'GNT': 'golem', 'IOST': 'ios', 'IOTA': 'iota', // https://github.com/ccxt/ccxt/issues/5833 'LEO': 'let', // 'LEO': 'les', // EOS chain 'LINK': 'link', 'LRC': 'lrc', 'LTC': 'litecoin', 'LYM': 'lym', 'MANA': 'mna', 'MIT': 'mit', 'MKR': 'mkr', 'MTN': 'mtn', 'NEO': 'neo', 'ODE': 'ode', 'OMG': 'omisego', 'OMNI': 'mastercoin', 'QASH': 'qash', 'QTUM': 'qtum', 'RCN': 'rcn', 'RDN': 'rdn', 'REP': 'rep', 'REQ': 'req', 'RLC': 'rlc', 'SAN': 'santiment', 'SNGLS': 'sng', 'SNT': 'status', 'SPANK': 'spk', 'STORJ': 'stj', 'TNB': 'tnb', 'TRX': 'trx', 'TUSD': 'tsd', 'USD': 'wire', 'USDC': 'udc', 'UTK': 'utk', 'USDT': 'tetheruso', // 'USDT': 'tetheruse', // Tether on ERC20 // 'USDT': 'tetherusl', // Tether on Liquid // 'USDT': 'tetherusx', // Tether on Tron // 'USDT': 'tetheruss', // Tether on EOS 'VEE': 'vee', 'WAX': 'wax', 'XLM': 'xlm', 'XMR': 'monero', 'XRP': 'ripple', 'XVG': 'xvg', 'YOYOW': 'yoyow', 'ZEC': 'zcash', 'ZRX': 'zrx', 'XTZ': 'xtz', }, 'orderTypes': { 'limit': 'exchange limit', 'market': 'exchange market', }, 'fiat': { 'USD': 'USD', 'EUR': 'EUR', 'JPY': 'JPY', 'GBP': 'GBP', 'CNH': 'CNH', }, 'accountsByType': { 'spot': 'exchange', 'margin': 'trading', 'funding': 'deposit', 'swap': 'trading', }, }, }); } async fetchTransactionFees(codes = undefined, params = {}) { /** * @method * @name bitfinex#fetchTransactionFees * @description *DEPRECATED* please use fetchDepositWithdrawFees instead * @see https://docs.bitfinex.com/v1/reference/rest-auth-fees * @param {[string]|undefined} codes list of unified currency codes * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {[object]} a list of [fees structures]{@link https://docs.ccxt.com/#/?id=fee-structure} */ await this.loadMarkets(); const result = {}; const response = await this.privatePostAccountFees(params); // // { // 'withdraw': { // 'BTC': '0.0004', // } // } // const fees = this.safeValue(response, 'withdraw'); const ids = Object.keys(fees); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const code = this.safeCurrencyCode(id); if ((codes !== undefined) && !this.inArray(code, codes)) { continue; } result[code] = { 'withdraw': this.safeNumber(fees, id), 'deposit': {}, 'info': this.safeNumber(fees, id), }; } return result; } async fetchDepositWithdrawFees(codes = undefined, params = {}) { /** * @method * @name bitfinex#fetchDepositWithdrawFees * @description fetch deposit and withdraw fees * @see https://docs.bitfinex.com/v1/reference/rest-auth-fees * @param {[string]|undefined} codes list of unified currency codes * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {[object]} a list of [fees structures]{@link https://docs.ccxt.com/#/?id=fee-structure} */ await this.loadMarkets(); const response = await this.privatePostAccountFees(params); // // { // 'withdraw': { // 'BTC': '0.0004', // ... // } // } // const withdraw = this.safeValue(response, 'withdraw'); return this.parseDepositWithdrawFees(withdraw, codes); } parseDepositWithdrawFee(fee, currency = undefined) { // // '0.0004' // return { 'withdraw': { 'fee': this.parseNumber(fee), 'percentage': undefined, }, 'deposit': { 'fee': undefined, 'percentage': undefined, }, 'networks': {}, 'info': fee, }; } async fetchTradingFees(params = {}) { /** * @method * @name bitfinex#fetchTradingFees * @description fetch the trading fees for multiple markets * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} a dictionary of [fee structures]{@link https://docs.ccxt.com/#/?id=fee-structure} indexed by market symbols */ await this.loadMarkets(); const response = await this.privatePostSummary(params); // // { // time: '2022-02-23T16:05:47.659000Z', // status: { resid_hint: null, login_last: '2022-02-23T16:05:48Z' }, // is_locked: false, // leo_lev: '0', // leo_amount_avg: '0.0', // trade_vol_30d: [ // { // curr: 'Total (USD)', // vol: '0.0', // vol_safe: '0.0', // vol_maker: '0.0', // vol_BFX: '0.0', // vol_BFX_safe: '0.0', // vol_BFX_maker: '0.0' // } // ], // fees_funding_30d: {}, // fees_funding_total_30d: '0', // fees_trading_30d: {}, // fees_trading_total_30d: '0', // rebates_trading_30d: {}, // rebates_trading_total_30d: '0', // maker_fee: '0.001', // taker_fee: '0.002', // maker_fee_2crypto: '0.001', // maker_fee_2stablecoin: '0.001', // maker_fee_2fiat: '0.001', // maker_fee_2deriv: '0.0002', // taker_fee_2crypto: '0.002', // taker_fee_2stablecoin: '0.002', // taker_fee_2fiat: '0.002', // taker_fee_2deriv: '0.00065', // deriv_maker_rebate: '0.0002', // deriv_taker_fee: '0.00065', // trade_last: null // } // const result = {}; const fiat = this.safeValue(this.options, 'fiat', {}); const makerFee = this.safeNumber(response, 'maker_fee'); const takerFee = this.safeNumber(response, 'taker_fee'); const makerFee2Fiat = this.safeNumber(response, 'maker_fee_2fiat'); const takerFee2Fiat = this.safeNumber(response, 'taker_fee_2fiat'); const makerFee2Deriv = this.safeNumber(response, 'maker_fee_2deriv'); const takerFee2Deriv = this.safeNumber(response, 'taker_fee_2deriv'); for (let i = 0; i < this.symbols.length; i++) { const symbol = this.symbols[i]; const market = this.market(symbol); const fee = { 'info': response, 'symbol': symbol, 'percentage': true, 'tierBased': true, }; if (market['quote'] in fiat) { fee['maker'] = makerFee2Fiat; fee['taker'] = takerFee2Fiat; } else if (market['contract']) { fee['maker'] = makerFee2Deriv; fee['taker'] = takerFee2Deriv; } else { fee['maker'] = makerFee; fee['taker'] = takerFee; } result[symbol] = fee; } return result; } async fetchMarkets(params = {}) { /** * @method * @name bitfinex#fetchMarkets * @description retrieves data on all markets for bitfinex * @param {object} params extra parameters specific to the exchange api endpoint * @returns {[object]} an array of objects representing market data */ const ids = await this.publicGetSymbols(); // // [ "btcusd", "ltcusd", "ltcbtc" ] // const details = await this.publicGetSymbolsDetails(); // // [ // { // "pair":"btcusd", // "price_precision":5, // "initial_margin":"10.0", // "minimum_margin":"5.0", // "maximum_order_size":"2000.0", // "minimum_order_size":"0.0002", // "expiration":"NA", // "margin":true // }, // ] // const result = []; for (let i = 0; i < details.length; i++) { const market = details[i]; let id = this.safeString(market, 'pair'); if (!this.inArray(id, ids)) { continue; } id = id.toUpperCase(); let baseId = undefined; let quoteId = undefined; if (id.indexOf(':') >= 0) { const parts = id.split(':'); baseId = parts[0]; quoteId = parts[1]; } else { baseId = id.slice(0, 3); quoteId = id.slice(3, 6); } const base = this.safeCurrencyCode(baseId); const quote = this.safeCurrencyCode(quoteId); const symbol = base + '/' + quote; let type = 'spot'; if (id.indexOf('F0') > -1) { type = 'swap'; } result.push({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'type': type, 'spot': (type === 'spot'), 'margin': this.safeValue(market, 'margin'), 'swap': (type === 'swap'), 'future': false, 'option': false, 'active': true, 'contract': (type === 'swap'), 'linear': undefined, 'inverse': undefined, 'contractSize': undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { // https://docs.bitfinex.com/docs/introduction#amount-precision // The amount field allows up to 8 decimals. // Anything exceeding this will be rounded to the 8th decimal. 'amount': parseInt('8'), 'price': this.safeInteger(market, 'price_precision'), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': this.safeNumber(market, 'minimum_order_size'), 'max': this.safeNumber(market, 'maximum_order_size'), }, 'price': { 'min': this.parseNumber('1e-8'), 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'info': market, }); } return result; } amountToPrecision(symbol, amount) { // https://docs.bitfinex.com/docs/introduction#amount-precision // The amount field allows up to 8 decimals. // Anything exceeding this will be rounded to the 8th decimal. symbol = this.safeSymbol(symbol); return this.decimalToPrecision(amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); } priceToPrecision(symbol, price) { symbol = this.safeSymbol(symbol); price = this.decimalToPrecision(price, ROUND, this.markets[symbol]['precision']['price'], this.precisionMode); // https://docs.bitfinex.com/docs/introduction#price-precision // The precision level of all trading prices is based on significant figures. // All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals (e.g. 1.2345, 123.45, 1234.5, 0.00012345). // Prices submit with a precision larger than 5 will be cut by the API. return this.decimalToPrecision(price, TRUNCATE, 8, DECIMAL_PLACES); } async fetchBalance(params = {}) { /** * @method * @name bitfinex#fetchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ await this.loadMarkets(); const accountsByType = this.safeValue(this.options, 'accountsByType', {}); const requestedType = this.safeString(params, 'type', 'exchange'); const accountType = this.safeString(accountsByType, requestedType, requestedType); if (accountType === undefined) { const keys = Object.keys(accountsByType); throw new ExchangeError(this.id + ' fetchBalance() type parameter must be one of ' + keys.join(', ')); } const query = this.omit(params, 'type'); const response = await this.privatePostBalances(query); // [ { type: 'deposit', // currency: 'btc', // amount: '0.00116721', // available: '0.00116721' }, // { type: 'exchange', // currency: 'ust', // amount: '0.0000002', // available: '0.0000002' }, // { type: 'trading', // currency: 'btc', // amount: '0.0005', // available: '0.0005' } ], const result = { 'info': response }; const isDerivative = requestedType === 'derivatives'; for (let i = 0; i < response.length; i++) { const balance = response[i]; const type = this.safeString(balance, 'type'); const currencyId = this.safeStringLower(balance, 'currency', ''); const start = currencyId.length - 2; const isDerivativeCode = currencyId.slice(start) === 'f0'; // this will only filter the derivative codes if the requestedType is 'derivatives' const derivativeCondition = (!isDerivative || isDerivativeCode); if ((accountType === type) && derivativeCondition) { const code = this.safeCurrencyCode(currencyId); // bitfinex had BCH previously, now it's BAB, but the old // BCH symbol is kept for backward-compatibility // we need a workaround here so that the old BCH balance // would not override the new BAB balance (BAB is unified to BCH) // https://github.com/ccxt/ccxt/issues/4989 if (!(code in result)) { const account = this.account(); account['free'] = this.safeString(balance, 'available'); account['total'] = this.safeString(balance, 'amount'); result[code] = account; } } } return this.safeBalance(result); } async transfer(code, amount, fromAccount, toAccount, params = {}) { /** * @method * @name bitfinex#transfer * @description transfer currency internally between wallets on the same account * @param {string} code unified currency code * @param {float} amount amount to transfer * @param {string} fromAccount account to transfer from * @param {string} toAccount account to transfer to * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} a [transfer structure]{@link https://docs.ccxt.com/#/?id=transfer-structure} */ // transferring between derivatives wallet and regular wallet is not documented in their API // however we support it in CCXT (from just looking at web inspector) await this.loadMarkets(); const accountsByType = this.safeValue(this.options, 'accountsByType', {}); const fromId = this.safeString(accountsByType, fromAccount, fromAccount); const toId = this.safeString(accountsByType, toAccount, toAccount); const currency = this.currency(code); const fromCurrencyId = this.convertDerivativesId(currency['id'], fromAccount); const toCurrencyId = this.convertDerivativesId(currency['id'], toAccount); const requestedAmount = this.currencyToPrecision(code, amount); const request = { 'amount': requestedAmount, 'currency': fromCurrencyId, 'currency_to': toCurrencyId, 'walletfrom': fromId, 'walletto': toId, }; const response = await this.privatePostTransfer(this.extend(request, params)); // // [ // { // status: 'success', // message: '0.0001 Bitcoin transfered from Margin to Exchange' // } // ] // const result = this.safeValue(response, 0); const message = this.safeString(result, 'message'); if (message === undefined) { throw new ExchangeError(this.id + ' transfer failed'); } return this.extend(this.parseTransfer(result, currency), { 'fromAccount': fromAccount, 'toAccount': toAccount, 'amount': this.parseNumber(requestedAmount), }); } parseTransfer(transfer, currency = undefined) { // // { // status: 'success', // message: '0.0001 Bitcoin transfered from Margin to Exchange' // } // const timestamp = this.milliseconds(); return { 'info': transfer, 'id': undefined, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'currency': this.safeCurrencyCode(undefined, currency), 'amount': undefined, 'fromAccount': undefined, 'toAccount': undefined, 'status': this.parseTransferStatus(this.safeString(transfer, 'status')), }; } parseTransferStatus(status) { const statuses = { 'SUCCESS': 'ok', }; return this.safeString(statuses, status, status); } convertDerivativesId(currencyId, type) { const start = currencyId.length - 2; const isDerivativeCode = currencyId.slice(start) === 'F0'; if ((type !== 'derivatives' && type !== 'trading' && type !== 'margin') && isDerivativeCode) { currencyId = currencyId.slice(0, start); } else if (type === 'derivatives' && !isDerivativeCode) { currencyId = currencyId + 'F0'; } return currencyId; } async fetchOrderBook(symbol, limit = undefined, params = {}) { /** * @method * @name bitfinex#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int|undefined} limit the maximum amount of order book entries to return * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['limit_bids'] = limit; request['limit_asks'] = limit; } const response = await this.publicGetBookSymbol(this.extend(request, params)); return this.parseOrderBook(response, market['symbol'], undefined, 'bids', 'asks', 'price', 'amount'); } async fetchTickers(symbols = undefined, params = {}) { /** * @method * @name bitfinex#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @param {[string]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ await this.loadMarkets(); symbols = this.marketSymbols(symbols); const response = await this.publicGetTickers(params); const result = {}; for (let i = 0; i < response.length; i++) { const ticker = this.parseTicker(response[i]); const symbol = ticker['symbol']; result[symbol] = ticker; } return this.filterByArray(result, 'symbol', symbols); } async fetchTicker(symbol, params = {}) { /** * @method * @name bitfinex#fetchTicker * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'symbol': market['id'], }; const ticker = await this.publicGetPubtickerSymbol(this.extend(request, params)); return this.parseTicker(ticker, market); } parseTicker(ticker, market = undefined) { const timestamp = this.safeTimestamp(ticker, 'timestamp'); const marketId = this.safeString(ticker, 'pair'); market = this.safeMarket(marketId, market); const symbol = market['symbol']; const last = this.safeString(ticker, 'last_price'); 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, 'ask'), 'askVolume': undefined, 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': this.safeString(ticker, 'mid'), 'baseVolume': this.safeString(ticker, 'volume'), 'quoteVolume': undefined, 'info': ticker, }, market); } parseTrade(trade, market = undefined) { // // fetchTrades (public) v1 // // { // "timestamp":1637258380, // "tid":894452833, // "price":"0.99941", // "amount":"261.38", // "exchange":"bitfinex", // "type":"sell" // } // // { "timestamp":1637258238, // "tid":894452800, // "price":"0.99958", // "amount":"261.90514", // "exchange":"bitfinex", // "type":"buy" // } // // fetchMyTrades (private) v1 // // { // "price":"0.99941", // "amount":"261.38", // "timestamp":"1637258380.0", // "type":"Sell", // "fee_currency":"UST", // "fee_amount":"-0.52245157", // "tid":894452833, // "order_id":78819731373 // } // // { // "price":"0.99958", // "amount":"261.90514", // "timestamp":"1637258238.0", // "type":"Buy", // "fee_currency":"UDC", // "fee_amount":"-0.52381028", // "tid":894452800, // "order_id":78819504838 // } // const id = this.safeString(trade, 'tid'); const timestamp = this.safeTimestamp(trade, 'timestamp'); const type = undefined; const side = this.safeStringLower(trade, 'type'); const orderId = this.safeString(trade, 'order_id'); const priceString = this.safeString(trade, 'price'); const amountString = this.safeString(trade, 'amount'); let fee = undefined; if ('fee_amount' in trade) { const feeCostString = Precise.stringNeg(this.safeString(trade, 'fee_amount')); const feeCurrencyId = this.safeString(trade, 'fee_currency'); const feeCurrencyCode = this.safeCurrencyCode(feeCurrencyId); fee = { 'cost': feeCostString, 'currency': feeCurrencyCode, }; } return this.safeTrade({ 'id': id, 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'symbol': market['symbol'], 'type': type, 'order': orderId, 'side': side, 'takerOrMaker': undefined, 'price': priceString, 'amount': amountString, 'cost': undefined, 'fee': fee, }, market); } async fetchTrades(symbol, since = undefined, limit = 50, params = {}) { /** * @method * @name bitfinex#fetchTrades * @description get the list of most recent trades for a particular symbol * @param {string} symbol unified symbol of the market to fetch trades for * @param {int|undefined} since timestamp in ms of the earliest trade to fetch * @param {int|undefined} limit the maximum amount of trades to fetch * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades} */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'symbol': market['id'], 'limit_trades': limit, }; if (since !== undefined) { request['timestamp'] = this.parseToInt(since / 1000); } const response = await this.publicGetTradesSymbol(this.extend(request, params)); return this.parseTrades(response, market, since, limit); } async fetchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bitfinex#fetchMyTrades * @description fetch all trades made by the user * @param {string} symbol unified market symbol * @param {int|undefined} since the earliest time in ms to fetch trades for * @param {int|undefined} limit the maximum number of trades structures to retrieve * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure} */ if (symbol === undefined) { throw new ArgumentsRequired(this.id + ' fetchMyTrades() requires a `symbol` argument'); } await this.loadMarkets(); const market = this.market(symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['limit_trades'] = limit; } if (since !== undefined) { request['timestamp'] = this.parseToInt(since / 1000); } const response = await this.privatePostMytrades(this.extend(request, params)); return this.parseTrades(response, market, since, limit); } async createOrder(symbol, type, side, amount, price = undefined, params = {}) { /** * @method * @name bitfinex#createOrder * @description create a trade order * @param {string} symbol unified symbol of the market to create an order in * @param {string} type 'market' or 'limit' * @param {string} side 'buy' or 'sell' * @param {float} amount how much of currency you want to trade in units of base currency * @param {float|undefined} price the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ await this.loadMarkets(); const market = this.market(symbol); const postOnly = this.safeValue(params, 'postOnly', false); type = type.toLowerCase(); params = this.omit(params, ['postOnly']); if (market['spot']) { // although they claim that type needs to be 'exchange limit' or 'exchange market' // in fact that's not the case for swap markets type = this.safeStringLower(this.options['orderTypes'], type, type); } const request = { 'symbol': market['id'], 'side': side, 'amount': this.amountToPrecision(symbol, amount), 'type': type, 'ocoorder': false, 'buy_price_oco': 0, 'sell_price_oco': 0, }; if (type.indexOf('market') > -1) { request['price'] = this.nonce().toString(); } else { request['price'] = this.priceToPrecision(symbol, price); } if (postOnly) { request['is_postonly'] = true; } const response = await this.privatePostOrderNew(this.extend(request, params)); return this.parseOrder(response, market); } async editOrder(id, symbol, type, side, amount = undefined, price = undefined, params = {}) { await this.loadMarkets(); const order = { 'order_id': parseInt(id), }; if (price !== undefined) { order['price'] = this.priceToPrecision(symbol, price); } if (amount !== undefined) { order['amount'] = this.numberToString(amount); } if (symbol !== undefined) { order['symbol'] = this.marketId(symbol); } if (side !== undefined) { order['side'] = side; } if (type !== undefined) { order['type'] = this.safeString(this.options['orderTypes'], type, type); } const response = await this.privatePostOrderCancelReplace(this.extend(order, params)); return this.parseOrder(response); } async cancelOrder(id, symbol = undefined, params = {}) { /** * @method * @name bitfinex#cancelOrder * @description cancels an open order * @param {string} id order id * @param {string|undefined} symbol not used by bitfinex cancelOrder () * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ await this.loadMarkets(); const request = { 'order_id': parseInt(id), }; return await this.privatePostOrderCancel(this.extend(request, params)); } async cancelAllOrders(symbol = undefined, params = {}) { /** * @method * @name bitfinex#cancelAllOrders * @description cancel all open orders * @param {string|undefined} symbol unified market symbol, only orders in the market of this symbol are cancelled when symbol is not undefined * @param {object} params extra parameters specific to the bitfinex api endpoint * @returns {object} response from exchange */ return await this.privatePostOrderCancelAll(params); } parseOrder(order, market = undefined) { // // { // id: 57334010955, // cid: 1611584840966, // cid_date: null, //