ccxt 4.4.36__py2.py3-none-any.whl → 4.4.38__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. ccxt/__init__.py +3 -3
  2. ccxt/abstract/bingx.py +0 -1
  3. ccxt/abstract/bitfinex.py +136 -65
  4. ccxt/abstract/bitfinex1.py +69 -0
  5. ccxt/async_support/__init__.py +3 -3
  6. ccxt/async_support/base/exchange.py +1 -1
  7. ccxt/async_support/binance.py +0 -2
  8. ccxt/async_support/bingx.py +89 -8
  9. ccxt/async_support/bitfinex.py +3005 -1084
  10. ccxt/async_support/bitfinex1.py +1704 -0
  11. ccxt/async_support/bitget.py +2 -4
  12. ccxt/async_support/bithumb.py +1 -1
  13. ccxt/async_support/bitmart.py +160 -13
  14. ccxt/async_support/bybit.py +3 -2
  15. ccxt/async_support/coinbase.py +86 -0
  16. ccxt/async_support/gate.py +1 -1
  17. ccxt/async_support/hyperliquid.py +124 -14
  18. ccxt/async_support/kucoin.py +75 -2
  19. ccxt/async_support/kucoinfutures.py +92 -5
  20. ccxt/async_support/ndax.py +5 -1
  21. ccxt/async_support/okx.py +0 -1
  22. ccxt/async_support/paradex.py +2 -2
  23. ccxt/async_support/probit.py +3 -1
  24. ccxt/base/exchange.py +9 -2
  25. ccxt/binance.py +0 -2
  26. ccxt/bingx.py +89 -8
  27. ccxt/bitfinex.py +3005 -1084
  28. ccxt/bitfinex1.py +1703 -0
  29. ccxt/bitget.py +2 -4
  30. ccxt/bithumb.py +1 -1
  31. ccxt/bitmart.py +160 -13
  32. ccxt/bybit.py +3 -2
  33. ccxt/coinbase.py +86 -0
  34. ccxt/gate.py +1 -1
  35. ccxt/hyperliquid.py +124 -14
  36. ccxt/kucoin.py +75 -2
  37. ccxt/kucoinfutures.py +92 -5
  38. ccxt/ndax.py +5 -1
  39. ccxt/okx.py +0 -1
  40. ccxt/paradex.py +2 -2
  41. ccxt/pro/__init__.py +3 -3
  42. ccxt/pro/bitfinex.py +725 -274
  43. ccxt/pro/bitfinex1.py +635 -0
  44. ccxt/pro/probit.py +1 -0
  45. ccxt/probit.py +3 -1
  46. ccxt/test/tests_async.py +5 -1
  47. ccxt/test/tests_sync.py +5 -1
  48. {ccxt-4.4.36.dist-info → ccxt-4.4.38.dist-info}/METADATA +8 -8
  49. {ccxt-4.4.36.dist-info → ccxt-4.4.38.dist-info}/RECORD +52 -49
  50. ccxt/abstract/bitfinex2.py +0 -140
  51. {ccxt-4.4.36.dist-info → ccxt-4.4.38.dist-info}/LICENSE.txt +0 -0
  52. {ccxt-4.4.36.dist-info → ccxt-4.4.38.dist-info}/WHEEL +0 -0
  53. {ccxt-4.4.36.dist-info → ccxt-4.4.38.dist-info}/top_level.txt +0 -0
@@ -7,12 +7,13 @@ from ccxt.async_support.base.exchange import Exchange
7
7
  from ccxt.abstract.bitfinex import ImplicitAPI
8
8
  import asyncio
9
9
  import hashlib
10
- from ccxt.base.types import Balances, Currency, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees, Transaction, TransferEntry
10
+ from ccxt.base.types import Balances, Currencies, Currency, DepositAddress, Int, LedgerEntry, MarginModification, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFees, Transaction, TransferEntry
11
11
  from typing import List
12
12
  from ccxt.base.errors import ExchangeError
13
13
  from ccxt.base.errors import AuthenticationError
14
14
  from ccxt.base.errors import PermissionDenied
15
15
  from ccxt.base.errors import ArgumentsRequired
16
+ from ccxt.base.errors import BadRequest
16
17
  from ccxt.base.errors import BadSymbol
17
18
  from ccxt.base.errors import InsufficientFunds
18
19
  from ccxt.base.errors import InvalidOrder
@@ -20,6 +21,7 @@ from ccxt.base.errors import OrderNotFound
20
21
  from ccxt.base.errors import NotSupported
21
22
  from ccxt.base.errors import RateLimitExceeded
22
23
  from ccxt.base.errors import ExchangeNotAvailable
24
+ from ccxt.base.errors import OnMaintenance
23
25
  from ccxt.base.errors import InvalidNonce
24
26
  from ccxt.base.decimal_to_precision import ROUND
25
27
  from ccxt.base.decimal_to_precision import TRUNCATE
@@ -35,57 +37,95 @@ class bitfinex(Exchange, ImplicitAPI):
35
37
  'id': 'bitfinex',
36
38
  'name': 'Bitfinex',
37
39
  'countries': ['VG'],
38
- 'version': 'v1',
39
- # cheapest is 90 requests a minute = 1.5 requests per second on average =>( 1000ms / 1.5) = 666.666 ms between requests on average
40
- 'rateLimit': 666.666,
40
+ 'version': 'v2',
41
+ 'certified': False,
41
42
  'pro': True,
42
43
  # new metainfo interface
43
44
  'has': {
44
45
  'CORS': None,
45
46
  'spot': True,
46
- 'margin': None, # has but unimplemented
47
- 'swap': None, # has but unimplemented
48
- 'future': None,
49
- 'option': None,
47
+ 'margin': True,
48
+ 'swap': True,
49
+ 'future': False,
50
+ 'option': False,
51
+ 'addMargin': False,
52
+ 'borrowCrossMargin': False,
53
+ 'borrowIsolatedMargin': False,
50
54
  'cancelAllOrders': True,
51
55
  'cancelOrder': True,
56
+ 'cancelOrders': True,
52
57
  'createDepositAddress': True,
58
+ 'createLimitOrder': True,
59
+ 'createMarketOrder': True,
53
60
  'createOrder': True,
61
+ 'createPostOnlyOrder': True,
62
+ 'createReduceOnlyOrder': True,
63
+ 'createStopLimitOrder': True,
64
+ 'createStopMarketOrder': True,
65
+ 'createStopOrder': True,
66
+ 'createTrailingAmountOrder': True,
67
+ 'createTrailingPercentOrder': False,
68
+ 'createTriggerOrder': True,
54
69
  'editOrder': True,
55
70
  'fetchBalance': True,
71
+ 'fetchBorrowInterest': False,
72
+ 'fetchBorrowRate': False,
73
+ 'fetchBorrowRateHistories': False,
74
+ 'fetchBorrowRateHistory': False,
75
+ 'fetchBorrowRates': False,
76
+ 'fetchBorrowRatesPerSymbol': False,
77
+ 'fetchClosedOrder': True,
56
78
  'fetchClosedOrders': True,
79
+ 'fetchCrossBorrowRate': False,
80
+ 'fetchCrossBorrowRates': False,
81
+ 'fetchCurrencies': True,
57
82
  'fetchDepositAddress': True,
58
83
  'fetchDepositAddresses': False,
59
84
  'fetchDepositAddressesByNetwork': False,
60
- 'fetchDeposits': False,
61
85
  'fetchDepositsWithdrawals': True,
62
- 'fetchDepositWithdrawFee': 'emulated',
63
- 'fetchDepositWithdrawFees': True,
64
86
  'fetchFundingHistory': False,
65
- 'fetchFundingRate': False, # Endpoint 'lendbook/{currency}' is related to interest rates on spot margin lending
66
- 'fetchFundingRateHistory': False,
67
- 'fetchFundingRates': False,
87
+ 'fetchFundingRate': 'emulated', # emulated in exchange
88
+ 'fetchFundingRateHistory': True,
89
+ 'fetchFundingRates': True,
68
90
  'fetchIndexOHLCV': False,
91
+ 'fetchIsolatedBorrowRate': False,
92
+ 'fetchIsolatedBorrowRates': False,
93
+ 'fetchLedger': True,
94
+ 'fetchLeverage': False,
69
95
  'fetchLeverageTiers': False,
96
+ 'fetchLiquidations': True,
70
97
  'fetchMarginMode': False,
71
- 'fetchMarkets': True,
98
+ 'fetchMarketLeverageTiers': False,
72
99
  'fetchMarkOHLCV': False,
73
100
  'fetchMyTrades': True,
74
101
  'fetchOHLCV': True,
102
+ 'fetchOpenInterest': True,
103
+ 'fetchOpenInterestHistory': True,
104
+ 'fetchOpenOrder': True,
75
105
  'fetchOpenOrders': True,
76
106
  'fetchOrder': True,
77
107
  'fetchOrderBook': True,
108
+ 'fetchOrderBooks': False,
109
+ 'fetchOrderTrades': True,
110
+ 'fetchPosition': False,
78
111
  'fetchPositionMode': False,
79
112
  'fetchPositions': True,
80
113
  'fetchPremiumIndexOHLCV': False,
81
- 'fetchTicker': True,
114
+ 'fetchStatus': True,
82
115
  'fetchTickers': True,
83
116
  'fetchTime': False,
84
- 'fetchTrades': True,
85
117
  'fetchTradingFee': False,
86
118
  'fetchTradingFees': True,
87
- 'fetchTransactionFees': True,
119
+ 'fetchTransactionFees': None,
88
120
  'fetchTransactions': 'emulated',
121
+ 'reduceMargin': False,
122
+ 'repayCrossMargin': False,
123
+ 'repayIsolatedMargin': False,
124
+ 'setLeverage': False,
125
+ 'setMargin': True,
126
+ 'setMarginMode': False,
127
+ 'setPositionMode': False,
128
+ 'signIn': False,
89
129
  'transfer': True,
90
130
  'withdraw': True,
91
131
  },
@@ -104,107 +144,177 @@ class bitfinex(Exchange, ImplicitAPI):
104
144
  '2w': '14D',
105
145
  '1M': '1M',
106
146
  },
147
+ # cheapest endpoint is 240 requests per minute => ~ 4 requests per second =>( 1000ms / 4 ) = 250ms between requests on average
148
+ 'rateLimit': 250,
107
149
  'urls': {
108
- 'logo': 'https://github.com/user-attachments/assets/9147c6c5-7197-481e-827b-7483672bb0e9',
150
+ 'logo': 'https://github.com/user-attachments/assets/4a8e947f-ab46-481a-a8ae-8b20e9b03178',
109
151
  'api': {
110
- 'v2': 'https://api-pub.bitfinex.com', # https://github.com/ccxt/ccxt/issues/5109
111
- 'public': 'https://api.bitfinex.com',
152
+ 'v1': 'https://api.bitfinex.com',
153
+ 'public': 'https://api-pub.bitfinex.com',
112
154
  'private': 'https://api.bitfinex.com',
113
155
  },
114
156
  'www': 'https://www.bitfinex.com',
115
- 'referral': 'https://www.bitfinex.com/?refcode=P61eYxFL',
116
157
  'doc': [
117
- 'https://docs.bitfinex.com/v1/docs',
158
+ 'https://docs.bitfinex.com/v2/docs/',
118
159
  'https://github.com/bitfinexcom/bitfinex-api-node',
119
160
  ],
161
+ 'fees': 'https://www.bitfinex.com/fees',
120
162
  },
121
163
  'api': {
122
- # v2 symbol ids require a 't' prefix
123
- # just the public part of it(use bitfinex2 for everything else)
124
- 'v2': {
125
- 'get': {
126
- 'platform/status': 3, # 30 requests per minute
127
- 'tickers': 1, # 90 requests a minute
128
- 'ticker/{symbol}': 1,
129
- 'tickers/hist': 1,
130
- 'trades/{symbol}/hist': 1,
131
- 'book/{symbol}/{precision}': 0.375, # 240 requests per minute = 4 requests per second(1000ms / rateLimit) / 4 = 0.37500375
132
- 'book/{symbol}/P0': 0.375,
133
- 'book/{symbol}/P1': 0.375,
134
- 'book/{symbol}/P2': 0.375,
135
- 'book/{symbol}/P3': 0.375,
136
- 'book/{symbol}/R0': 0.375,
137
- 'stats1/{key}:{size}:{symbol}:{side}/{section}': 1, # 90 requests a minute
138
- 'stats1/{key}:{size}:{symbol}/{section}': 1,
139
- 'stats1/{key}:{size}:{symbol}:long/last': 1,
140
- 'stats1/{key}:{size}:{symbol}:long/hist': 1,
141
- 'stats1/{key}:{size}:{symbol}:short/last': 1,
142
- 'stats1/{key}:{size}:{symbol}:short/hist': 1,
143
- 'candles/trade:{timeframe}:{symbol}/{section}': 1, # 90 requests a minute
144
- 'candles/trade:{timeframe}:{symbol}/last': 1,
145
- 'candles/trade:{timeframe}:{symbol}/hist': 1,
146
- },
147
- },
148
164
  'public': {
149
165
  'get': {
150
- 'book/{symbol}': 1, # 90 requests a minute
151
- # 'candles/{symbol}':0,
152
- 'lendbook/{currency}': 6, # 15 requests a minute
153
- 'lends/{currency}': 3, # 30 requests a minute
154
- 'pubticker/{symbol}': 3, # 30 requests a minute = 0.5 requests per second =>(1000ms / rateLimit) / 0.5 = 3.00003
155
- 'stats/{symbol}': 6, # 15 requests a minute = 0.25 requests per second =>(1000ms / rateLimit ) /0.25 = 6.00006(endpoint returns red html... or 'unknown symbol')
156
- 'symbols': 18, # 5 requests a minute = 0.08333 requests per second =>(1000ms / rateLimit) / 0.08333 = 18.0009
157
- 'symbols_details': 18, # 5 requests a minute
158
- 'tickers': 1, # endpoint not mentioned in v1 docs... but still responds
159
- 'trades/{symbol}': 3, # 60 requests a minute = 1 request per second =>(1000ms / rateLimit) / 1 = 1.5 ... but only works if set to 3
166
+ '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
167
+ 'conf/pub:{action}:{object}': 2.7,
168
+ 'conf/pub:{action}:{object}:{detail}': 2.7,
169
+ 'conf/pub:map:{object}': 2.7,
170
+ 'conf/pub:map:{object}:{detail}': 2.7,
171
+ 'conf/pub:map:currency:{detail}': 2.7,
172
+ 'conf/pub:map:currency:sym': 2.7, # maps symbols to their API symbols, BAB > BCH
173
+ 'conf/pub:map:currency:label': 2.7, # verbose friendly names, BNT > Bancor
174
+ 'conf/pub:map:currency:unit': 2.7, # maps symbols to unit of measure where applicable
175
+ 'conf/pub:map:currency:undl': 2.7, # maps derivatives symbols to their underlying currency
176
+ 'conf/pub:map:currency:pool': 2.7, # maps symbols to underlying network/protocol they operate on
177
+ 'conf/pub:map:currency:explorer': 2.7, # maps symbols to their recognised block explorer URLs
178
+ 'conf/pub:map:currency:tx:fee': 2.7, # maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745
179
+ 'conf/pub:map:tx:method': 2.7,
180
+ 'conf/pub:list:{object}': 2.7,
181
+ 'conf/pub:list:{object}:{detail}': 2.7,
182
+ 'conf/pub:list:currency': 2.7,
183
+ 'conf/pub:list:pair:exchange': 2.7,
184
+ 'conf/pub:list:pair:margin': 2.7,
185
+ 'conf/pub:list:pair:futures': 2.7,
186
+ 'conf/pub:list:competitions': 2.7,
187
+ 'conf/pub:info:{object}': 2.7,
188
+ 'conf/pub:info:{object}:{detail}': 2.7,
189
+ 'conf/pub:info:pair': 2.7,
190
+ 'conf/pub:info:pair:futures': 2.7,
191
+ 'conf/pub:info:tx:status': 2.7, # [deposit, withdrawal] statuses 1 = active, 0 = maintenance
192
+ 'conf/pub:fees': 2.7,
193
+ 'platform/status': 8, # 30 requests per minute = 0.5 requests per second =>( 1000ms / rateLimit ) / 0.5 = 8
194
+ 'tickers': 2.7, # 90 requests a minute = 1.5 requests per second =>( 1000 / rateLimit ) / 1.5 = 2.666666666
195
+ 'ticker/{symbol}': 2.7,
196
+ 'tickers/hist': 2.7,
197
+ 'trades/{symbol}/hist': 2.7,
198
+ 'book/{symbol}/{precision}': 1, # 240 requests a minute
199
+ 'book/{symbol}/P0': 1,
200
+ 'book/{symbol}/P1': 1,
201
+ 'book/{symbol}/P2': 1,
202
+ 'book/{symbol}/P3': 1,
203
+ 'book/{symbol}/R0': 1,
204
+ 'stats1/{key}:{size}:{symbol}:{side}/{section}': 2.7,
205
+ 'stats1/{key}:{size}:{symbol}:{side}/last': 2.7,
206
+ 'stats1/{key}:{size}:{symbol}:{side}/hist': 2.7,
207
+ 'stats1/{key}:{size}:{symbol}/{section}': 2.7,
208
+ 'stats1/{key}:{size}:{symbol}/last': 2.7,
209
+ 'stats1/{key}:{size}:{symbol}/hist': 2.7,
210
+ 'stats1/{key}:{size}:{symbol}:long/last': 2.7,
211
+ 'stats1/{key}:{size}:{symbol}:long/hist': 2.7,
212
+ 'stats1/{key}:{size}:{symbol}:short/last': 2.7,
213
+ 'stats1/{key}:{size}:{symbol}:short/hist': 2.7,
214
+ 'candles/trade:{timeframe}:{symbol}:{period}/{section}': 2.7,
215
+ 'candles/trade:{timeframe}:{symbol}/{section}': 2.7,
216
+ 'candles/trade:{timeframe}:{symbol}/last': 2.7,
217
+ 'candles/trade:{timeframe}:{symbol}/hist': 2.7,
218
+ 'status/{type}': 2.7,
219
+ 'status/deriv': 2.7,
220
+ 'status/deriv/{symbol}/hist': 2.7,
221
+ 'liquidations/hist': 80, # 3 requests a minute = 0.05 requests a second =>( 1000ms / rateLimit ) / 0.05 = 80
222
+ 'rankings/{key}:{timeframe}:{symbol}/{section}': 2.7,
223
+ 'rankings/{key}:{timeframe}:{symbol}/hist': 2.7,
224
+ 'pulse/hist': 2.7,
225
+ 'pulse/profile/{nickname}': 2.7,
226
+ 'funding/stats/{symbol}/hist': 10, # ratelimit not in docs
227
+ 'ext/vasps': 1,
228
+ },
229
+ 'post': {
230
+ 'calc/trade/avg': 2.7,
231
+ 'calc/fx': 2.7,
160
232
  },
161
233
  },
162
234
  'private': {
163
235
  'post': {
164
- 'account_fees': 18,
165
- 'account_infos': 6,
166
- 'balances': 9.036, # 10 requests a minute = 0.166 requests per second =>(1000ms / rateLimit) / 0.166 = 9.036
167
- 'basket_manage': 6,
168
- 'credits': 6,
169
- 'deposit/new': 18,
170
- 'funding/close': 6,
171
- 'history': 6, # 15 requests a minute
172
- 'history/movements': 6,
173
- 'key_info': 6,
174
- 'margin_infos': 3, # 30 requests a minute
175
- 'mytrades': 3,
176
- 'mytrades_funding': 6,
177
- 'offer/cancel': 6,
178
- 'offer/new': 6,
179
- 'offer/status': 6,
180
- 'offers': 6,
181
- 'offers/hist': 90.03, # one request per minute
182
- 'order/cancel': 0.2,
183
- 'order/cancel/all': 0.2,
184
- 'order/cancel/multi': 0.2,
185
- 'order/cancel/replace': 0.2,
186
- 'order/new': 0.2, # 450 requests a minute = 7.5 request a second =>(1000ms / rateLimit) / 7.5 = 0.2000002
187
- 'order/new/multi': 0.2,
188
- 'order/status': 0.2,
189
- 'orders': 0.2,
190
- 'orders/hist': 90.03, # one request per minute = 0.1666 =>(1000ms / rateLimit) / 0.01666 = 90.03
191
- 'position/claim': 18,
192
- 'position/close': 18,
193
- 'positions': 18,
194
- 'summary': 18,
195
- 'taken_funds': 6,
196
- 'total_taken_funds': 6,
197
- 'transfer': 18,
198
- 'unused_taken_funds': 6,
199
- 'withdraw': 18,
236
+ # 'auth/r/orders/{symbol}/new', # outdated
237
+ # 'auth/r/stats/perf:{timeframe}/hist', # outdated
238
+ 'auth/r/wallets': 2.7,
239
+ 'auth/r/wallets/hist': 2.7,
240
+ 'auth/r/orders': 2.7,
241
+ 'auth/r/orders/{symbol}': 2.7,
242
+ 'auth/w/order/submit': 2.7,
243
+ 'auth/w/order/update': 2.7,
244
+ 'auth/w/order/cancel': 2.7,
245
+ 'auth/w/order/multi': 2.7,
246
+ 'auth/w/order/cancel/multi': 2.7,
247
+ 'auth/r/orders/{symbol}/hist': 2.7,
248
+ 'auth/r/orders/hist': 2.7,
249
+ 'auth/r/order/{symbol}:{id}/trades': 2.7,
250
+ 'auth/r/trades/{symbol}/hist': 2.7,
251
+ 'auth/r/trades/hist': 2.7,
252
+ 'auth/r/ledgers/{currency}/hist': 2.7,
253
+ 'auth/r/ledgers/hist': 2.7,
254
+ 'auth/r/info/margin/{key}': 2.7,
255
+ 'auth/r/info/margin/base': 2.7,
256
+ 'auth/r/info/margin/sym_all': 2.7,
257
+ 'auth/r/positions': 2.7,
258
+ 'auth/w/position/claim': 2.7,
259
+ 'auth/w/position/increase:': 2.7,
260
+ 'auth/r/position/increase/info': 2.7,
261
+ 'auth/r/positions/hist': 2.7,
262
+ 'auth/r/positions/audit': 2.7,
263
+ 'auth/r/positions/snap': 2.7,
264
+ 'auth/w/deriv/collateral/set': 2.7,
265
+ 'auth/w/deriv/collateral/limits': 2.7,
266
+ 'auth/r/funding/offers': 2.7,
267
+ 'auth/r/funding/offers/{symbol}': 2.7,
268
+ 'auth/w/funding/offer/submit': 2.7,
269
+ 'auth/w/funding/offer/cancel': 2.7,
270
+ 'auth/w/funding/offer/cancel/all': 2.7,
271
+ 'auth/w/funding/close': 2.7,
272
+ 'auth/w/funding/auto': 2.7,
273
+ 'auth/w/funding/keep': 2.7,
274
+ 'auth/r/funding/offers/{symbol}/hist': 2.7,
275
+ 'auth/r/funding/offers/hist': 2.7,
276
+ 'auth/r/funding/loans': 2.7,
277
+ 'auth/r/funding/loans/hist': 2.7,
278
+ 'auth/r/funding/loans/{symbol}': 2.7,
279
+ 'auth/r/funding/loans/{symbol}/hist': 2.7,
280
+ 'auth/r/funding/credits': 2.7,
281
+ 'auth/r/funding/credits/hist': 2.7,
282
+ 'auth/r/funding/credits/{symbol}': 2.7,
283
+ 'auth/r/funding/credits/{symbol}/hist': 2.7,
284
+ 'auth/r/funding/trades/{symbol}/hist': 2.7,
285
+ 'auth/r/funding/trades/hist': 2.7,
286
+ 'auth/r/info/funding/{key}': 2.7,
287
+ 'auth/r/info/user': 2.7,
288
+ 'auth/r/summary': 2.7,
289
+ 'auth/r/logins/hist': 2.7,
290
+ 'auth/r/permissions': 2.7,
291
+ 'auth/w/token': 2.7,
292
+ 'auth/r/audit/hist': 2.7,
293
+ 'auth/w/transfer': 2.7, # ratelimit not in docs...
294
+ 'auth/w/deposit/address': 24, # 10 requests a minute = 0.166 requests per second =>( 1000ms / rateLimit ) / 0.166 = 24
295
+ 'auth/w/deposit/invoice': 24, # ratelimit not in docs
296
+ 'auth/w/withdraw': 24, # ratelimit not in docs
297
+ 'auth/r/movements/{currency}/hist': 2.7,
298
+ 'auth/r/movements/hist': 2.7,
299
+ 'auth/r/alerts': 5.34, # 45 requests a minute = 0.75 requests per second =>( 1000ms / rateLimit ) / 0.749 => 5.34
300
+ 'auth/w/alert/set': 2.7,
301
+ 'auth/w/alert/price:{symbol}:{price}/del': 2.7,
302
+ 'auth/w/alert/{type}:{symbol}:{price}/del': 2.7,
303
+ 'auth/calc/order/avail': 2.7,
304
+ 'auth/w/settings/set': 2.7,
305
+ 'auth/r/settings': 2.7,
306
+ 'auth/w/settings/del': 2.7,
307
+ 'auth/r/pulse/hist': 2.7,
308
+ 'auth/w/pulse/add': 16, # 15 requests a minute = 0.25 requests per second =>( 1000ms / rateLimit ) / 0.25 => 16
309
+ 'auth/w/pulse/del': 2.7,
200
310
  },
201
311
  },
202
312
  },
