ccxt 4.4.35__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.
Files changed (55) hide show
  1. ccxt/__init__.py +5 -3
  2. ccxt/abstract/bitfinex.py +136 -65
  3. ccxt/abstract/bitfinex1.py +69 -0
  4. ccxt/abstract/bitopro.py +1 -0
  5. ccxt/abstract/bybit.py +15 -0
  6. ccxt/abstract/defx.py +69 -0
  7. ccxt/abstract/deribit.py +1 -0
  8. ccxt/abstract/gate.py +14 -0
  9. ccxt/abstract/gateio.py +14 -0
  10. ccxt/async_support/__init__.py +5 -3
  11. ccxt/async_support/base/exchange.py +1 -1
  12. ccxt/async_support/bitfinex.py +3005 -1084
  13. ccxt/async_support/bitfinex1.py +1704 -0
  14. ccxt/async_support/bitfinex2.py +18 -13
  15. ccxt/async_support/bitmex.py +103 -1
  16. ccxt/async_support/bitopro.py +21 -4
  17. ccxt/async_support/bitso.py +2 -1
  18. ccxt/async_support/bybit.py +21 -1
  19. ccxt/async_support/coinbase.py +86 -0
  20. ccxt/async_support/defx.py +1981 -0
  21. ccxt/async_support/deribit.py +27 -12
  22. ccxt/async_support/gate.py +15 -1
  23. ccxt/async_support/htx.py +11 -2
  24. ccxt/async_support/hyperliquid.py +124 -14
  25. ccxt/async_support/kraken.py +39 -41
  26. ccxt/async_support/paradex.py +2 -2
  27. ccxt/base/exchange.py +6 -2
  28. ccxt/bitfinex.py +3005 -1084
  29. ccxt/bitfinex1.py +1703 -0
  30. ccxt/bitfinex2.py +18 -13
  31. ccxt/bitmex.py +103 -1
  32. ccxt/bitopro.py +21 -4
  33. ccxt/bitso.py +2 -1
  34. ccxt/bybit.py +21 -1
  35. ccxt/coinbase.py +86 -0
  36. ccxt/defx.py +1980 -0
  37. ccxt/deribit.py +27 -12
  38. ccxt/gate.py +15 -1
  39. ccxt/htx.py +11 -2
  40. ccxt/hyperliquid.py +124 -14
  41. ccxt/kraken.py +39 -41
  42. ccxt/paradex.py +2 -2
  43. ccxt/pro/__init__.py +5 -3
  44. ccxt/pro/bitfinex.py +725 -274
  45. ccxt/pro/bitfinex1.py +635 -0
  46. ccxt/pro/defx.py +832 -0
  47. ccxt/pro/probit.py +1 -0
  48. ccxt/test/tests_async.py +15 -1
  49. ccxt/test/tests_sync.py +15 -1
  50. {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/METADATA +11 -10
  51. {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/RECORD +54 -47
  52. ccxt/abstract/bitfinex2.py +0 -140
  53. {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/LICENSE.txt +0 -0
  54. {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/WHEEL +0 -0
  55. {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/top_level.txt +0 -0
ccxt/pro/bitfinex.py CHANGED
@@ -4,13 +4,14 @@
4
4
  # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
5
5
 
6
6
  import ccxt.async_support
7
- from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
7
+ from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
8
8
  import hashlib
9
- from ccxt.base.types import Int, Order, OrderBook, Str, Ticker, Trade
9
+ from ccxt.base.types import Balances, Int, Order, OrderBook, Str, Ticker, Trade
10
10
  from ccxt.async_support.base.ws.client import Client
11
11
  from typing import List
12
12
  from ccxt.base.errors import ExchangeError
13
13
  from ccxt.base.errors import AuthenticationError
14
+ from ccxt.base.errors import ChecksumError
14
15
  from ccxt.base.precise import Precise
15
16
 
16
17
 
@@ -25,14 +26,16 @@ class bitfinex(ccxt.async_support.bitfinex):
25
26
  'watchOrderBook': True,
26
27
  'watchTrades': True,
27
28
  'watchTradesForSymbols': False,
28
- 'watchBalance': False, # for now
29
- 'watchOHLCV': False, # missing on the exchange side in v1
29
+ 'watchMyTrades': True,
30
+ 'watchBalance': True,
31
+ 'watchOHLCV': True,
32
+ 'watchOrders': True,
30
33
  },
31
34
  'urls': {
32
35
  'api': {
33
36
  'ws': {
34
- 'public': 'wss://api-pub.bitfinex.com/ws/1',
35
- 'private': 'wss://api.bitfinex.com/ws/1',
37
+ 'public': 'wss://api-pub.bitfinex.com/ws/2',
38
+ 'private': 'wss://api.bitfinex.com/ws/2',
36
39
  },
37
40
  },
38
41
  },
@@ -40,6 +43,7 @@ class bitfinex(ccxt.async_support.bitfinex):
40
43
  'watchOrderBook': {
41
44
  'prec': 'P0',
42
45
  'freq': 'F0',
46
+ 'checksum': True,
43
47
  },
44
48
  'ordersLimit': 1000,
45
49
  },
@@ -50,132 +54,361 @@ class bitfinex(ccxt.async_support.bitfinex):
50
54
  market = self.market(symbol)
51
55
  marketId = market['id']
52
56
  url = self.urls['api']['ws']['public']
57
+ client = self.client(url)
53
58
  messageHash = channel + ':' + marketId
54
- # channel = 'trades'
55
59
  request: dict = {
56
60
  'event': 'subscribe',
57
61
  'channel': channel,
58
62
  'symbol': marketId,
59
- 'messageHash': messageHash,
60
63
  }
61
- return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
64
+ result = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash, {'checksum': False})
65
+ checksum = self.safe_bool(self.options, 'checksum', True)
66
+ if checksum and not client.subscriptions[messageHash]['checksum'] and (channel == 'book'):
67
+ client.subscriptions[messageHash]['checksum'] = True
68
+ await client.send({
69
+ 'event': 'conf',
70
+ 'flags': 131072,
71
+ })
72
+ return result
62
73
 
63
- async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
74
+ async def subscribe_private(self, messageHash):
75
+ await self.load_markets()
76
+ await self.authenticate()
77
+ url = self.urls['api']['ws']['private']
78
+ return await self.watch(url, messageHash, None, 1)
79
+
80
+ async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
64
81
  """
65
- get the list of most recent trades for a particular symbol
82
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
83
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
84
+ :param str timeframe: the length of time each candle represents
85
+ :param int [since]: timestamp in ms of the earliest candle to fetch
86
+ :param int [limit]: the maximum amount of candles to fetch
87
+ :param dict [params]: extra parameters specific to the exchange API endpoint
88
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
89
+ """
90
+ await self.load_markets()
91
+ market = self.market(symbol)
92
+ symbol = market['symbol']
93
+ interval = self.safe_string(self.timeframes, timeframe, timeframe)
94
+ channel = 'candles'
95
+ key = 'trade:' + interval + ':' + market['id']
96
+ messageHash = channel + ':' + interval + ':' + market['id']
97
+ request: dict = {
98
+ 'event': 'subscribe',
99
+ 'channel': channel,
100
+ 'key': key,
101
+ }
102
+ url = self.urls['api']['ws']['public']
103
+ # not using subscribe here because self message has a different format
104
+ ohlcv = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
105
+ if self.newUpdates:
106
+ limit = ohlcv.getLimit(symbol, limit)
107
+ return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
66
108
 
67
- https://docs.bitfinex.com/v1/reference/ws-public-trades
109
+ def handle_ohlcv(self, client: Client, message, subscription):
110
+ #
111
+ # initial snapshot
112
+ # [
113
+ # 341527, # channel id
114
+ # [
115
+ # [
116
+ # 1654705860000, # timestamp
117
+ # 1802.6, # open
118
+ # 1800.3, # close
119
+ # 1802.8, # high
120
+ # 1800.3, # low
121
+ # 86.49588236 # volume
122
+ # ],
123
+ # [
124
+ # 1654705800000,
125
+ # 1803.6,
126
+ # 1802.6,
127
+ # 1804.9,
128
+ # 1802.3,
129
+ # 74.6348086
130
+ # ],
131
+ # [
132
+ # 1654705740000,
133
+ # 1802.5,
134
+ # 1803.2,
135
+ # 1804.4,
136
+ # 1802.4,
137
+ # 23.61801085
138
+ # ]
139
+ # ]
140
+ # ]
141
+ #
142
+ # update
143
+ # [
144
+ # 211171,
145
+ # [
146
+ # 1654705680000,
147
+ # 1801,
148
+ # 1802.4,
149
+ # 1802.9,
150
+ # 1800.4,
151
+ # 23.91911091
152
+ # ]
153
+ # ]
154
+ #
155
+ data = self.safe_value(message, 1, [])
156
+ ohlcvs = None
157
+ first = self.safe_value(data, 0)
158
+ if isinstance(first, list):
159
+ # snapshot
160
+ ohlcvs = data
161
+ else:
162
+ # update
163
+ ohlcvs = [data]
164
+ channel = self.safe_value(subscription, 'channel')
165
+ key = self.safe_string(subscription, 'key')
166
+ keyParts = key.split(':')
167
+ interval = self.safe_string(keyParts, 1)
168
+ marketId = key
169
+ marketId = marketId.replace('trade:', '')
170
+ marketId = marketId.replace(interval + ':', '')
171
+ market = self.safe_market(marketId)
172
+ timeframe = self.find_timeframe(interval)
173
+ symbol = market['symbol']
174
+ messageHash = channel + ':' + interval + ':' + marketId
175
+ self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
176
+ stored = self.safe_value(self.ohlcvs[symbol], timeframe)
177
+ if stored is None:
178
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
179
+ stored = ArrayCacheByTimestamp(limit)
180
+ self.ohlcvs[symbol][timeframe] = stored
181
+ ohlcvsLength = len(ohlcvs)
182
+ for i in range(0, ohlcvsLength):
183
+ ohlcv = ohlcvs[ohlcvsLength - i - 1]
184
+ parsed = self.parse_ohlcv(ohlcv, market)
185
+ stored.append(parsed)
186
+ client.resolve(stored, messageHash)
68
187
 
188
+ async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
189
+ """
190
+ get the list of most recent trades for a particular symbol
69
191
  :param str symbol: unified symbol of the market to fetch trades for
70
192
  :param int [since]: timestamp in ms of the earliest trade to fetch
71
193
  :param int [limit]: the maximum amount of trades to fetch
72
194
  :param dict [params]: extra parameters specific to the exchange API endpoint
73
195
  :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
74
196
  """
75
- await self.load_markets()
76
- symbol = self.symbol(symbol)
77
197
  trades = await self.subscribe('trades', symbol, params)
78
198
  if self.newUpdates:
79
199
  limit = trades.getLimit(symbol, limit)
80
200
  return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
81
201
 
202
+ async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
203
+ """
204
+ watches information on multiple trades made by the user
205
+ :param str symbol: unified market symbol of the market trades were made in
206
+ :param int [since]: the earliest time in ms to fetch trades for
207
+ :param int [limit]: the maximum number of trade structures to retrieve
208
+ :param dict [params]: extra parameters specific to the exchange API endpoint
209
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
210
+ """
211
+ await self.load_markets()
212
+ messageHash = 'myTrade'
213
+ if symbol is not None:
214
+ market = self.market(symbol)
215
+ messageHash += ':' + market['id']
216
+ trades = await self.subscribe_private(messageHash)
217
+ if self.newUpdates:
218
+ limit = trades.getLimit(symbol, limit)
219
+ return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
220
+
82
221
  async def watch_ticker(self, symbol: str, params={}) -> Ticker:
83
222
  """
84
223
  watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
85
-
86
- https://docs.bitfinex.com/v1/reference/ws-public-ticker
87
-
88
224
  :param str symbol: unified symbol of the market to fetch the ticker for
89
225
  :param dict [params]: extra parameters specific to the exchange API endpoint
90
226
  :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
91
227
  """
92
228
  return await self.subscribe('ticker', symbol, params)
93
229
 
94
- def handle_trades(self, client: Client, message, subscription):
95
- #
96
- # initial snapshot
230
+ def handle_my_trade(self, client: Client, message, subscription={}):
97
231
  #
232
+ # trade execution
233
+ # [
234
+ # 0,
235
+ # "te", # or tu
98
236
  # [
99
- # 2,
100
- # [
101
- # [null, 1580565020, 9374.9, 0.005],
102
- # [null, 1580565004, 9374.9, 0.005],
103
- # [null, 1580565003, 9374.9, 0.005],
104
- # ]
237
+ # 1133411090,
238
+ # "tLTCUST",
239
+ # 1655110144598,
240
+ # 97084883506,
241
+ # 0.1,
242
+ # 42.821,
243
+ # "EXCHANGE MARKET",
244
+ # 42.799,
245
+ # -1,
246
+ # null,
247
+ # null,
248
+ # 1655110144596
105
249
  # ]
250
+ # ]
251
+ #
252
+ name = 'myTrade'
253
+ data = self.safe_value(message, 2)
254
+ trade = self.parse_ws_trade(data)
255
+ symbol = trade['symbol']
256
+ market = self.market(symbol)
257
+ messageHash = name + ':' + market['id']
258
+ if self.myTrades is None:
259
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
260
+ self.myTrades = ArrayCacheBySymbolById(limit)
261
+ tradesArray = self.myTrades
262
+ tradesArray.append(trade)
263
+ self.myTrades = tradesArray
264
+ # generic subscription
265
+ client.resolve(tradesArray, name)
266
+ # specific subscription
267
+ client.resolve(tradesArray, messageHash)
268
+
269
+ def handle_trades(self, client: Client, message, subscription):
106
270
  #
107
- # when a trade does not have an id yet
271
+ # initial snapshot
108
272
  #
109
- # # channel id, update type, seq, time, price, amount
110
- # [2, "te", "28462857-BTCUSD", 1580565041, 9374.9, 0.005],
273
+ # [
274
+ # 188687, # channel id
275
+ # [
276
+ # [1128060675, 1654701572690, 0.00217533, 1815.3], # id, mts, amount, price
277
+ # [1128060665, 1654701551231, -0.00280472, 1814.1],
278
+ # [1128060664, 1654701550996, -0.00364444, 1814.1],
279
+ # [1128060656, 1654701527730, -0.00265203, 1814.2],
280
+ # [1128060647, 1654701505193, 0.00262395, 1815.2],
281
+ # [1128060642, 1654701484656, -0.13411443, 1816],
282
+ # [1128060641, 1654701484656, -0.00088557, 1816],
283
+ # [1128060639, 1654701478326, -0.002, 1816],
284
+ # ]
285
+ # ]
286
+ # update
111
287
  #
112
- # when a trade already has an id
288
+ # [
289
+ # 360141,
290
+ # "te",
291
+ # [
292
+ # 1128060969, # id
293
+ # 1654702500098, # mts
294
+ # 0.00325131, # amount positive buy, negative sell
295
+ # 1818.5, # price
296
+ # ],
297
+ # ]
113
298
  #
114
- # # channel id, update type, seq, trade id, time, price, amount
115
- # [2, "tu", "28462857-BTCUSD", 413357662, 1580565041, 9374.9, 0.005]
116
299
  #
117
300
  channel = self.safe_value(subscription, 'channel')
118
- marketId = self.safe_string(subscription, 'pair')
301
+ marketId = self.safe_string(subscription, 'symbol')
302
+ market = self.safe_market(marketId)
119
303
  messageHash = channel + ':' + marketId
120
304
  tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
121
- market = self.safe_market(marketId)
122
305
  symbol = market['symbol']
123
- data = self.safe_value(message, 1)
124
306
  stored = self.safe_value(self.trades, symbol)
125
307
  if stored is None:
126
308
  stored = ArrayCache(tradesLimit)
127
309
  self.trades[symbol] = stored
128
- if isinstance(data, list):
129
- trades = self.parse_trades(data, market)
130
- for i in range(0, len(trades)):
131
- stored.append(trades[i])
310
+ messageLength = len(message)
311
+ if messageLength == 2:
312
+ # initial snapshot
313
+ trades = self.safe_list(message, 1, [])
314
+ # needs to be reversed to make chronological order
315
+ length = len(trades)
316
+ for i in range(0, length):
317
+ index = length - i - 1
318
+ parsed = self.parse_ws_trade(trades[index], market)
319
+ stored.append(parsed)
132
320
  else:
133
- second = self.safe_string(message, 1)
134
- if second != 'tu':
321
+ # update
322
+ type = self.safe_string(message, 1)
323
+ if type == 'tu':
324
+ # don't resolve for a duplicate update
325
+ # since te and tu updates are duplicated on the public stream
135
326
  return
136
- trade = self.parse_trade(message, market)
137
- stored.append(trade)
327
+ trade = self.safe_value(message, 2, [])
328
+ parsed = self.parse_ws_trade(trade, market)
329
+ stored.append(parsed)
138
330
  client.resolve(stored, messageHash)
139
331
 
140
- def parse_trade(self, trade, market=None) -> Trade:
141
- #
142
- # snapshot trade
332
+ def parse_ws_trade(self, trade, market=None):
143
333
  #
144
- # # null, time, price, amount
145
- # [null, 1580565020, 9374.9, 0.005],
334
+ # [
335
+ # 1128060969, # id
336
+ # 1654702500098, # mts
337
+ # 0.00325131, # amount positive buy, negative sell
338
+ # 1818.5, # price
339
+ # ]
146
340
  #
147
- # when a trade does not have an id yet
341
+ # trade execution
148
342
  #
149
- # # channel id, update type, seq, time, price, amount
150
- # [2, "te", "28462857-BTCUSD", 1580565041, 9374.9, 0.005],
343
+ # [
344
+ # 1133411090, # id
345
+ # "tLTCUST", # symbol
346
+ # 1655110144598, # create ms
347
+ # 97084883506, # order id
348
+ # 0.1, # amount
349
+ # 42.821, # price
350
+ # "EXCHANGE MARKET", # order type
351
+ # 42.799, # order price
352
+ # -1, # maker
353
+ # null, # fee
354
+ # null, # fee currency
355
+ # 1655110144596 # cid
356
+ # ]
151
357
  #
152
- # when a trade already has an id
358
+ # trade update
153
359
  #
154
- # # channel id, update type, seq, trade id, time, price, amount
155
- # [2, "tu", "28462857-BTCUSD", 413357662, 1580565041, 9374.9, 0.005]
360
+ # [
361
+ # 1133411090,
362
+ # "tLTCUST",
363
+ # 1655110144598,
364
+ # 97084883506,
365
+ # 0.1,
366
+ # 42.821,
367
+ # "EXCHANGE MARKET",
368
+ # 42.799,
369
+ # -1,
370
+ # -0.0002,
371
+ # "LTC",
372
+ # 1655110144596
373
+ # ]
156
374
  #
157
- if not isinstance(trade, list):
158
- return super(bitfinex, self).parse_trade(trade, market)
159
- tradeLength = len(trade)
160
- event = self.safe_string(trade, 1)
161
- id = None
162
- if event == 'tu':
163
- id = self.safe_string(trade, tradeLength - 4)
164
- timestamp = self.safe_timestamp(trade, tradeLength - 3)
165
- price = self.safe_string(trade, tradeLength - 2)
166
- amount = self.safe_string(trade, tradeLength - 1)
375
+ numFields = len(trade)
376
+ isPublic = numFields <= 8
377
+ marketId = self.safe_string(trade, 1) if (not isPublic) else None
378
+ market = self.safe_market(marketId, market)
379
+ createdKey = 1 if isPublic else 2
380
+ priceKey = 3 if isPublic else 5
381
+ amountKey = 2 if isPublic else 4
382
+ marketId = market['id']
383
+ type = self.safe_string(trade, 6)
384
+ if type is not None:
385
+ if type.find('LIMIT') > -1:
386
+ type = 'limit'
387
+ elif type.find('MARKET') > -1:
388
+ type = 'market'
389
+ orderId = self.safe_string(trade, 3) if (not isPublic) else None
390
+ id = self.safe_string(trade, 0)
391
+ timestamp = self.safe_integer(trade, createdKey)
392
+ price = self.safe_string(trade, priceKey)
393
+ amountString = self.safe_string(trade, amountKey)
394
+ amount = self.parse_number(Precise.string_abs(amountString))
167
395
  side = None
168
396
  if amount is not None:
169
- side = 'buy' if Precise.string_gt(amount, '0') else 'sell'
170
- amount = Precise.string_abs(amount)
171
- seq = self.safe_string(trade, 2)
172
- parts = seq.split('-')
173
- marketId = self.safe_string(parts, 1)
174
- if marketId is not None:
175
- marketId = marketId.replace('t', '')
397
+ side = 'buy' if Precise.string_gt(amountString, '0') else 'sell'
176
398
  symbol = self.safe_symbol(marketId, market)
399
+ feeValue = self.safe_string(trade, 9)
400
+ fee = None
401
+ if feeValue is not None:
402
+ currencyId = self.safe_string(trade, 10)
403
+ code = self.safe_currency_code(currencyId)
404
+ fee = {
405
+ 'cost': feeValue,
406
+ 'currency': code,
407
+ }
408
+ maker = self.safe_integer(trade, 8)
177
409
  takerOrMaker = None
178
- orderId = None
410
+ if maker is not None:
411
+ takerOrMaker = 'taker' if (maker == -1) else 'maker'
179
412
  return self.safe_trade({
180
413
  'info': trade,
181
414
  'timestamp': timestamp,
@@ -183,19 +416,20 @@ class bitfinex(ccxt.async_support.bitfinex):
183
416
  'symbol': symbol,
184
417
  'id': id,
185
418
  'order': orderId,
186
- 'type': None,
419
+ 'type': type,
187
420
  'takerOrMaker': takerOrMaker,
188
421
  'side': side,
189
422
  'price': price,
190
423
  'amount': amount,
191
424
  'cost': None,
192
- 'fee': None,
193
- })
425
+ 'fee': fee,
426
+ }, market)
194
427
 
