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