203
313
  'fees': {
204
314
  'trading': {
205
315
  'feeSide': 'get',
206
- 'tierBased': True,
207
316
  'percentage': True,
317
+ 'tierBased': True,
208
318
  'maker': self.parse_number('0.001'),
209
319
  'taker': self.parse_number('0.002'),
210
320
  'tiers': {
@@ -237,17 +347,101 @@ class bitfinex(Exchange, ImplicitAPI):
237
347
  },
238
348
  },
239
349
  'funding': {
240
- 'tierBased': False, # True for tier-based/progressive
241
- 'percentage': False, # fixed commission
242
- # Actually deposit fees are free for larger deposits(> $1000 USD equivalent)
243
- # these values below are deprecated, we should not hardcode fees and limits anymore
244
- # to be reimplemented with bitfinex funding fees from their API or web endpoints
245
- 'deposit': {},
246
350
  'withdraw': {},
247
351
  },
248
352
  },
249
- # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
353
+ 'precisionMode': SIGNIFICANT_DIGITS,
354
+ 'options': {
355
+ 'precision': 'R0', # P0, P1, P2, P3, P4, R0
356
+ # convert 'EXCHANGE MARKET' to lowercase 'market'
357
+ # convert 'EXCHANGE LIMIT' to lowercase 'limit'
358
+ # everything else remains uppercase
359
+ 'exchangeTypes': {
360
+ 'MARKET': 'market',
361
+ 'EXCHANGE MARKET': 'market',
362
+ 'LIMIT': 'limit',
363
+ 'EXCHANGE LIMIT': 'limit',
364
+ # 'STOP': None,
365
+ 'EXCHANGE STOP': 'market',
366
+ # 'TRAILING STOP': None,
367
+ # 'EXCHANGE TRAILING STOP': None,
368
+ # 'FOK': None,
369
+ 'EXCHANGE FOK': 'limit',
370
+ # 'STOP LIMIT': None,
371
+ 'EXCHANGE STOP LIMIT': 'limit',
372
+ # 'IOC': None,
373
+ 'EXCHANGE IOC': 'limit',
374
+ },
375
+ # convert 'market' to 'EXCHANGE MARKET'
376
+ # convert 'limit' 'EXCHANGE LIMIT'
377
+ # everything else remains
378
+ 'orderTypes': {
379
+ 'market': 'EXCHANGE MARKET',
380
+ 'limit': 'EXCHANGE LIMIT',
381
+ },
382
+ 'fiat': {
383
+ 'USD': 'USD',
384
+ 'EUR': 'EUR',
385
+ 'JPY': 'JPY',
386
+ 'GBP': 'GBP',
387
+ 'CHN': 'CHN',
388
+ },
389
+ # actually the correct names unlike the v1
390
+ # we don't want to self.extend self with accountsByType in v1
391
+ 'v2AccountsByType': {
392
+ 'spot': 'exchange',
393
+ 'exchange': 'exchange',
394
+ 'funding': 'funding',
395
+ 'margin': 'margin',
396
+ 'derivatives': 'margin',
397
+ 'future': 'margin',
398
+ 'swap': 'margin',
399
+ },
400
+ 'withdraw': {
401
+ 'includeFee': False,
402
+ },
403
+ 'networks': {
404
+ 'BTC': 'BITCOIN',
405
+ 'LTC': 'LITECOIN',
406
+ 'ERC20': 'ETHEREUM',
407
+ 'OMNI': 'TETHERUSO',
408
+ 'LIQUID': 'TETHERUSL',
409
+ 'TRC20': 'TETHERUSX',
410
+ 'EOS': 'TETHERUSS',
411
+ 'AVAX': 'TETHERUSDTAVAX',
412
+ 'SOL': 'TETHERUSDTSOL',
413
+ 'ALGO': 'TETHERUSDTALG',
414
+ 'BCH': 'TETHERUSDTBCH',
415
+ 'KSM': 'TETHERUSDTKSM',
416
+ 'DVF': 'TETHERUSDTDVF',
417
+ 'OMG': 'TETHERUSDTOMG',
418
+ },
419
+ 'networksById': {
420
+ 'TETHERUSE': 'ERC20',
421
+ },
422
+ },
423
+ 'exceptions': {
424
+ 'exact': {
425
+ '11010': RateLimitExceeded,
426
+ '10001': PermissionDenied, # api_key: permission invalid(#10001)
427
+ '10020': BadRequest,
428
+ '10100': AuthenticationError,
429
+ '10114': InvalidNonce,
430
+ '20060': OnMaintenance,
431
+ # {"code":503,"error":"temporarily_unavailable","error_description":"Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info."}
432
+ 'temporarily_unavailable': ExchangeNotAvailable,
433
+ },
434
+ 'broad': {
435
+ 'available balance is only': InsufficientFunds,
436
+ 'not enough exchange balance': InsufficientFunds,
437
+ 'Order not found': OrderNotFound,
438
+ 'symbol: invalid': BadSymbol,
439
+ },
440
+ },
250
441
  'commonCurrencies': {
442
+ 'UST': 'USDT',
443
+ 'EUTF0': 'EURT',
444
+ 'USTF0': 'USDT',
251
445
  'ALG': 'ALGO', # https://github.com/ccxt/ccxt/issues/6034
252
446
  'AMP': 'AMPL',
253
447
  'ATO': 'ATOM', # https://github.com/ccxt/ccxt/issues/5118
@@ -256,12 +450,10 @@ class bitfinex(Exchange, ImplicitAPI):
256
450
  'DAT': 'DATA',
257
451
  'DOG': 'MDOGE',
258
452
  'DSH': 'DASH',
259
- # https://github.com/ccxt/ccxt/issues/7399
260
- # https://coinmarketcap.com/currencies/pnetwork/
261
- # https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/
262
453
  'EDO': 'PNT',
263
454
  'EUS': 'EURS',
264
455
  'EUT': 'EURT',
456
+ 'HTX': 'HT',
265
457
  'IDX': 'ID',
266
458
  'IOT': 'IOTA',
267
459
  'IQX': 'IQ',
@@ -280,333 +472,109 @@ class bitfinex(Exchange, ImplicitAPI):
280
472
  'YGG': 'YEED', # conflict with Yield Guild Games
281
473
  'YYW': 'YOYOW',
282
474
  'UDC': 'USDC',
283
- 'UST': 'USDT',
284
475
  'VSY': 'VSYS',
285
476
  'WAX': 'WAXP',
286
477
  'XCH': 'XCHF',
287
478
  'ZBT': 'ZB',
288
479
  },
289
- 'exceptions': {
290
- 'exact': {
291
- 'temporarily_unavailable': ExchangeNotAvailable, # Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info.
292
- 'Order could not be cancelled.': OrderNotFound, # non-existent order
293
- 'No such order found.': OrderNotFound, # ?
294
- 'Order price must be positive.': InvalidOrder, # on price <= 0
295
- 'Could not find a key matching the given X-BFX-APIKEY.': AuthenticationError,
296
- 'Key price should be a decimal number, e.g. "123.456"': InvalidOrder, # on isNaN(price)
297
- 'Key amount should be a decimal number, e.g. "123.456"': InvalidOrder, # on isNaN(amount)
298
- 'ERR_RATE_LIMIT': RateLimitExceeded,
299
- 'Ratelimit': RateLimitExceeded,
300
- 'Nonce is too small.': InvalidNonce,
301
- 'No summary found.': ExchangeError, # fetchTradingFees(summary) endpoint can give self vague error message
302
- 'Cannot evaluate your available balance, please try again': ExchangeNotAvailable,
303
- 'Unknown symbol': BadSymbol,
304
- 'Cannot complete transfer. Exchange balance insufficient.': InsufficientFunds,
305
- 'Momentary balance check. Please wait few seconds and try the transfer again.': ExchangeError,
306
- },
307
- 'broad': {
308
- 'Invalid X-BFX-SIGNATURE': AuthenticationError,
309
- 'This API key does not have permission': PermissionDenied, # authenticated but not authorized
310
- 'not enough exchange balance for ': InsufficientFunds, # when buying cost is greater than the available quote currency
311
- 'minimum size for ': InvalidOrder, # when amount below limits.amount.min
312
- 'Invalid order': InvalidOrder, # ?
313
- 'The available balance is only': InsufficientFunds, # {"status":"error","message":"Cannot withdraw 1.0027 ETH from your exchange wallet. The available balance is only 0.0 ETH. If you have limit orders, open positions, unused or active margin funding, self will decrease your available balance. To increase it, you can cancel limit orders or reduce/close your positions.","withdrawal_id":0,"fees":"0.0027"}
314
- },
315
- },
316
- 'precisionMode': SIGNIFICANT_DIGITS,
317
- 'options': {
318
- 'currencyNames': {
319
- 'AGI': 'agi',
320
- 'AID': 'aid',
321
- 'AIO': 'aio',
322
- 'ANT': 'ant',
323
- 'AVT': 'aventus', # #1811
324
- 'BAT': 'bat',
325
- # https://github.com/ccxt/ccxt/issues/5833
326
- 'BCH': 'bab', # undocumented
327
- # 'BCH': 'bcash', # undocumented
328
- 'BCI': 'bci',
329
- 'BFT': 'bft',
330
- 'BSV': 'bsv',
331
- 'BTC': 'bitcoin',
332
- 'BTG': 'bgold',
333
- 'CFI': 'cfi',
334
- 'COMP': 'comp',
335
- 'DAI': 'dai',
336
- 'DADI': 'dad',
337
- 'DASH': 'dash',
338
- 'DATA': 'datacoin',
339
- 'DTH': 'dth',
340
- 'EDO': 'eidoo', # #1811
341
- 'ELF': 'elf',
342
- 'EOS': 'eos',
343
- 'ETC': 'ethereumc',
344
- 'ETH': 'ethereum',
345
- 'ETP': 'metaverse',
346
- 'FUN': 'fun',
347
- 'GNT': 'golem',
348
- 'IOST': 'ios',
349
- 'IOTA': 'iota',
350
- # https://github.com/ccxt/ccxt/issues/5833
351
- 'LEO': 'let', # ETH chain
352
- # 'LEO': 'les', # EOS chain
353
- 'LINK': 'link',
354
- 'LRC': 'lrc',
355
- 'LTC': 'litecoin',
356
- 'LYM': 'lym',
357
- 'MANA': 'mna',
358
- 'MIT': 'mit',
359
- 'MKR': 'mkr',
360
- 'MTN': 'mtn',
361
- 'NEO': 'neo',
362
- 'ODE': 'ode',
363
- 'OMG': 'omisego',
364
- 'OMNI': 'mastercoin',
365
- 'QASH': 'qash',
366
- 'QTUM': 'qtum', # #1811
367
- 'RCN': 'rcn',
368
- 'RDN': 'rdn',
369
- 'REP': 'rep',
370
- 'REQ': 'req',
371
- 'RLC': 'rlc',
372
- 'SAN': 'santiment',
373
- 'SNGLS': 'sng',
374
- 'SNT': 'status',
375
- 'SPANK': 'spk',
376
- 'STORJ': 'stj',
377
- 'TNB': 'tnb',
378
- 'TRX': 'trx',
379
- 'TUSD': 'tsd',
380
- 'USD': 'wire',
381
- 'USDC': 'udc', # https://github.com/ccxt/ccxt/issues/5833
382
- 'UTK': 'utk',
383
- 'USDT': 'tetheruso', # Tether on Omni
384
- # 'USDT': 'tetheruse', # Tether on ERC20
385
- # 'USDT': 'tetherusl', # Tether on Liquid
386
- # 'USDT': 'tetherusx', # Tether on Tron
387
- # 'USDT': 'tetheruss', # Tether on EOS
388
- 'VEE': 'vee',
389
- 'WAX': 'wax',
390
- 'XLM': 'xlm',
391
- 'XMR': 'monero',
392
- 'XRP': 'ripple',
393
- 'XVG': 'xvg',
394
- 'YOYOW': 'yoyow',
395
- 'ZEC': 'zcash',
396
- 'ZRX': 'zrx',
397
- 'XTZ': 'xtz',
398
- },
399
- 'orderTypes': {
400
- 'limit': 'exchange limit',
401
- 'market': 'exchange market',
402
- },
403
- 'fiat': {
404
- 'USD': 'USD',
405
- 'EUR': 'EUR',
406
- 'JPY': 'JPY',
407
- 'GBP': 'GBP',
408
- 'CNH': 'CNH',
409
- },
410
- 'accountsByType': {
411
- 'spot': 'exchange',
412
- 'margin': 'trading',
413
- 'funding': 'deposit',
414
- 'swap': 'trading',
415
- },
416
- },
417
480
  })
418
481
 
419
- async def fetch_transaction_fees(self, codes: Strings = None, params={}):
420
- """
421
- @deprecated
422
- please use fetchDepositWithdrawFees instead
482
+ def is_fiat(self, code):
483
+ return(code in self.options['fiat'])
423
484
 
424
- https://docs.bitfinex.com/v1/reference/rest-auth-fees
485
+ def get_currency_id(self, code):
486
+ return 'f' + code
425
487
 
426
- :param str[]|None codes: list of unified currency codes
427
- :param dict [params]: extra parameters specific to the exchange API endpoint
428
- :returns dict[]: a list of `fees structures <https://docs.ccxt.com/#/?id=fee-structure>`
429
- """
430
- await self.load_markets()
431
- result: dict = {}
432
- response = await self.privatePostAccountFees(params)
433
- #
434
- # {
435
- # "withdraw": {
436
- # "BTC": "0.0004",
437
- # }
438
- # }
439
- #
440
- fees = self.safe_dict(response, 'withdraw', {})
441
- ids = list(fees.keys())
442
- for i in range(0, len(ids)):
443
- id = ids[i]
444
- code = self.safe_currency_code(id)
445
- if (codes is not None) and not self.in_array(code, codes):
446
- continue
447
- result[code] = {
448
- 'withdraw': self.safe_number(fees, id),
449
- 'deposit': {},
450
- 'info': self.safe_number(fees, id),
451
- }
452
- return result
488
+ def get_currency_name(self, code):
489
+ # temporary fix for transpiler recognition, even though self is in parent class
490
+ if code in self.options['currencyNames']:
491
+ return self.options['currencyNames'][code]
492
+ raise NotSupported(self.id + ' ' + code + ' not supported for withdrawal')
493
+
494
+ def amount_to_precision(self, symbol, amount):
495
+ # https://docs.bitfinex.com/docs/introduction#amount-precision
496
+ # The amount field allows up to 8 decimals.
497
+ # Anything exceeding self will be rounded to the 8th decimal.
498
+ symbol = self.safe_symbol(symbol)
499
+ return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
453
500
 
454
- async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
501
+ def price_to_precision(self, symbol, price):
502
+ symbol = self.safe_symbol(symbol)
503
+ price = self.decimal_to_precision(price, ROUND, self.markets[symbol]['precision']['price'], self.precisionMode)
504
+ # https://docs.bitfinex.com/docs/introduction#price-precision
505
+ # The precision level of all trading prices is based on significant figures.
506
+ # 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).
507
+ # Prices submit with a precision larger than 5 will be cut by the API.
508
+ return self.decimal_to_precision(price, TRUNCATE, 8, DECIMAL_PLACES)
509
+
510
+ async def fetch_status(self, params={}):
455
511
  """
456
- fetch deposit and withdraw fees
512
+ the latest known information on the availability of the exchange API
457
513
 
458
- https://docs.bitfinex.com/v1/reference/rest-auth-fees
514
+ https://docs.bitfinex.com/reference/rest-public-platform-status
459
515
 
460
- :param str[]|None codes: list of unified currency codes
461
516
  :param dict [params]: extra parameters specific to the exchange API endpoint
462
- :returns dict[]: a list of `fees structures <https://docs.ccxt.com/#/?id=fee-structure>`
517
+ :returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
463
518
  """
464
- await self.load_markets()
465
- response = await self.privatePostAccountFees(params)
466
- #
467
- # {
468
- # "withdraw": {
469
- # "BTC": "0.0004",
470
- # ...
471
- # }
472
- # }
473
- #
474
- withdraw = self.safe_list(response, 'withdraw')
475
- return self.parse_deposit_withdraw_fees(withdraw, codes)
476
-
477
- def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
478
519
  #
479
- # '0.0004'
520
+ # [1] # operative
521
+ # [0] # maintenance
480
522
  #
523
+ response = await self.publicGetPlatformStatus(params)
524
+ statusRaw = self.safe_string(response, 0)
481
525
  return {
482
- 'withdraw': {
483
- 'fee': self.parse_number(fee),
484
- 'percentage': None,
485
- },
486
- 'deposit': {
487
- 'fee': None,
488
- 'percentage': None,
489
- },
490
- 'networks': {},
491
- 'info': fee,
526
+ 'status': self.safe_string({'0': 'maintenance', '1': 'ok'}, statusRaw, statusRaw),
527
+ 'updated': None,
528
+ 'eta': None,
529
+ 'url': None,
530
+ 'info': response,
492
531
  }
493
532
 
494
- async def fetch_trading_fees(self, params={}) -> TradingFees:
495
- """
496
- fetch the trading fees for multiple markets
497
-
498
- https://docs.bitfinex.com/v1/reference/rest-auth-summary
499
-
500
- :param dict [params]: extra parameters specific to the exchange API endpoint
501
- :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
502
- """
503
- await self.load_markets()
504
- response = await self.privatePostSummary(params)
505
- #
506
- # {
507
- # "time": "2022-02-23T16:05:47.659000Z",
508
- # "status": {resid_hint: null, login_last: "2022-02-23T16:05:48Z"},
509
- # "is_locked": False,
510
- # "leo_lev": "0",
511
- # "leo_amount_avg": "0.0",
512
- # "trade_vol_30d": [
513
- # {
514
- # "curr": "Total(USD)",
515
- # "vol": "0.0",
516
- # "vol_safe": "0.0",
517
- # "vol_maker": "0.0",
518
- # "vol_BFX": "0.0",
519
- # "vol_BFX_safe": "0.0",
520
- # "vol_BFX_maker": "0.0"
521
- # }
522
- # ],
523
- # "fees_funding_30d": {},
524
- # "fees_funding_total_30d": "0",
525
- # "fees_trading_30d": {},
526
- # "fees_trading_total_30d": "0",
527
- # "rebates_trading_30d": {},
528
- # "rebates_trading_total_30d": "0",
529
- # "maker_fee": "0.001",
530
- # "taker_fee": "0.002",
531
- # "maker_fee_2crypto": "0.001",
532
- # "maker_fee_2stablecoin": "0.001",
533
- # "maker_fee_2fiat": "0.001",
534
- # "maker_fee_2deriv": "0.0002",
535
- # "taker_fee_2crypto": "0.002",
536
- # "taker_fee_2stablecoin": "0.002",
537
- # "taker_fee_2fiat": "0.002",
538
- # "taker_fee_2deriv": "0.00065",
539
- # "deriv_maker_rebate": "0.0002",
540
- # "deriv_taker_fee": "0.00065",
541
- # "trade_last": null
542
- # }
543
- #
544
- result: dict = {}
545
- fiat = self.safe_dict(self.options, 'fiat', {})
546
- makerFee = self.safe_number(response, 'maker_fee')
547
- takerFee = self.safe_number(response, 'taker_fee')
548
- makerFee2Fiat = self.safe_number(response, 'maker_fee_2fiat')
549
- takerFee2Fiat = self.safe_number(response, 'taker_fee_2fiat')
550
- makerFee2Deriv = self.safe_number(response, 'maker_fee_2deriv')
551
- takerFee2Deriv = self.safe_number(response, 'taker_fee_2deriv')
552
- for i in range(0, len(self.symbols)):
553
- symbol = self.symbols[i]
554
- market = self.market(symbol)
555
- fee = {
556
- 'info': response,
557
- 'symbol': symbol,
558
- 'percentage': True,
559
- 'tierBased': True,
560
- }
561
- if market['quote'] in fiat:
562
- fee['maker'] = makerFee2Fiat
563
- fee['taker'] = takerFee2Fiat
564
- elif market['contract']:
565
- fee['maker'] = makerFee2Deriv
566
- fee['taker'] = takerFee2Deriv
567
- else:
568
- fee['maker'] = makerFee
569
- fee['taker'] = takerFee
570
- result[symbol] = fee
571
- return result
572
-
573
533
  async def fetch_markets(self, params={}) -> List[Market]:
574
534
  """
575
535
  retrieves data on all markets for bitfinex
576
536
 
577
- https://docs.bitfinex.com/v1/reference/rest-public-symbols
578
- https://docs.bitfinex.com/v1/reference/rest-public-symbol-details
537
+ https://docs.bitfinex.com/reference/rest-public-conf
579
538
 
580
539
  :param dict [params]: extra parameters specific to the exchange API endpoint
581
540
  :returns dict[]: an array of objects representing market data
582
541
  """
583
- idsPromise = self.publicGetSymbols()
584
- #
585
- # ["btcusd", "ltcusd", "ltcbtc"]
542
+ spotMarketsInfoPromise = self.publicGetConfPubInfoPair(params)
543
+ futuresMarketsInfoPromise = self.publicGetConfPubInfoPairFutures(params)
544
+ marginIdsPromise = self.publicGetConfPubListPairMargin(params)
545
+ spotMarketsInfo, futuresMarketsInfo, marginIds = await asyncio.gather(*[spotMarketsInfoPromise, futuresMarketsInfoPromise, marginIdsPromise])
546
+ spotMarketsInfo = self.safe_list(spotMarketsInfo, 0, [])
547
+ futuresMarketsInfo = self.safe_list(futuresMarketsInfo, 0, [])
548
+ markets = self.array_concat(spotMarketsInfo, futuresMarketsInfo)
549
+ marginIds = self.safe_value(marginIds, 0, [])
586
550
  #
587
- detailsPromise = self.publicGetSymbolsDetails()
588
- #
589
- # [
590
- # {
591
- # "pair":"btcusd",
592
- # "price_precision":5,
593
- # "initial_margin":"10.0",
594
- # "minimum_margin":"5.0",
595
- # "maximum_order_size":"2000.0",
596
- # "minimum_order_size":"0.0002",
597
- # "expiration":"NA",
598
- # "margin":true
599
- # },
600
- # ]
551
+ # [
552
+ # "1INCH:USD",
553
+ # [
554
+ # null,
555
+ # null,
556
+ # null,
557
+ # "2.0",
558
+ # "100000.0",
559
+ # null,
560
+ # null,
561
+ # null,
562
+ # null,
563
+ # null,
564
+ # null,
565
+ # null
566
+ # ]
567
+ # ]
601
568
  #
602
- ids, details = await asyncio.gather(*[idsPromise, detailsPromise])
603
569
  result = []
604
- for i in range(0, len(details)):
605
- market = details[i]
606
- id = self.safe_string(market, 'pair')
607
- if not self.in_array(id, ids):
608
- continue
609
- id = id.upper()
570
+ for i in range(0, len(markets)):
571
+ pair = markets[i]
572
+ id = self.safe_string_upper(pair, 0)
573
+ market = self.safe_value(pair, 1, {})
574
+ spot = True
575
+ if id.find('F0') >= 0:
576
+ spot = False
577
+ swap = not spot
610
578
  baseId = None
611
579
  quoteId = None
612
580
  if id.find(':') >= 0:
@@ -618,40 +586,51 @@ class bitfinex(Exchange, ImplicitAPI):
618
586
  quoteId = id[3:6]
619
587
  base = self.safe_currency_code(baseId)
620
588
  quote = self.safe_currency_code(quoteId)
589
+ splitBase = base.split('F0')
590
+ splitQuote = quote.split('F0')
591
+ base = self.safe_string(splitBase, 0)
592
+ quote = self.safe_string(splitQuote, 0)
621
593
  symbol = base + '/' + quote
622
- type = 'spot'
623
- if id.find('F0') > -1:
624
- type = 'swap'
594
+ baseId = self.get_currency_id(baseId)
595
+ quoteId = self.get_currency_id(quoteId)
596
+ settle = None
597
+ settleId = None
598
+ if swap:
599
+ settle = quote
600
+ settleId = quote
601
+ symbol = symbol + ':' + settle
602
+ minOrderSizeString = self.safe_string(market, 3)
603
+ maxOrderSizeString = self.safe_string(market, 4)
604
+ margin = False
605
+ if spot and self.in_array(id, marginIds):
606
+ margin = True
625
607
  result.append({
626
- 'id': id,
608
+ 'id': 't' + id,
627
609
  'symbol': symbol,
628
610
  'base': base,
629
611
  'quote': quote,
630
- 'settle': None,
612
+ 'settle': settle,
631
613
  'baseId': baseId,
632
614
  'quoteId': quoteId,
633
- 'settleId': None,
634
- 'type': type,
635
- 'spot': (type == 'spot'),
636
- 'margin': self.safe_bool(market, 'margin'),
637
- 'swap': (type == 'swap'),
615
+ 'settleId': settleId,
616
+ 'type': 'spot' if spot else 'swap',
617
+ 'spot': spot,
618
+ 'margin': margin,
619
+ 'swap': swap,
638
620
  'future': False,
639
621
  'option': False,
640
622
  'active': True,
641
- 'contract': (type == 'swap'),
642
- 'linear': None,
643
- 'inverse': None,
644
- 'contractSize': None,
623
+ 'contract': swap,
624
+ 'linear': True if swap else None,
625
+ 'inverse': False if swap else None,
626
+ 'contractSize': self.parse_number('1') if swap else None,
645
627
  'expiry': None,
646
628
  'expiryDatetime': None,
647
629
  'strike': None,
648
630
  'optionType': None,
649
631
  'precision': {
650
- # https://docs.bitfinex.com/docs/introduction#amount-precision
651
- # The amount field allows up to 8 decimals.
652
- # Anything exceeding self will be rounded to the 8th decimal.
653
- 'amount': int('8'),
654
- 'price': self.safe_integer(market, 'price_precision'),
632
+ 'amount': int('8'), # https://github.com/ccxt/ccxt/issues/7310
633
+ 'price': int('5'),
655
634
  },
656
635
  'limits': {
657
636
  'leverage': {
@@ -659,8 +638,8 @@ class bitfinex(Exchange, ImplicitAPI):
659
638
  'max': None,
660
639
  },
661
640
  'amount': {
662
- 'min': self.safe_number(market, 'minimum_order_size'),
663
- 'max': self.safe_number(market, 'maximum_order_size'),
641
+ 'min': self.parse_number(minOrderSizeString),
642
+ 'max': self.parse_number(maxOrderSizeString),
664
643
  },
665
644
  'price': {
666
645
  'min': self.parse_number('1e-8'),
@@ -671,86 +650,246 @@ class bitfinex(Exchange, ImplicitAPI):
671
650
  'max': None,
672
651
  },
673
652
  },
674
- 'created': None,
653
+ 'created': None, # todo: the api needs revision for extra params & endpoints for possibility of returning a timestamp for self
675
654
  'info': market,
676
655
  })
