3797 lines
164 KiB
Python
3797 lines
164 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# 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
|
|
|
|
from ccxt.base.exchange import Exchange
|
|
from ccxt.abstract.bitfinex import ImplicitAPI
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, Currencies, Currency, DepositAddress, Int, LedgerEntry, MarginModification, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFees, Transaction, TransferEntry
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import PermissionDenied
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import BadSymbol
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import OrderNotFound
|
|
from ccxt.base.errors import NotSupported
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.errors import ExchangeNotAvailable
|
|
from ccxt.base.errors import OnMaintenance
|
|
from ccxt.base.errors import InvalidNonce
|
|
from ccxt.base.decimal_to_precision import ROUND
|
|
from ccxt.base.decimal_to_precision import TRUNCATE
|
|
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
|
|
from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class bitfinex(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(bitfinex, self).describe(), {
|
|
'id': 'bitfinex',
|
|
'name': 'Bitfinex',
|
|
'countries': ['VG'],
|
|
'version': 'v2',
|
|
'certified': False,
|
|
'pro': True,
|
|
# new metainfo interface
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': True,
|
|
'margin': True,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'cancelOrders': True,
|
|
'createDepositAddress': True,
|
|
'createLimitOrder': True,
|
|
'createMarketOrder': True,
|
|
'createOrder': True,
|
|
'createPostOnlyOrder': True,
|
|
'createReduceOnlyOrder': True,
|
|
'createStopLimitOrder': True,
|
|
'createStopMarketOrder': True,
|
|
'createStopOrder': True,
|
|
'createTrailingAmountOrder': True,
|
|
'createTrailingPercentOrder': False,
|
|
'createTriggerOrder': True,
|
|
'editOrder': True,
|
|
'fetchBalance': True,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRate': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchBorrowRates': False,
|
|
'fetchBorrowRatesPerSymbol': False,
|
|
'fetchClosedOrder': True,
|
|
'fetchClosedOrders': True,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': True,
|
|
'fetchDepositAddress': True,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDepositAddressesByNetwork': False,
|
|
'fetchDepositsWithdrawals': True,
|
|
'fetchFundingHistory': False,
|
|
'fetchFundingRate': 'emulated', # emulated in exchange
|
|
'fetchFundingRateHistory': True,
|
|
'fetchFundingRates': True,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchLedger': True,
|
|
'fetchLeverage': False,
|
|
'fetchLeverageTiers': False,
|
|
'fetchLiquidations': True,
|
|
'fetchMarginMode': False,
|
|
'fetchMarketLeverageTiers': False,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterest': True,
|
|
'fetchOpenInterestHistory': True,
|
|
'fetchOpenInterests': True,
|
|
'fetchOpenOrder': True,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrderBooks': False,
|
|
'fetchOrderTrades': True,
|
|
'fetchPosition': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': True,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchStatus': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': False,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': True,
|
|
'fetchTransactionFees': None,
|
|
'fetchTransactions': 'emulated',
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'setLeverage': False,
|
|
'setMargin': True,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'signIn': False,
|
|
'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',
|
|
},
|
|
# cheapest endpoint is 240 requests per minute => ~ 4 requests per second =>( 1000ms / 4 ) = 250ms between requests on average
|
|
'rateLimit': 250,
|
|
'urls': {
|
|
'logo': 'https://github.com/user-attachments/assets/4a8e947f-ab46-481a-a8ae-8b20e9b03178',
|
|
'api': {
|
|
'v1': 'https://api.bitfinex.com',
|
|
'public': 'https://api-pub.bitfinex.com',
|
|
'private': 'https://api.bitfinex.com',
|
|
},
|
|
'www': 'https://www.bitfinex.com',
|
|
'doc': [
|
|
'https://docs.bitfinex.com/v2/docs/',
|
|
'https://github.com/bitfinexcom/bitfinex-api-node',
|
|
],
|
|
'fees': 'https://www.bitfinex.com/fees',
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': {
|
|
'conf/{config}': 2.7, # 90 requests a minute, 90/60 = 1.5, 1000 / (250 * 2.66) = 1.503, use 2.7 instead of 2.66 to ensure rateLimitExceeded is not triggered
|
|
'conf/pub:{action}:{object}': 2.7,
|
|
'conf/pub:{action}:{object}:{detail}': 2.7,
|
|
'conf/pub:map:{object}': 2.7,
|
|
'conf/pub:map:{object}:{detail}': 2.7,
|
|
'conf/pub:map:currency:{detail}': 2.7,
|
|
'conf/pub:map:currency:sym': 2.7, # maps symbols to their API symbols, BAB > BCH
|
|
'conf/pub:map:currency:label': 2.7, # verbose friendly names, BNT > Bancor
|
|
'conf/pub:map:currency:unit': 2.7, # maps symbols to unit of measure where applicable
|
|
'conf/pub:map:currency:undl': 2.7, # maps derivatives symbols to their underlying currency
|
|
'conf/pub:map:currency:pool': 2.7, # maps symbols to underlying network/protocol they operate on
|
|
'conf/pub:map:currency:explorer': 2.7, # maps symbols to their recognised block explorer URLs
|
|
'conf/pub:map:currency:tx:fee': 2.7, # maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745
|
|
'conf/pub:map:tx:method': 2.7,
|
|
'conf/pub:list:{object}': 2.7,
|
|
'conf/pub:list:{object}:{detail}': 2.7,
|
|
'conf/pub:list:currency': 2.7,
|
|
'conf/pub:list:pair:exchange': 2.7,
|
|
'conf/pub:list:pair:margin': 2.7,
|
|
'conf/pub:list:pair:futures': 2.7,
|
|
'conf/pub:list:competitions': 2.7,
|
|
'conf/pub:info:{object}': 2.7,
|
|
'conf/pub:info:{object}:{detail}': 2.7,
|
|
'conf/pub:info:pair': 2.7,
|
|
'conf/pub:info:pair:futures': 2.7,
|
|
'conf/pub:info:tx:status': 2.7, # [deposit, withdrawal] statuses 1 = active, 0 = maintenance
|
|
'conf/pub:fees': 2.7,
|
|
'platform/status': 8, # 30 requests per minute = 0.5 requests per second =>( 1000ms / rateLimit ) / 0.5 = 8
|
|
'tickers': 2.7, # 90 requests a minute = 1.5 requests per second =>( 1000 / rateLimit ) / 1.5 = 2.666666666
|
|
'ticker/{symbol}': 2.7,
|
|
'tickers/hist': 2.7,
|
|
'trades/{symbol}/hist': 2.7,
|
|
'book/{symbol}/{precision}': 1, # 240 requests a minute
|
|
'book/{symbol}/P0': 1,
|
|
'book/{symbol}/P1': 1,
|
|
'book/{symbol}/P2': 1,
|
|
'book/{symbol}/P3': 1,
|
|
'book/{symbol}/R0': 1,
|
|
'stats1/{key}:{size}:{symbol}:{side}/{section}': 2.7,
|
|
'stats1/{key}:{size}:{symbol}:{side}/last': 2.7,
|
|
'stats1/{key}:{size}:{symbol}:{side}/hist': 2.7,
|
|
'stats1/{key}:{size}:{symbol}/{section}': 2.7,
|
|
'stats1/{key}:{size}:{symbol}/last': 2.7,
|
|
'stats1/{key}:{size}:{symbol}/hist': 2.7,
|
|
'stats1/{key}:{size}:{symbol}:long/last': 2.7,
|
|
'stats1/{key}:{size}:{symbol}:long/hist': 2.7,
|
|
'stats1/{key}:{size}:{symbol}:short/last': 2.7,
|
|
'stats1/{key}:{size}:{symbol}:short/hist': 2.7,
|
|
'candles/trade:{timeframe}:{symbol}:{period}/{section}': 2.7,
|
|
'candles/trade:{timeframe}:{symbol}/{section}': 2.7,
|
|
'candles/trade:{timeframe}:{symbol}/last': 2.7,
|
|
'candles/trade:{timeframe}:{symbol}/hist': 2.7,
|
|
'status/{type}': 2.7,
|
|
'status/deriv': 2.7,
|
|
'status/deriv/{symbol}/hist': 2.7,
|
|
'liquidations/hist': 80, # 3 requests a minute = 0.05 requests a second =>( 1000ms / rateLimit ) / 0.05 = 80
|
|
'rankings/{key}:{timeframe}:{symbol}/{section}': 2.7,
|
|
'rankings/{key}:{timeframe}:{symbol}/hist': 2.7,
|
|
'pulse/hist': 2.7,
|
|
'pulse/profile/{nickname}': 2.7,
|
|
'funding/stats/{symbol}/hist': 10, # ratelimit not in docs
|
|
'ext/vasps': 1,
|
|
},
|
|
'post': {
|
|
'calc/trade/avg': 2.7,
|
|
'calc/fx': 2.7,
|
|
},
|
|
},
|
|
'private': {
|
|
'post': {
|
|
# 'auth/r/orders/{symbol}/new', # outdated
|
|
# 'auth/r/stats/perf:{timeframe}/hist', # outdated
|
|
'auth/r/wallets': 2.7,
|
|
'auth/r/wallets/hist': 2.7,
|
|
'auth/r/orders': 2.7,
|
|
'auth/r/orders/{symbol}': 2.7,
|
|
'auth/w/order/submit': 2.7,
|
|
'auth/w/order/update': 2.7,
|
|
'auth/w/order/cancel': 2.7,
|
|
'auth/w/order/multi': 2.7,
|
|
'auth/w/order/cancel/multi': 2.7,
|
|
'auth/r/orders/{symbol}/hist': 2.7,
|
|
'auth/r/orders/hist': 2.7,
|
|
'auth/r/order/{symbol}:{id}/trades': 2.7,
|
|
'auth/r/trades/{symbol}/hist': 2.7,
|
|
'auth/r/trades/hist': 2.7,
|
|
'auth/r/ledgers/{currency}/hist': 2.7,
|
|
'auth/r/ledgers/hist': 2.7,
|
|
'auth/r/info/margin/{key}': 2.7,
|
|
'auth/r/info/margin/base': 2.7,
|
|
'auth/r/info/margin/sym_all': 2.7,
|
|
'auth/r/positions': 2.7,
|
|
'auth/w/position/claim': 2.7,
|
|
'auth/w/position/increase:': 2.7,
|
|
'auth/r/position/increase/info': 2.7,
|
|
'auth/r/positions/hist': 2.7,
|
|
'auth/r/positions/audit': 2.7,
|
|
'auth/r/positions/snap': 2.7,
|
|
'auth/w/deriv/collateral/set': 2.7,
|
|
'auth/w/deriv/collateral/limits': 2.7,
|
|
'auth/r/funding/offers': 2.7,
|
|
'auth/r/funding/offers/{symbol}': 2.7,
|
|
'auth/w/funding/offer/submit': 2.7,
|
|
'auth/w/funding/offer/cancel': 2.7,
|
|
'auth/w/funding/offer/cancel/all': 2.7,
|
|
'auth/w/funding/close': 2.7,
|
|
'auth/w/funding/auto': 2.7,
|
|
'auth/w/funding/keep': 2.7,
|
|
'auth/r/funding/offers/{symbol}/hist': 2.7,
|
|
'auth/r/funding/offers/hist': 2.7,
|
|
'auth/r/funding/loans': 2.7,
|
|
'auth/r/funding/loans/hist': 2.7,
|
|
'auth/r/funding/loans/{symbol}': 2.7,
|
|
'auth/r/funding/loans/{symbol}/hist': 2.7,
|
|
'auth/r/funding/credits': 2.7,
|
|
'auth/r/funding/credits/hist': 2.7,
|
|
'auth/r/funding/credits/{symbol}': 2.7,
|
|
'auth/r/funding/credits/{symbol}/hist': 2.7,
|
|
'auth/r/funding/trades/{symbol}/hist': 2.7,
|
|
'auth/r/funding/trades/hist': 2.7,
|
|
'auth/r/info/funding/{key}': 2.7,
|
|
'auth/r/info/user': 2.7,
|
|
'auth/r/summary': 2.7,
|
|
'auth/r/logins/hist': 2.7,
|
|
'auth/r/permissions': 2.7,
|
|
'auth/w/token': 2.7,
|
|
'auth/r/audit/hist': 2.7,
|
|
'auth/w/transfer': 2.7, # ratelimit not in docs...
|
|
'auth/w/deposit/address': 24, # 10 requests a minute = 0.166 requests per second =>( 1000ms / rateLimit ) / 0.166 = 24
|
|
'auth/w/deposit/invoice': 24, # ratelimit not in docs
|
|
'auth/w/withdraw': 24, # ratelimit not in docs
|
|
'auth/r/movements/{currency}/hist': 2.7,
|
|
'auth/r/movements/hist': 2.7,
|
|
'auth/r/alerts': 5.34, # 45 requests a minute = 0.75 requests per second =>( 1000ms / rateLimit ) / 0.749 => 5.34
|
|
'auth/w/alert/set': 2.7,
|
|
'auth/w/alert/price:{symbol}:{price}/del': 2.7,
|
|
'auth/w/alert/{type}:{symbol}:{price}/del': 2.7,
|
|
'auth/calc/order/avail': 2.7,
|
|
'auth/w/settings/set': 2.7,
|
|
'auth/r/settings': 2.7,
|
|
'auth/w/settings/del': 2.7,
|
|
'auth/r/pulse/hist': 2.7,
|
|
'auth/w/pulse/add': 16, # 15 requests a minute = 0.25 requests per second =>( 1000ms / rateLimit ) / 0.25 => 16
|
|
'auth/w/pulse/del': 2.7,
|
|
},
|
|
},
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'feeSide': 'get',
|
|
'percentage': True,
|
|
'tierBased': True,
|
|
'maker': self.parse_number('0.001'),
|
|
'taker': self.parse_number('0.002'),
|
|
'tiers': {
|
|
'taker': [
|
|
[self.parse_number('0'), self.parse_number('0.002')],
|
|
[self.parse_number('500000'), self.parse_number('0.002')],
|
|
[self.parse_number('1000000'), self.parse_number('0.002')],
|
|
[self.parse_number('2500000'), self.parse_number('0.002')],
|
|
[self.parse_number('5000000'), self.parse_number('0.002')],
|
|
[self.parse_number('7500000'), self.parse_number('0.002')],
|
|
[self.parse_number('10000000'), self.parse_number('0.0018')],
|
|
[self.parse_number('15000000'), self.parse_number('0.0016')],
|
|
[self.parse_number('20000000'), self.parse_number('0.0014')],
|
|
[self.parse_number('25000000'), self.parse_number('0.0012')],
|
|
[self.parse_number('30000000'), self.parse_number('0.001')],
|
|
],
|
|
'maker': [
|
|
[self.parse_number('0'), self.parse_number('0.001')],
|
|
[self.parse_number('500000'), self.parse_number('0.0008')],
|
|
[self.parse_number('1000000'), self.parse_number('0.0006')],
|
|
[self.parse_number('2500000'), self.parse_number('0.0004')],
|
|
[self.parse_number('5000000'), self.parse_number('0.0002')],
|
|
[self.parse_number('7500000'), self.parse_number('0')],
|
|
[self.parse_number('10000000'), self.parse_number('0')],
|
|
[self.parse_number('15000000'), self.parse_number('0')],
|
|
[self.parse_number('20000000'), self.parse_number('0')],
|
|
[self.parse_number('25000000'), self.parse_number('0')],
|
|
[self.parse_number('30000000'), self.parse_number('0')],
|
|
],
|
|
},
|
|
},
|
|
'funding': {
|
|
'withdraw': {},
|
|
},
|
|
},
|
|
'precisionMode': SIGNIFICANT_DIGITS,
|
|
'options': {
|
|
'precision': 'R0', # P0, P1, P2, P3, P4, R0
|
|
# convert 'EXCHANGE MARKET' to lowercase 'market'
|
|
# convert 'EXCHANGE LIMIT' to lowercase 'limit'
|
|
# everything else remains uppercase
|
|
'exchangeTypes': {
|
|
'MARKET': 'market',
|
|
'EXCHANGE MARKET': 'market',
|
|
'LIMIT': 'limit',
|
|
'EXCHANGE LIMIT': 'limit',
|
|
# 'STOP': None,
|
|
'EXCHANGE STOP': 'market',
|
|
# 'TRAILING STOP': None,
|
|
# 'EXCHANGE TRAILING STOP': None,
|
|
# 'FOK': None,
|
|
'EXCHANGE FOK': 'limit',
|
|
# 'STOP LIMIT': None,
|
|
'EXCHANGE STOP LIMIT': 'limit',
|
|
# 'IOC': None,
|
|
'EXCHANGE IOC': 'limit',
|
|
},
|
|
# convert 'market' to 'EXCHANGE MARKET'
|
|
# convert 'limit' 'EXCHANGE LIMIT'
|
|
# everything else remains
|
|
'orderTypes': {
|
|
'market': 'EXCHANGE MARKET',
|
|
'limit': 'EXCHANGE LIMIT',
|
|
},
|
|
'fiat': {
|
|
'USD': 'USD',
|
|
'EUR': 'EUR',
|
|
'JPY': 'JPY',
|
|
'GBP': 'GBP',
|
|
'CHN': 'CHN',
|
|
},
|
|
# actually the correct names unlike the v1
|
|
# we don't want to self.extend self with accountsByType in v1
|
|
'v2AccountsByType': {
|
|
'spot': 'exchange',
|
|
'exchange': 'exchange',
|
|
'funding': 'funding',
|
|
'margin': 'margin',
|
|
'derivatives': 'margin',
|
|
'future': 'margin',
|
|
'swap': 'margin',
|
|
},
|
|
'withdraw': {
|
|
'includeFee': False,
|
|
},
|
|
'networks': {
|
|
'BTC': 'BITCOIN',
|
|
'LTC': 'LITECOIN',
|
|
'ERC20': 'ETHEREUM',
|
|
'OMNI': 'TETHERUSO',
|
|
'LIQUID': 'TETHERUSL',
|
|
'TRC20': 'TETHERUSX',
|
|
'EOS': 'TETHERUSS',
|
|
'AVAX': 'TETHERUSDTAVAX',
|
|
'SOL': 'TETHERUSDTSOL',
|
|
'ALGO': 'TETHERUSDTALG',
|
|
'BCH': 'TETHERUSDTBCH',
|
|
'KSM': 'TETHERUSDTKSM',
|
|
'DVF': 'TETHERUSDTDVF',
|
|
'OMG': 'TETHERUSDTOMG',
|
|
},
|
|
'networksById': {
|
|
'TETHERUSE': 'ERC20',
|
|
},
|
|
},
|
|
'features': {
|
|
'default': {
|
|
'sandbox': False,
|
|
'createOrder': {
|
|
'marginMode': True,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': False,
|
|
'stopLossPrice': True,
|
|
'takeProfitPrice': True,
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': True,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': True, # todo: implement
|
|
'leverage': True, # todo: implement
|
|
'marketBuyRequiresPrice': False,
|
|
'marketBuyByCost': True,
|
|
'selfTradePrevention': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': {
|
|
'max': 75,
|
|
},
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 2500,
|
|
'daysBack': None,
|
|
'untilDays': 100000, # todo: implement
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': None,
|
|
'fetchClosedOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'daysBack': None,
|
|
'daysBackCanceled': None,
|
|
'untilDays': 100000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOHLCV': {
|
|
'limit': 10000,
|
|
},
|
|
},
|
|
'spot': {
|
|
'extends': 'default',
|
|
},
|
|
'swap': {
|
|
'linear': {
|
|
'extends': 'default',
|
|
},
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
'exceptions': {
|
|
'exact': {
|
|
'11010': RateLimitExceeded,
|
|
'10001': PermissionDenied, # api_key: permission invalid(#10001)
|
|
'10020': BadRequest,
|
|
'10100': AuthenticationError,
|
|
'10114': InvalidNonce,
|
|
'20060': OnMaintenance,
|
|
# {"code":503,"error":"temporarily_unavailable","error_description":"Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info."}
|
|
'temporarily_unavailable': ExchangeNotAvailable,
|
|
},
|
|
'broad': {
|
|
'available balance is only': InsufficientFunds,
|
|
'not enough exchange balance': InsufficientFunds,
|
|
'Order not found': OrderNotFound,
|
|
'symbol: invalid': BadSymbol,
|
|
},
|
|
},
|
|
'commonCurrencies': {
|
|
'UST': 'USDT',
|
|
'EUTF0': 'EURT',
|
|
'USTF0': 'USDT',
|
|
'ALG': 'ALGO', # https://github.com/ccxt/ccxt/issues/6034
|
|
'AMP': 'AMPL',
|
|
'ATO': 'ATOM', # https://github.com/ccxt/ccxt/issues/5118
|
|
'BCHABC': 'XEC',
|
|
'BCHN': 'BCH',
|
|
'DAT': 'DATA',
|
|
'DOG': 'MDOGE',
|
|
'DSH': 'DASH',
|
|
'EDO': 'PNT',
|
|
'EUS': 'EURS',
|
|
'EUT': 'EURT',
|
|
'HTX': 'HT',
|
|
'IDX': 'ID',
|
|
'IOT': 'IOTA',
|
|
'IQX': 'IQ',
|
|
'LUNA': 'LUNC',
|
|
'LUNA2': 'LUNA',
|
|
'MNA': 'MANA',
|
|
'ORS': 'ORS Group', # conflict with Origin Sport #3230
|
|
'PAS': 'PASS',
|
|
'QSH': 'QASH',
|
|
'QTM': 'QTUM',
|
|
'RBT': 'RBTC',
|
|
'SNG': 'SNGLS',
|
|
'STJ': 'STORJ',
|
|
'TERRAUST': 'USTC',
|
|
'TSD': 'TUSD',
|
|
'YGG': 'YEED', # conflict with Yield Guild Games
|
|
'YYW': 'YOYOW',
|
|
'UDC': 'USDC',
|
|
'VSY': 'VSYS',
|
|
'WAX': 'WAXP',
|
|
'XCH': 'XCHF',
|
|
'ZBT': 'ZB',
|
|
},
|
|
})
|
|
|
|
def is_fiat(self, code):
|
|
return(code in self.options['fiat'])
|
|
|
|
def get_currency_id(self, code):
|
|
return 'f' + code
|
|
|
|
def get_currency_name(self, code):
|
|
# temporary fix for transpiler recognition, even though self is in parent class
|
|
if code in self.options['currencyNames']:
|
|
return self.options['currencyNames'][code]
|
|
raise NotSupported(self.id + ' ' + code + ' not supported for withdrawal')
|
|
|
|
def amount_to_precision(self, symbol, amount):
|
|
# https://docs.bitfinex.com/docs/introduction#amount-precision
|
|
# The amount field allows up to 8 decimals.
|
|
# Anything exceeding self will be rounded to the 8th decimal.
|
|
symbol = self.safe_symbol(symbol)
|
|
return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
|
|
|
|
def price_to_precision(self, symbol, price):
|
|
symbol = self.safe_symbol(symbol)
|
|
price = self.decimal_to_precision(price, ROUND, self.markets[symbol]['precision']['price'], self.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 self.decimal_to_precision(price, TRUNCATE, 8, DECIMAL_PLACES)
|
|
|
|
def fetch_status(self, params={}):
|
|
"""
|
|
the latest known information on the availability of the exchange API
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-platform-status
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
|
|
"""
|
|
#
|
|
# [1] # operative
|
|
# [0] # maintenance
|
|
#
|
|
response = self.publicGetPlatformStatus(params)
|
|
statusRaw = self.safe_string(response, 0)
|
|
return {
|
|
'status': self.safe_string({'0': 'maintenance', '1': 'ok'}, statusRaw, statusRaw),
|
|
'updated': None,
|
|
'eta': None,
|
|
'url': None,
|
|
'info': response,
|
|
}
|
|
|
|
def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for bitfinex
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-conf
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
spotMarketsInfoPromise = self.publicGetConfPubInfoPair(params)
|
|
futuresMarketsInfoPromise = self.publicGetConfPubInfoPairFutures(params)
|
|
marginIdsPromise = self.publicGetConfPubListPairMargin(params)
|
|
spotMarketsInfo, futuresMarketsInfo, marginIds = [spotMarketsInfoPromise, futuresMarketsInfoPromise, marginIdsPromise]
|
|
spotMarketsInfo = self.safe_list(spotMarketsInfo, 0, [])
|
|
futuresMarketsInfo = self.safe_list(futuresMarketsInfo, 0, [])
|
|
markets = self.array_concat(spotMarketsInfo, futuresMarketsInfo)
|
|
marginIds = self.safe_value(marginIds, 0, [])
|
|
#
|
|
# [
|
|
# "1INCH:USD",
|
|
# [
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# "2.0",
|
|
# "100000.0",
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# null
|
|
# ]
|
|
# ]
|
|
#
|
|
result = []
|
|
for i in range(0, len(markets)):
|
|
pair = markets[i]
|
|
id = self.safe_string_upper(pair, 0)
|
|
market = self.safe_value(pair, 1, {})
|
|
spot = True
|
|
if id.find('F0') >= 0:
|
|
spot = False
|
|
swap = not spot
|
|
baseId = None
|
|
quoteId = None
|
|
if id.find(':') >= 0:
|
|
parts = id.split(':')
|
|
baseId = parts[0]
|
|
quoteId = parts[1]
|
|
else:
|
|
baseId = id[0:3]
|
|
quoteId = id[3:6]
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
splitBase = base.split('F0')
|
|
splitQuote = quote.split('F0')
|
|
base = self.safe_string(splitBase, 0)
|
|
quote = self.safe_string(splitQuote, 0)
|
|
symbol = base + '/' + quote
|
|
baseId = self.get_currency_id(baseId)
|
|
quoteId = self.get_currency_id(quoteId)
|
|
settle = None
|
|
settleId = None
|
|
if swap:
|
|
settle = quote
|
|
settleId = quote
|
|
symbol = symbol + ':' + settle
|
|
minOrderSizeString = self.safe_string(market, 3)
|
|
maxOrderSizeString = self.safe_string(market, 4)
|
|
margin = False
|
|
if spot and self.in_array(id, marginIds):
|
|
margin = True
|
|
result.append({
|
|
'id': 't' + id,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId,
|
|
'type': 'spot' if spot else 'swap',
|
|
'spot': spot,
|
|
'margin': margin,
|
|
'swap': swap,
|
|
'future': False,
|
|
'option': False,
|
|
'active': True,
|
|
'contract': swap,
|
|
'linear': True if swap else None,
|
|
'inverse': False if swap else None,
|
|
'contractSize': self.parse_number('1') if swap else None,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': int('8'), # https://github.com/ccxt/ccxt/issues/7310
|
|
'price': int('5'),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': self.parse_number(minOrderSizeString),
|
|
'max': self.parse_number(maxOrderSizeString),
|
|
},
|
|
'price': {
|
|
'min': self.parse_number('1e-8'),
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': None, # todo: the api needs revision for extra params & endpoints for possibility of returning a timestamp for self
|
|
'info': market,
|
|
})
|
|
return result
|
|
|
|
def fetch_currencies(self, params={}) -> Currencies:
|
|
"""
|
|
fetches all available currencies on an exchange
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-conf
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an associative dictionary of currencies
|
|
"""
|
|
labels = [
|
|
'pub:list:currency',
|
|
'pub:map:currency:sym', # maps symbols to their API symbols, BAB > BCH
|
|
'pub:map:currency:label', # verbose friendly names, BNT > Bancor
|
|
'pub:map:currency:unit', # maps symbols to unit of measure where applicable
|
|
'pub:map:currency:undl', # maps derivatives symbols to their underlying currency
|
|
'pub:map:currency:pool', # maps symbols to underlying network/protocol they operate on
|
|
'pub:map:currency:explorer', # maps symbols to their recognised block explorer URLs
|
|
'pub:map:currency:tx:fee', # maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745,
|
|
'pub:map:tx:method', # maps withdrawal/deposit methods to their API symbols
|
|
'pub:info:tx:status', # maps withdrawal/deposit statuses, coins: 1 = enabled, 0 = maintenance
|
|
]
|
|
config = ','.join(labels)
|
|
request: dict = {
|
|
'config': config,
|
|
}
|
|
response = self.publicGetConfConfig(self.extend(request, params))
|
|
#
|
|
# [
|
|
#
|
|
# a list of symbols
|
|
# ["AAA","ABS","ADA"],
|
|
#
|
|
# # sym
|
|
# # maps symbols to their API symbols, BAB > BCH
|
|
# [
|
|
# ["BAB", "BCH"],
|
|
# ["CNHT", "CNHt"],
|
|
# ["DSH", "DASH"],
|
|
# ["IOT", "IOTA"],
|
|
# ["LES", "LEO-EOS"],
|
|
# ["LET", "LEO-ERC20"],
|
|
# ["STJ", "STORJ"],
|
|
# ["TSD", "TUSD"],
|
|
# ["UDC", "USDC"],
|
|
# ["USK", "USDK"],
|
|
# ["UST", "USDt"],
|
|
# ["USTF0", "USDt0"],
|
|
# ["XCH", "XCHF"],
|
|
# ["YYW", "YOYOW"],
|
|
# # ...
|
|
# ],
|
|
# # label
|
|
# # verbose friendly names, BNT > Bancor
|
|
# [
|
|
# ["BAB", "Bitcoin Cash"],
|
|
# ["BCH", "Bitcoin Cash"],
|
|
# ["LEO", "Unus Sed LEO"],
|
|
# ["LES", "Unus Sed LEO(EOS)"],
|
|
# ["LET", "Unus Sed LEO(ERC20)"],
|
|
# # ...
|
|
# ],
|
|
# # unit
|
|
# # maps symbols to unit of measure where applicable
|
|
# [
|
|
# ["IOT", "Mi|MegaIOTA"],
|
|
# ],
|
|
# # undl
|
|
# # maps derivatives symbols to their underlying currency
|
|
# [
|
|
# ["USTF0", "UST"],
|
|
# ["BTCF0", "BTC"],
|
|
# ["ETHF0", "ETH"],
|
|
# ],
|
|
# # pool
|
|
# # maps symbols to underlying network/protocol they operate on
|
|
# [
|
|
# ['SAN', 'ETH'], ['OMG', 'ETH'], ['AVT', 'ETH'], ["EDO", "ETH"],
|
|
# ['ESS', 'ETH'], ['ATD', 'EOS'], ['ADD', 'EOS'], ["MTO", "EOS"],
|
|
# ['PNK', 'ETH'], ['BAB', 'BCH'], ['WLO', 'XLM'], ["VLD", "ETH"],
|
|
# ['BTT', 'TRX'], ['IMP', 'ETH'], ['SCR', 'ETH'], ["GNO", "ETH"],
|
|
# # ...
|
|
# ],
|
|
# # explorer
|
|
# # maps symbols to their recognised block explorer URLs
|
|
# [
|
|
# [
|
|
# "AIO",
|
|
# [
|
|
# "https://mainnet.aion.network",
|
|
# "https://mainnet.aion.network/#/account/VAL",
|
|
# "https://mainnet.aion.network/#/transaction/VAL"
|
|
# ]
|
|
# ],
|
|
# # ...
|
|
# ],
|
|
# # fee
|
|
# # maps currencies to their withdrawal fees
|
|
# [
|
|
# ["AAA",[0,0]],
|
|
# ["ABS",[0,131.3]],
|
|
# ["ADA",[0,0.3]],
|
|
# ],
|
|
# # deposit/withdrawal data
|
|
# [
|
|
# ["BITCOIN", 1, 1, null, null, null, null, 0, 0, null, null, 3],
|
|
# ...
|
|
# ]
|
|
# ]
|
|
#
|
|
indexed: dict = {
|
|
'sym': self.index_by(self.safe_list(response, 1, []), 0),
|
|
'label': self.index_by(self.safe_list(response, 2, []), 0),
|
|
'unit': self.index_by(self.safe_list(response, 3, []), 0),
|
|
'undl': self.index_by(self.safe_list(response, 4, []), 0),
|
|
'pool': self.index_by(self.safe_list(response, 5, []), 0),
|
|
'explorer': self.index_by(self.safe_list(response, 6, []), 0),
|
|
'fees': self.index_by(self.safe_list(response, 7, []), 0),
|
|
'networks': self.safe_list(response, 8, []),
|
|
'statuses': self.index_by(self.safe_list(response, 9, []), 0),
|
|
}
|
|
indexedNetworks: dict = {}
|
|
for i in range(0, len(indexed['networks'])):
|
|
networkObj = indexed['networks'][i]
|
|
networkId = self.safe_string(networkObj, 0)
|
|
valuesList = self.safe_list(networkObj, 1)
|
|
networkName = self.safe_string(valuesList, 0)
|
|
# for GOlang transpiler, do with "safe" method
|
|
networksList = self.safe_list(indexedNetworks, networkName, [])
|
|
networksList.append(networkId)
|
|
indexedNetworks[networkName] = networksList
|
|
ids = self.safe_list(response, 0, [])
|
|
result: dict = {}
|
|
for i in range(0, len(ids)):
|
|
id = ids[i]
|
|
if id.endswith('F0'):
|
|
# we get a lot of F0 currencies, skip those
|
|
continue
|
|
code = self.safe_currency_code(id)
|
|
label = self.safe_list(indexed['label'], id, [])
|
|
name = self.safe_string(label, 1)
|
|
pool = self.safe_list(indexed['pool'], id, [])
|
|
rawType = self.safe_string(pool, 1)
|
|
isCryptoCoin = (rawType is not None) or (id in indexed['explorer']) # "hacky" solution
|
|
type = None
|
|
if isCryptoCoin:
|
|
type = 'crypto'
|
|
feeValues = self.safe_list(indexed['fees'], id, [])
|
|
fees = self.safe_list(feeValues, 1, [])
|
|
fee = self.safe_number(fees, 1)
|
|
undl = self.safe_list(indexed['undl'], id, [])
|
|
precision = '8' # default precision, todo: fix "magic constants"
|
|
fid = 'f' + id
|
|
dwStatuses = self.safe_list(indexed['statuses'], id, [])
|
|
depositEnabled = self.safe_integer(dwStatuses, 1) == 1
|
|
withdrawEnabled = self.safe_integer(dwStatuses, 2) == 1
|
|
networks: dict = {}
|
|
netwokIds = self.safe_list(indexedNetworks, id, [])
|
|
for j in range(0, len(netwokIds)):
|
|
networkId = netwokIds[j]
|
|
network = self.network_id_to_code(networkId)
|
|
networks[network] = {
|
|
'info': networkId,
|
|
'id': networkId.lower(),
|
|
'network': networkId,
|
|
'active': None,
|
|
'deposit': None,
|
|
'withdraw': None,
|
|
'fee': None,
|
|
'precision': None,
|
|
'limits': {
|
|
'withdraw': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
}
|
|
result[code] = self.safe_currency_structure({
|
|
'id': fid,
|
|
'uppercaseId': id,
|
|
'code': code,
|
|
'info': [id, label, pool, feeValues, undl],
|
|
'type': type,
|
|
'name': name,
|
|
'active': True,
|
|
'deposit': depositEnabled,
|
|
'withdraw': withdrawEnabled,
|
|
'fee': fee,
|
|
'precision': int(precision),
|
|
'limits': {
|
|
'amount': {
|
|
'min': self.parse_number(self.parse_precision(precision)),
|
|
'max': None,
|
|
},
|
|
'withdraw': {
|
|
'min': fee,
|
|
'max': None,
|
|
},
|
|
},
|
|
'networks': networks,
|
|
})
|
|
return result
|
|
|
|
def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-wallets
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
# self api call does not return the 'used' amount - use the v1 version instead(which also returns zero balances)
|
|
# there is a difference between self and the v1 api, namely trading wallet is called margin in v2
|
|
self.load_markets()
|
|
accountsByType = self.safe_value(self.options, 'v2AccountsByType', {})
|
|
requestedType = self.safe_string(params, 'type', 'exchange')
|
|
accountType = self.safe_string(accountsByType, requestedType, requestedType)
|
|
if accountType is None:
|
|
keys = list(accountsByType.keys())
|
|
raise ExchangeError(self.id + ' fetchBalance() type parameter must be one of ' + ', '.join(keys))
|
|
isDerivative = requestedType == 'derivatives'
|
|
query = self.omit(params, 'type')
|
|
response = self.privatePostAuthRWallets(query)
|
|
result: dict = {'info': response}
|
|
for i in range(0, len(response)):
|
|
balance = response[i]
|
|
account = self.account()
|
|
interest = self.safe_string(balance, 3)
|
|
if interest != '0':
|
|
account['debt'] = interest
|
|
type = self.safe_string(balance, 0)
|
|
currencyId = self.safe_string_lower(balance, 1, '')
|
|
start = len(currencyId) - 2
|
|
isDerivativeCode = currencyId[start:] == 'f0'
|
|
# self will only filter the derivative codes if the requestedType is 'derivatives'
|
|
derivativeCondition = (not isDerivative or isDerivativeCode)
|
|
if (accountType == type) and derivativeCondition:
|
|
code = self.safe_currency_code(currencyId)
|
|
account['total'] = self.safe_string(balance, 2)
|
|
account['free'] = self.safe_string(balance, 4)
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
|
"""
|
|
transfer currency internally between wallets on the same account
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-transfer
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: amount to transfer
|
|
:param str fromAccount: account to transfer from
|
|
:param str toAccount: account to transfer to
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transfer structure <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)
|
|
self.load_markets()
|
|
accountsByType = self.safe_value(self.options, 'v2AccountsByType', {})
|
|
fromId = self.safe_string(accountsByType, fromAccount)
|
|
if fromId is None:
|
|
keys = list(accountsByType.keys())
|
|
raise ArgumentsRequired(self.id + ' transfer() fromAccount must be one of ' + ', '.join(keys))
|
|
toId = self.safe_string(accountsByType, toAccount)
|
|
if toId is None:
|
|
keys = list(accountsByType.keys())
|
|
raise ArgumentsRequired(self.id + ' transfer() toAccount must be one of ' + ', '.join(keys))
|
|
currency = self.currency(code)
|
|
fromCurrencyId = self.convert_derivatives_id(currency, fromAccount)
|
|
toCurrencyId = self.convert_derivatives_id(currency, toAccount)
|
|
requestedAmount = self.currency_to_precision(code, amount)
|
|
# self request is slightly different from v1 fromAccount -> from
|
|
request: dict = {
|
|
'amount': requestedAmount,
|
|
'currency': fromCurrencyId,
|
|
'currency_to': toCurrencyId,
|
|
'from': fromId,
|
|
'to': toId,
|
|
}
|
|
response = self.privatePostAuthWTransfer(self.extend(request, params))
|
|
#
|
|
# [
|
|
# 1616451183763,
|
|
# "acc_tf",
|
|
# null,
|
|
# null,
|
|
# [
|
|
# 1616451183763,
|
|
# "exchange",
|
|
# "margin",
|
|
# null,
|
|
# "UST",
|
|
# "UST",
|
|
# null,
|
|
# 1
|
|
# ],
|
|
# null,
|
|
# "SUCCESS",
|
|
# "1.0 Tether USDt transfered from Exchange to Margin"
|
|
# ]
|
|
#
|
|
error = self.safe_string(response, 0)
|
|
if error == 'error':
|
|
message = self.safe_string(response, 2, '')
|
|
# same message v1
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, self.id + ' ' + message)
|
|
raise ExchangeError(self.id + ' ' + message)
|
|
return self.parse_transfer({'result': response}, currency)
|
|
|
|
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
|
#
|
|
# transfer
|
|
#
|
|
# [
|
|
# 1616451183763,
|
|
# "acc_tf",
|
|
# null,
|
|
# null,
|
|
# [
|
|
# 1616451183763,
|
|
# "exchange",
|
|
# "margin",
|
|
# null,
|
|
# "UST",
|
|
# "UST",
|
|
# null,
|
|
# 1
|
|
# ],
|
|
# null,
|
|
# "SUCCESS",
|
|
# "1.0 Tether USDt transfered from Exchange to Margin"
|
|
# ]
|
|
#
|
|
result = self.safe_list(transfer, 'result')
|
|
timestamp = self.safe_integer(result, 0)
|
|
info = self.safe_value(result, 4)
|
|
fromAccount = self.safe_string(info, 1)
|
|
toAccount = self.safe_string(info, 2)
|
|
currencyId = self.safe_string(info, 5)
|
|
status = self.safe_string(result, 6)
|
|
return {
|
|
'id': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'status': self.parse_transfer_status(status),
|
|
'amount': self.safe_number(info, 7),
|
|
'currency': self.safe_currency_code(currencyId, currency),
|
|
'fromAccount': fromAccount,
|
|
'toAccount': toAccount,
|
|
'info': result,
|
|
}
|
|
|
|
def parse_transfer_status(self, status: Str) -> Str:
|
|
statuses: dict = {
|
|
'SUCCESS': 'ok',
|
|
'ERROR': 'failed',
|
|
'FAILURE': 'failed',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def convert_derivatives_id(self, currency, type):
|
|
# there is a difference between self and the v1 api, namely trading wallet is called margin in v2
|
|
# {
|
|
# "id": "fUSTF0",
|
|
# "code": "USTF0",
|
|
# "info": ['USTF0', [], [], [], ["USTF0", "UST"]],
|
|
info = self.safe_value(currency, 'info')
|
|
transferId = self.safe_string(info, 0)
|
|
underlying = self.safe_value(info, 4, [])
|
|
currencyId = None
|
|
if type == 'derivatives':
|
|
currencyId = self.safe_string(underlying, 0, transferId)
|
|
start = len(currencyId) - 2
|
|
isDerivativeCode = currencyId[start:] == 'F0'
|
|
if not isDerivativeCode:
|
|
currencyId = currencyId + 'F0'
|
|
elif type != 'margin':
|
|
currencyId = self.safe_string(underlying, 1, transferId)
|
|
else:
|
|
currencyId = transferId
|
|
return currencyId
|
|
|
|
def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-book
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return, bitfinex only allows 1, 25, or 100
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
precision = self.safe_value(self.options, 'precision', 'R0')
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'precision': precision,
|
|
}
|
|
if limit is not None:
|
|
request['len'] = limit
|
|
fullRequest = self.extend(request, params)
|
|
orderbook = self.publicGetBookSymbolPrecision(fullRequest)
|
|
timestamp = self.milliseconds()
|
|
result: dict = {
|
|
'symbol': market['symbol'],
|
|
'bids': [],
|
|
'asks': [],
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'nonce': None,
|
|
}
|
|
priceIndex = 1 if (fullRequest['precision'] == 'R0') else 0
|
|
for i in range(0, len(orderbook)):
|
|
order = orderbook[i]
|
|
price = self.safe_number(order, priceIndex)
|
|
signedAmount = self.safe_string(order, 2)
|
|
amount = Precise.string_abs(signedAmount)
|
|
side = 'bids' if Precise.string_gt(signedAmount, '0') else 'asks'
|
|
resultSide = result[side]
|
|
resultSide.append([price, self.parse_number(amount)])
|
|
result['bids'] = self.sort_by(result['bids'], 0, True)
|
|
result['asks'] = self.sort_by(result['asks'], 0)
|
|
return result
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# on trading pairs(ex. tBTCUSD)
|
|
#
|
|
# [
|
|
# SYMBOL, # self index is not present in singular-ticker
|
|
# BID,
|
|
# BID_SIZE,
|
|
# ASK,
|
|
# ASK_SIZE,
|
|
# DAILY_CHANGE,
|
|
# DAILY_CHANGE_RELATIVE,
|
|
# LAST_PRICE,
|
|
# VOLUME,
|
|
# HIGH,
|
|
# LOW
|
|
# ]
|
|
#
|
|
#
|
|
# on funding currencies(ex. fUSD)
|
|
#
|
|
# [
|
|
# SYMBOL, # self index is not present in singular-ticker
|
|
# FRR,
|
|
# BID,
|
|
# BID_PERIOD,
|
|
# BID_SIZE,
|
|
# ASK,
|
|
# ASK_PERIOD,
|
|
# ASK_SIZE,
|
|
# DAILY_CHANGE,
|
|
# DAILY_CHANGE_RELATIVE,
|
|
# LAST_PRICE,
|
|
# VOLUME,
|
|
# HIGH,
|
|
# LOW,
|
|
# _PLACEHOLDER,
|
|
# _PLACEHOLDER,
|
|
# FRR_AMOUNT_AVAILABLE
|
|
# ]
|
|
#
|
|
length = len(ticker)
|
|
isFetchTicker = (length == 10) or (length == 16)
|
|
symbol: Str = None
|
|
minusIndex = 0
|
|
isFundingCurrency = False
|
|
if isFetchTicker:
|
|
minusIndex = 1
|
|
isFundingCurrency = (length == 16)
|
|
else:
|
|
marketId = self.safe_string(ticker, 0)
|
|
market = self.safe_market(marketId, market)
|
|
isFundingCurrency = (length == 17)
|
|
symbol = self.safe_symbol(None, market)
|
|
last: Str = None
|
|
bid: Str = None
|
|
ask: Str = None
|
|
change: Str = None
|
|
percentage: Str = None
|
|
volume: Str = None
|
|
high: Str = None
|
|
low: Str = None
|
|
if isFundingCurrency:
|
|
# per api docs, they are different array type
|
|
last = self.safe_string(ticker, 10 - minusIndex)
|
|
bid = self.safe_string(ticker, 2 - minusIndex)
|
|
ask = self.safe_string(ticker, 5 - minusIndex)
|
|
change = self.safe_string(ticker, 8 - minusIndex)
|
|
percentage = self.safe_string(ticker, 9 - minusIndex)
|
|
volume = self.safe_string(ticker, 11 - minusIndex)
|
|
high = self.safe_string(ticker, 12 - minusIndex)
|
|
low = self.safe_string(ticker, 13 - minusIndex)
|
|
else:
|
|
# on trading pairs(ex. tBTCUSD or tHMSTR:USD)
|
|
last = self.safe_string(ticker, 7 - minusIndex)
|
|
bid = self.safe_string(ticker, 1 - minusIndex)
|
|
ask = self.safe_string(ticker, 3 - minusIndex)
|
|
change = self.safe_string(ticker, 5 - minusIndex)
|
|
percentage = self.safe_string(ticker, 6 - minusIndex)
|
|
percentage = Precise.string_mul(percentage, '100')
|
|
volume = self.safe_string(ticker, 8 - minusIndex)
|
|
high = self.safe_string(ticker, 9 - minusIndex)
|
|
low = self.safe_string(ticker, 10 - minusIndex)
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'high': high,
|
|
'low': low,
|
|
'bid': bid,
|
|
'bidVolume': None,
|
|
'ask': ask,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': change,
|
|
'percentage': percentage,
|
|
'average': None,
|
|
'baseVolume': volume,
|
|
'quoteVolume': None,
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-tickers
|
|
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
request: dict = {}
|
|
if symbols is not None:
|
|
ids = self.market_ids(symbols)
|
|
request['symbols'] = ','.join(ids)
|
|
else:
|
|
request['symbols'] = 'ALL'
|
|
tickers = self.publicGetTickers(self.extend(request, params))
|
|
#
|
|
# [
|
|
# # on trading pairs(ex. tBTCUSD)
|
|
# [
|
|
# SYMBOL,
|
|
# BID,
|
|
# BID_SIZE,
|
|
# ASK,
|
|
# ASK_SIZE,
|
|
# DAILY_CHANGE,
|
|
# DAILY_CHANGE_RELATIVE,
|
|
# LAST_PRICE,
|
|
# VOLUME,
|
|
# HIGH,
|
|
# LOW
|
|
# ],
|
|
# # on funding currencies(ex. fUSD)
|
|
# [
|
|
# SYMBOL,
|
|
# FRR,
|
|
# BID,
|
|
# BID_PERIOD,
|
|
# BID_SIZE,
|
|
# ASK,
|
|
# ASK_PERIOD,
|
|
# ASK_SIZE,
|
|
# DAILY_CHANGE,
|
|
# DAILY_CHANGE_RELATIVE,
|
|
# LAST_PRICE,
|
|
# VOLUME,
|
|
# HIGH,
|
|
# LOW,
|
|
# _PLACEHOLDER,
|
|
# _PLACEHOLDER,
|
|
# FRR_AMOUNT_AVAILABLE
|
|
# ],
|
|
# ...
|
|
# ]
|
|
#
|
|
return self.parse_tickers(tickers, symbols)
|
|
|
|
def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-ticker
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
ticker = self.publicGetTickerSymbol(self.extend(request, params))
|
|
return self.parse_ticker(ticker, market)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades(public)
|
|
#
|
|
# [
|
|
# ID,
|
|
# MTS, # timestamp
|
|
# AMOUNT,
|
|
# PRICE
|
|
# ]
|
|
#
|
|
# fetchMyTrades(private)
|
|
#
|
|
# [
|
|
# ID,
|
|
# PAIR,
|
|
# MTS_CREATE,
|
|
# ORDER_ID,
|
|
# EXEC_AMOUNT,
|
|
# EXEC_PRICE,
|
|
# ORDER_TYPE,
|
|
# ORDER_PRICE,
|
|
# MAKER,
|
|
# FEE,
|
|
# FEE_CURRENCY,
|
|
# ...
|
|
# ]
|
|
#
|
|
tradeList = self.safe_list(trade, 'result', [])
|
|
tradeLength = len(tradeList)
|
|
isPrivate = (tradeLength > 5)
|
|
id = self.safe_string(tradeList, 0)
|
|
amountIndex = 4 if isPrivate else 2
|
|
side = None
|
|
amountString = self.safe_string(tradeList, amountIndex)
|
|
priceIndex = 5 if isPrivate else 3
|
|
priceString = self.safe_string(tradeList, priceIndex)
|
|
if amountString[0] == '-':
|
|
side = 'sell'
|
|
amountString = Precise.string_abs(amountString)
|
|
else:
|
|
side = 'buy'
|
|
orderId = None
|
|
takerOrMaker = None
|
|
type = None
|
|
fee = None
|
|
symbol = self.safe_symbol(None, market)
|
|
timestampIndex = 2 if isPrivate else 1
|
|
timestamp = self.safe_integer(tradeList, timestampIndex)
|
|
if isPrivate:
|
|
marketId = tradeList[1]
|
|
symbol = self.safe_symbol(marketId)
|
|
orderId = self.safe_string(tradeList, 3)
|
|
maker = self.safe_integer(tradeList, 8)
|
|
takerOrMaker = 'maker' if (maker == 1) else 'taker'
|
|
feeCostString = self.safe_string(tradeList, 9)
|
|
feeCostString = Precise.string_neg(feeCostString)
|
|
feeCurrencyId = self.safe_string(tradeList, 10)
|
|
feeCurrency = self.safe_currency_code(feeCurrencyId)
|
|
fee = {
|
|
'cost': feeCostString,
|
|
'currency': feeCurrency,
|
|
}
|
|
orderType = tradeList[6]
|
|
type = self.safe_string(self.options['exchangeTypes'], orderType)
|
|
return self.safe_trade({
|
|
'id': id,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'order': orderId,
|
|
'side': side,
|
|
'type': type,
|
|
'takerOrMaker': takerOrMaker,
|
|
'price': priceString,
|
|
'amount': amountString,
|
|
'cost': None,
|
|
'fee': fee,
|
|
'info': tradeList,
|
|
}, market)
|
|
|
|
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of most recent trades for a particular symbol
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-trades
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch, default 120, max 10000
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:param int [params.until]: the latest time in ms to fetch entries for
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchTrades', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_dynamic('fetchTrades', symbol, since, limit, params, 10000)
|
|
market = self.market(symbol)
|
|
sort = '-1'
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if since is not None:
|
|
request['start'] = since
|
|
sort = '1'
|
|
if limit is not None:
|
|
request['limit'] = min(limit, 10000) # default 120, max 10000
|
|
request['sort'] = sort
|
|
request, params = self.handle_until_option('end', request, params)
|
|
response = self.publicGetTradesSymbolHist(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# ID,
|
|
# MTS, # timestamp
|
|
# AMOUNT,
|
|
# PRICE
|
|
# ]
|
|
# ]
|
|
#
|
|
trades = self.sort_by(response, 1)
|
|
tradesList = []
|
|
for i in range(0, len(trades)):
|
|
tradesList.append({'result': trades[i]}) # convert to array of dicts to match parseOrder signature
|
|
return self.parse_trades(tradesList, market, None, limit)
|
|
|
|
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = 100, params={}) -> List[list]:
|
|
"""
|
|
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-candles
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str timeframe: the length of time each candle represents
|
|
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch, default 100 max 10000
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
:param int [params.until]: timestamp in ms of the latest candle to fetch
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 10000)
|
|
market = self.market(symbol)
|
|
if limit is None:
|
|
limit = 10000
|
|
else:
|
|
limit = min(limit, 10000)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'timeframe': self.safe_string(self.timeframes, timeframe, timeframe),
|
|
'sort': 1,
|
|
'limit': limit,
|
|
}
|
|
if since is not None:
|
|
request['start'] = since
|
|
request, params = self.handle_until_option('end', request, params)
|
|
response = self.publicGetCandlesTradeTimeframeSymbolHist(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [1591503840000,0.025069,0.025068,0.025069,0.025068,1.97828998],
|
|
# [1591504500000,0.025065,0.025065,0.025065,0.025065,1.0164],
|
|
# [1591504620000,0.025062,0.025062,0.025062,0.025062,0.5],
|
|
# ]
|
|
#
|
|
return self.parse_ohlcvs(response, market, timeframe, since, limit)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# [
|
|
# 1457539800000,
|
|
# 0.02594,
|
|
# 0.02594,
|
|
# 0.02594,
|
|
# 0.02594,
|
|
# 0.1
|
|
# ]
|
|
#
|
|
return [
|
|
self.safe_integer(ohlcv, 0),
|
|
self.safe_number(ohlcv, 1),
|
|
self.safe_number(ohlcv, 3),
|
|
self.safe_number(ohlcv, 4),
|
|
self.safe_number(ohlcv, 2),
|
|
self.safe_number(ohlcv, 5),
|
|
]
|
|
|
|
def parse_order_status(self, status: Str):
|
|
if status is None:
|
|
return status
|
|
parts = status.split(' ')
|
|
state = self.safe_string(parts, 0)
|
|
statuses: dict = {
|
|
'ACTIVE': 'open',
|
|
'PARTIALLY': 'open',
|
|
'EXECUTED': 'closed',
|
|
'CANCELED': 'canceled',
|
|
'INSUFFICIENT': 'canceled',
|
|
'POSTONLY CANCELED': 'canceled',
|
|
'RSN_DUST': 'rejected',
|
|
'RSN_PAUSE': 'rejected',
|
|
'IOC CANCELED': 'canceled',
|
|
'FILLORKILL CANCELED': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, state, status)
|
|
|
|
def parse_order_flags(self, flags):
|
|
# flags can be added to each other...
|
|
flagValues: dict = {
|
|
'1024': ['reduceOnly'],
|
|
'4096': ['postOnly'],
|
|
'5120': ['reduceOnly', 'postOnly'],
|
|
# '64': 'hidden', # The hidden order option ensures an order does not appear in the order book
|
|
# '512': 'close', # Close position if position present.
|
|
# '16384': 'OCO', # The one cancels other order option allows you to place a pair of orders stipulating that if one order is executed fully or partially, then the other is automatically canceled.
|
|
# '524288': 'No Var Rates' # Excludes variable rate funding offers from matching against self order, if on margin
|
|
}
|
|
return self.safe_value(flagValues, flags, None)
|
|
|
|
def parse_time_in_force(self, orderType):
|
|
orderTypes: dict = {
|
|
'EXCHANGE IOC': 'IOC',
|
|
'EXCHANGE FOK': 'FOK',
|
|
'IOC': 'IOC', # Margin
|
|
'FOK': 'FOK', # Margin
|
|
}
|
|
return self.safe_string(orderTypes, orderType, 'GTC')
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
orderList = self.safe_list(order, 'result')
|
|
id = self.safe_string(orderList, 0)
|
|
marketId = self.safe_string(orderList, 3)
|
|
symbol = self.safe_symbol(marketId)
|
|
# https://github.com/ccxt/ccxt/issues/6686
|
|
# timestamp = self.safe_timestamp(orderObject, 5)
|
|
timestamp = self.safe_integer(orderList, 5)
|
|
remaining = Precise.string_abs(self.safe_string(orderList, 6))
|
|
signedAmount = self.safe_string(orderList, 7)
|
|
amount = Precise.string_abs(signedAmount)
|
|
side = 'sell' if Precise.string_lt(signedAmount, '0') else 'buy'
|
|
orderType = self.safe_string(orderList, 8)
|
|
type = self.safe_string(self.safe_value(self.options, 'exchangeTypes'), orderType)
|
|
timeInForce = self.parse_time_in_force(orderType)
|
|
rawFlags = self.safe_string(orderList, 12)
|
|
flags = self.parse_order_flags(rawFlags)
|
|
postOnly = False
|
|
if flags is not None:
|
|
for i in range(0, len(flags)):
|
|
if flags[i] == 'postOnly':
|
|
postOnly = True
|
|
price = self.safe_string(orderList, 16)
|
|
triggerPrice = None
|
|
if (orderType == 'EXCHANGE STOP') or (orderType == 'EXCHANGE STOP LIMIT'):
|
|
price = None
|
|
triggerPrice = self.safe_string(orderList, 16)
|
|
if orderType == 'EXCHANGE STOP LIMIT':
|
|
price = self.safe_string(orderList, 19)
|
|
status = None
|
|
statusString = self.safe_string(orderList, 13)
|
|
if statusString is not None:
|
|
parts = statusString.split(' @ ')
|
|
status = self.parse_order_status(self.safe_string(parts, 0))
|
|
average = self.safe_string(orderList, 17)
|
|
clientOrderId = self.safe_string(orderList, 2)
|
|
return self.safe_order({
|
|
'info': orderList,
|
|
'id': id,
|
|
'clientOrderId': clientOrderId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': timeInForce,
|
|
'postOnly': postOnly,
|
|
'side': side,
|
|
'price': price,
|
|
'triggerPrice': triggerPrice,
|
|
'amount': amount,
|
|
'cost': None,
|
|
'average': average,
|
|
'filled': None,
|
|
'remaining': remaining,
|
|
'status': status,
|
|
'fee': None,
|
|
'trades': None,
|
|
}, market)
|
|
|
|
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
@ignore
|
|
helper function to build an order request
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market' or 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much you want to trade in units of the base currency
|
|
:param float [price]: the price of the order, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.triggerPrice]: The price at which a trigger order is triggered at
|
|
:param str [params.timeInForce]: "GTC", "IOC", "FOK", or "PO"
|
|
:param bool [params.postOnly]:
|
|
:param bool [params.reduceOnly]: Ensures that the executed order does not flip the opened position.
|
|
:param int [params.flags]: additional order parameters: 4096(Post Only), 1024(Reduce Only), 16384(OCO), 64(Hidden), 512(Close), 524288(No Var Rates)
|
|
:param int [params.lev]: leverage for a derivative order, supported by derivative symbol orders only. The value should be between 1 and 100 inclusive.
|
|
:param str [params.price_traling]: The trailing price for a trailing stop order
|
|
:param str [params.price_aux_limit]: Order price for stop limit orders
|
|
:param str [params.price_oco_stop]: OCO stop price
|
|
:returns dict: an `order structure <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
|
|
"""
|
|
market = self.market(symbol)
|
|
amountString = self.amount_to_precision(symbol, amount)
|
|
amountString = amountString if (side == 'buy') else Precise.string_neg(amountString)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'amount': amountString,
|
|
}
|
|
triggerPrice = self.safe_string_2(params, 'stopPrice', 'triggerPrice')
|
|
trailingAmount = self.safe_string(params, 'trailingAmount')
|
|
timeInForce = self.safe_string(params, 'timeInForce')
|
|
postOnlyParam = self.safe_bool(params, 'postOnly', False)
|
|
reduceOnly = self.safe_bool(params, 'reduceOnly', False)
|
|
clientOrderId = self.safe_value_2(params, 'cid', 'clientOrderId')
|
|
orderType = type.upper()
|
|
if trailingAmount is not None:
|
|
orderType = 'TRAILING STOP'
|
|
request['price_trailing'] = trailingAmount
|
|
elif triggerPrice is not None:
|
|
# request['price'] is taken for stop orders
|
|
request['price'] = self.price_to_precision(symbol, triggerPrice)
|
|
if type == 'limit':
|
|
orderType = 'STOP LIMIT'
|
|
request['price_aux_limit'] = self.price_to_precision(symbol, price)
|
|
else:
|
|
orderType = 'STOP'
|
|
ioc = (timeInForce == 'IOC')
|
|
fok = (timeInForce == 'FOK')
|
|
postOnly = (postOnlyParam or (timeInForce == 'PO'))
|
|
if (ioc or fok) and (price is None):
|
|
raise InvalidOrder(self.id + ' createOrder() requires a price argument with IOC and FOK orders')
|
|
if (ioc or fok) and (type == 'market'):
|
|
raise InvalidOrder(self.id + ' createOrder() does not allow market IOC and FOK orders')
|
|
if (type != 'market') and (triggerPrice is None):
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
if ioc:
|
|
orderType = 'IOC'
|
|
elif fok:
|
|
orderType = 'FOK'
|
|
marginMode = None
|
|
marginMode, params = self.handle_margin_mode_and_params('createOrder', params)
|
|
if market['spot'] and (marginMode is None):
|
|
# The EXCHANGE prefix is only required for non margin spot markets
|
|
orderType = 'EXCHANGE ' + orderType
|
|
request['type'] = orderType
|
|
# flag values may be summed to combine flags
|
|
flags = 0
|
|
if postOnly:
|
|
flags = self.sum(flags, 4096)
|
|
if reduceOnly:
|
|
flags = self.sum(flags, 1024)
|
|
if flags != 0:
|
|
request['flags'] = flags
|
|
if clientOrderId is not None:
|
|
request['cid'] = clientOrderId
|
|
params = self.omit(params, ['triggerPrice', 'stopPrice', 'timeInForce', 'postOnly', 'reduceOnly', 'trailingAmount', 'clientOrderId'])
|
|
return self.extend(request, params)
|
|
|
|
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create an order on the exchange
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-submit-order
|
|
|
|
:param str symbol: unified CCXT market symbol
|
|
:param str type: 'limit' or 'market'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: the amount of currency to trade
|
|
:param float [price]: price of the order
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.triggerPrice]: the price that triggers a trigger order
|
|
:param str [params.timeInForce]: "GTC", "IOC", "FOK", or "PO"
|
|
:param boolean [params.postOnly]: set to True if you want to make a post only order
|
|
:param boolean [params.reduceOnly]: indicates that the order is to reduce the size of a position
|
|
:param int [params.flags]: additional order parameters: 4096(Post Only), 1024(Reduce Only), 16384(OCO), 64(Hidden), 512(Close), 524288(No Var Rates)
|
|
:param int [params.lev]: leverage for a derivative order, supported by derivative symbol orders only. The value should be between 1 and 100 inclusive.
|
|
:param str [params.price_aux_limit]: order price for stop limit orders
|
|
:param str [params.price_oco_stop]: OCO stop price
|
|
:param str [params.trailingAmount]: *swap only* the quote amount to trail away from the current market price
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request = self.create_order_request(symbol, type, side, amount, price, params)
|
|
response = self.privatePostAuthWOrderSubmit(request)
|
|
#
|
|
# [
|
|
# 1653325121, # Timestamp in milliseconds
|
|
# "on-req", # Purpose of notification('on-req', 'oc-req', "uca", 'fon-req', "foc-req")
|
|
# null, # unique ID of the message
|
|
# null,
|
|
# [
|
|
# [
|
|
# 95412102131, # Order ID
|
|
# null, # Group ID
|
|
# 1653325121798, # Client Order ID
|
|
# "tDOGE:UST", # Market ID
|
|
# 1653325121798, # Millisecond timestamp of creation
|
|
# 1653325121798, # Millisecond timestamp of update
|
|
# -10, # Amount(Positive means buy, negative means sell)
|
|
# -10, # Original amount
|
|
# "EXCHANGE LIMIT", # Type of the order: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET, STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT, TRAILING STOP, EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK, IOC, EXCHANGE IOC.
|
|
# null, # Previous order type(stop-limit orders are converted to limit orders so for them previous type is always STOP)
|
|
# null, # Millisecond timestamp of Time-In-Force: automatic order cancellation
|
|
# null, # _PLACEHOLDER
|
|
# 4096, # Flags, see parseOrderFlags()
|
|
# "ACTIVE", # Order Status, see parseOrderStatus()
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# 0.071, # Price(Stop Price for stop-limit orders, Limit Price for limit orders)
|
|
# 0, # Average Price
|
|
# 0, # Trailing Price
|
|
# 0, # Auxiliary Limit price(for STOP LIMIT)
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# 0, # Hidden(0 if False, 1 if True)
|
|
# 0, # Placed ID(If another order caused self order to be placed(OCO) self will be that other order's ID)
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# "API>BFX", # Routing, indicates origin of action: BFX, ETHFX, API>BFX, API>ETHFX
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# {"$F7":1} # additional meta information about the order( $F7 = IS_POST_ONLY(0 if False, 1 if True), $F33 = Leverage(int))
|
|
# ]
|
|
# ],
|
|
# null, # CODE(work in progress)
|
|
# "SUCCESS", # Status of the request
|
|
# "Submitting 1 orders." # Message
|
|
# ]
|
|
#
|
|
status = self.safe_string(response, 6)
|
|
if status != 'SUCCESS':
|
|
errorCode = response[5]
|
|
errorText = response[7]
|
|
raise ExchangeError(self.id + ' ' + response[6] + ': ' + errorText + '(#' + errorCode + ')')
|
|
orders = self.safe_list(response, 4, [])
|
|
order = self.safe_list(orders, 0)
|
|
newOrder = {'result': order}
|
|
return self.parse_order(newOrder, market)
|
|
|
|
def create_orders(self, orders: List[OrderRequest], params={}):
|
|
"""
|
|
create a list of trade orders
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-order-multi
|
|
|
|
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
ordersRequests = []
|
|
for i in range(0, len(orders)):
|
|
rawOrder = orders[i]
|
|
symbol = self.safe_string(rawOrder, 'symbol')
|
|
type = self.safe_string(rawOrder, 'type')
|
|
side = self.safe_string(rawOrder, 'side')
|
|
amount = self.safe_number(rawOrder, 'amount')
|
|
price = self.safe_number(rawOrder, 'price')
|
|
orderParams = self.safe_dict(rawOrder, 'params', {})
|
|
orderRequest = self.create_order_request(symbol, type, side, amount, price, orderParams)
|
|
ordersRequests.append(['on', orderRequest])
|
|
request: dict = {
|
|
'ops': ordersRequests,
|
|
}
|
|
response = self.privatePostAuthWOrderMulti(request)
|
|
#
|
|
# [
|
|
# 1706762515553,
|
|
# "ox_multi-req",
|
|
# null,
|
|
# null,
|
|
# [
|
|
# [
|
|
# 1706762515,
|
|
# "on-req",
|
|
# null,
|
|
# null,
|
|
# [
|
|
# [139567428547,null,1706762515551,"tBTCUST",1706762515551,1706762515551,0.0001,0.0001,"EXCHANGE LIMIT",null,null,null,0,"ACTIVE",null,null,35000,0,0,0,null,null,null,0,0,null,null,null,"API>BFX",null,null,{}]
|
|
# ],
|
|
# null,
|
|
# "SUCCESS",
|
|
# "Submitting 1 orders."
|
|
# ],
|
|
# ],
|
|
# null,
|
|
# "SUCCESS",
|
|
# "Submitting 2 order operations."
|
|
# ]
|
|
#
|
|
results = []
|
|
data = self.safe_list(response, 4, [])
|
|
for i in range(0, len(data)):
|
|
entry = data[i]
|
|
individualOrder = entry[4]
|
|
results.append({'result': individualOrder[0]})
|
|
return self.parse_orders(results)
|
|
|
|
def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
cancel all open orders
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-cancel-orders-multiple
|
|
|
|
:param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
'all': 1,
|
|
}
|
|
response = self.privatePostAuthWOrderCancelMulti(self.extend(request, params))
|
|
orders = self.safe_list(response, 4, [])
|
|
ordersList = []
|
|
for i in range(0, len(orders)):
|
|
ordersList.append({'result': orders[i]})
|
|
return self.parse_orders(ordersList)
|
|
|
|
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-cancel-order
|
|
|
|
:param str id: order id
|
|
:param str symbol: Not used by bitfinex cancelOrder()
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
cid = self.safe_value_2(params, 'cid', 'clientOrderId') # client order id
|
|
request = None
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
if cid is not None:
|
|
cidDate = self.safe_value(params, 'cidDate') # client order id date
|
|
if cidDate is None:
|
|
raise InvalidOrder(self.id + " canceling an order by clientOrderId('cid') requires both 'cid' and 'cid_date'('YYYY-MM-DD')")
|
|
request = {
|
|
'cid': cid,
|
|
'cid_date': cidDate,
|
|
}
|
|
params = self.omit(params, ['cid', 'clientOrderId'])
|
|
else:
|
|
request = {
|
|
'id': int(id),
|
|
}
|
|
response = self.privatePostAuthWOrderCancel(self.extend(request, params))
|
|
order = self.safe_value(response, 4)
|
|
newOrder = {'result': order}
|
|
return self.parse_order(newOrder, market)
|
|
|
|
def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
|
|
"""
|
|
cancel multiple orders at the same time
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-cancel-orders-multiple
|
|
|
|
:param str[] ids: order ids
|
|
:param str symbol: unified market symbol, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an array of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
numericIds = []
|
|
for i in range(0, len(ids)):
|
|
# numericIds[i] = self.parse_to_numeric(ids[i])
|
|
numericIds.append(self.parse_to_numeric(ids[i]))
|
|
request: dict = {
|
|
'id': numericIds,
|
|
}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
response = self.privatePostAuthWOrderCancelMulti(self.extend(request, params))
|
|
#
|
|
# [
|
|
# 1706740198811,
|
|
# "oc_multi-req",
|
|
# null,
|
|
# null,
|
|
# [
|
|
# [
|
|
# 139530205057,
|
|
# null,
|
|
# 1706740132275,
|
|
# "tBTCF0:USTF0",
|
|
# 1706740132276,
|
|
# 1706740132276,
|
|
# 0.0001,
|
|
# 0.0001,
|
|
# "LIMIT",
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0,
|
|
# "ACTIVE",
|
|
# null,
|
|
# null,
|
|
# 39000,
|
|
# 0,
|
|
# 0,
|
|
# 0,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0,
|
|
# 0,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# "API>BFX",
|
|
# null,
|
|
# null,
|
|
# {
|
|
# "lev": 10,
|
|
# "$F33": 10
|
|
# }
|
|
# ],
|
|
# ],
|
|
# null,
|
|
# "SUCCESS",
|
|
# "Submitting 2 order cancellations."
|
|
# ]
|
|
#
|
|
orders = self.safe_list(response, 4, [])
|
|
ordersList = []
|
|
for i in range(0, len(orders)):
|
|
ordersList.append({'result': orders[i]})
|
|
return self.parse_orders(ordersList, market)
|
|
|
|
def fetch_open_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetch an open order by it's id
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified market symbol, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {
|
|
'id': [int(id)],
|
|
}
|
|
orders = self.fetch_open_orders(symbol, None, None, self.extend(request, params))
|
|
order = self.safe_value(orders, 0)
|
|
if order is None:
|
|
raise OrderNotFound(self.id + ' order ' + id + ' not found')
|
|
return order
|
|
|
|
def fetch_closed_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetch an open order by it's id
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified market symbol, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {
|
|
'id': [int(id)],
|
|
}
|
|
orders = self.fetch_closed_orders(symbol, None, None, self.extend(request, params))
|
|
order = self.safe_value(orders, 0)
|
|
if order is None:
|
|
raise OrderNotFound(self.id + ' order ' + id + ' not found')
|
|
return order
|
|
|
|
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetch all unfilled currently open orders
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch open orders for
|
|
:param int [limit]: the maximum number of open orders structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
market = None
|
|
response = None
|
|
if symbol is None:
|
|
response = self.privatePostAuthROrders(self.extend(request, params))
|
|
else:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
response = self.privatePostAuthROrdersSymbol(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# 95408916206, # Order ID
|
|
# null, # Group Order ID
|
|
# 1653322349926, # Client Order ID
|
|
# "tDOGE:UST", # Market ID
|
|
# 1653322349926, # Created Timestamp in milliseconds
|
|
# 1653322349927, # Updated Timestamp in milliseconds
|
|
# -10, # Amount remaining(Positive means buy, negative means sell)
|
|
# -10, # Original amount
|
|
# "EXCHANGE LIMIT", # Order type
|
|
# null, # Previous Order Type
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# 0, # Flags, see parseOrderFlags()
|
|
# "ACTIVE", # Order Status, see parseOrderStatus()
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# 0.11, # Price
|
|
# 0, # Average Price
|
|
# 0, # Trailing Price
|
|
# 0, # Auxiliary Limit price(for STOP LIMIT)
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# 0, # Hidden(0 if False, 1 if True)
|
|
# 0, # Placed ID(If another order caused self order to be placed(OCO) self will be that other order's ID)
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# "API>BFX", # Routing, indicates origin of action: BFX, ETHFX, API>BFX, API>ETHFX
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# {"$F7":1} # additional meta information about the order( $F7 = IS_POST_ONLY(0 if False, 1 if True), $F33 = Leverage(int))
|
|
# ],
|
|
# ]
|
|
#
|
|
ordersList = []
|
|
for i in range(0, len(response)):
|
|
ordersList.append({'result': response[i]})
|
|
return self.parse_orders(ordersList, market, since, limit)
|
|
|
|
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple closed orders made by the user
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch entries for
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
# returns the most recent closed or canceled orders up to circa two weeks ago
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchClosedOrders', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_dynamic('fetchClosedOrders', symbol, since, limit, params)
|
|
request: dict = {}
|
|
if since is not None:
|
|
request['start'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit # default 25, max 2500
|
|
request, params = self.handle_until_option('end', request, params)
|
|
market = None
|
|
response = None
|
|
if symbol is None:
|
|
response = self.privatePostAuthROrdersHist(self.extend(request, params))
|
|
else:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
response = self.privatePostAuthROrdersSymbolHist(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# 95412102131, # Order ID
|
|
# null, # Group Order ID
|
|
# 1653325121798, # Client Order ID
|
|
# "tDOGE:UST", # Market ID
|
|
# 1653325122000, # Created Timestamp in milliseconds
|
|
# 1653325122000, # Updated Timestamp in milliseconds
|
|
# -10, # Amount remaining(Positive means buy, negative means sell)
|
|
# -10, # Original amount
|
|
# "EXCHANGE LIMIT", # Order type
|
|
# null, # Previous Order Type
|
|
# null, # Millisecond timestamp of Time-In-Force: automatic order cancellation
|
|
# null, # _PLACEHOLDER
|
|
# "4096", # Flags, see parseOrderFlags()
|
|
# "POSTONLY CANCELED", # Order Status, see parseOrderStatus()
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# 0.071, # Price
|
|
# 0, # Average Price
|
|
# 0, # Trailing Price
|
|
# 0, # Auxiliary Limit price(for STOP LIMIT)
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# 0, # Notify(0 if False, 1 if True)
|
|
# 0, # Hidden(0 if False, 1 if True)
|
|
# null, # Placed ID(If another order caused self order to be placed(OCO) self will be that other order's ID)
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# "API>BFX", # Routing, indicates origin of action: BFX, ETHFX, API>BFX, API>ETHFX
|
|
# null, # _PLACEHOLDER
|
|
# null, # _PLACEHOLDER
|
|
# {"_$F7":1} # additional meta information about the order( _$F7 = IS_POST_ONLY(0 if False, 1 if True), _$F33 = Leverage(int))
|
|
# ]
|
|
# ]
|
|
#
|
|
ordersList = []
|
|
for i in range(0, len(response)):
|
|
ordersList.append({'result': response[i]})
|
|
return self.parse_orders(ordersList, market, since, limit)
|
|
|
|
def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all the trades made from a single order
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-order-trades
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch trades for
|
|
:param int [limit]: the maximum number of trades to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchOrderTrades() requires a symbol argument')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
orderId = int(id)
|
|
request: dict = {
|
|
'id': orderId,
|
|
'symbol': market['id'],
|
|
}
|
|
# valid for trades upto 10 days old
|
|
response = self.privatePostAuthROrderSymbolIdTrades(self.extend(request, params))
|
|
tradesList = []
|
|
for i in range(0, len(response)):
|
|
tradesList.append({'result': response[i]}) # convert to array of dicts to match parseOrder signature
|
|
return self.parse_trades(tradesList, market, since, limit)
|
|
|
|
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-trades
|
|
https://docs.bitfinex.com/reference/rest-auth-trades-by-symbol
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch trades for
|
|
:param int [limit]: the maximum number of trades structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = None
|
|
request: dict = {
|
|
'end': self.milliseconds(),
|
|
}
|
|
if since is not None:
|
|
request['start'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit # default 25, max 1000
|
|
response = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
response = self.privatePostAuthRTradesSymbolHist(self.extend(request, params))
|
|
else:
|
|
response = self.privatePostAuthRTradesHist(self.extend(request, params))
|
|
tradesList = []
|
|
for i in range(0, len(response)):
|
|
tradesList.append({'result': response[i]}) # convert to array of dicts to match parseOrder signature
|
|
return self.parse_trades(tradesList, market, since, limit)
|
|
|
|
def create_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
create a currency deposit address
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-deposit-address
|
|
|
|
:param str code: unified currency code of the currency for the deposit address
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
'op_renew': 1,
|
|
}
|
|
return self.fetch_deposit_address(code, self.extend(request, params))
|
|
|
|
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch the deposit address for a currency associated with self account
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-deposit-address
|
|
|
|
:param str code: unified currency code
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
# if not provided explicitly we will try to match using the currency name
|
|
network = self.safe_string(params, 'network', code)
|
|
currencyNetworks = self.safe_value(currency, 'networks', {})
|
|
currencyNetwork = self.safe_value(currencyNetworks, network)
|
|
networkId = self.safe_string(currencyNetwork, 'id')
|
|
if networkId is None:
|
|
raise ArgumentsRequired(self.id + " fetchDepositAddress() could not find a network for '" + code + "'. You can specify it by providing the 'network' value inside params")
|
|
wallet = self.safe_string(params, 'wallet', 'exchange') # 'exchange', 'margin', 'funding' and also old labels 'exchange', 'trading', 'deposit', respectively
|
|
params = self.omit(params, 'network', 'wallet')
|
|
request: dict = {
|
|
'method': networkId,
|
|
'wallet': wallet,
|
|
'op_renew': 0, # a value of 1 will generate a new address
|
|
}
|
|
response = self.privatePostAuthWDepositAddress(self.extend(request, params))
|
|
#
|
|
# [
|
|
# 1582269616687, # MTS Millisecond Time Stamp of the update
|
|
# "acc_dep", # TYPE Purpose of notification "acc_dep" for account deposit
|
|
# null, # MESSAGE_ID unique ID of the message
|
|
# null, # not documented
|
|
# [
|
|
# null, # PLACEHOLDER
|
|
# "BITCOIN", # METHOD Method of deposit
|
|
# "BTC", # CURRENCY_CODE Currency code of new address
|
|
# null, # PLACEHOLDER
|
|
# "1BC9PZqpUmjyEB54uggn8TFKj49zSDYzqG", # ADDRESS
|
|
# null, # POOL_ADDRESS
|
|
# ],
|
|
# null, # CODE null or integer work in progress
|
|
# "SUCCESS", # STATUS Status of the notification, SUCCESS, ERROR, FAILURE
|
|
# "success", # TEXT Text of the notification
|
|
# ]
|
|
#
|
|
result = self.safe_value(response, 4, [])
|
|
poolAddress = self.safe_string(result, 5)
|
|
address = self.safe_string(result, 4) if (poolAddress is None) else poolAddress
|
|
tag = None if (poolAddress is None) else self.safe_string(result, 4)
|
|
self.check_address(address)
|
|
return {
|
|
'currency': code,
|
|
'address': address,
|
|
'tag': tag,
|
|
'network': None,
|
|
'info': response,
|
|
}
|
|
|
|
def parse_transaction_status(self, status: Str):
|
|
statuses: dict = {
|
|
'SUCCESS': 'ok',
|
|
'COMPLETED': 'ok',
|
|
'ERROR': 'failed',
|
|
'FAILURE': 'failed',
|
|
'CANCELED': 'canceled',
|
|
'PENDING APPROVAL': 'pending',
|
|
'PENDING': 'pending',
|
|
'PENDING REVIEW': 'pending',
|
|
'PENDING CANCELLATION': 'pending',
|
|
'SENDING': 'pending',
|
|
'USER APPROVED': 'pending',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# withdraw
|
|
#
|
|
# [
|
|
# 1582271520931, # MTS Millisecond Time Stamp of the update
|
|
# "acc_wd-req", # TYPE Purpose of notification "acc_wd-req" account withdrawal request
|
|
# null, # MESSAGE_ID unique ID of the message
|
|
# null, # not documented
|
|
# [
|
|
# 0, # WITHDRAWAL_ID Unique Withdrawal ID
|
|
# null, # PLACEHOLDER
|
|
# "bitcoin", # METHOD Method of withdrawal
|
|
# null, # PAYMENT_ID Payment ID if relevant
|
|
# "exchange", # WALLET Sending wallet
|
|
# 1, # AMOUNT Amount of Withdrawal less fee
|
|
# null, # PLACEHOLDER
|
|
# null, # PLACEHOLDER
|
|
# 0.0004, # WITHDRAWAL_FEE Fee on withdrawal
|
|
# ],
|
|
# null, # CODE null or integer Work in progress
|
|
# "SUCCESS", # STATUS Status of the notification, it may vary over time SUCCESS, ERROR, FAILURE
|
|
# "Invalid bitcoin address(abcdef)", # TEXT Text of the notification
|
|
# ]
|
|
#
|
|
# fetchDepositsWithdrawals
|
|
#
|
|
# [
|
|
# 13293039, # ID
|
|
# "ETH", # CURRENCY
|
|
# "ETHEREUM", # CURRENCY_NAME
|
|
# null,
|
|
# null,
|
|
# 1574175052000, # MTS_STARTED
|
|
# 1574181326000, # MTS_UPDATED
|
|
# null,
|
|
# null,
|
|
# "CANCELED", # STATUS
|
|
# null,
|
|
# null,
|
|
# -0.24, # AMOUNT, negative for withdrawals
|
|
# -0.00135, # FEES
|
|
# null,
|
|
# null,
|
|
# "0x38110e0Fc932CB2BE...........", # DESTINATION_ADDRESS
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# "0x523ec8945500.....................................", # TRANSACTION_ID
|
|
# "Purchase of 100 pizzas", # WITHDRAW_TRANSACTION_NOTE, might also be: null
|
|
# ]
|
|
#
|
|
transactionLength = len(transaction)
|
|
timestamp = None
|
|
updated = None
|
|
code = None
|
|
amount = None
|
|
id = None
|
|
status = None
|
|
tag = None
|
|
type = None
|
|
feeCost = None
|
|
txid = None
|
|
addressTo = None
|
|
network = None
|
|
comment = None
|
|
if transactionLength == 8:
|
|
data = self.safe_value(transaction, 4, [])
|
|
timestamp = self.safe_integer(transaction, 0)
|
|
if currency is not None:
|
|
code = currency['code']
|
|
feeCost = self.safe_string(data, 8)
|
|
if feeCost is not None:
|
|
feeCost = Precise.string_abs(feeCost)
|
|
amount = self.safe_number(data, 5)
|
|
id = self.safe_integer(data, 0)
|
|
status = 'ok'
|
|
if id == 0:
|
|
id = None
|
|
status = 'failed'
|
|
tag = self.safe_string(data, 3)
|
|
type = 'withdrawal'
|
|
networkId = self.safe_string(data, 2)
|
|
network = self.network_id_to_code(networkId.upper()) # withdraw returns in lowercase
|
|
elif transactionLength == 22:
|
|
id = self.safe_string(transaction, 0)
|
|
currencyId = self.safe_string(transaction, 1)
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
networkId = self.safe_string(transaction, 2)
|
|
network = self.network_id_to_code(networkId)
|
|
timestamp = self.safe_integer(transaction, 5)
|
|
updated = self.safe_integer(transaction, 6)
|
|
status = self.parse_transaction_status(self.safe_string(transaction, 9))
|
|
signedAmount = self.safe_string(transaction, 12)
|
|
amount = Precise.string_abs(signedAmount)
|
|
if signedAmount is not None:
|
|
if Precise.string_lt(signedAmount, '0'):
|
|
type = 'withdrawal'
|
|
else:
|
|
type = 'deposit'
|
|
feeCost = self.safe_string(transaction, 13)
|
|
if feeCost is not None:
|
|
feeCost = Precise.string_abs(feeCost)
|
|
addressTo = self.safe_string(transaction, 16)
|
|
txid = self.safe_string(transaction, 20)
|
|
comment = self.safe_string(transaction, 21)
|
|
return {
|
|
'info': transaction,
|
|
'id': id,
|
|
'txid': txid,
|
|
'type': type,
|
|
'currency': code,
|
|
'network': network,
|
|
'amount': self.parse_number(amount),
|
|
'status': status,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'address': addressTo, # self is actually the tag for XRP transfers(the address is missing)
|
|
'addressFrom': None,
|
|
'addressTo': addressTo,
|
|
'tag': tag, # refix it properly for the tag from description
|
|
'tagFrom': None,
|
|
'tagTo': tag,
|
|
'updated': updated,
|
|
'comment': comment,
|
|
'internal': None,
|
|
'fee': {
|
|
'currency': code,
|
|
'cost': self.parse_number(feeCost),
|
|
'rate': None,
|
|
},
|
|
}
|
|
|
|
def fetch_trading_fees(self, params={}) -> TradingFees:
|
|
"""
|
|
fetch the trading fees for multiple markets
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-summary
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
response = self.privatePostAuthRSummary(params)
|
|
#
|
|
# Response Spec:
|
|
# [
|
|
# PLACEHOLDER,
|
|
# PLACEHOLDER,
|
|
# PLACEHOLDER,
|
|
# PLACEHOLDER,
|
|
# [
|
|
# [
|
|
# MAKER_FEE,
|
|
# MAKER_FEE,
|
|
# MAKER_FEE,
|
|
# PLACEHOLDER,
|
|
# PLACEHOLDER,
|
|
# DERIV_REBATE
|
|
# ],
|
|
# [
|
|
# TAKER_FEE_TO_CRYPTO,
|
|
# TAKER_FEE_TO_STABLE,
|
|
# TAKER_FEE_TO_FIAT,
|
|
# PLACEHOLDER,
|
|
# PLACEHOLDER,
|
|
# DERIV_TAKER_FEE
|
|
# ]
|
|
# ],
|
|
# PLACEHOLDER,
|
|
# PLACEHOLDER,
|
|
# PLACEHOLDER,
|
|
# PLACEHOLDER,
|
|
# {
|
|
# LEO_LEV,
|
|
# LEO_AMOUNT_AVG
|
|
# }
|
|
# ]
|
|
#
|
|
# Example response:
|
|
#
|
|
# [
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# [
|
|
# [0.001, 0.001, 0.001, null, null, 0.0002],
|
|
# [0.002, 0.002, 0.002, null, null, 0.00065]
|
|
# ],
|
|
# [
|
|
# [
|
|
# {
|
|
# "curr": "Total(USD)",
|
|
# "vol": "0",
|
|
# "vol_safe": "0",
|
|
# "vol_maker": "0",
|
|
# "vol_BFX": "0",
|
|
# "vol_BFX_safe": "0",
|
|
# "vol_BFX_maker": "0"
|
|
# }
|
|
# ],
|
|
# {},
|
|
# 0
|
|
# ],
|
|
# [null, {}, 0],
|
|
# null,
|
|
# null,
|
|
# {leo_lev: "0", leo_amount_avg: "0"}
|
|
# ]
|
|
#
|
|
result: dict = {}
|
|
fiat = self.safe_value(self.options, 'fiat', {})
|
|
feeData = self.safe_value(response, 4, [])
|
|
makerData = self.safe_value(feeData, 0, [])
|
|
takerData = self.safe_value(feeData, 1, [])
|
|
makerFee = self.safe_number(makerData, 0)
|
|
makerFeeFiat = self.safe_number(makerData, 2)
|
|
makerFeeDeriv = self.safe_number(makerData, 5)
|
|
takerFee = self.safe_number(takerData, 0)
|
|
takerFeeFiat = self.safe_number(takerData, 2)
|
|
takerFeeDeriv = self.safe_number(takerData, 5)
|
|
for i in range(0, len(self.symbols)):
|
|
symbol = self.symbols[i]
|
|
market = self.market(symbol)
|
|
fee = {
|
|
'info': response,
|
|
'symbol': symbol,
|
|
'percentage': True,
|
|
'tierBased': True,
|
|
}
|
|
if market['quote'] in fiat:
|
|
fee['maker'] = makerFeeFiat
|
|
fee['taker'] = takerFeeFiat
|
|
elif market['contract']:
|
|
fee['maker'] = makerFeeDeriv
|
|
fee['taker'] = takerFeeDeriv
|
|
else: # TODO check if stable coin
|
|
fee['maker'] = makerFee
|
|
fee['taker'] = takerFee
|
|
result[symbol] = fee
|
|
return result
|
|
|
|
def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch history of deposits and withdrawals
|
|
|
|
https://docs.bitfinex.com/reference/movement-info
|
|
https://docs.bitfinex.com/reference/rest-auth-movements
|
|
|
|
:param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
|
|
:param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
|
|
:param int [limit]: max number of deposit/withdrawals to return, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
self.load_markets()
|
|
currency = None
|
|
request: dict = {}
|
|
if since is not None:
|
|
request['start'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit # max 1000
|
|
response = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['currency'] = currency['uppercaseId']
|
|
response = self.privatePostAuthRMovementsCurrencyHist(self.extend(request, params))
|
|
else:
|
|
response = self.privatePostAuthRMovementsHist(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# 13293039, # ID
|
|
# "ETH", # CURRENCY
|
|
# "ETHEREUM", # CURRENCY_NAME
|
|
# null,
|
|
# null,
|
|
# 1574175052000, # MTS_STARTED
|
|
# 1574181326000, # MTS_UPDATED
|
|
# null,
|
|
# null,
|
|
# "CANCELED", # STATUS
|
|
# null,
|
|
# null,
|
|
# -0.24, # AMOUNT, negative for withdrawals
|
|
# -0.00135, # FEES
|
|
# null,
|
|
# null,
|
|
# "0x38110e0Fc932CB2BE...........", # DESTINATION_ADDRESS
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# "0x523ec8945500.....................................", # TRANSACTION_ID
|
|
# "Purchase of 100 pizzas", # WITHDRAW_TRANSACTION_NOTE, might also be: null
|
|
# ]
|
|
# ]
|
|
#
|
|
return self.parse_transactions(response, currency, since, limit)
|
|
|
|
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-withdraw
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: the amount to withdraw
|
|
:param str address: the address to withdraw to
|
|
:param str tag:
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
self.check_address(address)
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
# if not provided explicitly we will try to match using the currency name
|
|
network = self.safe_string(params, 'network', code)
|
|
params = self.omit(params, 'network')
|
|
currencyNetworks = self.safe_value(currency, 'networks', {})
|
|
currencyNetwork = self.safe_value(currencyNetworks, network)
|
|
networkId = self.safe_string(currencyNetwork, 'id')
|
|
if networkId is None:
|
|
raise ArgumentsRequired(self.id + " withdraw() could not find a network for '" + code + "'. You can specify it by providing the 'network' value inside params")
|
|
wallet = self.safe_string(params, 'wallet', 'exchange') # 'exchange', 'margin', 'funding' and also old labels 'exchange', 'trading', 'deposit', respectively
|
|
params = self.omit(params, 'network', 'wallet')
|
|
request: dict = {
|
|
'method': networkId,
|
|
'wallet': wallet,
|
|
'amount': self.number_to_string(amount),
|
|
'address': address,
|
|
}
|
|
if tag is not None:
|
|
request['payment_id'] = tag
|
|
withdrawOptions = self.safe_value(self.options, 'withdraw', {})
|
|
includeFee = self.safe_bool(withdrawOptions, 'includeFee', False)
|
|
if includeFee:
|
|
request['fee_deduct'] = 1
|
|
response = self.privatePostAuthWWithdraw(self.extend(request, params))
|
|
#
|
|
# [
|
|
# 1582271520931, # MTS Millisecond Time Stamp of the update
|
|
# "acc_wd-req", # TYPE Purpose of notification "acc_wd-req" account withdrawal request
|
|
# null, # MESSAGE_ID unique ID of the message
|
|
# null, # not documented
|
|
# [
|
|
# 0, # WITHDRAWAL_ID Unique Withdrawal ID
|
|
# null, # PLACEHOLDER
|
|
# "bitcoin", # METHOD Method of withdrawal
|
|
# null, # PAYMENT_ID Payment ID if relevant
|
|
# "exchange", # WALLET Sending wallet
|
|
# 1, # AMOUNT Amount of Withdrawal less fee
|
|
# null, # PLACEHOLDER
|
|
# null, # PLACEHOLDER
|
|
# 0.0004, # WITHDRAWAL_FEE Fee on withdrawal
|
|
# ],
|
|
# null, # CODE null or integer Work in progress
|
|
# "SUCCESS", # STATUS Status of the notification, it may vary over time SUCCESS, ERROR, FAILURE
|
|
# "Invalid bitcoin address(abcdef)", # TEXT Text of the notification
|
|
# ]
|
|
#
|
|
# in case of failure:
|
|
#
|
|
# [
|
|
# "error",
|
|
# 10001,
|
|
# "Momentary balance check. Please wait few seconds and try the transfer again."
|
|
# ]
|
|
#
|
|
statusMessage = self.safe_string(response, 0)
|
|
if statusMessage == 'error':
|
|
feedback = self.id + ' ' + response
|
|
message = self.safe_string(response, 2, '')
|
|
# same message v1
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
|
raise ExchangeError(feedback) # unknown message
|
|
text = self.safe_string(response, 7)
|
|
if text != 'success':
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], text, text)
|
|
return self.parse_transaction(response, currency)
|
|
|
|
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
fetch all open positions
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-positions
|
|
|
|
:param str[]|None symbols: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
response = self.privatePostAuthRPositions(params)
|
|
#
|
|
# [
|
|
# [
|
|
# "tBTCUSD", # SYMBOL
|
|
# "ACTIVE", # STATUS
|
|
# 0.0195, # AMOUNT
|
|
# 8565.0267019, # BASE_PRICE
|
|
# 0, # MARGIN_FUNDING
|
|
# 0, # MARGIN_FUNDING_TYPE
|
|
# -0.33455568705000516, # PL
|
|
# -0.0003117550117425625, # PL_PERC
|
|
# 7045.876419249083, # PRICE_LIQ
|
|
# 3.0673001895895604, # LEVERAGE
|
|
# null, # _PLACEHOLDER
|
|
# 142355652, # POSITION_ID
|
|
# 1574002216000, # MTS_CREATE
|
|
# 1574002216000, # MTS_UPDATE
|
|
# null, # _PLACEHOLDER
|
|
# 0, # TYPE
|
|
# null, # _PLACEHOLDER
|
|
# 0, # COLLATERAL
|
|
# 0, # COLLATERAL_MIN
|
|
# # META
|
|
# {
|
|
# "reason":"TRADE",
|
|
# "order_id":34271018124,
|
|
# "liq_stage":null,
|
|
# "trade_price":"8565.0267019",
|
|
# "trade_amount":"0.0195",
|
|
# "order_id_oppo":34277498022
|
|
# }
|
|
# ]
|
|
# ]
|
|
#
|
|
positionsList = []
|
|
for i in range(0, len(response)):
|
|
positionsList.append({'result': response[i]})
|
|
return self.parse_positions(positionsList, symbols)
|
|
|
|
def parse_position(self, position: dict, market: Market = None):
|
|
#
|
|
# [
|
|
# "tBTCUSD", # SYMBOL
|
|
# "ACTIVE", # STATUS
|
|
# 0.0195, # AMOUNT
|
|
# 8565.0267019, # BASE_PRICE
|
|
# 0, # MARGIN_FUNDING
|
|
# 0, # MARGIN_FUNDING_TYPE
|
|
# -0.33455568705000516, # PL
|
|
# -0.0003117550117425625, # PL_PERC
|
|
# 7045.876419249083, # PRICE_LIQ
|
|
# 3.0673001895895604, # LEVERAGE
|
|
# null, # _PLACEHOLDER
|
|
# 142355652, # POSITION_ID
|
|
# 1574002216000, # MTS_CREATE
|
|
# 1574002216000, # MTS_UPDATE
|
|
# null, # _PLACEHOLDER
|
|
# 0, # TYPE
|
|
# null, # _PLACEHOLDER
|
|
# 0, # COLLATERAL
|
|
# 0, # COLLATERAL_MIN
|
|
# # META
|
|
# {
|
|
# "reason": "TRADE",
|
|
# "order_id": 34271018124,
|
|
# "liq_stage": null,
|
|
# "trade_price": "8565.0267019",
|
|
# "trade_amount": "0.0195",
|
|
# "order_id_oppo": 34277498022
|
|
# }
|
|
# ]
|
|
#
|
|
positionList = self.safe_list(position, 'result')
|
|
marketId = self.safe_string(positionList, 0)
|
|
amount = self.safe_string(positionList, 2)
|
|
timestamp = self.safe_integer(positionList, 12)
|
|
meta = self.safe_string(positionList, 19)
|
|
tradePrice = self.safe_string(meta, 'trade_price')
|
|
tradeAmount = self.safe_string(meta, 'trade_amount')
|
|
return self.safe_position({
|
|
'info': positionList,
|
|
'id': self.safe_string(positionList, 11),
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'notional': self.parse_number(amount),
|
|
'marginMode': 'isolated', # derivatives use isolated, margin uses cross, https://support.bitfinex.com/hc/en-us/articles/360035475374-Derivatives-Trading-on-Bitfinex
|
|
'liquidationPrice': self.safe_number(positionList, 8),
|
|
'entryPrice': self.safe_number(positionList, 3),
|
|
'unrealizedPnl': self.safe_number(positionList, 6),
|
|
'percentage': self.safe_number(positionList, 7),
|
|
'contracts': None,
|
|
'contractSize': None,
|
|
'markPrice': None,
|
|
'lastPrice': None,
|
|
'side': 'long' if Precise.string_gt(amount, '0') else 'short',
|
|
'hedged': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastUpdateTimestamp': self.safe_integer(positionList, 13),
|
|
'maintenanceMargin': self.safe_number(positionList, 18),
|
|
'maintenanceMarginPercentage': None,
|
|
'collateral': self.safe_number(positionList, 17),
|
|
'initialMargin': self.parse_number(Precise.string_mul(tradeAmount, tradePrice)),
|
|
'initialMarginPercentage': None,
|
|
'leverage': self.safe_number(positionList, 9),
|
|
'marginRatio': None,
|
|
'stopLossPrice': None,
|
|
'takeProfitPrice': None,
|
|
})
|
|
|
|
def nonce(self):
|
|
return self.milliseconds()
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
request = '/' + self.implode_params(path, params)
|
|
query = self.omit(params, self.extract_params(path))
|
|
if api == 'v1':
|
|
request = api + request
|
|
else:
|
|
request = self.version + request
|
|
url = self.urls['api'][api] + '/' + request
|
|
if api == 'public':
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
if api == 'private':
|
|
self.check_required_credentials()
|
|
nonce = str(self.nonce())
|
|
body = self.json(query)
|
|
auth = '/api/' + request + nonce + body
|
|
signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha384)
|
|
headers = {
|
|
'bfx-nonce': nonce,
|
|
'bfx-apikey': self.apiKey,
|
|
'bfx-signature': signature,
|
|
'Content-Type': 'application/json',
|
|
}
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, statusCode, statusText, url, method, headers, body, response, requestHeaders, requestBody):
|
|
# ["error", 11010, "ratelimit: error"]
|
|
if response is not None:
|
|
if not isinstance(response, list):
|
|
message = self.safe_string_2(response, 'message', 'error')
|
|
feedback = self.id + ' ' + body
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
|
raise ExchangeError(self.id + ' ' + body)
|
|
elif response == '':
|
|
raise ExchangeError(self.id + ' returned empty response')
|
|
if statusCode == 429:
|
|
raise RateLimitExceeded(self.id + ' ' + body)
|
|
if statusCode == 500:
|
|
# See https://docs.bitfinex.com/docs/abbreviations-glossary#section-errorinfo-codes
|
|
errorCode = self.safe_string(response, 1, '')
|
|
errorText = self.safe_string(response, 2, '')
|
|
feedback = self.id + ' ' + errorText
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], errorText, feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], errorText, feedback)
|
|
raise ExchangeError(self.id + ' ' + errorText + '(#' + errorCode + ')')
|
|
return response
|
|
|
|
def parse_ledger_entry_type(self, type: Str):
|
|
if type is None:
|
|
return None
|
|
elif type.find('fee') >= 0 or type.find('charged') >= 0:
|
|
return 'fee'
|
|
elif type.find('rebate') >= 0:
|
|
return 'rebate'
|
|
elif type.find('deposit') >= 0 or type.find('withdrawal') >= 0:
|
|
return 'transaction'
|
|
elif type.find('transfer') >= 0:
|
|
return 'transfer'
|
|
elif type.find('payment') >= 0:
|
|
return 'payout'
|
|
elif type.find('exchange') >= 0 or type.find('position') >= 0:
|
|
return 'trade'
|
|
else:
|
|
return type
|
|
|
|
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
|
|
#
|
|
# [
|
|
# [
|
|
# 2531822314, # ID: Ledger identifier
|
|
# "USD", # CURRENCY: The symbol of the currency(ex. "BTC")
|
|
# null, # PLACEHOLDER
|
|
# 1573521810000, # MTS: Timestamp in milliseconds
|
|
# null, # PLACEHOLDER
|
|
# 0.01644445, # AMOUNT: Amount of funds moved
|
|
# 0, # BALANCE: New balance
|
|
# null, # PLACEHOLDER
|
|
# "Settlement @ 185.79 on wallet margin" # DESCRIPTION: Description of ledger transaction
|
|
# ]
|
|
# ]
|
|
#
|
|
itemList = self.safe_list(item, 'result', [])
|
|
type = None
|
|
id = self.safe_string(itemList, 0)
|
|
currencyId = self.safe_string(itemList, 1)
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
currency = self.safe_currency(currencyId, currency)
|
|
timestamp = self.safe_integer(itemList, 3)
|
|
amount = self.safe_number(itemList, 5)
|
|
after = self.safe_number(itemList, 6)
|
|
description = self.safe_string(itemList, 8)
|
|
if description is not None:
|
|
parts = description.split(' @ ')
|
|
first = self.safe_string_lower(parts, 0)
|
|
type = self.parse_ledger_entry_type(first)
|
|
return self.safe_ledger_entry({
|
|
'info': item,
|
|
'id': id,
|
|
'direction': None,
|
|
'account': None,
|
|
'referenceId': id,
|
|
'referenceAccount': None,
|
|
'type': type,
|
|
'currency': code,
|
|
'amount': amount,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'before': None,
|
|
'after': after,
|
|
'status': None,
|
|
'fee': None,
|
|
}, currency)
|
|
|
|
def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
|
|
"""
|
|
fetch the history of changes, actions done by the user or operations that altered the balance of the user
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-ledgers
|
|
|
|
:param str [code]: unified currency code, default is None
|
|
:param int [since]: timestamp in ms of the earliest ledger entry, default is None
|
|
:param int [limit]: max number of ledger entries to return, default is None, max is 2500
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest ledger entry
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_dynamic('fetchLedger', code, since, limit, params, 2500)
|
|
currency = None
|
|
request: dict = {}
|
|
if since is not None:
|
|
request['start'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
request, params = self.handle_until_option('end', request, params)
|
|
response = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['currency'] = currency['uppercaseId']
|
|
response = self.privatePostAuthRLedgersCurrencyHist(self.extend(request, params))
|
|
else:
|
|
response = self.privatePostAuthRLedgersHist(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# 2531822314, # ID: Ledger identifier
|
|
# "USD", # CURRENCY: The symbol of the currency(ex. "BTC")
|
|
# null, # PLACEHOLDER
|
|
# 1573521810000, # MTS: Timestamp in milliseconds
|
|
# null, # PLACEHOLDER
|
|
# 0.01644445, # AMOUNT: Amount of funds moved
|
|
# 0, # BALANCE: New balance
|
|
# null, # PLACEHOLDER
|
|
# "Settlement @ 185.79 on wallet margin" # DESCRIPTION: Description of ledger transaction
|
|
# ]
|
|
# ]
|
|
#
|
|
ledgerObjects = []
|
|
for i in range(0, len(response)):
|
|
item = response[i]
|
|
ledgerObjects.append({'result': item})
|
|
return self.parse_ledger(ledgerObjects, currency, since, limit)
|
|
|
|
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
|
|
"""
|
|
fetch the current funding rate for multiple symbols
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-derivatives-status
|
|
|
|
:param str[] symbols: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
|
"""
|
|
if symbols is None:
|
|
raise ArgumentsRequired(self.id + ' fetchFundingRates() requires a symbols argument')
|
|
self.load_markets()
|
|
marketIds = self.market_ids(symbols)
|
|
request: dict = {
|
|
'keys': ','.join(marketIds),
|
|
}
|
|
response = self.publicGetStatusDeriv(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# "tBTCF0:USTF0",
|
|
# 1691165059000,
|
|
# null,
|
|
# 29297.851276225,
|
|
# 29277.5,
|
|
# null,
|
|
# 36950860.76010306,
|
|
# null,
|
|
# 1691193600000,
|
|
# 0.00000527,
|
|
# 82,
|
|
# null,
|
|
# 0.00014548,
|
|
# null,
|
|
# null,
|
|
# 29278.8925,
|
|
# null,
|
|
# null,
|
|
# 9636.07644994,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005,
|
|
# 0.0025
|
|
# ]
|
|
# ]
|
|
#
|
|
return self.parse_funding_rates(response, symbols)
|
|
|
|
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches historical funding rate prices
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-derivatives-status-history
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: timestamp in ms of the earliest funding rate entry
|
|
:param int [limit]: max number of funding rate entrys to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest funding rate
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params, 5000)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if since is not None:
|
|
request['start'] = since
|
|
request, params = self.handle_until_option('end', request, params)
|
|
response = self.publicGetStatusDerivSymbolHist(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# "tBTCF0:USTF0",
|
|
# 1691165059000,
|
|
# null,
|
|
# 29297.851276225,
|
|
# 29277.5,
|
|
# null,
|
|
# 36950860.76010306,
|
|
# null,
|
|
# 1691193600000,
|
|
# 0.00000527,
|
|
# 82,
|
|
# null,
|
|
# 0.00014548,
|
|
# null,
|
|
# null,
|
|
# 29278.8925,
|
|
# null,
|
|
# null,
|
|
# 9636.07644994,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005,
|
|
# 0.0025
|
|
# ]
|
|
# ]
|
|
#
|
|
rates = []
|
|
for i in range(0, len(response)):
|
|
fr = response[i]
|
|
rate = self.parse_funding_rate_history(fr, market)
|
|
rates.append(rate)
|
|
reversedArray = []
|
|
rawRates = self.filter_by_symbol_since_limit(rates, symbol, since, limit)
|
|
ratesLength = len(rawRates)
|
|
for i in range(0, ratesLength):
|
|
index = ratesLength - i - 1
|
|
valueAtIndex = rawRates[index]
|
|
reversedArray.append(valueAtIndex)
|
|
return reversedArray
|
|
|
|
def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
|
|
#
|
|
# [
|
|
# "tBTCF0:USTF0",
|
|
# 1691165059000,
|
|
# null,
|
|
# 29297.851276225,
|
|
# 29277.5,
|
|
# null,
|
|
# 36950860.76010306,
|
|
# null,
|
|
# 1691193600000,
|
|
# 0.00000527,
|
|
# 82,
|
|
# null,
|
|
# 0.00014548,
|
|
# null,
|
|
# null,
|
|
# 29278.8925,
|
|
# null,
|
|
# null,
|
|
# 9636.07644994,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005,
|
|
# 0.0025
|
|
# ]
|
|
#
|
|
marketId = self.safe_string(contract, 0)
|
|
timestamp = self.safe_integer(contract, 1)
|
|
nextFundingTimestamp = self.safe_integer(contract, 8)
|
|
return {
|
|
'info': contract,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'markPrice': self.safe_number(contract, 15),
|
|
'indexPrice': self.safe_number(contract, 3),
|
|
'interestRate': None,
|
|
'estimatedSettlePrice': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'fundingRate': self.safe_number(contract, 12),
|
|
'fundingTimestamp': None,
|
|
'fundingDatetime': None,
|
|
'nextFundingRate': self.safe_number(contract, 9),
|
|
'nextFundingTimestamp': nextFundingTimestamp,
|
|
'nextFundingDatetime': self.iso8601(nextFundingTimestamp),
|
|
'previousFundingRate': None,
|
|
'previousFundingTimestamp': None,
|
|
'previousFundingDatetime': None,
|
|
'interval': None,
|
|
}
|
|
|
|
def parse_funding_rate_history(self, contract, market: Market = None):
|
|
#
|
|
# [
|
|
# 1691165494000,
|
|
# null,
|
|
# 29278.95838065,
|
|
# 29260.5,
|
|
# null,
|
|
# 36950860.76010305,
|
|
# null,
|
|
# 1691193600000,
|
|
# 0.00001449,
|
|
# 222,
|
|
# null,
|
|
# 0.00014548,
|
|
# null,
|
|
# null,
|
|
# 29260.005,
|
|
# null,
|
|
# null,
|
|
# 9635.86484562,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005,
|
|
# 0.0025
|
|
# ]
|
|
#
|
|
timestamp = self.safe_integer(contract, 0)
|
|
nextFundingTimestamp = self.safe_integer(contract, 7)
|
|
return {
|
|
'info': contract,
|
|
'symbol': self.safe_symbol(None, market),
|
|
'markPrice': self.safe_number(contract, 14),
|
|
'indexPrice': self.safe_number(contract, 2),
|
|
'interestRate': None,
|
|
'estimatedSettlePrice': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'fundingRate': self.safe_number(contract, 11),
|
|
'fundingTimestamp': None,
|
|
'fundingDatetime': None,
|
|
'nextFundingRate': self.safe_number(contract, 8),
|
|
'nextFundingTimestamp': nextFundingTimestamp,
|
|
'nextFundingDatetime': self.iso8601(nextFundingTimestamp),
|
|
'previousFundingRate': None,
|
|
'previousFundingTimestamp': None,
|
|
'previousFundingDatetime': None,
|
|
}
|
|
|
|
def fetch_open_interests(self, symbols: Strings = None, params={}):
|
|
"""
|
|
Retrieves the open interest for a list of symbols
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-derivatives-status
|
|
|
|
:param str[] [symbols]: a list of unified CCXT market symbols
|
|
:param dict [params]: exchange specific parameters
|
|
:returns dict[]: a list of `open interest structures <https://docs.ccxt.com/#/?id=open-interest-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
marketIds = ['ALL']
|
|
if symbols is not None:
|
|
marketIds = self.market_ids(symbols)
|
|
request: dict = {
|
|
'keys': ','.join(marketIds),
|
|
}
|
|
response = self.publicGetStatusDeriv(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# "tXRPF0:USTF0", # market id
|
|
# 1706256986000, # millisecond timestamp
|
|
# null,
|
|
# 0.512705, # derivative mid price
|
|
# 0.512395, # underlying spot mid price
|
|
# null,
|
|
# 37671483.04, # insurance fund balance
|
|
# null,
|
|
# 1706284800000, # timestamp of next funding
|
|
# 0.00002353, # accrued funding for next period
|
|
# 317, # next funding step
|
|
# null,
|
|
# 0, # current funding
|
|
# null,
|
|
# null,
|
|
# 0.5123016, # mark price
|
|
# null,
|
|
# null,
|
|
# 2233562.03115, # open interest in contracts
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005, # average spread without funding payment
|
|
# 0.0025 # funding payment cap
|
|
# ]
|
|
# ]
|
|
#
|
|
return self.parse_open_interests(response, symbols)
|
|
|
|
def fetch_open_interest(self, symbol: str, params={}):
|
|
"""
|
|
retrieves the open interest of a contract trading pair
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-derivatives-status
|
|
|
|
:param str symbol: unified CCXT market symbol
|
|
:param dict [params]: exchange specific parameters
|
|
:returns dict: an `open interest structure <https://docs.ccxt.com/#/?id=open-interest-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'keys': market['id'],
|
|
}
|
|
response = self.publicGetStatusDeriv(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# "tXRPF0:USTF0", # market id
|
|
# 1706256986000, # millisecond timestamp
|
|
# null,
|
|
# 0.512705, # derivative mid price
|
|
# 0.512395, # underlying spot mid price
|
|
# null,
|
|
# 37671483.04, # insurance fund balance
|
|
# null,
|
|
# 1706284800000, # timestamp of next funding
|
|
# 0.00002353, # accrued funding for next period
|
|
# 317, # next funding step
|
|
# null,
|
|
# 0, # current funding
|
|
# null,
|
|
# null,
|
|
# 0.5123016, # mark price
|
|
# null,
|
|
# null,
|
|
# 2233562.03115, # open interest in contracts
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005, # average spread without funding payment
|
|
# 0.0025 # funding payment cap
|
|
# ]
|
|
# ]
|
|
#
|
|
oi = self.safe_list(response, 0)
|
|
return self.parse_open_interest(oi, market)
|
|
|
|
def fetch_open_interest_history(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
retrieves the open interest history of a currency
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-derivatives-status-history
|
|
|
|
:param str symbol: unified CCXT market symbol
|
|
:param str timeframe: the time period of each row of data, not used by bitfinex
|
|
:param int [since]: the time in ms of the earliest record to retrieve unix timestamp
|
|
:param int [limit]: the number of records in the response
|
|
:param dict [params]: exchange specific parameters
|
|
:param int [params.until]: the time in ms of the latest record to retrieve unix timestamp
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns: An array of `open interest structures <https://docs.ccxt.com/#/?id=open-interest-structure>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchOpenInterestHistory', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_deterministic('fetchOpenInterestHistory', symbol, since, limit, '8h', params, 5000)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if since is not None:
|
|
request['start'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
request, params = self.handle_until_option('end', request, params)
|
|
response = self.publicGetStatusDerivSymbolHist(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# 1706295191000, # timestamp
|
|
# null,
|
|
# 42152.425382, # derivative mid price
|
|
# 42133, # spot mid price
|
|
# null,
|
|
# 37671589.7853521, # insurance fund balance
|
|
# null,
|
|
# 1706313600000, # timestamp of next funding
|
|
# 0.00018734, # accrued funding for next period
|
|
# 3343, # next funding step
|
|
# null,
|
|
# 0.00007587, # current funding
|
|
# null,
|
|
# null,
|
|
# 42134.1, # mark price
|
|
# null,
|
|
# null,
|
|
# 5775.20348804, # open interest number of contracts
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005, # average spread without funding payment
|
|
# 0.0025 # funding payment cap
|
|
# ],
|
|
# ]
|
|
#
|
|
return self.parse_open_interests_history(response, market, since, limit)
|
|
|
|
def parse_open_interest(self, interest, market: Market = None):
|
|
#
|
|
# fetchOpenInterest:
|
|
#
|
|
# [
|
|
# "tXRPF0:USTF0", # market id
|
|
# 1706256986000, # millisecond timestamp
|
|
# null,
|
|
# 0.512705, # derivative mid price
|
|
# 0.512395, # underlying spot mid price
|
|
# null,
|
|
# 37671483.04, # insurance fund balance
|
|
# null,
|
|
# 1706284800000, # timestamp of next funding
|
|
# 0.00002353, # accrued funding for next period
|
|
# 317, # next funding step
|
|
# null,
|
|
# 0, # current funding
|
|
# null,
|
|
# null,
|
|
# 0.5123016, # mark price
|
|
# null,
|
|
# null,
|
|
# 2233562.03115, # open interest in contracts
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005, # average spread without funding payment
|
|
# 0.0025 # funding payment cap
|
|
# ]
|
|
#
|
|
# fetchOpenInterestHistory:
|
|
#
|
|
# [
|
|
# 1706295191000, # timestamp
|
|
# null,
|
|
# 42152.425382, # derivative mid price
|
|
# 42133, # spot mid price
|
|
# null,
|
|
# 37671589.7853521, # insurance fund balance
|
|
# null,
|
|
# 1706313600000, # timestamp of next funding
|
|
# 0.00018734, # accrued funding for next period
|
|
# 3343, # next funding step
|
|
# null,
|
|
# 0.00007587, # current funding
|
|
# null,
|
|
# null,
|
|
# 42134.1, # mark price
|
|
# null,
|
|
# null,
|
|
# 5775.20348804, # open interest number of contracts
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0.0005, # average spread without funding payment
|
|
# 0.0025 # funding payment cap
|
|
# ]
|
|
#
|
|
interestLength = len(interest)
|
|
openInterestIndex = 17 if (interestLength == 23) else 18
|
|
timestamp = self.safe_integer(interest, 1)
|
|
marketId = self.safe_string(interest, 0)
|
|
return self.safe_open_interest({
|
|
'symbol': self.safe_symbol(marketId, market, None, 'swap'),
|
|
'openInterestAmount': self.safe_number(interest, openInterestIndex),
|
|
'openInterestValue': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'info': interest,
|
|
}, market)
|
|
|
|
def fetch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
retrieves the public liquidations of a trading pair
|
|
|
|
https://docs.bitfinex.com/reference/rest-public-liquidations
|
|
|
|
:param str symbol: unified CCXT market symbol
|
|
:param int [since]: the earliest time in ms to fetch liquidations for
|
|
:param int [limit]: the maximum number of liquidation structures to retrieve
|
|
:param dict [params]: exchange specific parameters
|
|
:param int [params.until]: timestamp in ms of the latest liquidation
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns dict: an array of `liquidation structures <https://docs.ccxt.com/#/?id=liquidation-structure>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchLiquidations', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_deterministic('fetchLiquidations', symbol, since, limit, '8h', params, 500)
|
|
market = self.market(symbol)
|
|
request: dict = {}
|
|
if since is not None:
|
|
request['start'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
request, params = self.handle_until_option('end', request, params)
|
|
response = self.publicGetLiquidationsHist(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# [
|
|
# "pos",
|
|
# 171085137,
|
|
# 1706395919788,
|
|
# null,
|
|
# "tAVAXF0:USTF0",
|
|
# -8,
|
|
# 32.868,
|
|
# null,
|
|
# 1,
|
|
# 1,
|
|
# null,
|
|
# 33.255
|
|
# ]
|
|
# ],
|
|
# ]
|
|
#
|
|
return self.parse_liquidations(response, market, since, limit)
|
|
|
|
def parse_liquidation(self, liquidation, market: Market = None):
|
|
#
|
|
# [
|
|
# [
|
|
# "pos",
|
|
# 171085137, # position id
|
|
# 1706395919788, # timestamp
|
|
# null,
|
|
# "tAVAXF0:USTF0", # market id
|
|
# -8, # amount in contracts
|
|
# 32.868, # base price
|
|
# null,
|
|
# 1,
|
|
# 1,
|
|
# null,
|
|
# 33.255 # acquired price
|
|
# ]
|
|
# ]
|
|
#
|
|
entry = liquidation[0]
|
|
timestamp = self.safe_integer(entry, 2)
|
|
marketId = self.safe_string(entry, 4)
|
|
contracts = Precise.string_abs(self.safe_string(entry, 5))
|
|
contractSize = self.safe_string(market, 'contractSize')
|
|
baseValue = Precise.string_mul(contracts, contractSize)
|
|
price = self.safe_string(entry, 11)
|
|
sideFlag = self.safe_integer(entry, 8)
|
|
side = 'buy' if (sideFlag == 1) else 'sell'
|
|
return self.safe_liquidation({
|
|
'info': entry,
|
|
'symbol': self.safe_symbol(marketId, market, None, 'contract'),
|
|
'contracts': self.parse_number(contracts),
|
|
'contractSize': self.parse_number(contractSize),
|
|
'price': self.parse_number(price),
|
|
'side': side,
|
|
'baseValue': self.parse_number(baseValue),
|
|
'quoteValue': self.parse_number(Precise.string_mul(baseValue, price)),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
})
|
|
|
|
def set_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
|
"""
|
|
either adds or reduces margin in a swap position in order to set the margin to a specific value
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-deriv-pos-collateral-set
|
|
|
|
:param str symbol: unified market symbol of the market to set margin in
|
|
:param float amount: the amount to set the margin to
|
|
:param dict [params]: parameters specific to the exchange API endpoint
|
|
:returns dict: A `margin structure <https://github.com/ccxt/ccxt/wiki/Manual#add-margin-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
if not market['swap']:
|
|
raise NotSupported(self.id + ' setMargin() only support swap markets')
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'collateral': self.parse_to_numeric(amount),
|
|
}
|
|
response = self.privatePostAuthWDerivCollateralSet(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# 1
|
|
# ]
|
|
# ]
|
|
#
|
|
data = self.safe_value(response, 0)
|
|
return self.parse_margin_modification(data, market)
|
|
|
|
def parse_margin_modification(self, data, market=None) -> MarginModification:
|
|
#
|
|
# setMargin
|
|
#
|
|
# [
|
|
# [
|
|
# 1
|
|
# ]
|
|
# ]
|
|
#
|
|
marginStatusRaw = data[0]
|
|
marginStatus = 'ok' if (marginStatusRaw == 1) else 'failed'
|
|
return {
|
|
'info': data,
|
|
'symbol': market['symbol'],
|
|
'type': None,
|
|
'marginMode': 'isolated',
|
|
'amount': None,
|
|
'total': None,
|
|
'code': None,
|
|
'status': marginStatus,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
}
|
|
|
|
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
|
|
https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
|
|
|
|
:param str id: the order id
|
|
:param str [symbol]: unified symbol of the market the order was made in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
'id': [self.parse_to_numeric(id)],
|
|
}
|
|
market = None
|
|
response = None
|
|
if symbol is None:
|
|
response = self.privatePostAuthROrders(self.extend(request, params))
|
|
else:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
response = self.privatePostAuthROrdersSymbol(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# 139658969116,
|
|
# null,
|
|
# 1706843908637,
|
|
# "tBTCUST",
|
|
# 1706843908637,
|
|
# 1706843908638,
|
|
# 0.0001,
|
|
# 0.0001,
|
|
# "EXCHANGE LIMIT",
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0,
|
|
# "ACTIVE",
|
|
# null,
|
|
# null,
|
|
# 35000,
|
|
# 0,
|
|
# 0,
|
|
# 0,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0,
|
|
# 0,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# "API>BFX",
|
|
# null,
|
|
# null,
|
|
# {}
|
|
# ]
|
|
# ]
|
|
#
|
|
order = self.safe_list(response, 0)
|
|
newOrder = {'result': order}
|
|
return self.parse_order(newOrder, market)
|
|
|
|
def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
|
"""
|
|
edit a trade order
|
|
|
|
https://docs.bitfinex.com/reference/rest-auth-update-order
|
|
|
|
:param str id: edit order id
|
|
:param str symbol: unified symbol of the market to edit an order in
|
|
:param str type: 'market' or 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much you want to trade in units of the base currency
|
|
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.triggerPrice]: the price that triggers a trigger order
|
|
:param boolean [params.postOnly]: set to True if you want to make a post only order
|
|
:param boolean [params.reduceOnly]: indicates that the order is to reduce the size of a position
|
|
:param int [params.flags]: additional order parameters: 4096(Post Only), 1024(Reduce Only), 16384(OCO), 64(Hidden), 512(Close), 524288(No Var Rates)
|
|
:param int [params.leverage]: leverage for a derivative order, supported by derivative symbol orders only, the value should be between 1 and 100 inclusive
|
|
:param int [params.clientOrderId]: a unique client order id for the order
|
|
:param float [params.trailingAmount]: *swap only* the quote amount to trail away from the current market price
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'id': self.parse_to_numeric(id),
|
|
}
|
|
if amount is not None:
|
|
amountString = self.amount_to_precision(symbol, amount)
|
|
amountString = amountString if (side == 'buy') else Precise.string_neg(amountString)
|
|
request['amount'] = amountString
|
|
triggerPrice = self.safe_string_2(params, 'stopPrice', 'triggerPrice')
|
|
trailingAmount = self.safe_string(params, 'trailingAmount')
|
|
timeInForce = self.safe_string(params, 'timeInForce')
|
|
postOnlyParam = self.safe_bool(params, 'postOnly', False)
|
|
reduceOnly = self.safe_bool(params, 'reduceOnly', False)
|
|
clientOrderId = self.safe_integer_2(params, 'cid', 'clientOrderId')
|
|
if trailingAmount is not None:
|
|
request['price_trailing'] = trailingAmount
|
|
elif triggerPrice is not None:
|
|
# request['price'] is taken for stop orders
|
|
request['price'] = self.price_to_precision(symbol, triggerPrice)
|
|
if type == 'limit':
|
|
request['price_aux_limit'] = self.price_to_precision(symbol, price)
|
|
postOnly = (postOnlyParam or (timeInForce == 'PO'))
|
|
if (type != 'market') and (triggerPrice is None):
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
# flag values may be summed to combine flags
|
|
flags = 0
|
|
if postOnly:
|
|
flags = self.sum(flags, 4096)
|
|
if reduceOnly:
|
|
flags = self.sum(flags, 1024)
|
|
if flags != 0:
|
|
request['flags'] = flags
|
|
if clientOrderId is not None:
|
|
request['cid'] = clientOrderId
|
|
leverage = self.safe_integer_2(params, 'leverage', 'lev')
|
|
if leverage is not None:
|
|
request['lev'] = leverage
|
|
params = self.omit(params, ['triggerPrice', 'stopPrice', 'timeInForce', 'postOnly', 'reduceOnly', 'trailingAmount', 'clientOrderId', 'leverage'])
|
|
response = self.privatePostAuthWOrderUpdate(self.extend(request, params))
|
|
#
|
|
# [
|
|
# 1706845376402,
|
|
# "ou-req",
|
|
# null,
|
|
# null,
|
|
# [
|
|
# 139658969116,
|
|
# null,
|
|
# 1706843908637,
|
|
# "tBTCUST",
|
|
# 1706843908637,
|
|
# 1706843908638,
|
|
# 0.0002,
|
|
# 0.0002,
|
|
# "EXCHANGE LIMIT",
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0,
|
|
# "ACTIVE",
|
|
# null,
|
|
# null,
|
|
# 35000,
|
|
# 0,
|
|
# 0,
|
|
# 0,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# 0,
|
|
# 0,
|
|
# null,
|
|
# null,
|
|
# null,
|
|
# "API>BFX",
|
|
# null,
|
|
# null,
|
|
# {}
|
|
# ],
|
|
# null,
|
|
# "SUCCESS",
|
|
# "Submitting update to exchange limit buy order for 0.0002 BTC."
|
|
# ]
|
|
#
|
|
status = self.safe_string(response, 6)
|
|
if status != 'SUCCESS':
|
|
errorCode = response[5]
|
|
errorText = response[7]
|
|
raise ExchangeError(self.id + ' ' + response[6] + ': ' + errorText + '(#' + errorCode + ')')
|
|
order = self.safe_list(response, 4, [])
|
|
newOrder = {'result': order}
|
|
return self.parse_order(newOrder, market)
|