195
428
  def handle_ticker(self, client: Client, message, subscription):
196
429
  #
430
+ # [
431
+ # 340432, # channel ID
197
432
  # [
198
- # 2, # 0 CHANNEL_ID integer Channel ID
199
433
  # 236.62, # 1 BID float Price of last highest bid
200
434
  # 9.0029, # 2 BID_SIZE float Size of the last highest bid
201
435
  # 236.88, # 3 ASK float Price of last lowest ask
@@ -207,47 +441,63 @@ class bitfinex(ccxt.async_support.bitfinex):
207
441
  # 250.01, # 9 HIGH float Daily high
208
442
  # 220.05, # 10 LOW float Daily low
209
443
  # ]
444
+ # ]
210
445
  #
211
- marketId = self.safe_string(subscription, 'pair')
446
+ ticker = self.safe_value(message, 1)
447
+ marketId = self.safe_string(subscription, 'symbol')
448
+ market = self.safe_market(marketId)
212
449
  symbol = self.safe_symbol(marketId)
450
+ parsed = self.parse_ws_ticker(ticker, market)
213
451
  channel = 'ticker'
214
452
  messageHash = channel + ':' + marketId
215
- last = self.safe_string(message, 7)
216
- change = self.safe_string(message, 5)
217
- open = None
218
- if (last is not None) and (change is not None):
219
- open = Precise.string_sub(last, change)
220
- result = self.safe_ticker({
453
+ self.tickers[symbol] = parsed
454
+ client.resolve(parsed, messageHash)
455
+
456
+ def parse_ws_ticker(self, ticker, market=None):
457
+ #
458
+ # [
459
+ # 236.62, # 1 BID float Price of last highest bid
460
+ # 9.0029, # 2 BID_SIZE float Size of the last highest bid
461
+ # 236.88, # 3 ASK float Price of last lowest ask
462
+ # 7.1138, # 4 ASK_SIZE float Size of the last lowest ask
463
+ # -1.02, # 5 DAILY_CHANGE float Amount that the last price has changed since yesterday
464
+ # 0, # 6 DAILY_CHANGE_PERC float Amount that the price has changed expressed in percentage terms
465
+ # 236.52, # 7 LAST_PRICE float Price of the last trade.
466
+ # 5191.36754297, # 8 VOLUME float Daily volume
467
+ # 250.01, # 9 HIGH float Daily high
468
+ # 220.05, # 10 LOW float Daily low
469
+ # ]
470
+ #
471
+ market = self.safe_market(None, market)
472
+ symbol = market['symbol']
473
+ last = self.safe_string(ticker, 6)
474
+ change = self.safe_string(ticker, 4)
475
+ return self.safe_ticker({
221
476
  'symbol': symbol,
222
477
  'timestamp': None,
223
478
  'datetime': None,
224
- 'high': self.safe_string(message, 9),
225
- 'low': self.safe_string(message, 10),
226
- 'bid': self.safe_string(message, 1),
227
- 'bidVolume': None,
228
- 'ask': self.safe_string(message, 3),
229
- 'askVolume': None,
479
+ 'high': self.safe_string(ticker, 8),
480
+ 'low': self.safe_string(ticker, 9),
481
+ 'bid': self.safe_string(ticker, 0),
482
+ 'bidVolume': self.safe_string(ticker, 1),
483
+ 'ask': self.safe_string(ticker, 2),
484
+ 'askVolume': self.safe_string(ticker, 3),
230
485
  'vwap': None,
231
- 'open': self.parse_number(open),
232
- 'close': self.parse_number(last),
233
- 'last': self.parse_number(last),
486
+ 'open': None,
487
+ 'close': last,
488
+ 'last': last,
234
489
  'previousClose': None,
235
- 'change': self.parse_number(change),
236
- 'percentage': self.safe_string(message, 6),
490
+ 'change': change,
491
+ 'percentage': self.safe_string(ticker, 5),
237
492
  'average': None,
238
- 'baseVolume': self.safe_string(message, 8),
493
+ 'baseVolume': self.safe_string(ticker, 7),
239
494
  'quoteVolume': None,
240
- 'info': message,
241
- })
242
- self.tickers[symbol] = result
243
- client.resolve(result, messageHash)
495
+ 'info': ticker,
496
+ }, market)
244
497
 