677
656
  return result
678
657
 
679
- def amount_to_precision(self, symbol, amount):
680
- # https://docs.bitfinex.com/docs/introduction#amount-precision
681
- # The amount field allows up to 8 decimals.
682
- # Anything exceeding self will be rounded to the 8th decimal.
683
- symbol = self.safe_symbol(symbol)
684
- return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
658
+ async def fetch_currencies(self, params={}) -> Currencies:
659
+ """
660
+ fetches all available currencies on an exchange
685
661
 
686
- def price_to_precision(self, symbol, price):
687
- symbol = self.safe_symbol(symbol)
688
- price = self.decimal_to_precision(price, ROUND, self.markets[symbol]['precision']['price'], self.precisionMode)
689
- # https://docs.bitfinex.com/docs/introduction#price-precision
690
- # The precision level of all trading prices is based on significant figures.
691
- # 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).
692
- # Prices submit with a precision larger than 5 will be cut by the API.
693
- return self.decimal_to_precision(price, TRUNCATE, 8, DECIMAL_PLACES)
662
+ https://docs.bitfinex.com/reference/rest-public-conf
663
+
664
+ :param dict [params]: extra parameters specific to the exchange API endpoint
665
+ :returns dict: an associative dictionary of currencies
666
+ """
667
+ labels = [
668
+ 'pub:list:currency',
669
+ 'pub:map:currency:sym', # maps symbols to their API symbols, BAB > BCH
670
+ 'pub:map:currency:label', # verbose friendly names, BNT > Bancor
671
+ 'pub:map:currency:unit', # maps symbols to unit of measure where applicable
672
+ 'pub:map:currency:undl', # maps derivatives symbols to their underlying currency
673
+ 'pub:map:currency:pool', # maps symbols to underlying network/protocol they operate on
674
+ 'pub:map:currency:explorer', # maps symbols to their recognised block explorer URLs
675
+ 'pub:map:currency:tx:fee', # maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745,
676
+ 'pub:map:tx:method', # maps withdrawal/deposit methods to their API symbols
677
+ ]
678
+ config = ','.join(labels)
679
+ request: dict = {
680
+ 'config': config,
681
+ }
682
+ response = await self.publicGetConfConfig(self.extend(request, params))
683
+ #
684
+ # [
685
+ #
686
+ # a list of symbols
687
+ # ["AAA","ABS","ADA"],
688
+ #
689
+ # # sym
690
+ # # maps symbols to their API symbols, BAB > BCH
691
+ # [
692
+ # ["BAB", "BCH"],
693
+ # ["CNHT", "CNHt"],
694
+ # ["DSH", "DASH"],
695
+ # ["IOT", "IOTA"],
696
+ # ["LES", "LEO-EOS"],
697
+ # ["LET", "LEO-ERC20"],
698
+ # ["STJ", "STORJ"],
699
+ # ["TSD", "TUSD"],
700
+ # ["UDC", "USDC"],
701
+ # ["USK", "USDK"],
702
+ # ["UST", "USDt"],
703
+ # ["USTF0", "USDt0"],
704
+ # ["XCH", "XCHF"],
705
+ # ["YYW", "YOYOW"],
706
+ # # ...
707
+ # ],
708
+ # # label
709
+ # # verbose friendly names, BNT > Bancor
710
+ # [
711
+ # ["BAB", "Bitcoin Cash"],
712
+ # ["BCH", "Bitcoin Cash"],
713
+ # ["LEO", "Unus Sed LEO"],
714
+ # ["LES", "Unus Sed LEO(EOS)"],
715
+ # ["LET", "Unus Sed LEO(ERC20)"],
716
+ # # ...
717
+ # ],
718
+ # # unit
719
+ # # maps symbols to unit of measure where applicable
720
+ # [
721
+ # ["IOT", "Mi|MegaIOTA"],
722
+ # ],
723
+ # # undl
724
+ # # maps derivatives symbols to their underlying currency
725
+ # [
726
+ # ["USTF0", "UST"],
727
+ # ["BTCF0", "BTC"],
728
+ # ["ETHF0", "ETH"],
729
+ # ],
730
+ # # pool
731
+ # # maps symbols to underlying network/protocol they operate on
732
+ # [
733
+ # ['SAN', 'ETH'], ['OMG', 'ETH'], ['AVT', 'ETH'], ["EDO", "ETH"],
734
+ # ['ESS', 'ETH'], ['ATD', 'EOS'], ['ADD', 'EOS'], ["MTO", "EOS"],
735
+ # ['PNK', 'ETH'], ['BAB', 'BCH'], ['WLO', 'XLM'], ["VLD", "ETH"],
736
+ # ['BTT', 'TRX'], ['IMP', 'ETH'], ['SCR', 'ETH'], ["GNO", "ETH"],
737
+ # # ...
738
+ # ],
739
+ # # explorer
740
+ # # maps symbols to their recognised block explorer URLs
741
+ # [
742
+ # [
743
+ # "AIO",
744
+ # [
745
+ # "https://mainnet.aion.network",
746
+ # "https://mainnet.aion.network/#/account/VAL",
747
+ # "https://mainnet.aion.network/#/transaction/VAL"
748
+ # ]
749
+ # ],
750
+ # # ...
751
+ # ],
752
+ # # fee
753
+ # # maps currencies to their withdrawal fees
754
+ # [
755
+ # ["AAA",[0,0]],
756
+ # ["ABS",[0,131.3]],
757
+ # ["ADA",[0,0.3]],
758
+ # ],
759
+ # ]
760
+ #
761
+ indexed: dict = {
762
+ 'sym': self.index_by(self.safe_value(response, 1, []), 0),
763
+ 'label': self.index_by(self.safe_value(response, 2, []), 0),
764
+ 'unit': self.index_by(self.safe_value(response, 3, []), 0),
765
+ 'undl': self.index_by(self.safe_value(response, 4, []), 0),
766
+ 'pool': self.index_by(self.safe_value(response, 5, []), 0),
767
+ 'explorer': self.index_by(self.safe_value(response, 6, []), 0),
768
+ 'fees': self.index_by(self.safe_value(response, 7, []), 0),
769
+ }
770
+ ids = self.safe_value(response, 0, [])
771
+ result: dict = {}
772
+ for i in range(0, len(ids)):
773
+ id = ids[i]
774
+ if id.find('F0') >= 0:
775
+ # we get a lot of F0 currencies, skip those
776
+ continue
777
+ code = self.safe_currency_code(id)
778
+ label = self.safe_value(indexed['label'], id, [])
779
+ name = self.safe_string(label, 1)
780
+ pool = self.safe_value(indexed['pool'], id, [])
781
+ rawType = self.safe_string(pool, 1)
782
+ isCryptoCoin = (rawType is not None) or (id in indexed['explorer']) # "hacky" solution
783
+ type = None
784
+ if isCryptoCoin:
785
+ type = 'crypto'
786
+ feeValues = self.safe_value(indexed['fees'], id, [])
787
+ fees = self.safe_value(feeValues, 1, [])
788
+ fee = self.safe_number(fees, 1)
789
+ undl = self.safe_value(indexed['undl'], id, [])
790
+ precision = '8' # default precision, todo: fix "magic constants"
791
+ fid = 'f' + id
792
+ result[code] = {
793
+ 'id': fid,
794
+ 'uppercaseId': id,
795
+ 'code': code,
796
+ 'info': [id, label, pool, feeValues, undl],
797
+ 'type': type,
798
+ 'name': name,
799
+ 'active': True,
800
+ 'deposit': None,
801
+ 'withdraw': None,
802
+ 'fee': fee,
803
+ 'precision': int(precision),
804
+ 'limits': {
805
+ 'amount': {
806
+ 'min': self.parse_number(self.parse_precision(precision)),
807
+ 'max': None,
808
+ },
809
+ 'withdraw': {
810
+ 'min': fee,
811
+ 'max': None,
812
+ },
813
+ },
814
+ 'networks': {},
815
+ }
816
+ networks: dict = {}
817
+ currencyNetworks = self.safe_value(response, 8, [])
818
+ cleanId = id.replace('F0', '')
819
+ for j in range(0, len(currencyNetworks)):
820
+ pair = currencyNetworks[j]
821
+ networkId = self.safe_string(pair, 0)
822
+ currencyId = self.safe_string(self.safe_value(pair, 1, []), 0)
823
+ if currencyId == cleanId:
824
+ network = self.network_id_to_code(networkId)
825
+ networks[network] = {
826
+ 'info': networkId,
827
+ 'id': networkId.lower(),
828
+ 'network': networkId,
829
+ 'active': None,
830
+ 'deposit': None,
831
+ 'withdraw': None,
832
+ 'fee': None,
833
+ 'precision': None,
834
+ 'limits': {
835
+ 'withdraw': {
836
+ 'min': None,
837
+ 'max': None,
838
+ },
839
+ },
840
+ }
841
+ keysNetworks = list(networks.keys())
842
+ networksLength = len(keysNetworks)
843
+ if networksLength > 0:
844
+ result[code]['networks'] = networks
845
+ return result
694
846
 
695
847
  async def fetch_balance(self, params={}) -> Balances:
696
848
  """
697
849
  query for balance and get the amount of funds available for trading or funds locked in orders
698
850
 
699
- https://docs.bitfinex.com/v1/reference/rest-auth-wallet-balances
851
+ https://docs.bitfinex.com/reference/rest-auth-wallets
700
852
 
701
853
  :param dict [params]: extra parameters specific to the exchange API endpoint
702
854
  :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
703
855
  """
856
+ # self api call does not return the 'used' amount - use the v1 version instead(which also returns zero balances)
857
+ # there is a difference between self and the v1 api, namely trading wallet is called margin in v2
704
858
  await self.load_markets()
705
- accountsByType = self.safe_dict(self.options, 'accountsByType', {})
859
+ accountsByType = self.safe_value(self.options, 'v2AccountsByType', {})
706
860
  requestedType = self.safe_string(params, 'type', 'exchange')
707
861
  accountType = self.safe_string(accountsByType, requestedType, requestedType)
708
862
  if accountType is None:
709
863
  keys = list(accountsByType.keys())
710
864
  raise ExchangeError(self.id + ' fetchBalance() type parameter must be one of ' + ', '.join(keys))
865
+ isDerivative = requestedType == 'derivatives'
711
866
  query = self.omit(params, 'type')
712
- response = await self.privatePostBalances(query)
713
- # [{type: "deposit",
714
- # "currency": "btc",
715
- # "amount": "0.00116721",
716
- # "available": "0.00116721"},
717
- # {type: "exchange",
718
- # "currency": "ust",
719
- # "amount": "0.0000002",
720
- # "available": "0.0000002"},
721
- # {type: "trading",
722
- # "currency": "btc",
723
- # "amount": "0.0005",
724
- # "available": "0.0005"}],
867
+ response = await self.privatePostAuthRWallets(query)
725
868
  result: dict = {'info': response}
726
- isDerivative = requestedType == 'derivatives'
727
869
  for i in range(0, len(response)):
728
870
  balance = response[i]
729
- type = self.safe_string(balance, 'type')
730
- currencyId = self.safe_string_lower(balance, 'currency', '')
871
+ account = self.account()
872
+ interest = self.safe_string(balance, 3)
873
+ if interest != '0':
874
+ account['debt'] = interest
875
+ type = self.safe_string(balance, 0)
876
+ currencyId = self.safe_string_lower(balance, 1, '')
731
877
  start = len(currencyId) - 2
732
878
  isDerivativeCode = currencyId[start:] == 'f0'
733
879
  # self will only filter the derivative codes if the requestedType is 'derivatives'
734
880
  derivativeCondition = (not isDerivative or isDerivativeCode)
735
881
  if (accountType == type) and derivativeCondition:
736
882
  code = self.safe_currency_code(currencyId)
737
- # bitfinex had BCH previously, now it's BAB, but the old
738
- # BCH symbol is kept for backward-compatibility
739
- # we need a workaround here so that the old BCH balance
740
- # would not override the new BAB balance(BAB is unified to BCH)
741
- # https://github.com/ccxt/ccxt/issues/4989
742
- if not (code in result):
743
- account = self.account()
744
- account['free'] = self.safe_string(balance, 'available')
745
- account['total'] = self.safe_string(balance, 'amount')
746
- result[code] = account
883
+ account['total'] = self.safe_string(balance, 2)
884
+ account['free'] = self.safe_string(balance, 4)
885
+ result[code] = account
747
886
  return self.safe_balance(result)
748
887
 
749
888
  async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
750
889
  """
751
890
  transfer currency internally between wallets on the same account
752
891
 
753
- https://docs.bitfinex.com/v1/reference/rest-auth-transfer-between-wallets
892
+ https://docs.bitfinex.com/reference/rest-auth-transfer
754
893
 
755
894
  :param str code: unified currency code
756
895
  :param float amount: amount to transfer
@@ -762,117 +901,318 @@ class bitfinex(Exchange, ImplicitAPI):
762
901
  # transferring between derivatives wallet and regular wallet is not documented in their API
763
902
  # however we support it in CCXT(from just looking at web inspector)
764
903
  await self.load_markets()
765
- accountsByType = self.safe_dict(self.options, 'accountsByType', {})
766
- fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
767
- toId = self.safe_string(accountsByType, toAccount, toAccount)
904
+ accountsByType = self.safe_value(self.options, 'v2AccountsByType', {})
905
+ fromId = self.safe_string(accountsByType, fromAccount)
906
+ if fromId is None:
907
+ keys = list(accountsByType.keys())
908
+ raise ArgumentsRequired(self.id + ' transfer() fromAccount must be one of ' + ', '.join(keys))
909
+ toId = self.safe_string(accountsByType, toAccount)
910
+ if toId is None:
911
+ keys = list(accountsByType.keys())
912
+ raise ArgumentsRequired(self.id + ' transfer() toAccount must be one of ' + ', '.join(keys))
768
913
  currency = self.currency(code)
769
- fromCurrencyId = self.convert_derivatives_id(currency['id'], fromAccount)
770
- toCurrencyId = self.convert_derivatives_id(currency['id'], toAccount)
914
+ fromCurrencyId = self.convert_derivatives_id(currency, fromAccount)
915
+ toCurrencyId = self.convert_derivatives_id(currency, toAccount)
771
916
  requestedAmount = self.currency_to_precision(code, amount)
917
+ # self request is slightly different from v1 fromAccount -> from
772
918
  request: dict = {
773
919
  'amount': requestedAmount,
774
920
  'currency': fromCurrencyId,
775
921
  'currency_to': toCurrencyId,
776
- 'walletfrom': fromId,
777
- 'walletto': toId,
922
+ 'from': fromId,
923
+ 'to': toId,
778
924
  }
779
- response = await self.privatePostTransfer(self.extend(request, params))
925
+ response = await self.privatePostAuthWTransfer(self.extend(request, params))
780
926
  #
781
927
  # [
782
- # {
783
- # "status": "success",
784
- # "message": "0.0001 Bitcoin transfered from Margin to Exchange"
785
- # }
928
+ # 1616451183763,
929
+ # "acc_tf",
930
+ # null,
931
+ # null,
932
+ # [
933
+ # 1616451183763,
934
+ # "exchange",
935
+ # "margin",
936
+ # null,
937
+ # "UST",
938
+ # "UST",
939
+ # null,
940
+ # 1
941
+ # ],
942
+ # null,
943
+ # "SUCCESS",
944
+ # "1.0 Tether USDt transfered from Exchange to Margin"
786
945
  # ]
787
946
  #
788
- result = self.safe_value(response, 0)
789
- message = self.safe_string(result, 'message')
790
- if message is None:
791
- raise ExchangeError(self.id + ' transfer failed')
792
- return self.extend(self.parse_transfer(result, currency), {
793
- 'fromAccount': fromAccount,
794
- 'toAccount': toAccount,
795
- 'amount': self.parse_number(requestedAmount),
796
- })
947
+ error = self.safe_string(response, 0)
948
+ if error == 'error':
949
+ message = self.safe_string(response, 2, '')
950
+ # same message v1
951
+ self.throw_exactly_matched_exception(self.exceptions['exact'], message, self.id + ' ' + message)
952
+ raise ExchangeError(self.id + ' ' + message)
953
+ return self.parse_transfer({'result': response}, currency)
797
954
 
798
955
  def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
799
956
  #
800
- # {
801
- # "status": "success",
802
- # "message": "0.0001 Bitcoin transfered from Margin to Exchange"
803
- # }
957
+ # transfer
804
958
  #
959
+ # [
960
+ # 1616451183763,
961
+ # "acc_tf",
962
+ # null,
963
+ # null,
964
+ # [
965
+ # 1616451183763,
966
+ # "exchange",
967
+ # "margin",
968
+ # null,
969
+ # "UST",
970
+ # "UST",
971
+ # null,
972
+ # 1
973
+ # ],
974
+ # null,
975
+ # "SUCCESS",
976
+ # "1.0 Tether USDt transfered from Exchange to Margin"
977
+ # ]
978
+ #
979
+ result = self.safe_list(transfer, 'result')
980
+ timestamp = self.safe_integer(result, 0)
981
+ info = self.safe_value(result, 4)
982
+ fromAccount = self.safe_string(info, 1)
983
+ toAccount = self.safe_string(info, 2)
984
+ currencyId = self.safe_string(info, 5)
985
+ status = self.safe_string(result, 6)
805
986
  return {
806
- 'info': transfer,
807
987
  'id': None,
808
- 'timestamp': None,
809
- 'datetime': None,
810
- 'currency': self.safe_currency_code(None, currency),
811
- 'amount': None,
812
- 'fromAccount': None,
813
- 'toAccount': None,
814
- 'status': self.parse_transfer_status(self.safe_string(transfer, 'status')),
988
+ 'timestamp': timestamp,
989
+ 'datetime': self.iso8601(timestamp),
990
+ 'status': self.parse_transfer_status(status),
991
+ 'amount': self.safe_number(info, 7),
992
+ 'currency': self.safe_currency_code(currencyId, currency),
993
+ 'fromAccount': fromAccount,
994
+ 'toAccount': toAccount,
995
+ 'info': result,
815
996
  }
816
997
 
817
998
  def parse_transfer_status(self, status: Str) -> Str:
818
999
  statuses: dict = {
819
1000
  'SUCCESS': 'ok',
1001
+ 'ERROR': 'failed',
1002
+ 'FAILURE': 'failed',
820
1003
  }
821
1004
  return self.safe_string(statuses, status, status)
822
1005
 
823
- def convert_derivatives_id(self, currencyId, type):
824
- start = len(currencyId) - 2
825
- isDerivativeCode = currencyId[start:] == 'F0'
826
- if (type != 'derivatives' and type != 'trading' and type != 'margin') and isDerivativeCode:
827
- currencyId = currencyId[0:start]
828
- elif type == 'derivatives' and not isDerivativeCode:
829
- currencyId = currencyId + 'F0'
1006
+ def convert_derivatives_id(self, currency, type):
1007
+ # there is a difference between self and the v1 api, namely trading wallet is called margin in v2
1008
+ # {
1009
+ # "id": "fUSTF0",
1010
+ # "code": "USTF0",
1011
+ # "info": ['USTF0', [], [], [], ["USTF0", "UST"]],
1012
+ info = self.safe_value(currency, 'info')
1013
+ transferId = self.safe_string(info, 0)
1014
+ underlying = self.safe_value(info, 4, [])
1015
+ currencyId = None
1016
+ if type == 'derivatives':
1017
+ currencyId = self.safe_string(underlying, 0, transferId)
1018
+ start = len(currencyId) - 2
1019
+ isDerivativeCode = currencyId[start:] == 'F0'
1020
+ if not isDerivativeCode:
1021
+ currencyId = currencyId + 'F0'
1022
+ elif type != 'margin':
1023
+ currencyId = self.safe_string(underlying, 1, transferId)
1024
+ else:
1025
+ currencyId = transferId
830
1026
  return currencyId
831
1027
 
832
1028
  async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
833
1029
  """
834
1030
  fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
835
1031
 
836
- https://docs.bitfinex.com/v1/reference/rest-public-orderbook
1032
+ https://docs.bitfinex.com/reference/rest-public-book
837
1033
 
838
1034
  :param str symbol: unified symbol of the market to fetch the order book for
839
- :param int [limit]: the maximum amount of order book entries to return
1035
+ :param int [limit]: the maximum amount of order book entries to return, bitfinex only allows 1, 25, or 100
840
1036
  :param dict [params]: extra parameters specific to the exchange API endpoint
841
1037
  :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
842
1038
  """
843
1039
  await self.load_markets()
1040
+ precision = self.safe_value(self.options, 'precision', 'R0')
844
1041
  market = self.market(symbol)
845
1042
  request: dict = {
846
1043
  'symbol': market['id'],
1044
+ 'precision': precision,
847
1045
  }
848
1046
  if limit is not None:
849
- request['limit_bids'] = limit
850
- request['limit_asks'] = limit
851
- response = await self.publicGetBookSymbol(self.extend(request, params))
852
- return self.parse_order_book(response, market['symbol'], None, 'bids', 'asks', 'price', 'amount')
1047
+ request['len'] = limit
1048
+ fullRequest = self.extend(request, params)
1049
+ orderbook = await self.publicGetBookSymbolPrecision(fullRequest)
1050
+ timestamp = self.milliseconds()
1051
+ result: dict = {
1052
+ 'symbol': market['symbol'],
1053
+ 'bids': [],
1054
+ 'asks': [],
1055
+ 'timestamp': timestamp,
1056
+ 'datetime': self.iso8601(timestamp),
1057
+ 'nonce': None,
1058
+ }
1059
+ priceIndex = 1 if (fullRequest['precision'] == 'R0') else 0
1060
+ for i in range(0, len(orderbook)):
1061
+ order = orderbook[i]
1062
+ price = self.safe_number(order, priceIndex)
1063
+ signedAmount = self.safe_string(order, 2)
1064
+ amount = Precise.string_abs(signedAmount)
1065
+ side = 'bids' if Precise.string_gt(signedAmount, '0') else 'asks'
1066
+ result[side].append([price, self.parse_number(amount)])
1067
+ result['bids'] = self.sort_by(result['bids'], 0, True)
1068
+ result['asks'] = self.sort_by(result['asks'], 0)
1069
+ return result
1070
+
1071
+ def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
1072
+ #
1073
+ # on trading pairs(ex. tBTCUSD)
1074
+ #
1075
+ # {
1076
+ # 'result': [
1077
+ # SYMBOL,
1078
+ # BID,
1079
+ # BID_SIZE,
1080
+ # ASK,
1081
+ # ASK_SIZE,
1082
+ # DAILY_CHANGE,
1083
+ # DAILY_CHANGE_RELATIVE,
1084
+ # LAST_PRICE,
1085
+ # VOLUME,
1086
+ # HIGH,
1087
+ # LOW
1088
+ # ]
1089
+ # }
1090
+ #
1091
+ #
1092
+ # on funding currencies(ex. fUSD)
1093
+ #
1094
+ # {
1095
+ # 'result': [
1096
+ # SYMBOL,
1097
+ # FRR,
1098
+ # BID,
1099
+ # BID_PERIOD,
1100
+ # BID_SIZE,
1101
+ # ASK,
1102
+ # ASK_PERIOD,
1103
+ # ASK_SIZE,
1104
+ # DAILY_CHANGE,
1105
+ # DAILY_CHANGE_RELATIVE,
1106
+ # LAST_PRICE,
1107
+ # VOLUME,
1108
+ # HIGH,
1109
+ # LOW,
1110
+ # _PLACEHOLDER,
1111
+ # _PLACEHOLDER,
1112
+ # FRR_AMOUNT_AVAILABLE
1113
+ # ]
1114
+ # }
1115
+ #
1116
+ result = self.safe_list(ticker, 'result')
1117
+ symbol = self.safe_symbol(None, market)
1118
+ length = len(result)
1119
+ last = self.safe_string(result, length - 4)
1120
+ percentage = self.safe_string(result, length - 5)
1121
+ return self.safe_ticker({
1122
+ 'symbol': symbol,
1123
+ 'timestamp': None,
1124
+ 'datetime': None,
1125
+ 'high': self.safe_string(result, length - 2),
1126
+ 'low': self.safe_string(result, length - 1),
1127
+ 'bid': self.safe_string(result, length - 10),
1128
+ 'bidVolume': self.safe_string(result, length - 9),
1129
+ 'ask': self.safe_string(result, length - 8),
1130
+ 'askVolume': self.safe_string(result, length - 7),
1131
+ 'vwap': None,
1132
+ 'open': None,
1133
+ 'close': last,
1134
+ 'last': last,
1135
+ 'previousClose': None,
1136
+ 'change': self.safe_string(result, length - 6),
1137
+ 'percentage': Precise.string_mul(percentage, '100'),
1138
+ 'average': None,
1139
+ 'baseVolume': self.safe_string(result, length - 3),
1140
+ 'quoteVolume': None,
1141
+ 'info': result,
1142
+ }, market)
853
1143
 
854
1144
  async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
855
1145
  """
856
1146
  fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
857
- :param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
1147
+
1148
+ https://docs.bitfinex.com/reference/rest-public-tickers
1149
+
1150
+ :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
858
1151
  :param dict [params]: extra parameters specific to the exchange API endpoint
859
1152
  :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
