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.
- ccxt/__init__.py +5 -3
- ccxt/abstract/bitfinex.py +136 -65
- ccxt/abstract/bitfinex1.py +69 -0
- ccxt/abstract/bitopro.py +1 -0
- ccxt/abstract/bybit.py +15 -0
- ccxt/abstract/defx.py +69 -0
- ccxt/abstract/deribit.py +1 -0
- ccxt/abstract/gate.py +14 -0
- ccxt/abstract/gateio.py +14 -0
- ccxt/async_support/__init__.py +5 -3
- ccxt/async_support/base/exchange.py +1 -1
- ccxt/async_support/bitfinex.py +3005 -1084
- ccxt/async_support/bitfinex1.py +1704 -0
- ccxt/async_support/bitfinex2.py +18 -13
- ccxt/async_support/bitmex.py +103 -1
- ccxt/async_support/bitopro.py +21 -4
- ccxt/async_support/bitso.py +2 -1
- ccxt/async_support/bybit.py +21 -1
- ccxt/async_support/coinbase.py +86 -0
- ccxt/async_support/defx.py +1981 -0
- ccxt/async_support/deribit.py +27 -12
- ccxt/async_support/gate.py +15 -1
- ccxt/async_support/htx.py +11 -2
- ccxt/async_support/hyperliquid.py +124 -14
- ccxt/async_support/kraken.py +39 -41
- ccxt/async_support/paradex.py +2 -2
- ccxt/base/exchange.py +6 -2
- ccxt/bitfinex.py +3005 -1084
- ccxt/bitfinex1.py +1703 -0
- ccxt/bitfinex2.py +18 -13
- ccxt/bitmex.py +103 -1
- ccxt/bitopro.py +21 -4
- ccxt/bitso.py +2 -1
- ccxt/bybit.py +21 -1
- ccxt/coinbase.py +86 -0
- ccxt/defx.py +1980 -0
- ccxt/deribit.py +27 -12
- ccxt/gate.py +15 -1
- ccxt/htx.py +11 -2
- ccxt/hyperliquid.py +124 -14
- ccxt/kraken.py +39 -41
- ccxt/paradex.py +2 -2
- ccxt/pro/__init__.py +5 -3
- ccxt/pro/bitfinex.py +725 -274
- ccxt/pro/bitfinex1.py +635 -0
- ccxt/pro/defx.py +832 -0
- ccxt/pro/probit.py +1 -0
- ccxt/test/tests_async.py +15 -1
- ccxt/test/tests_sync.py +15 -1
- {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/METADATA +11 -10
- {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/RECORD +54 -47
- ccxt/abstract/bitfinex2.py +0 -140
- {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/LICENSE.txt +0 -0
- {ccxt-4.4.35.dist-info → ccxt-4.4.37.dist-info}/WHEEL +0 -0
- {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
|
-
'
|
29
|
-
'
|
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/
|
35
|
-
'private': 'wss://api.bitfinex.com/ws/
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
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
|
-
#
|
271
|
+
# initial snapshot
|
108
272
|
#
|
109
|
-
#
|
110
|
-
#
|
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
|
-
#
|
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, '
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
134
|
-
|
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.
|
137
|
-
|
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
|
141
|
-
#
|
142
|
-
# snapshot trade
|
332
|
+
def parse_ws_trade(self, trade, market=None):
|
143
333
|
#
|
144
|
-
#
|
145
|
-
#
|
334
|
+
# [
|
335
|
+
# 1128060969, # id
|
336
|
+
# 1654702500098, # mts
|
337
|
+
# 0.00325131, # amount positive buy, negative sell
|
338
|
+
# 1818.5, # price
|
339
|
+
# ]
|
146
340
|
#
|
147
|
-
#
|
341
|
+
# trade execution
|
148
342
|
#
|
149
|
-
#
|
150
|
-
#
|
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
|
-
#
|
358
|
+
# trade update
|
153
359
|
#
|
154
|
-
#
|
155
|
-
#
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
if
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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(
|
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
|
-
|
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':
|
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':
|
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
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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(
|
225
|
-
'low': self.safe_string(
|
226
|
-
'bid': self.safe_string(
|
227
|
-
'bidVolume':
|
228
|
-
'ask': self.safe_string(
|
229
|
-
'askVolume':
|
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':
|
232
|
-
'close':
|
233
|
-
'last':
|
486
|
+
'open': None,
|
487
|
+
'close': last,
|
488
|
+
'last': last,
|
234
489
|
'previousClose': None,
|
235
|
-
'change':
|
236
|
-
'percentage': self.safe_string(
|
490
|
+
'change': change,
|
491
|
+
'percentage': self.safe_string(ticker, 5),
|
237
492
|
'average': None,
|
238
|
-
'baseVolume': self.safe_string(
|
493
|
+
'baseVolume': self.safe_string(ticker, 7),
|
239
494
|
'quoteVolume': None,
|
240
|
-
'info':
|
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
|
-
#
|
293
|
-
#
|
294
|
-
#
|
295
|
-
#
|
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, '
|
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
|
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
|
-
|
319
|
-
|
320
|
-
|
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
|
-
|
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
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
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
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
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
|
-
|
600
|
+
idString = self.safe_string(deltas, 0)
|
601
|
+
bookside.storeArray([self.parse_number(price), self.parse_number(amount), idString])
|
347
602
|
else:
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
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
|
-
#
|
716
|
+
# margin
|
358
717
|
#
|
359
|
-
#
|
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
|
-
|
362
|
-
|
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
|
752
|
+
def parse_ws_balance(self, balance):
|
365
753
|
#
|
366
|
-
#
|
367
|
-
#
|
368
|
-
#
|
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
|
-
|
400
|
-
|
401
|
-
authenticated = self.safe_value(client.subscriptions,
|
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':
|
412
|
-
'filter': [
|
413
|
-
'trading',
|
414
|
-
'wallet',
|
415
|
-
],
|
816
|
+
'event': event,
|
416
817
|
}
|
417
|
-
self.
|
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,
|
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,
|
831
|
+
client.reject(error, messageHash)
|
429
832
|
# allows further authentication attempts
|
430
|
-
|
431
|
-
|
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
|
-
|
846
|
+
messageHash = 'orders'
|
455
847
|
if symbol is not None:
|
456
|
-
|
457
|
-
|
458
|
-
orders = await self.
|
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
|
466
|
-
#
|
467
|
-
#
|
468
|
-
#
|
469
|
-
#
|
470
|
-
#
|
471
|
-
#
|
472
|
-
#
|
473
|
-
#
|
474
|
-
#
|
475
|
-
#
|
476
|
-
#
|
477
|
-
#
|
478
|
-
#
|
479
|
-
#
|
480
|
-
#
|
481
|
-
#
|
482
|
-
#
|
483
|
-
#
|
484
|
-
#
|
485
|
-
#
|
486
|
-
#
|
487
|
-
#
|
488
|
-
#
|
489
|
-
#
|
490
|
-
#
|
491
|
-
#
|
492
|
-
#
|
493
|
-
#
|
494
|
-
#
|
495
|
-
#
|
496
|
-
#
|
497
|
-
#
|
498
|
-
#
|
499
|
-
#
|
500
|
-
#
|
501
|
-
#
|
502
|
-
#
|
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.
|
909
|
+
parsed = self.parse_ws_order(value)
|
910
|
+
symbol = parsed['symbol']
|
911
|
+
symbolIds[symbol] = True
|
912
|
+
orders.append(parsed)
|
515
913
|
else:
|
516
|
-
self.
|
517
|
-
|
518
|
-
|
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
|
528
|
-
#
|
529
|
-
#
|
530
|
-
#
|
531
|
-
#
|
532
|
-
#
|
533
|
-
#
|
534
|
-
#
|
535
|
-
#
|
536
|
-
#
|
537
|
-
#
|
538
|
-
#
|
539
|
-
#
|
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
|
-
|
974
|
+
clientOrderId = self.safe_string(order, 1)
|
975
|
+
marketId = self.safe_string(order, 3)
|
542
976
|
symbol = self.safe_symbol(marketId)
|
543
|
-
|
544
|
-
|
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
|
-
|
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
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
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':
|
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':
|
571
|
-
'triggerPrice':
|
572
|
-
'average':
|
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
|
-
|
1053
|
+
publicMethods: dict = {
|
604
1054
|
'book': self.handle_order_book,
|
605
|
-
|
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 =
|
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
|
}
|