245
498
  async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
246
499
  """
247
500
  watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
248
-
249
- https://docs.bitfinex.com/v1/reference/ws-public-order-books
250
-
251
501
  :param str symbol: unified symbol of the market to fetch the order book for
252
502
  :param int [limit]: the maximum amount of order book entries to return
253
503
  :param dict [params]: extra parameters specific to the exchange API endpoint
@@ -260,13 +510,11 @@ class bitfinex(ccxt.async_support.bitfinex):
260
510
  prec = self.safe_string(options, 'prec', 'P0')
261
511
  freq = self.safe_string(options, 'freq', 'F0')
262
512
  request: dict = {
263
- # "event": "subscribe", # added in subscribe()
264
- # "channel": channel, # added in subscribe()
265
- # "symbol": marketId, # added in subscribe()
266
513
  'prec': prec, # string, level of price aggregation, 'P0', 'P1', 'P2', 'P3', 'P4', default P0
267
514
  'freq': freq, # string, frequency of updates 'F0' = realtime, 'F1' = 2 seconds, default is 'F0'
268
- 'len': limit, # string, number of price points, '25', '100', default = '25'
269
515
  }
516
+ if limit is not None:
517
+ request['len'] = limit # string, number of price points, '25', '100', default = '25'
270
518
  orderbook = await self.subscribe('book', symbol, self.deep_extend(request, params))
271
519
  return orderbook.limit()
272
520
 
@@ -289,20 +537,22 @@ class bitfinex(ccxt.async_support.bitfinex):
289
537
  # subsequent updates
290
538
  #
291
539
  # [
292
- # 30, # channel id
293
- # 9339.9, # price
294
- # 0, # count
295
- # -1, # size > 0 = bid, size < 0 = ask
540
+ # 358169, # channel id
541
+ # [
542
+ # 1807.1, # price
543
+ # 0, # cound
544
+ # 1 # size
545
+ # ]
296
546
  # ]
297
547
  #
298
- marketId = self.safe_string(subscription, 'pair')
548
+ marketId = self.safe_string(subscription, 'symbol')
299
549
  symbol = self.safe_symbol(marketId)
300
550
  channel = 'book'
301
551
  messageHash = channel + ':' + marketId
302
552
  prec = self.safe_string(subscription, 'prec', 'P0')
303
553
  isRaw = (prec == 'R0')
304
554
  # if it is an initial snapshot
305
- if isinstance(message[1], list):
555
+ if not (symbol in self.orderbooks):
306
556
  limit = self.safe_integer(subscription, 'len')
307
557
  if isRaw:
308
558
  # raw order books
@@ -315,57 +565,211 @@ class bitfinex(ccxt.async_support.bitfinex):
315
565
  deltas = message[1]
316
566
  for i in range(0, len(deltas)):
317
567
  delta = deltas[i]
318
- id = self.safe_string(delta, 0)
319
- price = self.safe_float(delta, 1)
320
- delta2Value = delta[2]
321
- size = -delta2Value if (delta2Value < 0) else delta2Value
322
- side = 'asks' if (delta2Value < 0) else 'bids'
568
+ delta2 = delta[2]
569
+ size = -delta2 if (delta2 < 0) else delta2
570
+ side = 'asks' if (delta2 < 0) else 'bids'
323
571
  bookside = orderbook[side]
324
- bookside.storeArray([price, size, id])
572
+ idString = self.safe_string(delta, 0)
573
+ price = self.safe_float(delta, 1)
574
+ bookside.storeArray([price, size, idString])
325
575
  else:
326
576
  deltas = message[1]
327
577
  for i in range(0, len(deltas)):
328
578
  delta = deltas[i]
329
- delta2 = delta[2]
330
- size = -delta2 if (delta2 < 0) else delta2
331
- side = 'asks' if (delta2 < 0) else 'bids'
332
- countedBookSide = orderbook[side]
333
- countedBookSide.storeArray([delta[0], size, delta[1]])
579
+ amount = self.safe_number(delta, 2)
580
+ counter = self.safe_number(delta, 1)
581
+ price = self.safe_number(delta, 0)
582
+ size = -amount if (amount < 0) else amount
583
+ side = 'asks' if (amount < 0) else 'bids'
584
+ bookside = orderbook[side]
585
+ bookside.storeArray([price, size, counter])
586
+ orderbook['symbol'] = symbol
334
587
  client.resolve(orderbook, messageHash)
335
588
  else:
336
589
  orderbook = self.orderbooks[symbol]
590
+ deltas = message[1]
591
+ orderbookItem = self.orderbooks[symbol]
337
592
  if isRaw:
338
- id = self.safe_string(message, 1)
339
- price = self.safe_string(message, 2)
340
- message3 = message[3]
341
- size = -message3 if (message3 < 0) else message3
342
- side = 'asks' if (message3 < 0) else 'bids'
343
- bookside = orderbook[side]
593
+ price = self.safe_string(deltas, 1)
594
+ deltas2 = deltas[2]
595
+ size = -deltas2 if (deltas2 < 0) else deltas2
596
+ side = 'asks' if (deltas2 < 0) else 'bids'
597
+ bookside = orderbookItem[side]
344
598
  # price = 0 means that you have to remove the order from your book
345
599
  amount = size if Precise.string_gt(price, '0') else '0'
346
- bookside.storeArray([self.parse_number(price), self.parse_number(amount), id])
600
+ idString = self.safe_string(deltas, 0)
601
+ bookside.storeArray([self.parse_number(price), self.parse_number(amount), idString])
347
602
  else:
348
- message3Value = message[3]
349
- size = -message3Value if (message3Value < 0) else message3Value
350
- side = 'asks' if (message3Value < 0) else 'bids'
351
- countedBookSide = orderbook[side]
352
- countedBookSide.storeArray([message[1], size, message[2]])
603
+ amount = self.safe_string(deltas, 2)
604
+ counter = self.safe_string(deltas, 1)
605
+ price = self.safe_string(deltas, 0)
606
+ size = Precise.string_neg(amount) if Precise.string_lt(amount, '0') else amount
607
+ side = 'asks' if Precise.string_lt(amount, '0') else 'bids'
608
+ bookside = orderbookItem[side]
609
+ bookside.storeArray([self.parse_number(price), self.parse_number(size), self.parse_number(counter)])
353
610
  client.resolve(orderbook, messageHash)
354
611
 
355
- def handle_heartbeat(self, client: Client, message):
612
+ def handle_checksum(self, client: Client, message, subscription):
613
+ #
614
+ # [173904, "cs", -890884919]
615
+ #
616
+ marketId = self.safe_string(subscription, 'symbol')
617
+ symbol = self.safe_symbol(marketId)
618
+ channel = 'book'
619
+ messageHash = channel + ':' + marketId
620
+ book = self.safe_value(self.orderbooks, symbol)
621
+ if book is None:
622
+ return
623
+ depth = 25 # covers the first 25 bids and asks
624
+ stringArray = []
625
+ bids = book['bids']
626
+ asks = book['asks']
627
+ prec = self.safe_string(subscription, 'prec', 'P0')
628
+ isRaw = (prec == 'R0')
629
+ idToCheck = 2 if isRaw else 0
630
+ # pepperoni pizza from bitfinex
631
+ for i in range(0, depth):
632
+ bid = self.safe_value(bids, i)
633
+ ask = self.safe_value(asks, i)
634
+ if bid is not None:
635
+ stringArray.append(self.number_to_string(bids[i][idToCheck]))
636
+ stringArray.append(self.number_to_string(bids[i][1]))
637
+ if ask is not None:
638
+ stringArray.append(self.number_to_string(asks[i][idToCheck]))
639
+ aski1 = asks[i][1]
640
+ stringArray.append(self.number_to_string(-aski1))
641
+ payload = ':'.join(stringArray)
642
+ localChecksum = self.crc32(payload, True)
643
+ responseChecksum = self.safe_integer(message, 2)
644
+ if responseChecksum != localChecksum:
645
+ del client.subscriptions[messageHash]
646
+ del self.orderbooks[symbol]
647
+ checksum = self.handle_option('watchOrderBook', 'checksum', True)
648
+ if checksum:
649
+ error = ChecksumError(self.id + ' ' + self.orderbook_checksum_message(symbol))
650
+ client.reject(error, messageHash)
651
+
652
+ async def watch_balance(self, params={}) -> Balances:
653
+ """
654
+ watch balance and get the amount of funds available for trading or funds locked in orders
655
+ :param dict [params]: extra parameters specific to the exchange API endpoint
656
+ :param str [params.type]: spot or contract if not provided self.options['defaultType'] is used
657
+ :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
658
+ """
659
+ await self.load_markets()
660
+ balanceType = self.safe_string(params, 'wallet', 'exchange') # exchange, margin
661
+ params = self.omit(params, 'wallet')
662
+ messageHash = 'balance:' + balanceType
663
+ return await self.subscribe_private(messageHash)
664
+
665
+ def handle_balance(self, client: Client, message, subscription):
666
+ #
667
+ # snapshot(exchange + margin together)
668
+ # [
669
+ # 0,
670
+ # "ws",
671
+ # [
672
+ # [
673
+ # "exchange",
674
+ # "LTC",
675
+ # 0.05479727,
676
+ # 0,
677
+ # null,
678
+ # "Trading fees for 0.05 LTC(LTCUST) @ 51.872 on BFX(0.2%)",
679
+ # null,
680
+ # ]
681
+ # [
682
+ # "margin",
683
+ # "USTF0",
684
+ # 11.960650700086292,
685
+ # 0,
686
+ # null,
687
+ # "Trading fees for 0.1 LTCF0(LTCF0:USTF0) @ 51.844 on BFX(0.065%)",
688
+ # null,
689
+ # ],
690
+ # ],
691
+ # ]
692
+ #
693
+ # spot
694
+ # [
695
+ # 0,
696
+ # "wu",
697
+ # [
698
+ # "exchange",
699
+ # "LTC", # currency
700
+ # 0.06729727, # wallet balance
701
+ # 0, # unsettled balance
702
+ # 0.06729727, # available balance might be null
703
+ # "Exchange 0.4 LTC for UST @ 65.075",
704
+ # {
705
+ # "reason": "TRADE",
706
+ # "order_id": 96596397973,
707
+ # "order_id_oppo": 96596632735,
708
+ # "trade_price": "65.075",
709
+ # "trade_amount": "-0.4",
710
+ # "order_cid": 1654636218766,
711
+ # "order_gid": null
712
+ # }
713
+ # ]
714
+ # ]
356
715
  #
357
- # every second(approx) if no other updates are sent
716
+ # margin
358
717
  #
359
- # {"event": "heartbeat"}
718
+ # [
719
+ # "margin",
720
+ # "USTF0",
721
+ # 11.960650700086292, # total
722
+ # 0,
723
+ # 6.776250700086292, # available
724
+ # "Trading fees for 0.1 LTCF0(LTCF0:USTF0) @ 51.844 on BFX(0.065%)",
725
+ # null
726
+ # ]
360
727
  #
361
- event = self.safe_string(message, 'event')
362
- client.resolve(message, event)
728
+ updateType = self.safe_value(message, 1)
729
+ data = None
730
+ if updateType == 'ws':
731
+ data = self.safe_value(message, 2)
732
+ else:
733
+ data = [self.safe_value(message, 2)]
734
+ updatedTypes: dict = {}
735
+ for i in range(0, len(data)):
736
+ rawBalance = data[i]
737
+ currencyId = self.safe_string(rawBalance, 1)
738
+ code = self.safe_currency_code(currencyId)
739
+ balance = self.parse_ws_balance(rawBalance)
740
+ balanceType = self.safe_string(rawBalance, 0)
741
+ oldBalance = self.safe_value(self.balance, balanceType, {})
742
+ oldBalance[code] = balance
743
+ oldBalance['info'] = message
744
+ self.balance[balanceType] = self.safe_balance(oldBalance)
745
+ updatedTypes[balanceType] = True
746
+ updatesKeys = list(updatedTypes.keys())
747
+ for i in range(0, len(updatesKeys)):
748
+ type = updatesKeys[i]
749
+ messageHash = 'balance:' + type
750
+ client.resolve(self.balance[type], messageHash)
363
751
 
364
- def handle_system_status(self, client: Client, message):
752
+ def parse_ws_balance(self, balance):
365
753
  #
366
- # todo: answer the question whether handleSystemStatus should be renamed
367
- # and unified for any usage pattern that
368
- # involves system status and maintenance updates
754
+ # [
755
+ # "exchange",
756
+ # "LTC",
757
+ # 0.05479727, # balance
758
+ # 0,
759
+ # null, # available null if not calculated yet
760
+ # "Trading fees for 0.05 LTC(LTCUST) @ 51.872 on BFX(0.2%)",
761
+ # null,
762
+ # ]
763
+ #
764
+ totalBalance = self.safe_string(balance, 2)
765
+ availableBalance = self.safe_string(balance, 4)
766
+ account = self.account()
767
+ if availableBalance is not None:
768
+ account['free'] = availableBalance
769
+ account['total'] = totalBalance
770
+ return account
771
+
772
+ def handle_system_status(self, client: Client, message):
369
773
  #
370
774
  # {
371
775
  # "event": "info",
@@ -396,54 +800,42 @@ class bitfinex(ccxt.async_support.bitfinex):
396
800
  async def authenticate(self, params={}):
397
801
  url = self.urls['api']['ws']['private']
398
802
  client = self.client(url)
399
- future = client.future('authenticated')
400
- method = 'auth'
401
- authenticated = self.safe_value(client.subscriptions, method)
803
+ messageHash = 'authenticated'
804
+ future = client.future(messageHash)
805
+ authenticated = self.safe_value(client.subscriptions, messageHash)
402
806
  if authenticated is None:
403
807
  nonce = self.milliseconds()
404
808
  payload = 'AUTH' + str(nonce)
405
809
  signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha384, 'hex')
810
+ event = 'auth'
406
811
  request: dict = {
407
812
  'apiKey': self.apiKey,
408
813
  'authSig': signature,
409
814
  'authNonce': nonce,
410
815
  'authPayload': payload,
411
- 'event': method,
412
- 'filter': [
413
- 'trading',
414
- 'wallet',
415
- ],
816
+ 'event': event,
416
817
  }
417
- self.spawn(self.watch, url, method, request, 1)
818
+ message = self.extend(request, params)
819
+ self.watch(url, messageHash, message, messageHash)
418
820
  return await future
419
821
 
420
822
  def handle_authentication_message(self, client: Client, message):
823
+ messageHash = 'authenticated'
421
824
  status = self.safe_string(message, 'status')
422
825
  if status == 'OK':
423
826
  # we resolve the future here permanently so authentication only happens once
424
- future = self.safe_value(client.futures, 'authenticated')
827
+ future = self.safe_value(client.futures, messageHash)
425
828
  future.resolve(True)
426
829
  else:
427
830
  error = AuthenticationError(self.json(message))
428
- client.reject(error, 'authenticated')
831
+ client.reject(error, messageHash)
429
832
  # allows further authentication attempts
430
- method = self.safe_string(message, 'event')
431
- if method in client.subscriptions:
432
- del client.subscriptions[method]
433
-
434
- async def watch_order(self, id, symbol: Str = None, params={}):
435
- await self.load_markets()
436
- url = self.urls['api']['ws']['private']
437
- await self.authenticate()
438
- return await self.watch(url, id, None, 1)
833
+ if messageHash in client.subscriptions:
834
+ del client.subscriptions[messageHash]
439
835
 
440
836
  async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
441
837
  """