860
1153
  """
861
1154
  await self.load_markets()
862
1155
  symbols = self.market_symbols(symbols)
863
- response = await self.publicGetTickers(params)
1156
+ request: dict = {}
1157
+ if symbols is not None:
1158
+ ids = self.market_ids(symbols)
1159
+ request['symbols'] = ','.join(ids)
1160
+ else:
1161
+ request['symbols'] = 'ALL'
1162
+ tickers = await self.publicGetTickers(self.extend(request, params))
1163
+ #
1164
+ # [
1165
+ # # on trading pairs(ex. tBTCUSD)
1166
+ # [
1167
+ # SYMBOL,
1168
+ # BID,
1169
+ # BID_SIZE,
1170
+ # ASK,
1171
+ # ASK_SIZE,
1172
+ # DAILY_CHANGE,
1173
+ # DAILY_CHANGE_RELATIVE,
1174
+ # LAST_PRICE,
1175
+ # VOLUME,
1176
+ # HIGH,
1177
+ # LOW
1178
+ # ],
1179
+ # # on funding currencies(ex. fUSD)
1180
+ # [
1181
+ # SYMBOL,
1182
+ # FRR,
1183
+ # BID,
1184
+ # BID_PERIOD,
1185
+ # BID_SIZE,
1186
+ # ASK,
1187
+ # ASK_PERIOD,
1188
+ # ASK_SIZE,
1189
+ # DAILY_CHANGE,
1190
+ # DAILY_CHANGE_RELATIVE,
1191
+ # LAST_PRICE,
1192
+ # VOLUME,
1193
+ # HIGH,
1194
+ # LOW,
1195
+ # _PLACEHOLDER,
1196
+ # _PLACEHOLDER,
1197
+ # FRR_AMOUNT_AVAILABLE
1198
+ # ],
1199
+ # ...
1200
+ # ]
1201
+ #
864
1202
  result: dict = {}
865
- for i in range(0, len(response)):
866
- ticker = self.parse_ticker(response[i])
867
- symbol = ticker['symbol']
868
- result[symbol] = ticker
1203
+ for i in range(0, len(tickers)):
1204
+ ticker = tickers[i]
1205
+ marketId = self.safe_string(ticker, 0)
1206
+ market = self.safe_market(marketId)
1207
+ symbol = market['symbol']
1208
+ result[symbol] = self.parse_ticker({'result': ticker}, market)
869
1209
  return self.filter_by_array_tickers(result, 'symbol', symbols)
870
1210
 
871
1211
  async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
872
1212
  """
873
1213
  fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
874
1214
 
875
- https://docs.bitfinex.com/v1/reference/rest-public-ticker
1215
+ https://docs.bitfinex.com/reference/rest-public-ticker
876
1216
 
877
1217
  :param str symbol: unified symbol of the market to fetch the ticker for
878
1218
  :param dict [params]: extra parameters specific to the exchange API endpoint
@@ -883,388 +1223,707 @@ class bitfinex(Exchange, ImplicitAPI):
883
1223
  request: dict = {
884
1224
  'symbol': market['id'],
885
1225
  }
886
- ticker = await self.publicGetPubtickerSymbol(self.extend(request, params))
1226
+ ticker = await self.publicGetTickerSymbol(self.extend(request, params))
1227
+ result: dict = {'result': ticker}
1228
+ return self.parse_ticker(result, market)
1229
+
1230
+ def parse_trade(self, trade: dict, market: Market = None) -> Trade:
887
1231
  #
888
- # {
889
- # mid: '63560.5',
890
- # bid: '63560.0',
891
- # ask: '63561.0',
892
- # last_price: '63547.0',
893
- # low: '62812.0',
894
- # high: '64480.0',
895
- # volume: '517.25634977',
896
- # timestamp: '1715102384.9849467'
897
- # }
1232
+ # fetchTrades(public)
898
1233
  #
899
- return self.parse_ticker(ticker, market)
900
-
901
- def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
1234
+ # [
1235
+ # ID,
1236
+ # MTS, # timestamp
1237
+ # AMOUNT,
1238
+ # PRICE
1239
+ # ]
902
1240
  #
903
- # {
904
- # mid: '63560.5',
905
- # bid: '63560.0',
906
- # ask: '63561.0',
907
- # last_price: '63547.0',
908
- # low: '62812.0',
909
- # high: '64480.0',
910
- # volume: '517.25634977',
911
- # timestamp: '1715102384.9849467'
912
- # }
1241
+ # fetchMyTrades(private)
913
1242
  #
914
- timestamp = self.safe_timestamp(ticker, 'timestamp')
915
- marketId = self.safe_string(ticker, 'pair')
916
- market = self.safe_market(marketId, market)
917
- symbol = market['symbol']
918
- last = self.safe_string(ticker, 'last_price')
919
- return self.safe_ticker({
920
- 'symbol': symbol,
921
- 'timestamp': timestamp,
922
- 'datetime': self.iso8601(timestamp),
923
- 'high': self.safe_string(ticker, 'high'),
924
- 'low': self.safe_string(ticker, 'low'),
925
- 'bid': self.safe_string(ticker, 'bid'),
926
- 'bidVolume': None,
927
- 'ask': self.safe_string(ticker, 'ask'),
928
- 'askVolume': None,
929
- 'vwap': None,
930
- 'open': None,
931
- 'close': last,
932
- 'last': last,
933
- 'previousClose': None,
934
- 'change': None,
935
- 'percentage': None,
936
- 'average': self.safe_string(ticker, 'mid'),
937
- 'baseVolume': self.safe_string(ticker, 'volume'),
938
- 'quoteVolume': None,
939
- 'info': ticker,
940
- }, market)
941
-
942
- def parse_trade(self, trade: dict, market: Market = None) -> Trade:
1243
+ # [
1244
+ # ID,
1245
+ # PAIR,
1246
+ # MTS_CREATE,
1247
+ # ORDER_ID,
1248
+ # EXEC_AMOUNT,
1249
+ # EXEC_PRICE,
1250
+ # ORDER_TYPE,
1251
+ # ORDER_PRICE,
1252
+ # MAKER,
1253
+ # FEE,
1254
+ # FEE_CURRENCY,
1255
+ # ...
1256
+ # ]
943
1257
  #
944
- # fetchTrades(public) v1
945
- #
946
- # {
947
- # "timestamp":1637258380,
948
- # "tid":894452833,
949
- # "price":"0.99941",
950
- # "amount":"261.38",
951
- # "exchange":"bitfinex",
952
- # "type":"sell"
953
- # }
954
- #
955
- # fetchMyTrades(private) v1
956
- #
957
- # {
958
- # "price":"0.99941",
959
- # "amount":"261.38",
960
- # "timestamp":"1637258380.0",
961
- # "type":"Sell",
962
- # "fee_currency":"UST",
963
- # "fee_amount":"-0.52245157",
964
- # "tid":894452833,
965
- # "order_id":78819731373
966
- # }
967
- #
968
- # {
969
- # "price":"0.99958",
970
- # "amount":"261.90514",
971
- # "timestamp":"1637258238.0",
972
- # "type":"Buy",
973
- # "fee_currency":"UDC",
974
- # "fee_amount":"-0.52381028",
975
- # "tid":894452800,
976
- # "order_id":78819504838
977
- # }
978
- #
979
- id = self.safe_string(trade, 'tid')
980
- timestamp = self.safe_timestamp(trade, 'timestamp')
1258
+ tradeList = self.safe_list(trade, 'result', [])
1259
+ tradeLength = len(tradeList)
1260
+ isPrivate = (tradeLength > 5)
1261
+ id = self.safe_string(tradeList, 0)
1262
+ amountIndex = 4 if isPrivate else 2
1263
+ side = None
1264
+ amountString = self.safe_string(tradeList, amountIndex)
1265
+ priceIndex = 5 if isPrivate else 3
1266
+ priceString = self.safe_string(tradeList, priceIndex)
1267
+ if amountString[0] == '-':
1268
+ side = 'sell'
1269
+ amountString = Precise.string_abs(amountString)
1270
+ else:
1271
+ side = 'buy'
1272
+ orderId = None
1273
+ takerOrMaker = None
981
1274
  type = None
982
- side = self.safe_string_lower(trade, 'type')
983
- orderId = self.safe_string(trade, 'order_id')
984
- priceString = self.safe_string(trade, 'price')
985
- amountString = self.safe_string(trade, 'amount')
986
1275
  fee = None
987
- if 'fee_amount' in trade:
988
- feeCostString = Precise.string_neg(self.safe_string(trade, 'fee_amount'))
989
- feeCurrencyId = self.safe_string(trade, 'fee_currency')
990
- feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
1276
+ symbol = self.safe_symbol(None, market)
1277
+ timestampIndex = 2 if isPrivate else 1
1278
+ timestamp = self.safe_integer(tradeList, timestampIndex)
1279
+ if isPrivate:
1280
+ marketId = tradeList[1]
1281
+ symbol = self.safe_symbol(marketId)
1282
+ orderId = self.safe_string(tradeList, 3)
1283
+ maker = self.safe_integer(tradeList, 8)
1284
+ takerOrMaker = 'maker' if (maker == 1) else 'taker'
1285
+ feeCostString = self.safe_string(tradeList, 9)
1286
+ feeCostString = Precise.string_neg(feeCostString)
1287
+ feeCurrencyId = self.safe_string(tradeList, 10)
1288
+ feeCurrency = self.safe_currency_code(feeCurrencyId)
991
1289
  fee = {
992
1290
  'cost': feeCostString,
993
- 'currency': feeCurrencyCode,
1291
+ 'currency': feeCurrency,
994
1292
  }
1293
+ orderType = tradeList[6]
1294
+ type = self.safe_string(self.options['exchangeTypes'], orderType)
995
1295
  return self.safe_trade({
996
1296
  'id': id,
997
- 'info': trade,
998
1297
  'timestamp': timestamp,
999
1298
  'datetime': self.iso8601(timestamp),
1000
- 'symbol': market['symbol'],
1001
- 'type': type,
1299
+ 'symbol': symbol,
1002
1300
  'order': orderId,
1003
1301
  'side': side,
1004
- 'takerOrMaker': None,
1302
+ 'type': type,
1303
+ 'takerOrMaker': takerOrMaker,
1005
1304
  'price': priceString,
1006
1305
  'amount': amountString,
1007
1306
  'cost': None,
1008
1307
  'fee': fee,
1308
+ 'info': tradeList,
1009
1309
  }, market)
1010
1310
 
1011
- async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = 50, params={}) -> List[Trade]:
1311
+ async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
1012
1312
  """
1013
1313
  get the list of most recent trades for a particular symbol
1014
1314
 
1015
- https://docs.bitfinex.com/v1/reference/rest-public-trades
1315
+ https://docs.bitfinex.com/reference/rest-public-trades
1016
1316
 
1017
1317
  :param str symbol: unified symbol of the market to fetch trades for
1018
1318
  :param int [since]: timestamp in ms of the earliest trade to fetch
1019
- :param int [limit]: the maximum amount of trades to fetch
1319
+ :param int [limit]: the maximum amount of trades to fetch, default 120, max 10000
1020
1320
  :param dict [params]: extra parameters specific to the exchange API endpoint
1321
+ :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)
1322
+ :param int [params.until]: the latest time in ms to fetch entries for
1021
1323
  :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
1022
1324
  """
1023
1325
  await self.load_markets()
1326
+ paginate = False
1327
+ paginate, params = self.handle_option_and_params(params, 'fetchTrades', 'paginate')
1328
+ if paginate:
1329
+ return await self.fetch_paginated_call_dynamic('fetchTrades', symbol, since, limit, params, 10000)
1024
1330
  market = self.market(symbol)
1331
+ sort = '-1'
1025
1332
  request: dict = {
1026
1333
  'symbol': market['id'],
1027
- 'limit_trades': limit,
1028
1334
  }
1029
1335
  if since is not None:
1030
- request['timestamp'] = self.parse_to_int(since / 1000)
1031
- response = await self.publicGetTradesSymbol(self.extend(request, params))
1336
+ request['start'] = since
1337
+ sort = '1'
1338
+ if limit is not None:
1339
+ request['limit'] = min(limit, 10000) # default 120, max 10000
1340
+ request['sort'] = sort
1341
+ request, params = self.handle_until_option('end', request, params)
1342
+ response = await self.publicGetTradesSymbolHist(self.extend(request, params))
1032
1343
  #
1033
- # [
1034
- # {
1035
- # "timestamp": "1694284565",
1036
- # "tid": "1415415034",
1037
- # "price": "25862.0",
1038
- # "amount": "0.00020685",
1039
- # "exchange": "bitfinex",
1040
- # "type": "buy"
1041
- # },
1042
- # ]
1344
+ # [
1345
+ # [
1346
+ # ID,
1347
+ # MTS, # timestamp
1348
+ # AMOUNT,
1349
+ # PRICE
1350
+ # ]
1351
+ # ]
1043
1352
  #
1044
- return self.parse_trades(response, market, since, limit)
1353
+ trades = self.sort_by(response, 1)
1354
+ tradesList = []
1355
+ for i in range(0, len(trades)):
1356
+ tradesList.append({'result': trades[i]}) # convert to array of dicts to match parseOrder signature
1357
+ return self.parse_trades(tradesList, market, None, limit)
1045
1358
 
1046
- async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
1359
+ async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = 100, params={}) -> List[list]:
1047
1360
  """
1048
- fetch all trades made by the user
1361
+ fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
1049
1362
 
1050
- https://docs.bitfinex.com/v1/reference/rest-auth-past-trades
1363
+ https://docs.bitfinex.com/reference/rest-public-candles
1051
1364
 
1052
- :param str symbol: unified market symbol
1053
- :param int [since]: the earliest time in ms to fetch trades for
1054
- :param int [limit]: the maximum number of trades structures to retrieve
1365
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
1366
+ :param str timeframe: the length of time each candle represents
1367
+ :param int [since]: timestamp in ms of the earliest candle to fetch
1368
+ :param int [limit]: the maximum amount of candles to fetch, default 100 max 10000
1055
1369
  :param dict [params]: extra parameters specific to the exchange API endpoint
1056
- :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
1370
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
1371
+ :param int [params.until]: timestamp in ms of the latest candle to fetch
1372
+ :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)
1057
1373
  """
1058
- if symbol is None:
1059
- raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
1060
1374
  await self.load_markets()
1375
+ paginate = False
1376
+ paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate')
1377
+ if paginate:
1378
+ return await self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 10000)
1061
1379
  market = self.market(symbol)
1380
+ if limit is None:
1381
+ limit = 10000
1382
+ else:
1383
+ limit = min(limit, 10000)
1062
1384
  request: dict = {
1063
1385
  'symbol': market['id'],
1386
+ 'timeframe': self.safe_string(self.timeframes, timeframe, timeframe),
1387
+ 'sort': 1,
1388
+ 'limit': limit,
1064
1389
  }
1065
- if limit is not None:
1066
- request['limit_trades'] = limit
1067
1390
  if since is not None:
1068
- request['timestamp'] = self.parse_to_int(since / 1000)
1069
- response = await self.privatePostMytrades(self.extend(request, params))
1070
- return self.parse_trades(response, market, since, limit)
1391
+ request['start'] = since
1392
+ request, params = self.handle_until_option('end', request, params)
1393
+ response = await self.publicGetCandlesTradeTimeframeSymbolHist(self.extend(request, params))
1394
+ #
1395
+ # [
1396
+ # [1591503840000,0.025069,0.025068,0.025069,0.025068,1.97828998],
1397
+ # [1591504500000,0.025065,0.025065,0.025065,0.025065,1.0164],
1398
+ # [1591504620000,0.025062,0.025062,0.025062,0.025062,0.5],
1399
+ # ]
1400
+ #
1401
+ return self.parse_ohlcvs(response, market, timeframe, since, limit)
1071
1402
 
1072
- async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
1073
- """
1074
- create a trade order
1403
+ def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
1404
+ #
1405
+ # [
1406
+ # 1457539800000,
1407
+ # 0.02594,
1408
+ # 0.02594,
1409
+ # 0.02594,
1410
+ # 0.02594,
1411
+ # 0.1
1412
+ # ]
1413
+ #
1414
+ return [
1415
+ self.safe_integer(ohlcv, 0),
1416
+ self.safe_number(ohlcv, 1),
1417
+ self.safe_number(ohlcv, 3),
1418
+ self.safe_number(ohlcv, 4),
1419
+ self.safe_number(ohlcv, 2),
1420
+ self.safe_number(ohlcv, 5),
1421
+ ]
1422
+
1423
+ def parse_order_status(self, status: Str):
1424
+ if status is None:
1425
+ return status
1426
+ parts = status.split(' ')
1427
+ state = self.safe_string(parts, 0)
1428
+ statuses: dict = {
1429
+ 'ACTIVE': 'open',
1430
+ 'PARTIALLY': 'open',
1431
+ 'EXECUTED': 'closed',
1432
+ 'CANCELED': 'canceled',
1433
+ 'INSUFFICIENT': 'canceled',
1434
+ 'POSTONLY CANCELED': 'canceled',
1435
+ 'RSN_DUST': 'rejected',
1436
+ 'RSN_PAUSE': 'rejected',
1437
+ 'IOC CANCELED': 'canceled',
1438
+ 'FILLORKILL CANCELED': 'canceled',
1439
+ }
1440
+ return self.safe_string(statuses, state, status)
1441
+
1442
+ def parse_order_flags(self, flags):
1443
+ # flags can be added to each other...
1444
+ flagValues: dict = {
1445
+ '1024': ['reduceOnly'],
1446
+ '4096': ['postOnly'],
1447
+ '5120': ['reduceOnly', 'postOnly'],
1448
+ # '64': 'hidden', # The hidden order option ensures an order does not appear in the order book
1449
+ # '512': 'close', # Close position if position present.
1450
+ # '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.
1451
+ # '524288': 'No Var Rates' # Excludes variable rate funding offers from matching against self order, if on margin
1452
+ }
1453
+ return self.safe_value(flagValues, flags, None)
1454
+
1455
+ def parse_time_in_force(self, orderType):
1456
+ orderTypes: dict = {
1457
+ 'EXCHANGE IOC': 'IOC',
1458
+ 'EXCHANGE FOK': 'FOK',
1459
+ 'IOC': 'IOC', # Margin
1460
+ 'FOK': 'FOK', # Margin
1461
+ }
1462
+ return self.safe_string(orderTypes, orderType, 'GTC')
1075
1463
 
1076
- https://docs.bitfinex.com/v1/reference/rest-auth-new-order
1464
+ def parse_order(self, order: dict, market: Market = None) -> Order:
1465
+ orderList = self.safe_list(order, 'result')
1466
+ id = self.safe_string(orderList, 0)
1467
+ marketId = self.safe_string(orderList, 3)
1468
+ symbol = self.safe_symbol(marketId)
1469
+ # https://github.com/ccxt/ccxt/issues/6686
1470
+ # timestamp = self.safe_timestamp(orderObject, 5)
1471
+ timestamp = self.safe_integer(orderList, 5)
1472
+ remaining = Precise.string_abs(self.safe_string(orderList, 6))
1473
+ signedAmount = self.safe_string(orderList, 7)
1474
+ amount = Precise.string_abs(signedAmount)
1475
+ side = 'sell' if Precise.string_lt(signedAmount, '0') else 'buy'
1476
+ orderType = self.safe_string(orderList, 8)
1477
+ type = self.safe_string(self.safe_value(self.options, 'exchangeTypes'), orderType)
1478
+ timeInForce = self.parse_time_in_force(orderType)
1479
+ rawFlags = self.safe_string(orderList, 12)
1480
+ flags = self.parse_order_flags(rawFlags)
1481
+ postOnly = False
1482
+ if flags is not None:
1483
+ for i in range(0, len(flags)):
1484
+ if flags[i] == 'postOnly':
1485
+ postOnly = True
1486
+ price = self.safe_string(orderList, 16)
1487
+ stopPrice = None
1488
+ if (orderType == 'EXCHANGE STOP') or (orderType == 'EXCHANGE STOP LIMIT'):
1489
+ price = None
1490
+ stopPrice = self.safe_string(orderList, 16)
1491
+ if orderType == 'EXCHANGE STOP LIMIT':
1492
+ price = self.safe_string(orderList, 19)
1493
+ status = None
1494
+ statusString = self.safe_string(orderList, 13)
1495
+ if statusString is not None:
1496
+ parts = statusString.split(' @ ')
1497
+ status = self.parse_order_status(self.safe_string(parts, 0))
1498
+ average = self.safe_string(orderList, 17)
1499
+ clientOrderId = self.safe_string(orderList, 2)
1500
+ return self.safe_order({
1501
+ 'info': orderList,
1502
+ 'id': id,
1503
+ 'clientOrderId': clientOrderId,
1504
+ 'timestamp': timestamp,
1505
+ 'datetime': self.iso8601(timestamp),
1506
+ 'lastTradeTimestamp': None,
1507
+ 'symbol': symbol,
1508
+ 'type': type,
1509
+ 'timeInForce': timeInForce,
1510
+ 'postOnly': postOnly,
1511
+ 'side': side,
1512
+ 'price': price,
1513
+ 'stopPrice': stopPrice,
1514
+ 'triggerPrice': stopPrice,
1515
+ 'amount': amount,
1516
+ 'cost': None,
1517
+ 'average': average,
1518
+ 'filled': None,
1519
+ 'remaining': remaining,
1520
+ 'status': status,
1521
+ 'fee': None,
1522
+ 'trades': None,
1523
+ }, market)
1077
1524
 
1525
+ def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
1526
+ """
1527
+ @ignore
1528
+ helper function to build an order request
1078
1529
  :param str symbol: unified symbol of the market to create an order in
1079
1530
  :param str type: 'market' or 'limit'
1080
1531
  :param str side: 'buy' or 'sell'
1081
- :param float amount: how much of currency you want to trade in units of base currency
1082
- :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
1532
+ :param float amount: how much you want to trade in units of the base currency
1533
+ :param float [price]: the price of the order, in units of the quote currency, ignored in market orders
1083
1534
  :param dict [params]: extra parameters specific to the exchange API endpoint