442
838
  watches information on multiple orders made by the user
443
-
444
- https://docs.bitfinex.com/v1/reference/ws-auth-order-updates
445
- https://docs.bitfinex.com/v1/reference/ws-auth-order-snapshots
446
-
447
839
  :param str symbol: unified market symbol of the market orders were made in
448
840
  :param int [since]: the earliest time in ms to fetch orders for
449
841
  :param int [limit]: the maximum number of order structures to retrieve
@@ -451,115 +843,161 @@ class bitfinex(ccxt.async_support.bitfinex):
451
843
  :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
452
844
  """
453
845
  await self.load_markets()
454
- await self.authenticate()
846
+ messageHash = 'orders'
455
847
  if symbol is not None:
456
- symbol = self.symbol(symbol)
457
- url = self.urls['api']['ws']['private']
458
- orders = await self.watch(url, 'os', None, 1)
848
+ market = self.market(symbol)
849
+ messageHash += ':' + market['id']
850
+ orders = await self.subscribe_private(messageHash)
459
851
  if self.newUpdates:
460
852
  limit = orders.getLimit(symbol, limit)
461
853
  return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
462
854
 
463
855
  def handle_orders(self, client: Client, message, subscription):
464
856
  #
465
- # order snapshot
466
- #
467
- # [
468
- # 0,
469
- # "os",
470
- # [
471
- # [
472
- # 45287766631,
473
- # "ETHUST",
474
- # -0.07,
475
- # -0.07,
476
- # "EXCHANGE LIMIT",
477
- # "ACTIVE",
478
- # 210,
479
- # 0,
480
- # "2020-05-16T13:17:46Z",
481
- # 0,
482
- # 0,
483
- # 0
484
- # ]
485
- # ]
486
- # ]
487
- #
488
- # order cancel
489
- #
490
- # [
491
- # 0,
492
- # "oc",
493
- # [
494
- # 45287766631,
495
- # "ETHUST",
496
- # -0.07,
497
- # -0.07,
498
- # "EXCHANGE LIMIT",
499
- # "CANCELED",
500
- # 210,
501
- # 0,
502
- # "2020-05-16T13:17:46Z",
503
- # 0,
504
- # 0,
505
- # 0,
506
- # ]
507
- # ]
857
+ # limit order
858
+ # [
859
+ # 0,
860
+ # "on", # ou or oc
861
+ # [
862
+ # 96923856256, # order id
863
+ # null, # gid
864
+ # 1655029337026, # cid
865
+ # "tLTCUST", # symbol
866
+ # 1655029337027, # created timestamp
867
+ # 1655029337029, # updated timestamp
868
+ # 0.1, # amount
869
+ # 0.1, # amount_orig
870
+ # "EXCHANGE LIMIT", # order type
871
+ # null, # type_prev
872
+ # null, # mts_tif
873
+ # null, # placeholder
874
+ # 0, # flags
875
+ # "ACTIVE", # status
876
+ # null,
877
+ # null,
878
+ # 30, # price
879
+ # 0, # price average
880
+ # 0, # price_trailling
881
+ # 0, # price_aux_limit
882
+ # null,
883
+ # null,
884
+ # null,
885
+ # 0, # notify
886
+ # 0,
887
+ # null,
888
+ # null,
889
+ # null,
890
+ # "BFX",
891
+ # null,
892
+ # null,
893
+ # ]
894
+ # ]
508
895
  #
509
896
  data = self.safe_value(message, 2, [])
510
897
  messageType = self.safe_string(message, 1)
898
+ if self.orders is None:
899
+ limit = self.safe_integer(self.options, 'ordersLimit', 1000)
900
+ self.orders = ArrayCacheBySymbolById(limit)
901
+ orders = self.orders
902
+ symbolIds: dict = {}
511
903
  if messageType == 'os':
904
+ snapshotLength = len(data)
905
+ if snapshotLength == 0:
906
+ return
512
907
  for i in range(0, len(data)):
513
908
  value = data[i]
514
- self.handle_order(client, value)
909
+ parsed = self.parse_ws_order(value)
910
+ symbol = parsed['symbol']
911
+ symbolIds[symbol] = True
912
+ orders.append(parsed)
515
913
  else:
516
- self.handle_order(client, data)
517
- if self.orders is not None:
518
- client.resolve(self.orders, 'os')
914
+ parsed = self.parse_ws_order(data)
915
+ orders.append(parsed)
916
+ symbol = parsed['symbol']
917
+ symbolIds[symbol] = True
918
+ name = 'orders'
919
+ client.resolve(self.orders, name)
920
+ keys = list(symbolIds.keys())
921
+ for i in range(0, len(keys)):
922
+ symbol = keys[i]
923
+ market = self.market(symbol)
924
+ messageHash = name + ':' + market['id']
925
+ client.resolve(self.orders, messageHash)
519
926
 
520
927
  def parse_ws_order_status(self, status):
521
928
  statuses: dict = {
522
929
  'ACTIVE': 'open',
523
930
  'CANCELED': 'canceled',
931
+ 'EXECUTED': 'closed',
932
+ 'PARTIALLY': 'open',
524
933
  }
525
934
  return self.safe_string(statuses, status, status)
526
935
 
527
- def handle_order(self, client: Client, order):
528
- # [45287766631,
529
- # "ETHUST",
530
- # -0.07,
531
- # -0.07,
532
- # "EXCHANGE LIMIT",
533
- # "CANCELED",
534
- # 210,
535
- # 0,
536
- # "2020-05-16T13:17:46Z",
537
- # 0,
538
- # 0,
539
- # 0]
936
+ def parse_ws_order(self, order, market=None):
937
+ #
938
+ # [
939
+ # 97084883506, # order id
940
+ # null,
941
+ # 1655110144596, # clientOrderId
942
+ # "tLTCUST", # symbol
943
+ # 1655110144596, # created timestamp
944
+ # 1655110144598, # updated timestamp
945
+ # 0, # amount
946
+ # 0.1, # amount_orig negative if sell order
947
+ # "EXCHANGE MARKET", # type
948
+ # null,
949
+ # null,
950
+ # null,
951
+ # 0,
952
+ # "EXECUTED @ 42.821(0.1)", # status
953
+ # null,
954
+ # null,
955
+ # 42.799, # price
956
+ # 42.821, # price average
957
+ # 0, # price trailling
958
+ # 0, # price_aux_limit
959
+ # null,
960
+ # null,
961
+ # null,
962
+ # 0,
963
+ # 0,
964
+ # null,
965
+ # null,
966
+ # null,
967
+ # "BFX",
968
+ # null,
969
+ # null,
970
+ # {}
971
+ # ]
972
+ #
540
973
  id = self.safe_string(order, 0)
541
- marketId = self.safe_string(order, 1)
974
+ clientOrderId = self.safe_string(order, 1)
975
+ marketId = self.safe_string(order, 3)
542
976
  symbol = self.safe_symbol(marketId)
543
- amount = self.safe_string(order, 2)
544
- remaining = self.safe_string(order, 3)
977
+ market = self.safe_market(symbol)
978
+ amount = self.safe_string(order, 7)
545
979
  side = 'buy'
546
980
  if Precise.string_lt(amount, '0'):
547
981
  amount = Precise.string_abs(amount)
548
- remaining = Precise.string_abs(remaining)
549
982
  side = 'sell'
550
- type = self.safe_string(order, 4)
983
+ remaining = Precise.string_abs(self.safe_string(order, 6))
984
+ type = self.safe_string(order, 8)
551
985
  if type.find('LIMIT') > -1:
552
986
  type = 'limit'
553
987
  elif type.find('MARKET') > -1:
554
988
  type = 'market'
555
- status = self.parse_ws_order_status(self.safe_string(order, 5))
556
- price = self.safe_string(order, 6)
557
- rawDatetime = self.safe_string(order, 8)
558
- timestamp = self.parse8601(rawDatetime)
559
- parsed = self.safe_order({
989
+ rawState = self.safe_string(order, 13)
990
+ stateParts = rawState.split(' ')
991
+ trimmedStatus = self.safe_string(stateParts, 0)
992
+ status = self.parse_ws_order_status(trimmedStatus)
993
+ price = self.safe_string(order, 16)
994
+ timestamp = self.safe_integer_2(order, 5, 4)
995
+ average = self.safe_string(order, 17)
996
+ stopPrice = self.omit_zero(self.safe_string(order, 18))
997
+ return self.safe_order({
560
998
  'info': order,
561
999
  'id': id,
562
- 'clientOrderId': None,
1000
+ 'clientOrderId': clientOrderId,
563
1001
  'timestamp': timestamp,
564
1002
  'datetime': self.iso8601(timestamp),
565
1003
  'lastTradeTimestamp': None,
@@ -567,9 +1005,9 @@ class bitfinex(ccxt.async_support.bitfinex):
567
1005
  'type': type,
568
1006
  'side': side,
569
1007
  'price': price,
570
- 'stopPrice': None,
571
- 'triggerPrice': None,
572
- 'average': None,
1008
+ 'stopPrice': stopPrice,
1009
+ 'triggerPrice': stopPrice,
1010
+ 'average': average,
573
1011
  'amount': amount,
574
1012
  'remaining': remaining,
575
1013
  'filled': None,
@@ -577,56 +1015,69 @@ class bitfinex(ccxt.async_support.bitfinex):
577
1015
  'fee': None,
578
1016
  'cost': None,
579
1017
  'trades': None,
580
- })
581
- if self.orders is None:
582
- limit = self.safe_integer(self.options, 'ordersLimit', 1000)
583
- self.orders = ArrayCacheBySymbolById(limit)
584
- orders = self.orders
585
- orders.append(parsed)
586
- client.resolve(parsed, id)
587
- return parsed
1018
+ }, market)
588
1019
 
589
1020
  def handle_message(self, client: Client, message):
1021
+ channelId = self.safe_string(message, 0)
1022
+ #
1023
+ # [
1024
+ # 1231,
1025
+ # "hb",
1026
+ # ]
1027
+ #
1028
+ # auth message
1029
+ # {
1030
+ # "event": "auth",
1031
+ # "status": "OK",
1032
+ # "chanId": 0,
1033
+ # "userId": 3159883,
1034
+ # "auth_id": "ac7108e7-2f26-424d-9982-c24700dc02ca",
1035
+ # "caps": {
1036
+ # "orders": {read: 1, write: 1},
1037
+ # "account": {read: 1, write: 1},
1038
+ # "funding": {read: 1, write: 1},
1039
+ # "history": {read: 1, write: 0},
1040
+ # "wallets": {read: 1, write: 1},
1041
+ # "withdraw": {read: 0, write: 1},
1042
+ # "positions": {read: 1, write: 1},
1043
+ # "ui_withdraw": {read: 0, write: 0}
1044
+ # }
1045
+ # }
1046
+ #
590
1047
  if isinstance(message, list):
591
- channelId = self.safe_string(message, 0)
592
- #
593
- # [
594
- # 1231,
595
- # "hb",
596
- # ]
597
- #
598
1048
  if message[1] == 'hb':
599
1049
  return # skip heartbeats within subscription channels for now
600
1050
  subscription = self.safe_value(client.subscriptions, channelId, {})
601
1051
  channel = self.safe_string(subscription, 'channel')
602
1052
  name = self.safe_string(message, 1)
603
- methods: dict = {
1053
+ publicMethods: dict = {
604
1054
  'book': self.handle_order_book,
605
- # 'ohlc': self.handleOHLCV,
1055
+ 'cs': self.handle_checksum,
1056
+ 'candles': self.handle_ohlcv,
606
1057
  'ticker': self.handle_ticker,
607
1058
  'trades': self.handle_trades,
1059
+ }
1060
+ privateMethods: dict = {
608
1061
  'os': self.handle_orders,
1062
+ 'ou': self.handle_orders,
609
1063
  'on': self.handle_orders,
610
1064
  'oc': self.handle_orders,
1065
+ 'wu': self.handle_balance,
1066
+ 'ws': self.handle_balance,
1067
+ 'tu': self.handle_my_trade,
611
1068
  }
612
- method = self.safe_value_2(methods, channel, name)
1069
+ method = None
1070
+ if channelId == '0':
1071
+ method = self.safe_value(privateMethods, name)
1072
+ else:
1073
+ method = self.safe_value_2(publicMethods, name, channel)
613
1074
  if method is not None:
614
1075
  method(client, message, subscription)
615
1076
  else:
616
- # todo add bitfinex handleErrorMessage
617
- #
618
- # {
619
- # "event": "info",
620
- # "version": 2,
621
- # "serverId": "e293377e-7bb7-427e-b28c-5db045b2c1d1",
622
- # "platform": {status: 1}, # 1 for operative, 0 for maintenance
623
- # }
624
- #
625
1077
  event = self.safe_string(message, 'event')
626
1078
  if event is not None:
627
1079
  methods: dict = {
628
1080
  'info': self.handle_system_status,
629
- # 'book': 'handleOrderBook',
630
1081
  'subscribed': self.handle_subscription_status,
631
1082
  'auth': self.handle_authentication_message,
632
1083
  }