1084
- :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1535
+ :param float [params.stopPrice]: The price at which a trigger order is triggered at
1536
+ :param str [params.timeInForce]: "GTC", "IOC", "FOK", or "PO"
1537
+ :param bool [params.postOnly]:
1538
+ :param bool [params.reduceOnly]: Ensures that the executed order does not flip the opened position.
1539
+ :param int [params.flags]: additional order parameters: 4096(Post Only), 1024(Reduce Only), 16384(OCO), 64(Hidden), 512(Close), 524288(No Var Rates)
1540
+ :param int [params.lev]: leverage for a derivative order, supported by derivative symbol orders only. The value should be between 1 and 100 inclusive.
1541
+ :param str [params.price_traling]: The trailing price for a trailing stop order
1542
+ :param str [params.price_aux_limit]: Order price for stop limit orders
1543
+ :param str [params.price_oco_stop]: OCO stop price
1544
+ :returns dict: an `order structure <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
1085
1545
  """
1086
- await self.load_markets()
1087
1546
  market = self.market(symbol)
1088
- postOnly = self.safe_bool(params, 'postOnly', False)
1089
- type = type.lower()
1090
- params = self.omit(params, ['postOnly'])
1091
- if market['spot']:
1092
- # although they claim that type needs to be 'exchange limit' or 'exchange market'
1093
- # in fact that's not the case for swap markets
1094
- type = self.safe_string_lower(self.options['orderTypes'], type, type)
1547
+ amountString = self.amount_to_precision(symbol, amount)
1548
+ amountString = amountString if (side == 'buy') else Precise.string_neg(amountString)
1095
1549
  request: dict = {
1096
1550
  'symbol': market['id'],
1097
- 'side': side,
1098
- 'amount': self.amount_to_precision(symbol, amount),
1099
- 'type': type,
1100
- 'ocoorder': False,
1101
- 'buy_price_oco': 0,
1102
- 'sell_price_oco': 0,
1551
+ 'amount': amountString,
1103
1552
  }
1104
- if type.find('market') > -1:
1105
- request['price'] = str(self.nonce())
1106
- else:
1553
+ stopPrice = self.safe_string_2(params, 'stopPrice', 'triggerPrice')
1554
+ trailingAmount = self.safe_string(params, 'trailingAmount')
1555
+ timeInForce = self.safe_string(params, 'timeInForce')
1556
+ postOnlyParam = self.safe_bool(params, 'postOnly', False)
1557
+ reduceOnly = self.safe_bool(params, 'reduceOnly', False)
1558
+ clientOrderId = self.safe_value_2(params, 'cid', 'clientOrderId')
1559
+ orderType = type.upper()
1560
+ if trailingAmount is not None:
1561
+ orderType = 'TRAILING STOP'
1562
+ request['price_trailing'] = trailingAmount
1563
+ elif stopPrice is not None:
1564
+ # request['price'] is taken for stop orders
1565
+ request['price'] = self.price_to_precision(symbol, stopPrice)
1566
+ if type == 'limit':
1567
+ orderType = 'STOP LIMIT'
1568
+ request['price_aux_limit'] = self.price_to_precision(symbol, price)
1569
+ else:
1570
+ orderType = 'STOP'
1571
+ ioc = (timeInForce == 'IOC')
1572
+ fok = (timeInForce == 'FOK')
1573
+ postOnly = (postOnlyParam or (timeInForce == 'PO'))
1574
+ if (ioc or fok) and (price is None):
1575
+ raise InvalidOrder(self.id + ' createOrder() requires a price argument with IOC and FOK orders')
1576
+ if (ioc or fok) and (type == 'market'):
1577
+ raise InvalidOrder(self.id + ' createOrder() does not allow market IOC and FOK orders')
1578
+ if (type != 'market') and (stopPrice is None):
1107
1579
  request['price'] = self.price_to_precision(symbol, price)
1580
+ if ioc:
1581
+ orderType = 'IOC'
1582
+ elif fok:
1583
+ orderType = 'FOK'
1584
+ marginMode = None
1585
+ marginMode, params = self.handle_margin_mode_and_params('createOrder', params)
1586
+ if market['spot'] and (marginMode is None):
1587
+ # The EXCHANGE prefix is only required for non margin spot markets
1588
+ orderType = 'EXCHANGE ' + orderType
1589
+ request['type'] = orderType
1590
+ # flag values may be summed to combine flags
1591
+ flags = 0
1108
1592
  if postOnly:
1109
- request['is_postonly'] = True
1110
- response = await self.privatePostOrderNew(self.extend(request, params))
1111
- return self.parse_order(response, market)
1593
+ flags = self.sum(flags, 4096)
1594
+ if reduceOnly:
1595
+ flags = self.sum(flags, 1024)
1596
+ if flags != 0:
1597
+ request['flags'] = flags
1598
+ if clientOrderId is not None:
1599
+ request['cid'] = clientOrderId
1600
+ params = self.omit(params, ['triggerPrice', 'stopPrice', 'timeInForce', 'postOnly', 'reduceOnly', 'trailingAmount', 'clientOrderId'])
1601
+ return self.extend(request, params)
1112
1602
 
1113
- async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
1603
+ async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
1604
+ """
1605
+ create an order on the exchange
1606
+
1607
+ https://docs.bitfinex.com/reference/rest-auth-submit-order
1608
+
1609
+ :param str symbol: unified CCXT market symbol
1610
+ :param str type: 'limit' or 'market'
1611
+ :param str side: 'buy' or 'sell'
1612
+ :param float amount: the amount of currency to trade
1613
+ :param float [price]: price of the order
1614
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1615
+ :param float [params.stopPrice]: the price that triggers a trigger order
1616
+ :param str [params.timeInForce]: "GTC", "IOC", "FOK", or "PO"
1617
+ :param boolean [params.postOnly]: set to True if you want to make a post only order
1618
+ :param boolean [params.reduceOnly]: indicates that the order is to reduce the size of a position
1619
+ :param int [params.flags]: additional order parameters: 4096(Post Only), 1024(Reduce Only), 16384(OCO), 64(Hidden), 512(Close), 524288(No Var Rates)
1620
+ :param int [params.lev]: leverage for a derivative order, supported by derivative symbol orders only. The value should be between 1 and 100 inclusive.
1621
+ :param str [params.price_aux_limit]: order price for stop limit orders
1622
+ :param str [params.price_oco_stop]: OCO stop price
1623
+ :param str [params.trailingAmount]: *swap only* the quote amount to trail away from the current market price
1624
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1625
+ """
1626
+ await self.load_markets()
1627
+ market = self.market(symbol)
1628
+ request = self.create_order_request(symbol, type, side, amount, price, params)
1629
+ response = await self.privatePostAuthWOrderSubmit(request)
1630
+ #
1631
+ # [
1632
+ # 1653325121, # Timestamp in milliseconds
1633
+ # "on-req", # Purpose of notification('on-req', 'oc-req', "uca", 'fon-req', "foc-req")
1634
+ # null, # unique ID of the message
1635
+ # null,
1636
+ # [
1637
+ # [
1638
+ # 95412102131, # Order ID
1639
+ # null, # Group ID
1640
+ # 1653325121798, # Client Order ID
1641
+ # "tDOGE:UST", # Market ID
1642
+ # 1653325121798, # Millisecond timestamp of creation
1643
+ # 1653325121798, # Millisecond timestamp of update
1644
+ # -10, # Amount(Positive means buy, negative means sell)
1645
+ # -10, # Original amount
1646
+ # "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.
1647
+ # null, # Previous order type(stop-limit orders are converted to limit orders so for them previous type is always STOP)
1648
+ # null, # Millisecond timestamp of Time-In-Force: automatic order cancellation
1649
+ # null, # _PLACEHOLDER
1650
+ # 4096, # Flags, see parseOrderFlags()
1651
+ # "ACTIVE", # Order Status, see parseOrderStatus()
1652
+ # null, # _PLACEHOLDER
1653
+ # null, # _PLACEHOLDER
1654
+ # 0.071, # Price(Stop Price for stop-limit orders, Limit Price for limit orders)
1655
+ # 0, # Average Price
1656
+ # 0, # Trailing Price
1657
+ # 0, # Auxiliary Limit price(for STOP LIMIT)
1658
+ # null, # _PLACEHOLDER
1659
+ # null, # _PLACEHOLDER
1660
+ # null, # _PLACEHOLDER
1661
+ # 0, # Hidden(0 if False, 1 if True)
1662
+ # 0, # Placed ID(If another order caused self order to be placed(OCO) self will be that other order's ID)
1663
+ # null, # _PLACEHOLDER
1664
+ # null, # _PLACEHOLDER
1665
+ # null, # _PLACEHOLDER
1666
+ # "API>BFX", # Routing, indicates origin of action: BFX, ETHFX, API>BFX, API>ETHFX
1667
+ # null, # _PLACEHOLDER
1668
+ # null, # _PLACEHOLDER
1669
+ # {"$F7":1} # additional meta information about the order( $F7 = IS_POST_ONLY(0 if False, 1 if True), $F33 = Leverage(int))
1670
+ # ]
1671
+ # ],
1672
+ # null, # CODE(work in progress)
1673
+ # "SUCCESS", # Status of the request
1674
+ # "Submitting 1 orders." # Message
1675
+ # ]
1676
+ #
1677
+ status = self.safe_string(response, 6)
1678
+ if status != 'SUCCESS':
1679
+ errorCode = response[5]
1680
+ errorText = response[7]
1681
+ raise ExchangeError(self.id + ' ' + response[6] + ': ' + errorText + '(#' + errorCode + ')')
1682
+ orders = self.safe_list(response, 4, [])
1683
+ order = self.safe_list(orders, 0)
1684
+ newOrder = {'result': order}
1685
+ return self.parse_order(newOrder, market)
1686
+
1687
+ async def create_orders(self, orders: List[OrderRequest], params={}):
1688
+ """
1689
+ create a list of trade orders
1690
+
1691
+ https://docs.bitfinex.com/reference/rest-auth-order-multi
1692
+
1693
+ :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
1694
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1695
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1696
+ """
1114
1697
  await self.load_markets()
1115
- order: dict = {
1116
- 'order_id': int(id),
1698
+ ordersRequests = []
1699
+ for i in range(0, len(orders)):
1700
+ rawOrder = orders[i]
1701
+ symbol = self.safe_string(rawOrder, 'symbol')
1702
+ type = self.safe_string(rawOrder, 'type')
1703
+ side = self.safe_string(rawOrder, 'side')
1704
+ amount = self.safe_number(rawOrder, 'amount')
1705
+ price = self.safe_number(rawOrder, 'price')
1706
+ orderParams = self.safe_dict(rawOrder, 'params', {})
1707
+ orderRequest = self.create_order_request(symbol, type, side, amount, price, orderParams)
1708
+ ordersRequests.append(['on', orderRequest])
1709
+ request: dict = {
1710
+ 'ops': ordersRequests,
1117
1711
  }
1118
- if price is not None:
1119
- order['price'] = self.price_to_precision(symbol, price)
1120
- if amount is not None:
1121
- order['amount'] = self.number_to_string(amount)
1122
- if symbol is not None:
1123
- order['symbol'] = self.market_id(symbol)
1124
- if side is not None:
1125
- order['side'] = side
1126
- if type is not None:
1127
- order['type'] = self.safe_string(self.options['orderTypes'], type, type)
1128
- response = await self.privatePostOrderCancelReplace(self.extend(order, params))
1129
- return self.parse_order(response)
1712
+ response = await self.privatePostAuthWOrderMulti(request)
1713
+ #
1714
+ # [
1715
+ # 1706762515553,
1716
+ # "ox_multi-req",
1717
+ # null,
1718
+ # null,
1719
+ # [
1720
+ # [
1721
+ # 1706762515,
1722
+ # "on-req",
1723
+ # null,
1724
+ # null,
1725
+ # [
1726
+ # [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,{}]
1727
+ # ],
1728
+ # null,
1729
+ # "SUCCESS",
1730
+ # "Submitting 1 orders."
1731
+ # ],
1732
+ # ],
1733
+ # null,
1734
+ # "SUCCESS",
1735
+ # "Submitting 2 order operations."
1736
+ # ]
1737
+ #
1738
+ results = []
1739
+ data = self.safe_list(response, 4, [])
1740
+ for i in range(0, len(data)):
1741
+ entry = data[i]
1742
+ individualOrder = entry[4]
1743
+ results.append({'result': individualOrder[0]})
1744
+ return self.parse_orders(results)
1745
+
1746
+ async def cancel_all_orders(self, symbol: Str = None, params={}):
1747
+ """
1748
+ cancel all open orders
1749
+
1750
+ https://docs.bitfinex.com/reference/rest-auth-cancel-orders-multiple
1751
+
1752
+ :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
1753
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1754
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1755
+ """
1756
+ await self.load_markets()
1757
+ request: dict = {
1758
+ 'all': 1,
1759
+ }
1760
+ response = await self.privatePostAuthWOrderCancelMulti(self.extend(request, params))
1761
+ orders = self.safe_list(response, 4, [])
1762
+ ordersList = []
1763
+ for i in range(0, len(orders)):
1764
+ ordersList.append({'result': orders[i]})
1765
+ return self.parse_orders(ordersList)
1130
1766
 
1131
1767
  async def cancel_order(self, id: str, symbol: Str = None, params={}):
1132
1768
  """
1133
1769
  cancels an open order
1134
1770
 
1135
- https://docs.bitfinex.com/v1/reference/rest-auth-cancel-order
1771
+ https://docs.bitfinex.com/reference/rest-auth-cancel-order
1136
1772
 
1137
1773
  :param str id: order id
1138
- :param str symbol: not used by bitfinex cancelOrder()
1774
+ :param str symbol: Not used by bitfinex cancelOrder()
1139
1775
  :param dict [params]: extra parameters specific to the exchange API endpoint
1140
1776
  :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1141
1777
  """
1142
1778
  await self.load_markets()
1779
+ cid = self.safe_value_2(params, 'cid', 'clientOrderId') # client order id
1780
+ request = None
1781
+ market = None
1782
+ if symbol is not None:
1783
+ market = self.market(symbol)
1784
+ if cid is not None:
1785
+ cidDate = self.safe_value(params, 'cidDate') # client order id date
1786
+ if cidDate is None:
1787
+ raise InvalidOrder(self.id + " canceling an order by clientOrderId('cid') requires both 'cid' and 'cid_date'('YYYY-MM-DD')")
1788
+ request = {
1789
+ 'cid': cid,
1790
+ 'cid_date': cidDate,
1791
+ }
1792
+ params = self.omit(params, ['cid', 'clientOrderId'])
1793
+ else:
1794
+ request = {
1795
+ 'id': int(id),
1796
+ }
1797
+ response = await self.privatePostAuthWOrderCancel(self.extend(request, params))
1798
+ order = self.safe_value(response, 4)
1799
+ newOrder = {'result': order}
1800
+ return self.parse_order(newOrder, market)
1801
+
1802
+ async def cancel_orders(self, ids, symbol: Str = None, params={}):
1803
+ """
1804
+ cancel multiple orders at the same time
1805
+
1806
+ https://docs.bitfinex.com/reference/rest-auth-cancel-orders-multiple
1807
+
1808
+ :param str[] ids: order ids
1809
+ :param str symbol: unified market symbol, default is None
1810
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1811
+ :returns dict: an array of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1812
+ """
1813
+ await self.load_markets()
1814
+ for i in range(0, len(ids)):
1815
+ ids[i] = self.parse_to_numeric(ids[i])
1143
1816
  request: dict = {
1144
- 'order_id': int(id),
1817
+ 'id': ids,
1145
1818
  }
1146
- response = await self.privatePostOrderCancel(self.extend(request, params))
1819
+ market = None
1820
+ if symbol is not None:
1821
+ market = self.market(symbol)
1822
+ response = await self.privatePostAuthWOrderCancelMulti(self.extend(request, params))
1147
1823
  #
1148
- # {
1149
- # id: '161236928925',
1150
- # cid: '1720172026812',
1151
- # cid_date: '2024-07-05',
1152
- # gid: null,
1153
- # symbol: 'adaust',
1154
- # exchange: 'bitfinex',
1155
- # price: '0.33',
1156
- # avg_execution_price: '0.0',
1157
- # side: 'buy',
1158
- # type: 'exchange limit',
1159
- # timestamp: '1720172026.813',
1160
- # is_live: True,
1161
- # is_cancelled: False,
1162
- # is_hidden: False,
1163
- # oco_order: null,
1164
- # was_forced: False,
1165
- # original_amount: '10.0',
1166
- # remaining_amount: '10.0',
1167
- # executed_amount: '0.0',
1168
- # src: 'api',
1169
- # meta: {}
1170
- # }
1824
+ # [
1825
+ # 1706740198811,
1826
+ # "oc_multi-req",
1827
+ # null,
1828
+ # null,
1829
+ # [
1830
+ # [
1831
+ # 139530205057,
1832
+ # null,
1833
+ # 1706740132275,
1834
+ # "tBTCF0:USTF0",
1835
+ # 1706740132276,
1836
+ # 1706740132276,
1837
+ # 0.0001,
1838
+ # 0.0001,
1839
+ # "LIMIT",
1840
+ # null,
1841
+ # null,
1842
+ # null,
1843
+ # 0,
1844
+ # "ACTIVE",
1845
+ # null,
1846
+ # null,
1847
+ # 39000,
1848
+ # 0,
1849
+ # 0,
1850
+ # 0,
1851
+ # null,
1852
+ # null,
1853
+ # null,
1854
+ # 0,
1855
+ # 0,
1856
+ # null,
1857
+ # null,
1858
+ # null,
1859
+ # "API>BFX",
1860
+ # null,
1861
+ # null,
1862
+ # {
1863
+ # "lev": 10,
1864
+ # "$F33": 10
1865
+ # }
1866
+ # ],
1867
+ # ],
1868
+ # null,
1869
+ # "SUCCESS",
1870
+ # "Submitting 2 order cancellations."
1871
+ # ]
1171
1872
  #
1172
- return self.parse_order(response)
1873
+ orders = self.safe_list(response, 4, [])
1874
+ ordersList = []
1875
+ for i in range(0, len(orders)):
1876
+ ordersList.append({'result': orders[i]})
1877
+ return self.parse_orders(ordersList, market)
1173
1878
 
1174
- async def cancel_all_orders(self, symbol: Str = None, params={}):
1879
+ async def fetch_open_order(self, id: str, symbol: Str = None, params={}):
1175
1880
  """
1176
- cancel all open orders
1881
+ fetch an open order by it's id
1177
1882
 
1178
- https://docs.bitfinex.com/v1/reference/rest-auth-cancel-all-orders
1883
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
1884
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
1179
1885
 
1180
- :param str symbol: not used by bitfinex cancelAllOrders
1886
+ :param str id: order id
1887
+ :param str symbol: unified market symbol, default is None
1181
1888
  :param dict [params]: extra parameters specific to the exchange API endpoint
1182
- :returns dict: response from exchange
1889
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1183
1890
  """
1184
- response = await self.privatePostOrderCancelAll(params)
1185
- #
1186
- # {result: 'Submitting 1 order cancellations.'}
1187
- #
1188
- return [
1189
- self.safe_order({
1190
- 'info': response,
1191
- }),
1192
- ]
1891
+ request: dict = {
1892
+ 'id': [int(id)],
1893
+ }
1894
+ orders = await self.fetch_open_orders(symbol, None, None, self.extend(request, params))
1895
+ order = self.safe_value(orders, 0)
1896
+ if order is None:
1897
+ raise OrderNotFound(self.id + ' order ' + id + ' not found')
1898
+ return order
1193
1899
 
1194
- def parse_order(self, order: dict, market: Market = None) -> Order:
1195
- #
1196
- # {
1197
- # "id": 57334010955,
1198
- # "cid": 1611584840966,
1199
- # "cid_date": null,
1200
- # "gid": null,
1201
- # "symbol": "ltcbtc",
1202
- # "exchange": null,
1203
- # "price": "0.0042125",
1204
- # "avg_execution_price": "0.0042097",
1205
- # "side": "sell",
1206
- # "type": "exchange market",
1207
- # "timestamp": "1611584841.0",
1208
- # "is_live": False,
1209
- # "is_cancelled": False,
1210
- # "is_hidden": 0,
1211
- # "oco_order": 0,
1212
- # "was_forced": False,
1213
- # "original_amount": "0.205176",
1214
- # "remaining_amount": "0.0",
1215
- # "executed_amount": "0.205176",
1216
- # "src": "web"
1217
- # }
1218
- #
1219
- side = self.safe_string(order, 'side')
1220
- open = self.safe_bool(order, 'is_live')
1221
- canceled = self.safe_bool(order, 'is_cancelled')
1222
- status = None
1223
- if open:
1224
- status = 'open'
1225
- elif canceled:
1226
- status = 'canceled'
1227
- else:
1228
- status = 'closed'
1229
- marketId = self.safe_string_upper(order, 'symbol')
1230
- symbol = self.safe_symbol(marketId, market)
1231
- orderType = self.safe_string(order, 'type', '')
1232
- exchange = orderType.find('exchange ') >= 0
1233
- if exchange:
1234
- parts = order['type'].split(' ')
1235
- orderType = parts[1]
1236
- timestamp = self.safe_timestamp(order, 'timestamp')
1237
- id = self.safe_string(order, 'id')
1238
- return self.safe_order({
1239
- 'info': order,
1240
- 'id': id,
1241
- 'clientOrderId': None,
1242
- 'timestamp': timestamp,
1243
- 'datetime': self.iso8601(timestamp),
1244
- 'lastTradeTimestamp': None,
1245
- 'symbol': symbol,
1246
- 'type': orderType,
1247
- 'timeInForce': None,
1248
- 'postOnly': None,
1249
- 'side': side,
1250
- 'price': self.safe_string(order, 'price'),
1251
- 'stopPrice': None,
1252
- 'triggerPrice': None,
1253
- 'average': self.safe_string(order, 'avg_execution_price'),
1254
- 'amount': self.safe_string(order, 'original_amount'),
1255
- 'remaining': self.safe_string(order, 'remaining_amount'),
1256
- 'filled': self.safe_string(order, 'executed_amount'),
1257
- 'status': status,
1258
- 'fee': None,
1259
- 'cost': None,
1260
- 'trades': None,
1261
- }, market)
1900
+ async def fetch_closed_order(self, id: str, symbol: Str = None, params={}):
1901
+ """
1902
+ fetch an open order by it's id
1903
+
1904
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
1905
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
1906
+
1907
+ :param str id: order id
1908
+ :param str symbol: unified market symbol, default is None
1909
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1910
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1911
+ """
1912
+ request: dict = {
1913
+ 'id': [int(id)],
1914
+ }
1915
+ orders = await self.fetch_closed_orders(symbol, None, None, self.extend(request, params))
1916
+ order = self.safe_value(orders, 0)
1917
+ if order is None:
1918
+ raise OrderNotFound(self.id + ' order ' + id + ' not found')
1919
+ return order
1262
1920
 
1263
1921
  async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1264
1922
  """
1265
1923
  fetch all unfilled currently open orders
1266
1924
 
1267
- https://docs.bitfinex.com/v1/reference/rest-auth-active-orders
1925
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
1926
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
1268
1927
 
1269
1928
  :param str symbol: unified market symbol
1270
1929
  :param int [since]: the earliest time in ms to fetch open orders for
@@ -1273,126 +1932,204 @@ class bitfinex(Exchange, ImplicitAPI):
1273
1932
  :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1274
1933
  """
1275
1934
  await self.load_markets()
1276
- if symbol is not None:
1277
- if not (symbol in self.markets):
1278
- raise ExchangeError(self.id + ' has no symbol ' + symbol)
1279
- response = await self.privatePostOrders(params)
1280
- orders = self.parse_orders(response, None, since, limit)
1281
- if symbol is not None:
1282
- orders = self.filter_by(orders, 'symbol', symbol)
1283
- return orders
1935
+ request: dict = {}
1936
+ market = None
1937
+ response = None
1938
+ if symbol is None:
1939
+ response = await self.privatePostAuthROrders(self.extend(request, params))
1940
+ else:
1941
+ market = self.market(symbol)
1942
+ request['symbol'] = market['id']
1943
+ response = await self.privatePostAuthROrdersSymbol(self.extend(request, params))
1944
+ #
1945
+ # [
1946
+ # [
1947
+ # 95408916206, # Order ID
1948
+ # null, # Group Order ID
1949
+ # 1653322349926, # Client Order ID
1950
+ # "tDOGE:UST", # Market ID
1951
+ # 1653322349926, # Created Timestamp in milliseconds
1952
+ # 1653322349927, # Updated Timestamp in milliseconds
1953
+ # -10, # Amount remaining(Positive means buy, negative means sell)
1954
+ # -10, # Original amount
1955
+ # "EXCHANGE LIMIT", # Order type
1956
+ # null, # Previous Order Type
1957
+ # null, # _PLACEHOLDER
1958
+ # null, # _PLACEHOLDER
1959
+ # 0, # Flags, see parseOrderFlags()
1960
+ # "ACTIVE", # Order Status, see parseOrderStatus()
1961
+ # null, # _PLACEHOLDER
1962
+ # null, # _PLACEHOLDER
1963
+ # 0.11, # Price
1964
+ # 0, # Average Price
1965
+ # 0, # Trailing Price
1966
+ # 0, # Auxiliary Limit price(for STOP LIMIT)
1967
+ # null, # _PLACEHOLDER
1968
+ # null, # _PLACEHOLDER
1969
+ # null, # _PLACEHOLDER
1970
+ # 0, # Hidden(0 if False, 1 if True)
1971
+ # 0, # Placed ID(If another order caused self order to be placed(OCO) self will be that other order's ID)
1972
+ # null, # _PLACEHOLDER
1973
+ # null, # _PLACEHOLDER
1974
+ # null, # _PLACEHOLDER
1975
+ # "API>BFX", # Routing, indicates origin of action: BFX, ETHFX, API>BFX, API>ETHFX
1976
+ # null, # _PLACEHOLDER
1977
+ # null, # _PLACEHOLDER
1978
+ # {"$F7":1} # additional meta information about the order( $F7 = IS_POST_ONLY(0 if False, 1 if True), $F33 = Leverage(int))
1979
+ # ],
1980
+ # ]
1981
+ #
1982
+ ordersList = []
1983
+ for i in range(0, len(response)):
1984
+ ordersList.append({'result': response[i]})
1985
+ return self.parse_orders(ordersList, market, since, limit)
1284
1986
 
1285
1987
  async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1286
1988
  """
1287
1989
  fetches information on multiple closed orders made by the user
1288
1990
 
1289
- https://docs.bitfinex.com/v1/reference/rest-auth-orders-history
1991
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
1992
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
1290
1993
 
1291
1994
  :param str symbol: unified market symbol of the market orders were made in
1292
1995
  :param int [since]: the earliest time in ms to fetch orders for
1293
1996
  :param int [limit]: the maximum number of order structures to retrieve
1294
1997
  :param dict [params]: extra parameters specific to the exchange API endpoint
1998
+ :param int [params.until]: the latest time in ms to fetch entries for
1999
+ :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)
1295
2000
  :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1296
2001
  """
2002
+ # returns the most recent closed or canceled orders up to circa two weeks ago
1297
2003
  await self.load_markets()
1298
- symbol = self.symbol(symbol)
2004
+ paginate = False
2005
+ paginate, params = self.handle_option_and_params(params, 'fetchClosedOrders', 'paginate')
2006
+ if paginate:
2007
+ return await self.fetch_paginated_call_dynamic('fetchClosedOrders', symbol, since, limit, params)
1299
2008
  request: dict = {}
2009
+ if since is not None:
2010
+ request['start'] = since
1300
2011
  if limit is not None:
1301
- request['limit'] = limit
1302
- response = await self.privatePostOrdersHist(self.extend(request, params))
1303
- orders = self.parse_orders(response, None, since, limit)
1304
- if symbol is not None:
1305
- orders = self.filter_by(orders, 'symbol', symbol)
1306
- orders = self.filter_by_array(orders, 'status', ['closed', 'canceled'], False)
1307
- return orders
2012
+ request['limit'] = limit # default 25, max 2500
2013
+ request, params = self.handle_until_option('end', request, params)
2014
+ market = None
2015
+ response = None
2016
+ if symbol is None:
2017
+ response = await self.privatePostAuthROrdersHist(self.extend(request, params))
2018
+ else:
2019
+ market = self.market(symbol)
2020
+ request['symbol'] = market['id']
2021
+ response = await self.privatePostAuthROrdersSymbolHist(self.extend(request, params))
2022
+ #
2023
+ # [
2024
+ # [
2025
+ # 95412102131, # Order ID
2026
+ # null, # Group Order ID
2027
+ # 1653325121798, # Client Order ID
2028
+ # "tDOGE:UST", # Market ID
2029
+ # 1653325122000, # Created Timestamp in milliseconds
2030
+ # 1653325122000, # Updated Timestamp in milliseconds
2031
+ # -10, # Amount remaining(Positive means buy, negative means sell)
2032
+ # -10, # Original amount
2033
+ # "EXCHANGE LIMIT", # Order type
2034
+ # null, # Previous Order Type
2035
+ # null, # Millisecond timestamp of Time-In-Force: automatic order cancellation
2036
+ # null, # _PLACEHOLDER
2037
+ # "4096", # Flags, see parseOrderFlags()
2038
+ # "POSTONLY CANCELED", # Order Status, see parseOrderStatus()
2039
+ # null, # _PLACEHOLDER
2040
+ # null, # _PLACEHOLDER
2041
+ # 0.071, # Price
2042
+ # 0, # Average Price
2043
+ # 0, # Trailing Price
2044
+ # 0, # Auxiliary Limit price(for STOP LIMIT)
2045
+ # null, # _PLACEHOLDER
2046
+ # null, # _PLACEHOLDER
2047
+ # null, # _PLACEHOLDER
2048
+ # 0, # Notify(0 if False, 1 if True)
2049
+ # 0, # Hidden(0 if False, 1 if True)
2050
+ # null, # Placed ID(If another order caused self order to be placed(OCO) self will be that other order's ID)
2051
+ # null, # _PLACEHOLDER
2052
+ # null, # _PLACEHOLDER
2053
+ # "API>BFX", # Routing, indicates origin of action: BFX, ETHFX, API>BFX, API>ETHFX
2054
+ # null, # _PLACEHOLDER
2055
+ # null, # _PLACEHOLDER
2056
+ # {"_$F7":1} # additional meta information about the order( _$F7 = IS_POST_ONLY(0 if False, 1 if True), _$F33 = Leverage(int))
2057
+ # ]
2058
+ # ]
2059
+ #
2060
+ ordersList = []
2061
+ for i in range(0, len(response)):
2062
+ ordersList.append({'result': response[i]})
2063
+ return self.parse_orders(ordersList, market, since, limit)
1308
2064
 
1309
- async def fetch_order(self, id: str, symbol: Str = None, params={}):
2065
+ async def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
1310
2066
  """
1311
- fetches information on an order made by the user
2067
+ fetch all the trades made from a single order
1312
2068
 
1313
- https://docs.bitfinex.com/v1/reference/rest-auth-order-status
2069
+ https://docs.bitfinex.com/reference/rest-auth-order-trades
1314
2070
 
1315
- :param str id: the order id
1316
- :param str symbol: not used by bitfinex fetchOrder
2071
+ :param str id: order id
2072
+ :param str symbol: unified market symbol
2073
+ :param int [since]: the earliest time in ms to fetch trades for
2074
+ :param int [limit]: the maximum number of trades to retrieve
1317
2075
  :param dict [params]: extra parameters specific to the exchange API endpoint
1318
- :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
2076
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
1319
2077
  """
2078
+ if symbol is None:
2079
+ raise ArgumentsRequired(self.id + ' fetchOrderTrades() requires a symbol argument')
1320
2080
  await self.load_markets()
2081
+ market = self.market(symbol)
2082
+ orderId = int(id)
1321
2083
  request: dict = {
1322
- 'order_id': int(id),
2084
+ 'id': orderId,
2085
+ 'symbol': market['id'],
1323
2086
  }
1324
- response = await self.privatePostOrderStatus(self.extend(request, params))
1325
- return self.parse_order(response)
1326
-
1327
- def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
1328
- #
1329
- # [
1330
- # 1457539800000,
1331
- # 0.02594,
1332
- # 0.02594,
1333
- # 0.02594,
1334
- # 0.02594,
1335
- # 0.1
1336
- # ]
1337
- #
1338
- return [
1339
- self.safe_integer(ohlcv, 0),
1340
- self.safe_number(ohlcv, 1),
1341
- self.safe_number(ohlcv, 3),
1342
- self.safe_number(ohlcv, 4),
1343
- self.safe_number(ohlcv, 2),
1344
- self.safe_number(ohlcv, 5),
1345
- ]
2087
+ # valid for trades upto 10 days old
2088
+ response = await self.privatePostAuthROrderSymbolIdTrades(self.extend(request, params))
2089
+ tradesList = []
2090
+ for i in range(0, len(response)):
2091
+ tradesList.append({'result': response[i]}) # convert to array of dicts to match parseOrder signature
2092
+ return self.parse_trades(tradesList, market, since, limit)
1346
2093
 
1347
- async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
2094
+ async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
1348
2095
  """
1349
- fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
2096
+ fetch all trades made by the user
1350
2097
 
1351
- https://docs.bitfinex.com/reference/rest-public-candles#aggregate-funding-currency-candles
2098
+ https://docs.bitfinex.com/reference/rest-auth-trades
2099
+ https://docs.bitfinex.com/reference/rest-auth-trades-by-symbol
1352
2100
 
1353
- :param str symbol: unified symbol of the market to fetch OHLCV data for
1354
- :param str timeframe: the length of time each candle represents
1355
- :param int [since]: timestamp in ms of the earliest candle to fetch
1356
- :param int [limit]: the maximum amount of candles to fetch
2101
+ :param str symbol: unified market symbol
2102
+ :param int [since]: the earliest time in ms to fetch trades for
2103
+ :param int [limit]: the maximum number of trades structures to retrieve
1357
2104
  :param dict [params]: extra parameters specific to the exchange API endpoint
1358
- :returns int[][]: A list of candles ordered, open, high, low, close, volume
2105
+ :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
1359
2106
  """
1360
2107
  await self.load_markets()
1361
- if limit is None:
1362
- limit = 100
1363
- else:
1364
- limit = min(limit, 10000)
1365
- market = self.market(symbol)
1366
- v2id = 't' + market['id']
2108
+ market = None
1367
2109
  request: dict = {
1368
- 'symbol': v2id,
1369
- 'timeframe': self.safe_string(self.timeframes, timeframe, timeframe),
1370
- 'sort': 1,
1371
- 'limit': limit,
2110
+ 'end': self.milliseconds(),
1372
2111
  }
1373
2112
  if since is not None:
1374
2113
  request['start'] = since
1375
- response = await self.v2GetCandlesTradeTimeframeSymbolHist(self.extend(request, params))
1376
- #
1377
- # [
1378
- # [1457539800000,0.02594,0.02594,0.02594,0.02594,0.1],
1379
- # [1457547300000,0.02577,0.02577,0.02577,0.02577,0.01],
1380
- # [1457550240000,0.0255,0.0253,0.0255,0.0252,3.2640000000000002],
1381
- # ]
1382
- #
1383
- return self.parse_ohlcvs(response, market, timeframe, since, limit)
1384
-
1385
- def get_currency_name(self, code):
1386
- # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
1387
- if code in self.options['currencyNames']:
1388
- return self.options['currencyNames'][code]
1389
- raise NotSupported(self.id + ' ' + code + ' not supported for withdrawal')
2114
+ if limit is not None:
2115
+ request['limit'] = limit # default 25, max 1000
2116
+ response = None
2117
+ if symbol is not None:
2118
+ market = self.market(symbol)
2119
+ request['symbol'] = market['id']
2120
+ response = await self.privatePostAuthRTradesSymbolHist(self.extend(request, params))
2121
+ else:
2122
+ response = await self.privatePostAuthRTradesHist(self.extend(request, params))
2123
+ tradesList = []
2124
+ for i in range(0, len(response)):
2125
+ tradesList.append({'result': response[i]}) # convert to array of dicts to match parseOrder signature
2126
+ return self.parse_trades(tradesList, market, since, limit)
1390
2127
 
1391
2128
  async def create_deposit_address(self, code: str, params={}):
1392
2129
  """
1393
2130
  create a currency deposit address
1394
2131
 
1395
- https://docs.bitfinex.com/v1/reference/rest-auth-deposit
2132
+ https://docs.bitfinex.com/reference/rest-auth-deposit-address
1396
2133
 
1397
2134
  :param str code: unified currency code of the currency for the deposit address
1398
2135
  :param dict [params]: extra parameters specific to the exchange API endpoint
@@ -1400,7 +2137,7 @@ class bitfinex(Exchange, ImplicitAPI):
1400
2137
  """
1401
2138
  await self.load_markets()
1402
2139
  request: dict = {
1403
- 'renew': 1,
2140
+ 'op_renew': 1,
1404
2141
  }
1405
2142
  return await self.fetch_deposit_address(code, self.extend(request, params))
1406
2143
 
@@ -1408,26 +2145,52 @@ class bitfinex(Exchange, ImplicitAPI):
1408
2145
  """
1409
2146
  fetch the deposit address for a currency associated with self account
1410
2147
 
1411
- https://docs.bitfinex.com/v1/reference/rest-auth-deposit
2148
+ https://docs.bitfinex.com/reference/rest-auth-deposit-address
1412
2149
 
1413
2150
  :param str code: unified currency code
1414
2151
  :param dict [params]: extra parameters specific to the exchange API endpoint
1415
2152
  :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
1416
2153
  """
1417
2154
  await self.load_markets()
1418
- # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
1419
- name = self.get_currency_name(code)
2155
+ currency = self.currency(code)
2156
+ # if not provided explicitly we will try to match using the currency name
2157
+ network = self.safe_string(params, 'network', code)
2158
+ currencyNetworks = self.safe_value(currency, 'networks', {})
2159
+ currencyNetwork = self.safe_value(currencyNetworks, network)
2160
+ networkId = self.safe_string(currencyNetwork, 'id')
2161
+ if networkId is None:
2162
+ raise ArgumentsRequired(self.id + " fetchDepositAddress() could not find a network for '" + code + "'. You can specify it by providing the 'network' value inside params")
2163
+ wallet = self.safe_string(params, 'wallet', 'exchange') # 'exchange', 'margin', 'funding' and also old labels 'exchange', 'trading', 'deposit', respectively
2164
+ params = self.omit(params, 'network', 'wallet')
1420
2165
  request: dict = {
1421
- 'method': name,
1422
- 'wallet_name': 'exchange',
1423
- 'renew': 0, # a value of 1 will generate a new address
2166
+ 'method': networkId,
2167
+ 'wallet': wallet,
2168
+ 'op_renew': 0, # a value of 1 will generate a new address
1424
2169
  }
1425
- response = await self.privatePostDepositNew(self.extend(request, params))
1426
- address = self.safe_value(response, 'address')
1427
- tag = None
1428
- if 'address_pool' in response:
1429
- tag = address
1430
- address = response['address_pool']
2170
+ response = await self.privatePostAuthWDepositAddress(self.extend(request, params))
2171
+ #
2172
+ # [
2173
+ # 1582269616687, # MTS Millisecond Time Stamp of the update
2174
+ # "acc_dep", # TYPE Purpose of notification "acc_dep" for account deposit
2175
+ # null, # MESSAGE_ID unique ID of the message
2176
+ # null, # not documented
2177
+ # [
2178
+ # null, # PLACEHOLDER
2179
+ # "BITCOIN", # METHOD Method of deposit
2180
+ # "BTC", # CURRENCY_CODE Currency code of new address
2181
+ # null, # PLACEHOLDER
2182
+ # "1BC9PZqpUmjyEB54uggn8TFKj49zSDYzqG", # ADDRESS
2183
+ # null, # POOL_ADDRESS
2184
+ # ],
2185
+ # null, # CODE null or integer work in progress
2186
+ # "SUCCESS", # STATUS Status of the notification, SUCCESS, ERROR, FAILURE
2187
+ # "success", # TEXT Text of the notification
2188
+ # ]
2189
+ #
2190
+ result = self.safe_value(response, 4, [])
2191
+ poolAddress = self.safe_string(result, 5)
2192
+ address = self.safe_string(result, 4) if (poolAddress is None) else poolAddress
2193
+ tag = None if (poolAddress is None) else self.safe_string(result, 4)
1431
2194
  self.check_address(address)
1432
2195
  return {
1433
2196
  'currency': code,
@@ -1437,121 +2200,147 @@ class bitfinex(Exchange, ImplicitAPI):
1437
2200
  'info': response,
1438
2201
  }
1439
2202
 
1440
- async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
1441
- """
1442
- fetch history of deposits and withdrawals
1443
-
1444
- https://docs.bitfinex.com/v1/reference/rest-auth-deposit-withdrawal-history
2203
+ def parse_transaction_status(self, status: Str):
2204
+ statuses: dict = {
2205
+ 'SUCCESS': 'ok',
2206
+ 'COMPLETED': 'ok',
2207
+ 'ERROR': 'failed',
2208
+ 'FAILURE': 'failed',
2209
+ 'CANCELED': 'canceled',
2210
+ 'PENDING APPROVAL': 'pending',
2211
+ 'PENDING': 'pending',
2212
+ 'PENDING REVIEW': 'pending',
2213
+ 'PENDING CANCELLATION': 'pending',
2214
+ 'SENDING': 'pending',
2215
+ 'USER APPROVED': 'pending',
2216
+ }
2217
+ return self.safe_string(statuses, status, status)
1445
2218
 
1446
- :param str code: unified currency code for the currency of the deposit/withdrawals
1447
- :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
1448
- :param int [limit]: max number of deposit/withdrawals to return, default is None
1449
- :param dict [params]: extra parameters specific to the exchange API endpoint
1450
- :returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
1451
- """
1452
- await self.load_markets()
1453
- currencyId = self.safe_string(params, 'currency')
1454
- query = self.omit(params, 'currency')
1455
- currency = None
1456
- if currencyId is None:
1457
- if code is None:
1458
- raise ArgumentsRequired(self.id + ' fetchDepositsWithdrawals() requires a currency `code` argument or a `currency` parameter')
1459
- else:
1460
- currency = self.currency(code)
1461
- currencyId = currency['id']
1462
- query['currency'] = currencyId
1463
- if since is not None:
1464
- query['since'] = self.parse_to_int(since / 1000)
1465
- response = await self.privatePostHistoryMovements(self.extend(query, params))
2219
+ def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
2220
+ #
2221
+ # withdraw
1466
2222
  #
1467
2223
  # [
1468
- # {
1469
- # "id": 581183,
1470
- # "txid": 123456,
1471
- # "currency": "BTC",
1472
- # "method": "BITCOIN",
1473
- # "type": "WITHDRAWAL",
1474
- # "amount": ".01",
1475
- # "description": "3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ, offchain transfer ",
1476
- # "address": "3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ",
1477
- # "status": "COMPLETED",
1478
- # "timestamp": "1443833327.0",
1479
- # "timestamp_created": "1443833327.1",
1480
- # "fee": 0.1,
1481
- # }
2224
+ # 1582271520931, # MTS Millisecond Time Stamp of the update
2225
+ # "acc_wd-req", # TYPE Purpose of notification "acc_wd-req" account withdrawal request
2226
+ # null, # MESSAGE_ID unique ID of the message
2227
+ # null, # not documented
2228
+ # [
2229
+ # 0, # WITHDRAWAL_ID Unique Withdrawal ID
2230
+ # null, # PLACEHOLDER
2231
+ # "bitcoin", # METHOD Method of withdrawal
2232
+ # null, # PAYMENT_ID Payment ID if relevant
2233
+ # "exchange", # WALLET Sending wallet
2234
+ # 1, # AMOUNT Amount of Withdrawal less fee
2235
+ # null, # PLACEHOLDER
2236
+ # null, # PLACEHOLDER
2237
+ # 0.0004, # WITHDRAWAL_FEE Fee on withdrawal
2238
+ # ],
2239
+ # null, # CODE null or integer Work in progress
2240
+ # "SUCCESS", # STATUS Status of the notification, it may vary over time SUCCESS, ERROR, FAILURE
2241
+ # "Invalid bitcoin address(abcdef)", # TEXT Text of the notification
1482
2242
  # ]
1483
2243
  #
1484
- return self.parse_transactions(response, currency, since, limit)
1485
-
1486
- def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
1487
- #
1488
- # crypto
1489
- #
1490
- # {
1491
- # "id": 12042490,
1492
- # "fee": "-0.02",
1493
- # "txid": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D",
1494
- # "type": "DEPOSIT",
1495
- # "amount": "2099.849999",
1496
- # "method": "RIPPLE",
1497
- # "status": "COMPLETED",
1498
- # "address": "2505189261",
1499
- # "currency": "XRP",
1500
- # "timestamp": "1551730524.0",
1501
- # "description": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D",
1502
- # "timestamp_created": "1551730523.0"
1503
- # }
1504
- #
1505
- # fiat
1506
- #
1507
- # {
1508
- # "id": 12725095,
1509
- # "fee": "-60.0",
1510
- # "txid": null,
1511
- # "type": "WITHDRAWAL",
1512
- # "amount": "9943.0",
1513
- # "method": "WIRE",
1514
- # "status": "SENDING",
1515
- # "address": null,
1516
- # "currency": "EUR",
1517
- # "timestamp": "1561802484.0",
1518
- # "description": "Name: bob, AccountAddress: some address, Account: someaccountno, Bank: bank address, SWIFT: foo, Country: UK, Details of Payment: withdrawal name, Intermediary Bank Name: , Intermediary Bank Address: , Intermediary Bank City: , Intermediary Bank Country: , Intermediary Bank Account: , Intermediary Bank SWIFT: , Fee: -60.0",
1519
- # "timestamp_created": "1561716066.0"
1520
- # }
1521
- #
1522
- # withdraw
2244
+ # fetchDepositsWithdrawals
1523
2245
  #
1524
- # {
1525
- # "status": "success",
1526
- # "message": "Your withdrawal request has been successfully submitted.",
1527
- # "withdrawal_id": 586829
1528
- # }
2246
+ # [
2247
+ # 13293039, # ID
2248
+ # "ETH", # CURRENCY
2249
+ # "ETHEREUM", # CURRENCY_NAME
2250
+ # null,
2251
+ # null,
2252
+ # 1574175052000, # MTS_STARTED
2253
+ # 1574181326000, # MTS_UPDATED
2254
+ # null,
2255
+ # null,
2256
+ # "CANCELED", # STATUS
2257
+ # null,
2258
+ # null,
2259
+ # -0.24, # AMOUNT, negative for withdrawals
2260
+ # -0.00135, # FEES
2261
+ # null,
2262
+ # null,
2263
+ # "0x38110e0Fc932CB2BE...........", # DESTINATION_ADDRESS
2264
+ # null,
2265
+ # null,
2266
+ # null,
2267
+ # "0x523ec8945500.....................................", # TRANSACTION_ID
2268
+ # "Purchase of 100 pizzas", # WITHDRAW_TRANSACTION_NOTE, might also be: null
2269
+ # ]
1529
2270
  #
1530
- timestamp = self.safe_timestamp(transaction, 'timestamp_created')
1531
- currencyId = self.safe_string(transaction, 'currency')
1532
- code = self.safe_currency_code(currencyId, currency)
1533
- feeCost = self.safe_string(transaction, 'fee')
1534
- if feeCost is not None:
1535
- feeCost = Precise.string_abs(feeCost)
2271
+ transactionLength = len(transaction)
2272
+ timestamp = None
2273
+ updated = None
2274
+ code = None
2275
+ amount = None
2276
+ id = None
2277
+ status = None
2278
+ tag = None
2279
+ type = None
2280
+ feeCost = None
2281
+ txid = None
2282
+ addressTo = None
2283
+ network = None
2284
+ comment = None
2285
+ if transactionLength == 8:
2286
+ data = self.safe_value(transaction, 4, [])
2287
+ timestamp = self.safe_integer(transaction, 0)
2288
+ if currency is not None:
2289
+ code = currency['code']
2290
+ feeCost = self.safe_string(data, 8)
2291
+ if feeCost is not None:
2292
+ feeCost = Precise.string_abs(feeCost)
2293
+ amount = self.safe_number(data, 5)
2294
+ id = self.safe_integer(data, 0)
2295
+ status = 'ok'
2296
+ if id == 0:
2297
+ id = None
2298
+ status = 'failed'
2299
+ tag = self.safe_string(data, 3)
2300
+ type = 'withdrawal'
2301
+ networkId = self.safe_string(data, 2)
2302
+ network = self.network_id_to_code(networkId.upper()) # withdraw returns in lowercase
2303
+ elif transactionLength == 22:
2304
+ id = self.safe_string(transaction, 0)
2305
+ currencyId = self.safe_string(transaction, 1)
2306
+ code = self.safe_currency_code(currencyId, currency)
2307
+ networkId = self.safe_string(transaction, 2)
2308
+ network = self.network_id_to_code(networkId)
2309
+ timestamp = self.safe_integer(transaction, 5)
2310
+ updated = self.safe_integer(transaction, 6)
2311
+ status = self.parse_transaction_status(self.safe_string(transaction, 9))
2312
+ signedAmount = self.safe_string(transaction, 12)
2313
+ amount = Precise.string_abs(signedAmount)
2314
+ if signedAmount is not None:
2315
+ if Precise.string_lt(signedAmount, '0'):
2316
+ type = 'withdrawal'
2317
+ else:
2318
+ type = 'deposit'
2319
+ feeCost = self.safe_string(transaction, 13)
2320
+ if feeCost is not None:
2321
+ feeCost = Precise.string_abs(feeCost)
2322
+ addressTo = self.safe_string(transaction, 16)
2323
+ txid = self.safe_string(transaction, 20)
2324
+ comment = self.safe_string(transaction, 21)
1536
2325
  return {
1537
2326
  'info': transaction,
1538
- 'id': self.safe_string_2(transaction, 'id', 'withdrawal_id'),
1539
- 'txid': self.safe_string(transaction, 'txid'),
1540
- 'type': self.safe_string_lower(transaction, 'type'), # DEPOSIT or WITHDRAWAL,
2327
+ 'id': id,
2328
+ 'txid': txid,
2329
+ 'type': type,
1541
2330
  'currency': code,
1542
- 'network': None,
1543
- 'amount': self.safe_number(transaction, 'amount'),
1544
- 'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
2331
+ 'network': network,
2332
+ 'amount': self.parse_number(amount),
2333
+ 'status': status,
1545
2334
  'timestamp': timestamp,
1546
2335
  'datetime': self.iso8601(timestamp),
1547
- 'address': self.safe_string(transaction, 'address'), # todo: self is actually the tag for XRP transfers(the address is missing)
2336
+ 'address': addressTo, # self is actually the tag for XRP transfers(the address is missing)
1548
2337
  'addressFrom': None,
1549
- 'addressTo': None,
1550
- 'tag': self.safe_string(transaction, 'description'),
2338
+ 'addressTo': addressTo,
2339
+ 'tag': tag, # refix it properly for the tag from description
1551
2340
  'tagFrom': None,
1552
- 'tagTo': None,
1553
- 'updated': self.safe_timestamp(transaction, 'timestamp'),
1554
- 'comment': None,
2341
+ 'tagTo': tag,
2342
+ 'updated': updated,
2343
+ 'comment': comment,
1555
2344
  'internal': None,
1556
2345
  'fee': {
1557
2346
  'currency': code,
@@ -1560,20 +2349,178 @@ class bitfinex(Exchange, ImplicitAPI):
1560
2349
  },
1561
2350
  }
1562
2351
 
1563
- def parse_transaction_status(self, status: Str):
1564
- statuses: dict = {
1565
- 'SENDING': 'pending',
1566
- 'CANCELED': 'canceled',
1567
- 'ZEROCONFIRMED': 'failed', # ZEROCONFIRMED happens e.g. in a double spend attempt(I had one in my movementsnot )
1568
- 'COMPLETED': 'ok',
1569
- }
1570
- return self.safe_string(statuses, status, status)
2352
+ async def fetch_trading_fees(self, params={}) -> TradingFees:
2353
+ """
2354
+ fetch the trading fees for multiple markets
2355
+
2356
+ https://docs.bitfinex.com/reference/rest-auth-summary
2357
+
2358
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2359
+ :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
2360
+ """
2361
+ await self.load_markets()
2362
+ response = await self.privatePostAuthRSummary(params)
2363
+ #
2364
+ # Response Spec:
2365
+ # [
2366
+ # PLACEHOLDER,
2367
+ # PLACEHOLDER,
2368
+ # PLACEHOLDER,
2369
+ # PLACEHOLDER,
2370
+ # [
2371
+ # [
2372
+ # MAKER_FEE,
2373
+ # MAKER_FEE,
2374
+ # MAKER_FEE,
2375
+ # PLACEHOLDER,
2376
+ # PLACEHOLDER,
2377
+ # DERIV_REBATE
2378
+ # ],
2379
+ # [
2380
+ # TAKER_FEE_TO_CRYPTO,
2381
+ # TAKER_FEE_TO_STABLE,
2382
+ # TAKER_FEE_TO_FIAT,
2383
+ # PLACEHOLDER,
2384
+ # PLACEHOLDER,
2385
+ # DERIV_TAKER_FEE
2386
+ # ]
2387
+ # ],
2388
+ # PLACEHOLDER,
2389
+ # PLACEHOLDER,
2390
+ # PLACEHOLDER,
2391
+ # PLACEHOLDER,
2392
+ # {
2393
+ # LEO_LEV,
2394
+ # LEO_AMOUNT_AVG
2395
+ # }
2396
+ # ]
2397
+ #
2398
+ # Example response:
2399
+ #
2400
+ # [
2401
+ # null,
2402
+ # null,
2403
+ # null,
2404
+ # null,
2405
+ # [
2406
+ # [0.001, 0.001, 0.001, null, null, 0.0002],
2407
+ # [0.002, 0.002, 0.002, null, null, 0.00065]
2408
+ # ],
2409
+ # [
2410
+ # [
2411
+ # {
2412
+ # "curr": "Total(USD)",
2413
+ # "vol": "0",
2414
+ # "vol_safe": "0",
2415
+ # "vol_maker": "0",
2416
+ # "vol_BFX": "0",
2417
+ # "vol_BFX_safe": "0",
2418
+ # "vol_BFX_maker": "0"
2419
+ # }
2420
+ # ],
2421
+ # {},
2422
+ # 0
2423
+ # ],
2424
+ # [null, {}, 0],
2425
+ # null,
2426
+ # null,
2427
+ # {leo_lev: "0", leo_amount_avg: "0"}
2428
+ # ]
2429
+ #
2430
+ result: dict = {}
2431
+ fiat = self.safe_value(self.options, 'fiat', {})
2432
+ feeData = self.safe_value(response, 4, [])
2433
+ makerData = self.safe_value(feeData, 0, [])
2434
+ takerData = self.safe_value(feeData, 1, [])
2435
+ makerFee = self.safe_number(makerData, 0)
2436
+ makerFeeFiat = self.safe_number(makerData, 2)
2437
+ makerFeeDeriv = self.safe_number(makerData, 5)
2438
+ takerFee = self.safe_number(takerData, 0)
2439
+ takerFeeFiat = self.safe_number(takerData, 2)
2440
+ takerFeeDeriv = self.safe_number(takerData, 5)
2441
+ for i in range(0, len(self.symbols)):
2442
+ symbol = self.symbols[i]
2443
+ market = self.market(symbol)
2444
+ fee = {
2445
+ 'info': response,
2446
+ 'symbol': symbol,
2447
+ 'percentage': True,
2448
+ 'tierBased': True,
2449
+ }
2450
+ if market['quote'] in fiat:
2451
+ fee['maker'] = makerFeeFiat
2452
+ fee['taker'] = takerFeeFiat
2453
+ elif market['contract']:
2454
+ fee['maker'] = makerFeeDeriv
2455
+ fee['taker'] = takerFeeDeriv
2456
+ else: # TODO check if stable coin
2457
+ fee['maker'] = makerFee
2458
+ fee['taker'] = takerFee
2459
+ result[symbol] = fee
2460
+ return result
2461
+
2462
+ async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
2463
+ """
2464
+ fetch history of deposits and withdrawals
2465
+
2466
+ https://docs.bitfinex.com/reference/movement-info
2467
+ https://docs.bitfinex.com/reference/rest-auth-movements
2468
+
2469
+ :param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
2470
+ :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
2471
+ :param int [limit]: max number of deposit/withdrawals to return, default is None
2472
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2473
+ :returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
2474
+ """
2475
+ await self.load_markets()
2476
+ currency = None
2477
+ request: dict = {}
2478
+ if since is not None:
2479
+ request['start'] = since
2480
+ if limit is not None:
2481
+ request['limit'] = limit # max 1000
2482
+ response = None
2483
+ if code is not None:
2484
+ currency = self.currency(code)
2485
+ request['currency'] = currency['uppercaseId']
2486
+ response = await self.privatePostAuthRMovementsCurrencyHist(self.extend(request, params))
2487
+ else:
2488
+ response = await self.privatePostAuthRMovementsHist(self.extend(request, params))
2489
+ #
2490
+ # [
2491
+ # [
2492
+ # 13293039, # ID
2493
+ # "ETH", # CURRENCY
2494
+ # "ETHEREUM", # CURRENCY_NAME
2495
+ # null,
2496
+ # null,
2497
+ # 1574175052000, # MTS_STARTED
2498
+ # 1574181326000, # MTS_UPDATED
2499
+ # null,
2500
+ # null,
2501
+ # "CANCELED", # STATUS
2502
+ # null,
2503
+ # null,
2504
+ # -0.24, # AMOUNT, negative for withdrawals
2505
+ # -0.00135, # FEES
2506
+ # null,
2507
+ # null,
2508
+ # "0x38110e0Fc932CB2BE...........", # DESTINATION_ADDRESS
2509
+ # null,
2510
+ # null,
2511
+ # null,
2512
+ # "0x523ec8945500.....................................", # TRANSACTION_ID
2513
+ # "Purchase of 100 pizzas", # WITHDRAW_TRANSACTION_NOTE, might also be: null
2514
+ # ]
2515
+ # ]
2516
+ #
2517
+ return self.parse_transactions(response, currency, since, limit)
1571
2518
 
1572
2519
  async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}) -> Transaction:
1573
2520
  """
1574
2521
  make a withdrawal
1575
2522
 
1576
- https://docs.bitfinex.com/v1/reference/rest-auth-withdrawal
2523
+ https://docs.bitfinex.com/reference/rest-auth-withdraw
1577
2524
 
1578
2525
  :param str code: unified currency code
1579
2526
  :param float amount: the amount to withdraw
@@ -1582,123 +2529,1097 @@ class bitfinex(Exchange, ImplicitAPI):
1582
2529
  :param dict [params]: extra parameters specific to the exchange API endpoint
1583
2530
  :returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
1584
2531
  """
1585
- tag, params = self.handle_withdraw_tag_and_params(tag, params)
1586
2532
  self.check_address(address)
1587
2533
  await self.load_markets()
1588
- # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
1589
- name = self.get_currency_name(code)
1590
2534
  currency = self.currency(code)
2535
+ # if not provided explicitly we will try to match using the currency name
2536
+ network = self.safe_string(params, 'network', code)
2537
+ params = self.omit(params, 'network')
2538
+ currencyNetworks = self.safe_value(currency, 'networks', {})
2539
+ currencyNetwork = self.safe_value(currencyNetworks, network)
2540
+ networkId = self.safe_string(currencyNetwork, 'id')
2541
+ if networkId is None:
2542
+ raise ArgumentsRequired(self.id + " withdraw() could not find a network for '" + code + "'. You can specify it by providing the 'network' value inside params")
2543
+ wallet = self.safe_string(params, 'wallet', 'exchange') # 'exchange', 'margin', 'funding' and also old labels 'exchange', 'trading', 'deposit', respectively
2544
+ params = self.omit(params, 'network', 'wallet')
1591
2545
  request: dict = {
1592
- 'withdraw_type': name,
1593
- 'walletselected': 'exchange',
2546
+ 'method': networkId,
2547
+ 'wallet': wallet,
1594
2548
  'amount': self.number_to_string(amount),
1595
2549
  'address': address,
1596
2550
  }
1597
2551
  if tag is not None:
1598
2552
  request['payment_id'] = tag
1599
- responses = await self.privatePostWithdraw(self.extend(request, params))
2553
+ withdrawOptions = self.safe_value(self.options, 'withdraw', {})
2554
+ includeFee = self.safe_bool(withdrawOptions, 'includeFee', False)
2555
+ if includeFee:
2556
+ request['fee_deduct'] = 1
2557
+ response = await self.privatePostAuthWWithdraw(self.extend(request, params))
1600
2558
  #
1601
2559
  # [
1602
- # {
1603
- # "status":"success",
1604
- # "message":"Your withdrawal request has been successfully submitted.",
1605
- # "withdrawal_id":586829
1606
- # }
2560
+ # 1582271520931, # MTS Millisecond Time Stamp of the update
2561
+ # "acc_wd-req", # TYPE Purpose of notification "acc_wd-req" account withdrawal request
2562
+ # null, # MESSAGE_ID unique ID of the message
2563
+ # null, # not documented
2564
+ # [
2565
+ # 0, # WITHDRAWAL_ID Unique Withdrawal ID
2566
+ # null, # PLACEHOLDER
2567
+ # "bitcoin", # METHOD Method of withdrawal
2568
+ # null, # PAYMENT_ID Payment ID if relevant
2569
+ # "exchange", # WALLET Sending wallet
2570
+ # 1, # AMOUNT Amount of Withdrawal less fee
2571
+ # null, # PLACEHOLDER
2572
+ # null, # PLACEHOLDER
2573
+ # 0.0004, # WITHDRAWAL_FEE Fee on withdrawal
2574
+ # ],
2575
+ # null, # CODE null or integer Work in progress
2576
+ # "SUCCESS", # STATUS Status of the notification, it may vary over time SUCCESS, ERROR, FAILURE
2577
+ # "Invalid bitcoin address(abcdef)", # TEXT Text of the notification
1607
2578
  # ]
1608
2579
  #
1609
- response = self.safe_dict(responses, 0, {})
1610
- id = self.safe_integer(response, 'withdrawal_id')
1611
- message = self.safe_string(response, 'message')
1612
- errorMessage = self.find_broadly_matched_key(self.exceptions['broad'], message)
1613
- if id == 0:
1614
- if errorMessage is not None:
1615
- ExceptionClass = self.exceptions['broad'][errorMessage]
1616
- raise ExceptionClass(self.id + ' ' + message)
1617
- raise ExchangeError(self.id + ' withdraw returned an id of zero: ' + self.json(response))
2580
+ # in case of failure:
2581
+ #
2582
+ # [
2583
+ # "error",
2584
+ # 10001,
2585
+ # "Momentary balance check. Please wait few seconds and try the transfer again."
2586
+ # ]
2587
+ #
2588
+ statusMessage = self.safe_string(response, 0)
2589
+ if statusMessage == 'error':
2590
+ feedback = self.id + ' ' + response
2591
+ message = self.safe_string(response, 2, '')
2592
+ # same message v1
2593
+ self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
2594
+ self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
2595
+ raise ExchangeError(feedback) # unknown message
2596
+ text = self.safe_string(response, 7)
2597
+ if text != 'success':
2598
+ self.throw_broadly_matched_exception(self.exceptions['broad'], text, text)
1618
2599
  return self.parse_transaction(response, currency)
1619
2600
 
1620
2601
  async def fetch_positions(self, symbols: Strings = None, params={}):
1621
2602
  """
1622
2603
  fetch all open positions
1623
2604
 
1624
- https://docs.bitfinex.com/v1/reference/rest-auth-active-positions
2605
+ https://docs.bitfinex.com/reference/rest-auth-positions
1625
2606
 
1626
2607
  :param str[]|None symbols: list of unified market symbols
1627
2608
  :param dict [params]: extra parameters specific to the exchange API endpoint
1628
2609
  :returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
1629
2610
  """
1630
2611
  await self.load_markets()
1631
- response = await self.privatePostPositions(params)
2612
+ symbols = self.market_symbols(symbols)
2613
+ response = await self.privatePostAuthRPositions(params)
1632
2614
  #
1633
2615
  # [
1634
- # {
1635
- # "id":943715,
1636
- # "symbol":"btcusd",
1637
- # "status":"ACTIVE",
1638
- # "base":"246.94",
1639
- # "amount":"1.0",
1640
- # "timestamp":"1444141857.0",
1641
- # "swap":"0.0",
1642
- # "pl":"-2.22042"
1643
- # }
2616
+ # [
2617
+ # "tBTCUSD", # SYMBOL
2618
+ # "ACTIVE", # STATUS
2619
+ # 0.0195, # AMOUNT
2620
+ # 8565.0267019, # BASE_PRICE
2621
+ # 0, # MARGIN_FUNDING
2622
+ # 0, # MARGIN_FUNDING_TYPE
2623
+ # -0.33455568705000516, # PL
2624
+ # -0.0003117550117425625, # PL_PERC
2625
+ # 7045.876419249083, # PRICE_LIQ
2626
+ # 3.0673001895895604, # LEVERAGE
2627
+ # null, # _PLACEHOLDER
2628
+ # 142355652, # POSITION_ID
2629
+ # 1574002216000, # MTS_CREATE
2630
+ # 1574002216000, # MTS_UPDATE
2631
+ # null, # _PLACEHOLDER
2632
+ # 0, # TYPE
2633
+ # null, # _PLACEHOLDER
2634
+ # 0, # COLLATERAL
2635
+ # 0, # COLLATERAL_MIN
2636
+ # # META
2637
+ # {
2638
+ # "reason":"TRADE",
2639
+ # "order_id":34271018124,
2640
+ # "liq_stage":null,
2641
+ # "trade_price":"8565.0267019",
2642
+ # "trade_amount":"0.0195",
2643
+ # "order_id_oppo":34277498022
2644
+ # }
2645
+ # ]
1644
2646
  # ]
1645
2647
  #
1646
- # todo unify parsePosition/parsePositions
1647
- return response
2648
+ positionsList = []
2649
+ for i in range(0, len(response)):
2650
+ positionsList.append({'result': response[i]})
2651
+ return self.parse_positions(positionsList, symbols)
2652
+
2653
+ def parse_position(self, position: dict, market: Market = None):
2654
+ #
2655
+ # [
2656
+ # "tBTCUSD", # SYMBOL
2657
+ # "ACTIVE", # STATUS
2658
+ # 0.0195, # AMOUNT
2659
+ # 8565.0267019, # BASE_PRICE
2660
+ # 0, # MARGIN_FUNDING
2661
+ # 0, # MARGIN_FUNDING_TYPE
2662
+ # -0.33455568705000516, # PL
2663
+ # -0.0003117550117425625, # PL_PERC
2664
+ # 7045.876419249083, # PRICE_LIQ
2665
+ # 3.0673001895895604, # LEVERAGE
2666
+ # null, # _PLACEHOLDER
2667
+ # 142355652, # POSITION_ID
2668
+ # 1574002216000, # MTS_CREATE
2669
+ # 1574002216000, # MTS_UPDATE
2670
+ # null, # _PLACEHOLDER
2671
+ # 0, # TYPE
2672
+ # null, # _PLACEHOLDER
2673
+ # 0, # COLLATERAL
2674
+ # 0, # COLLATERAL_MIN
2675
+ # # META
2676
+ # {
2677
+ # "reason": "TRADE",
2678
+ # "order_id": 34271018124,
2679
+ # "liq_stage": null,
2680
+ # "trade_price": "8565.0267019",
2681
+ # "trade_amount": "0.0195",
2682
+ # "order_id_oppo": 34277498022
2683
+ # }
2684
+ # ]
2685
+ #
2686
+ positionList = self.safe_list(position, 'result')
2687
+ marketId = self.safe_string(positionList, 0)
2688
+ amount = self.safe_string(positionList, 2)
2689
+ timestamp = self.safe_integer(positionList, 12)
2690
+ meta = self.safe_string(positionList, 19)
2691
+ tradePrice = self.safe_string(meta, 'trade_price')
2692
+ tradeAmount = self.safe_string(meta, 'trade_amount')
2693
+ return self.safe_position({
2694
+ 'info': positionList,
2695
+ 'id': self.safe_string(positionList, 11),
2696
+ 'symbol': self.safe_symbol(marketId, market),
2697
+ 'notional': self.parse_number(amount),
2698
+ 'marginMode': 'isolated', # derivatives use isolated, margin uses cross, https://support.bitfinex.com/hc/en-us/articles/360035475374-Derivatives-Trading-on-Bitfinex
2699
+ 'liquidationPrice': self.safe_number(positionList, 8),
2700
+ 'entryPrice': self.safe_number(positionList, 3),
2701
+ 'unrealizedPnl': self.safe_number(positionList, 6),
2702
+ 'percentage': self.safe_number(positionList, 7),
2703
+ 'contracts': None,
2704
+ 'contractSize': None,
2705
+ 'markPrice': None,
2706
+ 'lastPrice': None,
2707
+ 'side': 'long' if Precise.string_gt(amount, '0') else 'short',
2708
+ 'hedged': None,
2709
+ 'timestamp': timestamp,
2710
+ 'datetime': self.iso8601(timestamp),
2711
+ 'lastUpdateTimestamp': self.safe_integer(positionList, 13),
2712
+ 'maintenanceMargin': self.safe_number(positionList, 18),
2713
+ 'maintenanceMarginPercentage': None,
2714
+ 'collateral': self.safe_number(positionList, 17),
2715
+ 'initialMargin': self.parse_number(Precise.string_mul(tradeAmount, tradePrice)),
2716
+ 'initialMarginPercentage': None,
2717
+ 'leverage': self.safe_number(positionList, 9),
2718
+ 'marginRatio': None,
2719
+ 'stopLossPrice': None,
2720
+ 'takeProfitPrice': None,
2721
+ })
1648
2722
 
1649
2723
  def nonce(self):
1650
- return self.microseconds()
2724
+ return self.milliseconds()
1651
2725
 
1652
2726
  def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
1653
2727
  request = '/' + self.implode_params(path, params)
1654
- if api == 'v2':
1655
- request = '/' + api + request
1656
- else:
1657
- request = '/' + self.version + request
1658
2728
  query = self.omit(params, self.extract_params(path))
1659
- url = self.urls['api'][api] + request
1660
- if (api == 'public') or (path.find('/hist') >= 0):
2729
+ if api == 'v1':
2730
+ request = api + request
2731
+ else:
2732
+ request = self.version + request
2733
+ url = self.urls['api'][api] + '/' + request
2734
+ if api == 'public':
1661
2735
  if query:
1662
- suffix = '?' + self.urlencode(query)
1663
- url += suffix
1664
- request += suffix
2736
+ url += '?' + self.urlencode(query)
1665
2737
  if api == 'private':
1666
2738
  self.check_required_credentials()
1667
- nonce = self.nonce()
1668
- query = self.extend({
1669
- 'nonce': str(nonce),
1670
- 'request': request,
1671
- }, query)
2739
+ nonce = str(self.nonce())
1672
2740
  body = self.json(query)
1673
- payload = self.string_to_base64(body)
1674
- secret = self.encode(self.secret)
1675
- signature = self.hmac(self.encode(payload), secret, hashlib.sha384)
2741
+ auth = '/api/' + request + nonce + body
2742
+ signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha384)
1676
2743
  headers = {
1677
- 'X-BFX-APIKEY': self.apiKey,
1678
- 'X-BFX-PAYLOAD': payload,
1679
- 'X-BFX-SIGNATURE': signature,
2744
+ 'bfx-nonce': nonce,
2745
+ 'bfx-apikey': self.apiKey,
2746
+ 'bfx-signature': signature,
1680
2747
  'Content-Type': 'application/json',
1681
2748
  }
1682
2749
  return {'url': url, 'method': method, 'body': body, 'headers': headers}
1683
2750
 
1684
- def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
1685
- if response is None:
2751
+ def handle_errors(self, statusCode, statusText, url, method, headers, body, response, requestHeaders, requestBody):
2752
+ # ["error", 11010, "ratelimit: error"]
2753
+ if response is not None:
2754
+ if not isinstance(response, list):
2755
+ message = self.safe_string_2(response, 'message', 'error')
2756
+ feedback = self.id + ' ' + body
2757
+ self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
2758
+ self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
2759
+ raise ExchangeError(self.id + ' ' + body)
2760
+ elif response == '':
2761
+ raise ExchangeError(self.id + ' returned empty response')
2762
+ if statusCode == 429:
2763
+ raise RateLimitExceeded(self.id + ' ' + body)
2764
+ if statusCode == 500:
2765
+ # See https://docs.bitfinex.com/docs/abbreviations-glossary#section-errorinfo-codes
2766
+ errorCode = self.safe_string(response, 1, '')
2767
+ errorText = self.safe_string(response, 2, '')
2768
+ feedback = self.id + ' ' + errorText
2769
+ self.throw_broadly_matched_exception(self.exceptions['broad'], errorText, feedback)
2770
+ self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
2771
+ self.throw_exactly_matched_exception(self.exceptions['exact'], errorText, feedback)
2772
+ raise ExchangeError(self.id + ' ' + errorText + '(#' + errorCode + ')')
2773
+ return response
2774
+
2775
+ def parse_ledger_entry_type(self, type: Str):
2776
+ if type is None:
1686
2777
  return None
1687
- throwError = False
1688
- if code >= 400:
1689
- if body[0] == '{':
1690
- throwError = True
2778
+ elif type.find('fee') >= 0 or type.find('charged') >= 0:
2779
+ return 'fee'
2780
+ elif type.find('rebate') >= 0:
2781
+ return 'rebate'
2782
+ elif type.find('deposit') >= 0 or type.find('withdrawal') >= 0:
2783
+ return 'transaction'
2784
+ elif type.find('transfer') >= 0:
2785
+ return 'transfer'
2786
+ elif type.find('payment') >= 0:
2787
+ return 'payout'
2788
+ elif type.find('exchange') >= 0 or type.find('position') >= 0:
2789
+ return 'trade'
1691
2790
  else:
1692
- # json response with error, i.e:
1693
- # [{"status":"error","message":"Momentary balance check. Please wait few seconds and try the transfer again."}]
1694
- responseObject = self.safe_dict(response, 0, {})
1695
- status = self.safe_string(responseObject, 'status', '')
1696
- if status == 'error':
1697
- throwError = True
1698
- if throwError:
1699
- feedback = self.id + ' ' + body
1700
- message = self.safe_string_2(response, 'message', 'error')
1701
- self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
1702
- self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
1703
- raise ExchangeError(feedback) # unknown message
1704
- return None
2791
+ return type
2792
+
2793
+ def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
2794
+ #
2795
+ # [
2796
+ # [
2797
+ # 2531822314, # ID: Ledger identifier
2798
+ # "USD", # CURRENCY: The symbol of the currency(ex. "BTC")
2799
+ # null, # PLACEHOLDER
2800
+ # 1573521810000, # MTS: Timestamp in milliseconds
2801
+ # null, # PLACEHOLDER
2802
+ # 0.01644445, # AMOUNT: Amount of funds moved
2803
+ # 0, # BALANCE: New balance
2804
+ # null, # PLACEHOLDER
2805
+ # "Settlement @ 185.79 on wallet margin" # DESCRIPTION: Description of ledger transaction
2806
+ # ]
2807
+ # ]
2808
+ #
2809
+ itemList = self.safe_list(item, 'result', [])
2810
+ type = None
2811
+ id = self.safe_string(itemList, 0)
2812
+ currencyId = self.safe_string(itemList, 1)
2813
+ code = self.safe_currency_code(currencyId, currency)
2814
+ currency = self.safe_currency(currencyId, currency)
2815
+ timestamp = self.safe_integer(itemList, 3)
2816
+ amount = self.safe_number(itemList, 5)
2817
+ after = self.safe_number(itemList, 6)
2818
+ description = self.safe_string(itemList, 8)
2819
+ if description is not None:
2820
+ parts = description.split(' @ ')
2821
+ first = self.safe_string_lower(parts, 0)
2822
+ type = self.parse_ledger_entry_type(first)
2823
+ return self.safe_ledger_entry({
2824
+ 'info': item,
2825
+ 'id': id,
2826
+ 'direction': None,
2827
+ 'account': None,
2828
+ 'referenceId': id,
2829
+ 'referenceAccount': None,
2830
+ 'type': type,
2831
+ 'currency': code,
2832
+ 'amount': amount,
2833
+ 'timestamp': timestamp,
2834
+ 'datetime': self.iso8601(timestamp),
2835
+ 'before': None,
2836
+ 'after': after,
2837
+ 'status': None,
2838
+ 'fee': None,
2839
+ }, currency)
2840
+
2841
+ async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
2842
+ """
2843
+ fetch the history of changes, actions done by the user or operations that altered the balance of the user
2844
+
2845
+ https://docs.bitfinex.com/reference/rest-auth-ledgers
2846
+
2847
+ :param str [code]: unified currency code, default is None
2848
+ :param int [since]: timestamp in ms of the earliest ledger entry, default is None
2849
+ :param int [limit]: max number of ledger entries to return, default is None, max is 2500
2850
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2851
+ :param int [params.until]: timestamp in ms of the latest ledger entry
2852
+ :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)
2853
+ :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger-structure>`
2854
+ """
2855
+ await self.load_markets()
2856
+ paginate = False
2857
+ paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate')
2858
+ if paginate:
2859
+ return await self.fetch_paginated_call_dynamic('fetchLedger', code, since, limit, params, 2500)
2860
+ currency = None
2861
+ request: dict = {}
2862
+ if since is not None:
2863
+ request['start'] = since
2864
+ if limit is not None:
2865
+ request['limit'] = limit
2866
+ request, params = self.handle_until_option('end', request, params)
2867
+ response = None
2868
+ if code is not None:
2869
+ currency = self.currency(code)
2870
+ request['currency'] = currency['uppercaseId']
2871
+ response = await self.privatePostAuthRLedgersCurrencyHist(self.extend(request, params))
2872
+ else:
2873
+ response = await self.privatePostAuthRLedgersHist(self.extend(request, params))
2874
+ #
2875
+ # [
2876
+ # [
2877
+ # 2531822314, # ID: Ledger identifier
2878
+ # "USD", # CURRENCY: The symbol of the currency(ex. "BTC")
2879
+ # null, # PLACEHOLDER
2880
+ # 1573521810000, # MTS: Timestamp in milliseconds
2881
+ # null, # PLACEHOLDER
2882
+ # 0.01644445, # AMOUNT: Amount of funds moved
2883
+ # 0, # BALANCE: New balance
2884
+ # null, # PLACEHOLDER
2885
+ # "Settlement @ 185.79 on wallet margin" # DESCRIPTION: Description of ledger transaction
2886
+ # ]
2887
+ # ]
2888
+ #
2889
+ ledgerObjects = []
2890
+ for i in range(0, len(response)):
2891
+ item = response[i]
2892
+ ledgerObjects.append({'result': item})
2893
+ return self.parse_ledger(ledgerObjects, currency, since, limit)
2894
+
2895
+ async def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
2896
+ """
2897
+ fetch the current funding rate for multiple symbols
2898
+
2899
+ https://docs.bitfinex.com/reference/rest-public-derivatives-status
2900
+
2901
+ :param str[] symbols: list of unified market symbols
2902
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2903
+ :returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-structure>`
2904
+ """
2905
+ if symbols is None:
2906
+ raise ArgumentsRequired(self.id + ' fetchFundingRates() requires a symbols argument')
2907
+ await self.load_markets()
2908
+ marketIds = self.market_ids(symbols)
2909
+ request: dict = {
2910
+ 'keys': ','.join(marketIds),
2911
+ }
2912
+ response = await self.publicGetStatusDeriv(self.extend(request, params))
2913
+ #
2914
+ # [
2915
+ # [
2916
+ # "tBTCF0:USTF0",
2917
+ # 1691165059000,
2918
+ # null,
2919
+ # 29297.851276225,
2920
+ # 29277.5,
2921
+ # null,
2922
+ # 36950860.76010306,
2923
+ # null,
2924
+ # 1691193600000,
2925
+ # 0.00000527,
2926
+ # 82,
2927
+ # null,
2928
+ # 0.00014548,
2929
+ # null,
2930
+ # null,
2931
+ # 29278.8925,
2932
+ # null,
2933
+ # null,
2934
+ # 9636.07644994,
2935
+ # null,
2936
+ # null,
2937
+ # null,
2938
+ # 0.0005,
2939
+ # 0.0025
2940
+ # ]
2941
+ # ]
2942
+ #
2943
+ return self.parse_funding_rates(response)
2944
+
2945
+ async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
2946
+ """
2947
+ fetches historical funding rate prices
2948
+
2949
+ https://docs.bitfinex.com/reference/rest-public-derivatives-status-history
2950
+
2951
+ :param str symbol: unified market symbol
2952
+ :param int [since]: timestamp in ms of the earliest funding rate entry
2953
+ :param int [limit]: max number of funding rate entrys to return
2954
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2955
+ :param int [params.until]: timestamp in ms of the latest funding rate
2956
+ :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)
2957
+ :returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
2958
+ """
2959
+ if symbol is None:
2960
+ raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
2961
+ await self.load_markets()
2962
+ paginate = False
2963
+ paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
2964
+ if paginate:
2965
+ return await self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params, 5000)
2966
+ market = self.market(symbol)
2967
+ request: dict = {
2968
+ 'symbol': market['id'],
2969
+ }
2970
+ if since is not None:
2971
+ request['start'] = since
2972
+ request, params = self.handle_until_option('end', request, params)
2973
+ response = await self.publicGetStatusDerivSymbolHist(self.extend(request, params))
2974
+ #
2975
+ # [
2976
+ # [
2977
+ # "tBTCF0:USTF0",
2978
+ # 1691165059000,
2979
+ # null,
2980
+ # 29297.851276225,
2981
+ # 29277.5,
2982
+ # null,
2983
+ # 36950860.76010306,
2984
+ # null,
2985
+ # 1691193600000,
2986
+ # 0.00000527,
2987
+ # 82,
2988
+ # null,
2989
+ # 0.00014548,
2990
+ # null,
2991
+ # null,
2992
+ # 29278.8925,
2993
+ # null,
2994
+ # null,
2995
+ # 9636.07644994,
2996
+ # null,
2997
+ # null,
2998
+ # null,
2999
+ # 0.0005,
3000
+ # 0.0025
3001
+ # ]
3002
+ # ]
3003
+ #
3004
+ rates = []
3005
+ for i in range(0, len(response)):
3006
+ fr = response[i]
3007
+ rate = self.parse_funding_rate_history(fr, market)
3008
+ rates.append(rate)
3009
+ reversedArray = []
3010
+ rawRates = self.filter_by_symbol_since_limit(rates, symbol, since, limit)
3011
+ ratesLength = len(rawRates)
3012
+ for i in range(0, ratesLength):
3013
+ index = ratesLength - i - 1
3014
+ valueAtIndex = rawRates[index]
3015
+ reversedArray.append(valueAtIndex)
3016
+ return reversedArray
3017
+
3018
+ def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
3019
+ #
3020
+ # [
3021
+ # "tBTCF0:USTF0",
3022
+ # 1691165059000,
3023
+ # null,
3024
+ # 29297.851276225,
3025
+ # 29277.5,
3026
+ # null,
3027
+ # 36950860.76010306,
3028
+ # null,
3029
+ # 1691193600000,
3030
+ # 0.00000527,
3031
+ # 82,
3032
+ # null,
3033
+ # 0.00014548,
3034
+ # null,
3035
+ # null,
3036
+ # 29278.8925,
3037
+ # null,
3038
+ # null,
3039
+ # 9636.07644994,
3040
+ # null,
3041
+ # null,
3042
+ # null,
3043
+ # 0.0005,
3044
+ # 0.0025
3045
+ # ]
3046
+ #
3047
+ marketId = self.safe_string(contract, 0)
3048
+ timestamp = self.safe_integer(contract, 1)
3049
+ nextFundingTimestamp = self.safe_integer(contract, 8)
3050
+ return {
3051
+ 'info': contract,
3052
+ 'symbol': self.safe_symbol(marketId, market),
3053
+ 'markPrice': self.safe_number(contract, 15),
3054
+ 'indexPrice': self.safe_number(contract, 3),
3055
+ 'interestRate': None,
3056
+ 'estimatedSettlePrice': None,
3057
+ 'timestamp': timestamp,
3058
+ 'datetime': self.iso8601(timestamp),
3059
+ 'fundingRate': self.safe_number(contract, 12),
3060
+ 'fundingTimestamp': None,
3061
+ 'fundingDatetime': None,
3062
+ 'nextFundingRate': self.safe_number(contract, 9),
3063
+ 'nextFundingTimestamp': nextFundingTimestamp,
3064
+ 'nextFundingDatetime': self.iso8601(nextFundingTimestamp),
3065
+ 'previousFundingRate': None,
3066
+ 'previousFundingTimestamp': None,
3067
+ 'previousFundingDatetime': None,
3068
+ 'interval': None,
3069
+ }
3070
+
3071
+ def parse_funding_rate_history(self, contract, market: Market = None):
3072
+ #
3073
+ # [
3074
+ # 1691165494000,
3075
+ # null,
3076
+ # 29278.95838065,
3077
+ # 29260.5,
3078
+ # null,
3079
+ # 36950860.76010305,
3080
+ # null,
3081
+ # 1691193600000,
3082
+ # 0.00001449,
3083
+ # 222,
3084
+ # null,
3085
+ # 0.00014548,
3086
+ # null,
3087
+ # null,
3088
+ # 29260.005,
3089
+ # null,
3090
+ # null,
3091
+ # 9635.86484562,
3092
+ # null,
3093
+ # null,
3094
+ # null,
3095
+ # 0.0005,
3096
+ # 0.0025
3097
+ # ]
3098
+ #
3099
+ timestamp = self.safe_integer(contract, 0)
3100
+ nextFundingTimestamp = self.safe_integer(contract, 7)
3101
+ return {
3102
+ 'info': contract,
3103
+ 'symbol': self.safe_symbol(None, market),
3104
+ 'markPrice': self.safe_number(contract, 14),
3105
+ 'indexPrice': self.safe_number(contract, 2),
3106
+ 'interestRate': None,
3107
+ 'estimatedSettlePrice': None,
3108
+ 'timestamp': timestamp,
3109
+ 'datetime': self.iso8601(timestamp),
3110
+ 'fundingRate': self.safe_number(contract, 11),
3111
+ 'fundingTimestamp': None,
3112
+ 'fundingDatetime': None,
3113
+ 'nextFundingRate': self.safe_number(contract, 8),
3114
+ 'nextFundingTimestamp': nextFundingTimestamp,
3115
+ 'nextFundingDatetime': self.iso8601(nextFundingTimestamp),
3116
+ 'previousFundingRate': None,
3117
+ 'previousFundingTimestamp': None,
3118
+ 'previousFundingDatetime': None,
3119
+ }
3120
+
3121
+ async def fetch_open_interest(self, symbol: str, params={}):
3122
+ """
3123
+ retrieves the open interest of a contract trading pair
3124
+
3125
+ https://docs.bitfinex.com/reference/rest-public-derivatives-status
3126
+
3127
+ :param str symbol: unified CCXT market symbol
3128
+ :param dict [params]: exchange specific parameters
3129
+ :returns dict: an `open interest structure <https://docs.ccxt.com/#/?id=open-interest-structure>`
3130
+ """
3131
+ await self.load_markets()
3132
+ market = self.market(symbol)
3133
+ request: dict = {
3134
+ 'keys': market['id'],
3135
+ }
3136
+ response = await self.publicGetStatusDeriv(self.extend(request, params))
3137
+ #
3138
+ # [
3139
+ # [
3140
+ # "tXRPF0:USTF0", # market id
3141
+ # 1706256986000, # millisecond timestamp
3142
+ # null,
3143
+ # 0.512705, # derivative mid price
3144
+ # 0.512395, # underlying spot mid price
3145
+ # null,
3146
+ # 37671483.04, # insurance fund balance
3147
+ # null,
3148
+ # 1706284800000, # timestamp of next funding
3149
+ # 0.00002353, # accrued funding for next period
3150
+ # 317, # next funding step
3151
+ # null,
3152
+ # 0, # current funding
3153
+ # null,
3154
+ # null,
3155
+ # 0.5123016, # mark price
3156
+ # null,
3157
+ # null,
3158
+ # 2233562.03115, # open interest in contracts
3159
+ # null,
3160
+ # null,
3161
+ # null,
3162
+ # 0.0005, # average spread without funding payment
3163
+ # 0.0025 # funding payment cap
3164
+ # ]
3165
+ # ]
3166
+ #
3167
+ oi = self.safe_list(response, 0)
3168
+ return self.parse_open_interest(oi, market)
3169
+
3170
+ async def fetch_open_interest_history(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
3171
+ """
3172
+ retrieves the open interest history of a currency
3173
+
3174
+ https://docs.bitfinex.com/reference/rest-public-derivatives-status-history
3175
+
3176
+ :param str symbol: unified CCXT market symbol
3177
+ :param str timeframe: the time period of each row of data, not used by bitfinex
3178
+ :param int [since]: the time in ms of the earliest record to retrieve unix timestamp
3179
+ :param int [limit]: the number of records in the response
3180
+ :param dict [params]: exchange specific parameters
3181
+ :param int [params.until]: the time in ms of the latest record to retrieve unix timestamp
3182
+ :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)
3183
+ :returns: An array of `open interest structures <https://docs.ccxt.com/#/?id=open-interest-structure>`
3184
+ """
3185
+ await self.load_markets()
3186
+ paginate = False
3187
+ paginate, params = self.handle_option_and_params(params, 'fetchOpenInterestHistory', 'paginate')
3188
+ if paginate:
3189
+ return await self.fetch_paginated_call_deterministic('fetchOpenInterestHistory', symbol, since, limit, '8h', params, 5000)
3190
+ market = self.market(symbol)
3191
+ request: dict = {
3192
+ 'symbol': market['id'],
3193
+ }
3194
+ if since is not None:
3195
+ request['start'] = since
3196
+ if limit is not None:
3197
+ request['limit'] = limit
3198
+ request, params = self.handle_until_option('end', request, params)
3199
+ response = await self.publicGetStatusDerivSymbolHist(self.extend(request, params))
3200
+ #
3201
+ # [
3202
+ # [
3203
+ # 1706295191000, # timestamp
3204
+ # null,
3205
+ # 42152.425382, # derivative mid price
3206
+ # 42133, # spot mid price
3207
+ # null,
3208
+ # 37671589.7853521, # insurance fund balance
3209
+ # null,
3210
+ # 1706313600000, # timestamp of next funding
3211
+ # 0.00018734, # accrued funding for next period
3212
+ # 3343, # next funding step
3213
+ # null,
3214
+ # 0.00007587, # current funding
3215
+ # null,
3216
+ # null,
3217
+ # 42134.1, # mark price
3218
+ # null,
3219
+ # null,
3220
+ # 5775.20348804, # open interest number of contracts
3221
+ # null,
3222
+ # null,
3223
+ # null,
3224
+ # 0.0005, # average spread without funding payment
3225
+ # 0.0025 # funding payment cap
3226
+ # ],
3227
+ # ]
3228
+ #
3229
+ return self.parse_open_interests(response, market, since, limit)
3230
+
3231
+ def parse_open_interest(self, interest, market: Market = None):
3232
+ #
3233
+ # fetchOpenInterest:
3234
+ #
3235
+ # [
3236
+ # "tXRPF0:USTF0", # market id
3237
+ # 1706256986000, # millisecond timestamp
3238
+ # null,
3239
+ # 0.512705, # derivative mid price
3240
+ # 0.512395, # underlying spot mid price
3241
+ # null,
3242
+ # 37671483.04, # insurance fund balance
3243
+ # null,
3244
+ # 1706284800000, # timestamp of next funding
3245
+ # 0.00002353, # accrued funding for next period
3246
+ # 317, # next funding step
3247
+ # null,
3248
+ # 0, # current funding
3249
+ # null,
3250
+ # null,
3251
+ # 0.5123016, # mark price
3252
+ # null,
3253
+ # null,
3254
+ # 2233562.03115, # open interest in contracts
3255
+ # null,
3256
+ # null,
3257
+ # null,
3258
+ # 0.0005, # average spread without funding payment
3259
+ # 0.0025 # funding payment cap
3260
+ # ]
3261
+ #
3262
+ # fetchOpenInterestHistory:
3263
+ #
3264
+ # [
3265
+ # 1706295191000, # timestamp
3266
+ # null,
3267
+ # 42152.425382, # derivative mid price
3268
+ # 42133, # spot mid price
3269
+ # null,
3270
+ # 37671589.7853521, # insurance fund balance
3271
+ # null,
3272
+ # 1706313600000, # timestamp of next funding
3273
+ # 0.00018734, # accrued funding for next period
3274
+ # 3343, # next funding step
3275
+ # null,
3276
+ # 0.00007587, # current funding
3277
+ # null,
3278
+ # null,
3279
+ # 42134.1, # mark price
3280
+ # null,
3281
+ # null,
3282
+ # 5775.20348804, # open interest number of contracts
3283
+ # null,
3284
+ # null,
3285
+ # null,
3286
+ # 0.0005, # average spread without funding payment
3287
+ # 0.0025 # funding payment cap
3288
+ # ]
3289
+ #
3290
+ interestLength = len(interest)
3291
+ openInterestIndex = 17 if (interestLength == 23) else 18
3292
+ timestamp = self.safe_integer(interest, 1)
3293
+ marketId = self.safe_string(interest, 0)
3294
+ return self.safe_open_interest({
3295
+ 'symbol': self.safe_symbol(marketId, market, None, 'swap'),
3296
+ 'openInterestAmount': self.safe_number(interest, openInterestIndex),
3297
+ 'openInterestValue': None,
3298
+ 'timestamp': timestamp,
3299
+ 'datetime': self.iso8601(timestamp),
3300
+ 'info': interest,
3301
+ }, market)
3302
+
3303
+ async def fetch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}):
3304
+ """
3305
+ retrieves the public liquidations of a trading pair
3306
+
3307
+ https://docs.bitfinex.com/reference/rest-public-liquidations
3308
+
3309
+ :param str symbol: unified CCXT market symbol
3310
+ :param int [since]: the earliest time in ms to fetch liquidations for
3311
+ :param int [limit]: the maximum number of liquidation structures to retrieve
3312
+ :param dict [params]: exchange specific parameters
3313
+ :param int [params.until]: timestamp in ms of the latest liquidation
3314
+ :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)
3315
+ :returns dict: an array of `liquidation structures <https://docs.ccxt.com/#/?id=liquidation-structure>`
3316
+ """
3317
+ await self.load_markets()
3318
+ paginate = False
3319
+ paginate, params = self.handle_option_and_params(params, 'fetchLiquidations', 'paginate')
3320
+ if paginate:
3321
+ return await self.fetch_paginated_call_deterministic('fetchLiquidations', symbol, since, limit, '8h', params, 500)
3322
+ market = self.market(symbol)
3323
+ request: dict = {}
3324
+ if since is not None:
3325
+ request['start'] = since
3326
+ if limit is not None:
3327
+ request['limit'] = limit
3328
+ request, params = self.handle_until_option('end', request, params)
3329
+ response = await self.publicGetLiquidationsHist(self.extend(request, params))
3330
+ #
3331
+ # [
3332
+ # [
3333
+ # [
3334
+ # "pos",
3335
+ # 171085137,
3336
+ # 1706395919788,
3337
+ # null,
3338
+ # "tAVAXF0:USTF0",
3339
+ # -8,
3340
+ # 32.868,
3341
+ # null,
3342
+ # 1,
3343
+ # 1,
3344
+ # null,
3345
+ # 33.255
3346
+ # ]
3347
+ # ],
3348
+ # ]
3349
+ #
3350
+ return self.parse_liquidations(response, market, since, limit)
3351
+
3352
+ def parse_liquidation(self, liquidation, market: Market = None):
3353
+ #
3354
+ # [
3355
+ # [
3356
+ # "pos",
3357
+ # 171085137, # position id
3358
+ # 1706395919788, # timestamp
3359
+ # null,
3360
+ # "tAVAXF0:USTF0", # market id
3361
+ # -8, # amount in contracts
3362
+ # 32.868, # base price
3363
+ # null,
3364
+ # 1,
3365
+ # 1,
3366
+ # null,
3367
+ # 33.255 # acquired price
3368
+ # ]
3369
+ # ]
3370
+ #
3371
+ entry = liquidation[0]
3372
+ timestamp = self.safe_integer(entry, 2)
3373
+ marketId = self.safe_string(entry, 4)
3374
+ contracts = Precise.string_abs(self.safe_string(entry, 5))
3375
+ contractSize = self.safe_string(market, 'contractSize')
3376
+ baseValue = Precise.string_mul(contracts, contractSize)
3377
+ price = self.safe_string(entry, 11)
3378
+ return self.safe_liquidation({
3379
+ 'info': entry,
3380
+ 'symbol': self.safe_symbol(marketId, market, None, 'contract'),
3381
+ 'contracts': self.parse_number(contracts),
3382
+ 'contractSize': self.parse_number(contractSize),
3383
+ 'price': self.parse_number(price),
3384
+ 'baseValue': self.parse_number(baseValue),
3385
+ 'quoteValue': self.parse_number(Precise.string_mul(baseValue, price)),
3386
+ 'timestamp': timestamp,
3387
+ 'datetime': self.iso8601(timestamp),
3388
+ })
3389
+
3390
+ async def set_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
3391
+ """
3392
+ either adds or reduces margin in a swap position in order to set the margin to a specific value
3393
+
3394
+ https://docs.bitfinex.com/reference/rest-auth-deriv-pos-collateral-set
3395
+
3396
+ :param str symbol: unified market symbol of the market to set margin in
3397
+ :param float amount: the amount to set the margin to
3398
+ :param dict [params]: parameters specific to the exchange API endpoint
3399
+ :returns dict: A `margin structure <https://github.com/ccxt/ccxt/wiki/Manual#add-margin-structure>`
3400
+ """
3401
+ await self.load_markets()
3402
+ market = self.market(symbol)
3403
+ if not market['swap']:
3404
+ raise NotSupported(self.id + ' setMargin() only support swap markets')
3405
+ request: dict = {
3406
+ 'symbol': market['id'],
3407
+ 'collateral': self.parse_to_numeric(amount),
3408
+ }
3409
+ response = await self.privatePostAuthWDerivCollateralSet(self.extend(request, params))
3410
+ #
3411
+ # [
3412
+ # [
3413
+ # 1
3414
+ # ]
3415
+ # ]
3416
+ #
3417
+ data = self.safe_value(response, 0)
3418
+ return self.parse_margin_modification(data, market)
3419
+
3420
+ def parse_margin_modification(self, data, market=None) -> MarginModification:
3421
+ #
3422
+ # setMargin
3423
+ #
3424
+ # [
3425
+ # [
3426
+ # 1
3427
+ # ]
3428
+ # ]
3429
+ #
3430
+ marginStatusRaw = data[0]
3431
+ marginStatus = 'ok' if (marginStatusRaw == 1) else 'failed'
3432
+ return {
3433
+ 'info': data,
3434
+ 'symbol': market['symbol'],
3435
+ 'type': None,
3436
+ 'marginMode': 'isolated',
3437
+ 'amount': None,
3438
+ 'total': None,
3439
+ 'code': None,
3440
+ 'status': marginStatus,
3441
+ 'timestamp': None,
3442
+ 'datetime': None,
3443
+ }
3444
+
3445
+ async def fetch_order(self, id: str, symbol: Str = None, params={}):
3446
+ """
3447
+ fetches information on an order made by the user
3448
+
3449
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders
3450
+ https://docs.bitfinex.com/reference/rest-auth-retrieve-orders-by-symbol
3451
+
3452
+ :param str id: the order id
3453
+ :param str [symbol]: unified symbol of the market the order was made in
3454
+ :param dict [params]: extra parameters specific to the exchange API endpoint
3455
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
3456
+ """
3457
+ await self.load_markets()
3458
+ request: dict = {
3459
+ 'id': [self.parse_to_numeric(id)],
3460
+ }
3461
+ market = None
3462
+ response = None
3463
+ if symbol is None:
3464
+ response = await self.privatePostAuthROrders(self.extend(request, params))
3465
+ else:
3466
+ market = self.market(symbol)
3467
+ request['symbol'] = market['id']
3468
+ response = await self.privatePostAuthROrdersSymbol(self.extend(request, params))
3469
+ #
3470
+ # [
3471
+ # [
3472
+ # 139658969116,
3473
+ # null,
3474
+ # 1706843908637,
3475
+ # "tBTCUST",
3476
+ # 1706843908637,
3477
+ # 1706843908638,
3478
+ # 0.0001,
3479
+ # 0.0001,
3480
+ # "EXCHANGE LIMIT",
3481
+ # null,
3482
+ # null,
3483
+ # null,
3484
+ # 0,
3485
+ # "ACTIVE",
3486
+ # null,
3487
+ # null,
3488
+ # 35000,
3489
+ # 0,
3490
+ # 0,
3491
+ # 0,
3492
+ # null,
3493
+ # null,
3494
+ # null,
3495
+ # 0,
3496
+ # 0,
3497
+ # null,
3498
+ # null,
3499
+ # null,
3500
+ # "API>BFX",
3501
+ # null,
3502
+ # null,
3503
+ # {}
3504
+ # ]
3505
+ # ]
3506
+ #
3507
+ order = self.safe_list(response, 0)
3508
+ newOrder = {'result': order}
3509
+ return self.parse_order(newOrder, market)
3510
+
3511
+ async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
3512
+ """
3513
+ edit a trade order
3514
+
3515
+ https://docs.bitfinex.com/reference/rest-auth-update-order
3516
+
3517
+ :param str id: edit order id
3518
+ :param str symbol: unified symbol of the market to edit an order in
3519
+ :param str type: 'market' or 'limit'
3520
+ :param str side: 'buy' or 'sell'
3521
+ :param float amount: how much you want to trade in units of the base currency
3522
+ :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
3523
+ :param dict [params]: extra parameters specific to the exchange API endpoint
3524
+ :param float [params.stopPrice]: the price that triggers a trigger order
3525
+ :param boolean [params.postOnly]: set to True if you want to make a post only order
3526
+ :param boolean [params.reduceOnly]: indicates that the order is to reduce the size of a position
3527
+ :param int [params.flags]: additional order parameters: 4096(Post Only), 1024(Reduce Only), 16384(OCO), 64(Hidden), 512(Close), 524288(No Var Rates)
3528
+ :param int [params.leverage]: leverage for a derivative order, supported by derivative symbol orders only, the value should be between 1 and 100 inclusive
3529
+ :param int [params.clientOrderId]: a unique client order id for the order
3530
+ :param float [params.trailingAmount]: *swap only* the quote amount to trail away from the current market price
3531
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
3532
+ """
3533
+ await self.load_markets()
3534
+ market = self.market(symbol)
3535
+ request: dict = {
3536
+ 'id': self.parse_to_numeric(id),
3537
+ }
3538
+ if amount is not None:
3539
+ amountString = self.amount_to_precision(symbol, amount)
3540
+ amountString = amountString if (side == 'buy') else Precise.string_neg(amountString)
3541
+ request['amount'] = amountString
3542
+ stopPrice = self.safe_string_2(params, 'stopPrice', 'triggerPrice')
3543
+ trailingAmount = self.safe_string(params, 'trailingAmount')
3544
+ timeInForce = self.safe_string(params, 'timeInForce')
3545
+ postOnlyParam = self.safe_bool(params, 'postOnly', False)
3546
+ reduceOnly = self.safe_bool(params, 'reduceOnly', False)
3547
+ clientOrderId = self.safe_integer_2(params, 'cid', 'clientOrderId')
3548
+ if trailingAmount is not None:
3549
+ request['price_trailing'] = trailingAmount
3550
+ elif stopPrice is not None:
3551
+ # request['price'] is taken for stop orders
3552
+ request['price'] = self.price_to_precision(symbol, stopPrice)
3553
+ if type == 'limit':
3554
+ request['price_aux_limit'] = self.price_to_precision(symbol, price)
3555
+ postOnly = (postOnlyParam or (timeInForce == 'PO'))
3556
+ if (type != 'market') and (stopPrice is None):
3557
+ request['price'] = self.price_to_precision(symbol, price)
3558
+ # flag values may be summed to combine flags
3559
+ flags = 0
3560
+ if postOnly:
3561
+ flags = self.sum(flags, 4096)
3562
+ if reduceOnly:
3563
+ flags = self.sum(flags, 1024)
3564
+ if flags != 0:
3565
+ request['flags'] = flags
3566
+ if clientOrderId is not None:
3567
+ request['cid'] = clientOrderId
3568
+ leverage = self.safe_integer_2(params, 'leverage', 'lev')
3569
+ if leverage is not None:
3570
+ request['lev'] = leverage
3571
+ params = self.omit(params, ['triggerPrice', 'stopPrice', 'timeInForce', 'postOnly', 'reduceOnly', 'trailingAmount', 'clientOrderId', 'leverage'])
3572
+ response = await self.privatePostAuthWOrderUpdate(self.extend(request, params))
3573
+ #
3574
+ # [
3575
+ # 1706845376402,
3576
+ # "ou-req",
3577
+ # null,
3578
+ # null,
3579
+ # [
3580
+ # 139658969116,
3581
+ # null,
3582
+ # 1706843908637,
3583
+ # "tBTCUST",
3584
+ # 1706843908637,
3585
+ # 1706843908638,
3586
+ # 0.0002,
3587
+ # 0.0002,
3588
+ # "EXCHANGE LIMIT",
3589
+ # null,
3590
+ # null,
3591
+ # null,
3592
+ # 0,
3593
+ # "ACTIVE",
3594
+ # null,
3595
+ # null,
3596
+ # 35000,
3597
+ # 0,
3598
+ # 0,
3599
+ # 0,
3600
+ # null,
3601
+ # null,
3602
+ # null,
3603
+ # 0,
3604
+ # 0,
3605
+ # null,
3606
+ # null,
3607
+ # null,
3608
+ # "API>BFX",
3609
+ # null,
3610
+ # null,
3611
+ # {}
3612
+ # ],
3613
+ # null,
3614
+ # "SUCCESS",
3615
+ # "Submitting update to exchange limit buy order for 0.0002 BTC."
3616
+ # ]
3617
+ #
3618
+ status = self.safe_string(response, 6)
3619
+ if status != 'SUCCESS':
3620
+ errorCode = response[5]
3621
+ errorText = response[7]
3622
+ raise ExchangeError(self.id + ' ' + response[6] + ': ' + errorText + '(#' + errorCode + ')')
3623
+ order = self.safe_list(response, 4, [])
3624
+ newOrder = {'result': order}
3625
+ return self.parse_order(newOrder, market)