ccxt 4.1.84__py2.py3-none-any.whl → 4.1.86__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.
Potentially problematic release.
This version of ccxt might be problematic. Click here for more details.
- ccxt/__init__.py +1 -1
- ccxt/alpaca.py +1 -1
- ccxt/ascendex.py +1 -1
- ccxt/async_support/__init__.py +1 -1
- ccxt/async_support/alpaca.py +1 -1
- ccxt/async_support/ascendex.py +1 -1
- ccxt/async_support/base/exchange.py +7 -2
- ccxt/async_support/binance.py +1 -1
- ccxt/async_support/bingx.py +9 -8
- ccxt/async_support/bitget.py +1 -2
- ccxt/async_support/bithumb.py +2 -2
- ccxt/async_support/bitmart.py +90 -73
- ccxt/async_support/bitmex.py +1 -1
- ccxt/async_support/bitopro.py +2 -2
- ccxt/async_support/bybit.py +2 -4
- ccxt/async_support/coinex.py +2 -1
- ccxt/async_support/cryptocom.py +2 -2
- ccxt/async_support/digifinex.py +119 -103
- ccxt/async_support/hitbtc.py +1 -1
- ccxt/async_support/htx.py +1 -1
- ccxt/async_support/kraken.py +1 -1
- ccxt/async_support/krakenfutures.py +2 -2
- ccxt/async_support/kucoin.py +1 -5
- ccxt/async_support/kucoinfutures.py +0 -3
- ccxt/async_support/latoken.py +1 -1
- ccxt/async_support/mexc.py +76 -49
- ccxt/async_support/okx.py +3 -3
- ccxt/async_support/p2b.py +0 -2
- ccxt/async_support/poloniex.py +42 -42
- ccxt/async_support/probit.py +25 -17
- ccxt/async_support/tokocrypto.py +6 -3
- ccxt/base/exchange.py +21 -8
- ccxt/binance.py +1 -1
- ccxt/bingx.py +9 -8
- ccxt/bitget.py +1 -2
- ccxt/bitmart.py +90 -73
- ccxt/bitmex.py +1 -1
- ccxt/bybit.py +2 -4
- ccxt/coinex.py +2 -1
- ccxt/cryptocom.py +2 -2
- ccxt/digifinex.py +119 -103
- ccxt/hitbtc.py +1 -1
- ccxt/htx.py +1 -1
- ccxt/kraken.py +1 -1
- ccxt/krakenfutures.py +2 -2
- ccxt/kucoin.py +1 -5
- ccxt/kucoinfutures.py +0 -3
- ccxt/latoken.py +1 -1
- ccxt/mexc.py +76 -49
- ccxt/okx.py +3 -3
- ccxt/p2b.py +0 -2
- ccxt/poloniex.py +42 -42
- ccxt/pro/__init__.py +1 -1
- ccxt/pro/bitmart.py +965 -247
- ccxt/pro/bitmex.py +200 -2
- ccxt/pro/krakenfutures.py +4 -4
- ccxt/pro/mexc.py +1 -1
- ccxt/pro/poloniex.py +1 -1
- ccxt/pro/poloniexfutures.py +3 -3
- ccxt/probit.py +25 -17
- ccxt/tokocrypto.py +6 -3
- {ccxt-4.1.84.dist-info → ccxt-4.1.86.dist-info}/METADATA +6 -5
- {ccxt-4.1.84.dist-info → ccxt-4.1.86.dist-info}/RECORD +65 -65
- {ccxt-4.1.84.dist-info → ccxt-4.1.86.dist-info}/WHEEL +0 -0
- {ccxt-4.1.84.dist-info → ccxt-4.1.86.dist-info}/top_level.txt +0 -0
ccxt/pro/bitmart.py
CHANGED
@@ -4,11 +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, ArrayCacheByTimestamp
|
7
|
+
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
|
8
|
+
from ccxt.async_support.base.ws.order_book_side import Asks, Bids
|
8
9
|
import hashlib
|
9
|
-
from ccxt.base.types import Int, Str
|
10
|
+
from ccxt.base.types import Int, Market, Str, Strings
|
10
11
|
from ccxt.async_support.base.ws.client import Client
|
12
|
+
from ccxt.base.errors import ExchangeError
|
11
13
|
from ccxt.base.errors import ArgumentsRequired
|
14
|
+
from ccxt.base.errors import NotSupported
|
12
15
|
from ccxt.base.errors import AuthenticationError
|
13
16
|
|
14
17
|
|
@@ -25,24 +28,38 @@ class bitmart(ccxt.async_support.bitmart):
|
|
25
28
|
'cancelOrdersWs': False,
|
26
29
|
'cancelAllOrdersWs': False,
|
27
30
|
'ws': True,
|
31
|
+
'watchBalance': True,
|
28
32
|
'watchTicker': True,
|
33
|
+
'watchTickers': True,
|
29
34
|
'watchOrderBook': True,
|
30
35
|
'watchOrders': True,
|
31
36
|
'watchTrades': True,
|
32
37
|
'watchOHLCV': True,
|
38
|
+
'watchPosition': 'emulated',
|
39
|
+
'watchPositions': True,
|
33
40
|
},
|
34
41
|
'urls': {
|
35
42
|
'api': {
|
36
43
|
'ws': {
|
37
|
-
'
|
38
|
-
|
44
|
+
'spot': {
|
45
|
+
'public': 'wss://ws-manager-compress.{hostname}/api?protocol=1.1',
|
46
|
+
'private': 'wss://ws-manager-compress.{hostname}/user?protocol=1.1',
|
47
|
+
},
|
48
|
+
'swap': {
|
49
|
+
'public': 'wss://openapi-ws.{hostname}/api?protocol=1.1',
|
50
|
+
'private': 'wss://openapi-ws.{hostname}/user?protocol=1.1',
|
51
|
+
},
|
39
52
|
},
|
40
53
|
},
|
41
54
|
},
|
42
55
|
'options': {
|
43
56
|
'defaultType': 'spot',
|
57
|
+
'watchBalance': {
|
58
|
+
'fetchBalanceSnapshot': True, # or False
|
59
|
+
'awaitBalanceSnapshot': True, # whether to wait for the balance snapshot before providing updates
|
60
|
+
},
|
44
61
|
'watchOrderBook': {
|
45
|
-
'depth': '
|
62
|
+
'depth': 'depth50', # depth5, depth20, depth50
|
46
63
|
},
|
47
64
|
'ws': {
|
48
65
|
'inflate': True,
|
@@ -68,31 +85,147 @@ class bitmart(ccxt.async_support.bitmart):
|
|
68
85
|
},
|
69
86
|
})
|
70
87
|
|
71
|
-
async def subscribe(self, channel, symbol, params={}):
|
72
|
-
await self.load_markets()
|
88
|
+
async def subscribe(self, channel, symbol, type, params={}):
|
73
89
|
market = self.market(symbol)
|
74
|
-
url = self.implode_hostname(self.urls['api']['ws']['public'])
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
'
|
79
|
-
|
90
|
+
url = self.implode_hostname(self.urls['api']['ws'][type]['public'])
|
91
|
+
request = {}
|
92
|
+
messageHash = None
|
93
|
+
if type == 'spot':
|
94
|
+
messageHash = 'spot/' + channel + ':' + market['id']
|
95
|
+
request = {
|
96
|
+
'op': 'subscribe',
|
97
|
+
'args': [messageHash],
|
98
|
+
}
|
99
|
+
else:
|
100
|
+
messageHash = 'futures/' + channel + ':' + market['id']
|
101
|
+
request = {
|
102
|
+
'action': 'subscribe',
|
103
|
+
'args': [messageHash],
|
104
|
+
}
|
80
105
|
return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
|
81
106
|
|
82
|
-
async def
|
107
|
+
async def watch_balance(self, params={}):
|
108
|
+
"""
|
109
|
+
:see: https://developer-pro.bitmart.com/en/spot/#private-balance-change
|
110
|
+
:see: https://developer-pro.bitmart.com/en/futures/#private-assets-channel
|
111
|
+
watch balance and get the amount of funds available for trading or funds locked in orders
|
112
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
113
|
+
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
114
|
+
"""
|
83
115
|
await self.load_markets()
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
116
|
+
type = 'spot'
|
117
|
+
type, params = self.handle_market_type_and_params('watchBalance', None, params)
|
118
|
+
await self.authenticate(type, params)
|
119
|
+
request = {}
|
120
|
+
if type == 'spot':
|
121
|
+
request = {
|
122
|
+
'op': 'subscribe',
|
123
|
+
'args': ['spot/user/balance:BALANCE_UPDATE'],
|
124
|
+
}
|
125
|
+
else:
|
126
|
+
request = {
|
127
|
+
'action': 'subscribe',
|
128
|
+
'args': ['futures/asset:USDT', 'futures/asset:BTC', 'futures/asset:ETH'],
|
129
|
+
}
|
130
|
+
messageHash = 'balance:' + type
|
131
|
+
url = self.implode_hostname(self.urls['api']['ws'][type]['private'])
|
132
|
+
client = self.client(url)
|
133
|
+
self.set_balance_cache(client, type)
|
134
|
+
fetchBalanceSnapshot = self.handle_option_and_params(self.options, 'watchBalance', 'fetchBalanceSnapshot', True)
|
135
|
+
awaitBalanceSnapshot = self.handle_option_and_params(self.options, 'watchBalance', 'awaitBalanceSnapshot', False)
|
136
|
+
if fetchBalanceSnapshot and awaitBalanceSnapshot:
|
137
|
+
await client.future(type + ':fetchBalanceSnapshot')
|
92
138
|
return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
|
93
139
|
|
140
|
+
def set_balance_cache(self, client: Client, type):
|
141
|
+
if type in client.subscriptions:
|
142
|
+
return None
|
143
|
+
options = self.safe_value(self.options, 'watchBalance')
|
144
|
+
fetchBalanceSnapshot = self.handle_option_and_params(options, 'watchBalance', 'fetchBalanceSnapshot', True)
|
145
|
+
if fetchBalanceSnapshot:
|
146
|
+
messageHash = type + ':fetchBalanceSnapshot'
|
147
|
+
if not (messageHash in client.futures):
|
148
|
+
client.future(messageHash)
|
149
|
+
self.spawn(self.load_balance_snapshot, client, messageHash, type)
|
150
|
+
else:
|
151
|
+
self.balance[type] = {}
|
152
|
+
|
153
|
+
async def load_balance_snapshot(self, client, messageHash, type):
|
154
|
+
response = await self.fetch_balance({'type': type})
|
155
|
+
self.balance[type] = self.extend(response, self.safe_value(self.balance, type, {}))
|
156
|
+
# don't remove the future from the .futures cache
|
157
|
+
future = client.futures[messageHash]
|
158
|
+
future.resolve()
|
159
|
+
client.resolve(self.balance[type], 'balance:' + type)
|
160
|
+
|
161
|
+
def handle_balance(self, client: Client, message):
|
162
|
+
#
|
163
|
+
# spot
|
164
|
+
# {
|
165
|
+
# "data":[
|
166
|
+
# {
|
167
|
+
# "balance_details":[
|
168
|
+
# {
|
169
|
+
# "av_bal":"0.206000000000000000000000000000",
|
170
|
+
# "ccy":"LTC",
|
171
|
+
# "fz_bal":"0.100000000000000000000000000000"
|
172
|
+
# }
|
173
|
+
# ],
|
174
|
+
# "event_time":"1701632345415",
|
175
|
+
# "event_type":"TRANSACTION_COMPLETED"
|
176
|
+
# }
|
177
|
+
# ],
|
178
|
+
# "table":"spot/user/balance"
|
179
|
+
# }
|
180
|
+
# swap
|
181
|
+
# {
|
182
|
+
# group: 'futures/asset:USDT',
|
183
|
+
# data: {
|
184
|
+
# currency: 'USDT',
|
185
|
+
# available_balance: '37.19688649135',
|
186
|
+
# position_deposit: '0.788687546',
|
187
|
+
# frozen_balance: '0'
|
188
|
+
# }
|
189
|
+
# }
|
190
|
+
#
|
191
|
+
channel = self.safe_string_2(message, 'table', 'group')
|
192
|
+
data = self.safe_value(message, 'data')
|
193
|
+
if data is None:
|
194
|
+
return
|
195
|
+
isSpot = (channel.find('spot') >= 0)
|
196
|
+
type = 'spot' if isSpot else 'swap'
|
197
|
+
self.balance['info'] = message
|
198
|
+
if isSpot:
|
199
|
+
if not isinstance(data, list):
|
200
|
+
return
|
201
|
+
for i in range(0, len(data)):
|
202
|
+
timestamp = self.safe_integer(message, 'event_time')
|
203
|
+
self.balance['timestamp'] = timestamp
|
204
|
+
self.balance['datetime'] = self.iso8601(timestamp)
|
205
|
+
balanceDetails = self.safe_value(data[i], 'balance_details', [])
|
206
|
+
for ii in range(0, len(balanceDetails)):
|
207
|
+
rawBalance = balanceDetails[i]
|
208
|
+
account = self.account()
|
209
|
+
currencyId = self.safe_string(rawBalance, 'ccy')
|
210
|
+
code = self.safe_currency_code(currencyId)
|
211
|
+
account['free'] = self.safe_string(rawBalance, 'av_bal')
|
212
|
+
account['total'] = self.safe_string(rawBalance, 'fz_bal')
|
213
|
+
self.balance[code] = account
|
214
|
+
else:
|
215
|
+
currencyId = self.safe_string(data, 'currency')
|
216
|
+
code = self.safe_currency_code(currencyId)
|
217
|
+
account = self.account()
|
218
|
+
account['free'] = self.safe_string(data, 'available_balance')
|
219
|
+
account['used'] = self.safe_string(data, 'frozen_balance')
|
220
|
+
self.balance[type][code] = account
|
221
|
+
self.balance[type] = self.safe_balance(self.balance[type])
|
222
|
+
messageHash = 'balance:' + type
|
223
|
+
client.resolve(self.balance[type], messageHash)
|
224
|
+
|
94
225
|
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
|
95
226
|
"""
|
227
|
+
:see: https://developer-pro.bitmart.com/en/spot/#public-trade-channel
|
228
|
+
:see: https://developer-pro.bitmart.com/en/futures/#public-trade-channel
|
96
229
|
get the list of most recent trades for a particular symbol
|
97
230
|
:param str symbol: unified symbol of the market to fetch trades for
|
98
231
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
@@ -102,22 +235,65 @@ class bitmart(ccxt.async_support.bitmart):
|
|
102
235
|
"""
|
103
236
|
await self.load_markets()
|
104
237
|
symbol = self.symbol(symbol)
|
105
|
-
|
238
|
+
market = self.market(symbol)
|
239
|
+
type = 'spot'
|
240
|
+
type, params = self.handle_market_type_and_params('watchTrades', market, params)
|
241
|
+
trades = await self.subscribe('trade', symbol, type, params)
|
106
242
|
if self.newUpdates:
|
107
243
|
limit = trades.getLimit(symbol, limit)
|
108
244
|
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
109
245
|
|
110
246
|
async def watch_ticker(self, symbol: str, params={}):
|
111
247
|
"""
|
248
|
+
:see: https://developer-pro.bitmart.com/en/spot/#public-ticker-channel
|
112
249
|
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
113
250
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
114
251
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
115
252
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
116
253
|
"""
|
117
|
-
|
254
|
+
await self.load_markets()
|
255
|
+
symbol = self.symbol(symbol)
|
256
|
+
market = self.market(symbol)
|
257
|
+
type = 'spot'
|
258
|
+
type, params = self.handle_market_type_and_params('watchTicker', market, params)
|
259
|
+
if type == 'swap':
|
260
|
+
raise NotSupported(self.id + ' watchTicker() does not support ' + type + ' markets. Use watchTickers() instead')
|
261
|
+
return await self.subscribe('ticker', symbol, type, params)
|
262
|
+
|
263
|
+
async def watch_tickers(self, symbols: Strings = None, params={}):
|
264
|
+
"""
|
265
|
+
:see: https://developer-pro.bitmart.com/en/futures/#overview
|
266
|
+
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
|
267
|
+
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
268
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
269
|
+
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
270
|
+
"""
|
271
|
+
await self.load_markets()
|
272
|
+
market = self.get_market_from_symbols(symbols)
|
273
|
+
type = 'spot'
|
274
|
+
type, params = self.handle_market_type_and_params('watchTickers', market, params)
|
275
|
+
symbols = self.market_symbols(symbols)
|
276
|
+
if type == 'spot':
|
277
|
+
raise NotSupported(self.id + ' watchTickers() does not support ' + type + ' markets. Use watchTicker() instead')
|
278
|
+
url = self.implode_hostname(self.urls['api']['ws'][type]['public'])
|
279
|
+
if type == 'swap':
|
280
|
+
type = 'futures'
|
281
|
+
messageHash = 'tickers'
|
282
|
+
if symbols is not None:
|
283
|
+
messageHash += '::' + ','.join(symbols)
|
284
|
+
request = {
|
285
|
+
'action': 'subscribe',
|
286
|
+
'args': ['futures/ticker'],
|
287
|
+
}
|
288
|
+
newTickers = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
|
289
|
+
if self.newUpdates:
|
290
|
+
return newTickers
|
291
|
+
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
118
292
|
|
119
293
|
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
120
294
|
"""
|
295
|
+
:see: https://developer-pro.bitmart.com/en/spot/#private-order-channel
|
296
|
+
:see: https://developer-pro.bitmart.com/en/futures/#private-order-channel
|
121
297
|
watches information on multiple orders made by the user
|
122
298
|
:param str symbol: unified market symbol of the market orders were made in
|
123
299
|
:param int [since]: the earliest time in ms to fetch orders for
|
@@ -125,184 +301,593 @@ class bitmart(ccxt.async_support.bitmart):
|
|
125
301
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
126
302
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
127
303
|
"""
|
128
|
-
if symbol is None:
|
129
|
-
raise ArgumentsRequired(self.id + ' watchOrders() requires a symbol argument')
|
130
304
|
await self.load_markets()
|
131
|
-
market =
|
132
|
-
|
133
|
-
if
|
134
|
-
|
135
|
-
|
136
|
-
|
305
|
+
market = None
|
306
|
+
messageHash = 'orders'
|
307
|
+
if symbol is not None:
|
308
|
+
symbol = self.symbol(symbol)
|
309
|
+
market = self.market(symbol)
|
310
|
+
messageHash = 'orders::' + symbol
|
311
|
+
type = 'spot'
|
312
|
+
type, params = self.handle_market_type_and_params('watchOrders', market, params)
|
313
|
+
await self.authenticate(type, params)
|
314
|
+
request = None
|
315
|
+
if type == 'spot':
|
316
|
+
if symbol is None:
|
317
|
+
raise ArgumentsRequired(self.id + ' watchOrders() requires a symbol argument for spot markets')
|
318
|
+
request = {
|
319
|
+
'op': 'subscribe',
|
320
|
+
'args': ['spot/user/order:' + market['id']],
|
321
|
+
}
|
322
|
+
else:
|
323
|
+
request = {
|
324
|
+
'action': 'subscribe',
|
325
|
+
'args': ['futures/order'],
|
326
|
+
}
|
327
|
+
url = self.implode_hostname(self.urls['api']['ws'][type]['private'])
|
328
|
+
newOrders = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
|
137
329
|
if self.newUpdates:
|
138
|
-
|
139
|
-
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
|
330
|
+
return newOrders
|
331
|
+
return self.filter_by_symbol_since_limit(self.orders, symbol, since, limit, True)
|
140
332
|
|
141
333
|
def handle_orders(self, client: Client, message):
|
142
334
|
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
335
|
+
# spot
|
336
|
+
# {
|
337
|
+
# "data":[
|
338
|
+
# {
|
339
|
+
# "symbol": "LTC_USDT",
|
340
|
+
# "notional": '',
|
341
|
+
# "side": "buy",
|
342
|
+
# "last_fill_time": "0",
|
343
|
+
# "ms_t": "1646216634000",
|
344
|
+
# "type": "limit",
|
345
|
+
# "filled_notional": "0.000000000000000000000000000000",
|
346
|
+
# "last_fill_price": "0",
|
347
|
+
# "size": "0.500000000000000000000000000000",
|
348
|
+
# "price": "50.000000000000000000000000000000",
|
349
|
+
# "last_fill_count": "0",
|
350
|
+
# "filled_size": "0.000000000000000000000000000000",
|
351
|
+
# "margin_trading": "0",
|
352
|
+
# "state": "8",
|
353
|
+
# "order_id": "24807076628",
|
354
|
+
# "order_type": "0"
|
355
|
+
# }
|
356
|
+
# ],
|
357
|
+
# "table":"spot/user/order"
|
358
|
+
# }
|
359
|
+
# swap
|
360
|
+
# {
|
361
|
+
# "group":"futures/order",
|
362
|
+
# "data":[
|
363
|
+
# {
|
364
|
+
# "action":2,
|
365
|
+
# "order":{
|
366
|
+
# "order_id":"2312045036986775",
|
367
|
+
# "client_order_id":"",
|
368
|
+
# "price":"71.61707928",
|
369
|
+
# "size":"1",
|
370
|
+
# "symbol":"LTCUSDT",
|
371
|
+
# "state":1,
|
372
|
+
# "side":4,
|
373
|
+
# "type":"market",
|
374
|
+
# "leverage":"1",
|
375
|
+
# "open_type":"cross",
|
376
|
+
# "deal_avg_price":"0",
|
377
|
+
# "deal_size":"0",
|
378
|
+
# "create_time":1701625324646,
|
379
|
+
# "update_time":1701625324640,
|
380
|
+
# "plan_order_id":"",
|
381
|
+
# "last_trade":null
|
382
|
+
# }
|
162
383
|
# }
|
163
|
-
#
|
164
|
-
#
|
165
|
-
# }
|
384
|
+
# ]
|
385
|
+
# }
|
166
386
|
#
|
167
|
-
|
168
|
-
orders
|
387
|
+
orders = self.safe_value(message, 'data')
|
388
|
+
if orders is None:
|
389
|
+
return
|
169
390
|
ordersLength = len(orders)
|
391
|
+
newOrders = []
|
392
|
+
symbols = {}
|
170
393
|
if ordersLength > 0:
|
171
394
|
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
172
395
|
if self.orders is None:
|
173
396
|
self.orders = ArrayCacheBySymbolById(limit)
|
174
397
|
stored = self.orders
|
175
|
-
marketIds = []
|
176
398
|
for i in range(0, len(orders)):
|
177
399
|
order = self.parse_ws_order(orders[i])
|
178
400
|
stored.append(order)
|
401
|
+
newOrders.append(order)
|
179
402
|
symbol = order['symbol']
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
#
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
403
|
+
symbols[symbol] = True
|
404
|
+
newOrderSymbols = list(symbols.keys())
|
405
|
+
for i in range(0, len(newOrderSymbols)):
|
406
|
+
symbol = newOrderSymbols[i]
|
407
|
+
self.resolve_promise_if_messagehash_matches(client, 'orders::', symbol, newOrders)
|
408
|
+
client.resolve(newOrders, 'orders')
|
409
|
+
|
410
|
+
def parse_ws_order(self, order, market: Market = None):
|
411
|
+
#
|
412
|
+
# spot
|
413
|
+
# {
|
414
|
+
# "symbol": "LTC_USDT",
|
415
|
+
# "notional": '',
|
416
|
+
# "side": "buy",
|
417
|
+
# "last_fill_time": "0",
|
418
|
+
# "ms_t": "1646216634000",
|
419
|
+
# "type": "limit",
|
420
|
+
# "filled_notional": "0.000000000000000000000000000000",
|
421
|
+
# "last_fill_price": "0",
|
422
|
+
# "size": "0.500000000000000000000000000000",
|
423
|
+
# "price": "50.000000000000000000000000000000",
|
424
|
+
# "last_fill_count": "0",
|
425
|
+
# "filled_size": "0.000000000000000000000000000000",
|
426
|
+
# "margin_trading": "0",
|
427
|
+
# "state": "8",
|
428
|
+
# "order_id": "24807076628",
|
429
|
+
# "order_type": "0"
|
430
|
+
# }
|
431
|
+
# swap
|
432
|
+
# {
|
433
|
+
# "action":2,
|
434
|
+
# "order":{
|
435
|
+
# "order_id":"2312045036986775",
|
436
|
+
# "client_order_id":"",
|
437
|
+
# "price":"71.61707928",
|
438
|
+
# "size":"1",
|
439
|
+
# "symbol":"LTCUSDT",
|
440
|
+
# "state":1,
|
441
|
+
# "side":4,
|
442
|
+
# "type":"market",
|
443
|
+
# "leverage":"1",
|
444
|
+
# "open_type":"cross",
|
445
|
+
# "deal_avg_price":"0",
|
446
|
+
# "deal_size":"0",
|
447
|
+
# "create_time":1701625324646,
|
448
|
+
# "update_time":1701625324640,
|
449
|
+
# "plan_order_id":"",
|
450
|
+
# "last_trade":null
|
451
|
+
# }
|
452
|
+
# }
|
453
|
+
#
|
454
|
+
action = self.safe_number(order, 'action')
|
455
|
+
isSpot = (action is None)
|
456
|
+
if isSpot:
|
457
|
+
marketId = self.safe_string(order, 'symbol')
|
458
|
+
market = self.safe_market(marketId, market, '_', 'spot')
|
459
|
+
id = self.safe_string(order, 'order_id')
|
460
|
+
clientOrderId = self.safe_string(order, 'clientOid')
|
461
|
+
price = self.safe_string(order, 'price')
|
462
|
+
filled = self.safe_string(order, 'filled_size')
|
463
|
+
amount = self.safe_string(order, 'size')
|
464
|
+
type = self.safe_string(order, 'type')
|
465
|
+
rawState = self.safe_string(order, 'state')
|
466
|
+
status = self.parseOrderStatusByType(market['type'], rawState)
|
467
|
+
timestamp = self.safe_integer(order, 'ms_t')
|
468
|
+
symbol = market['symbol']
|
469
|
+
side = self.safe_string_lower(order, 'side')
|
470
|
+
return self.safe_order({
|
471
|
+
'info': order,
|
472
|
+
'symbol': symbol,
|
473
|
+
'id': id,
|
474
|
+
'clientOrderId': clientOrderId,
|
475
|
+
'timestamp': None,
|
476
|
+
'datetime': None,
|
477
|
+
'lastTradeTimestamp': timestamp,
|
478
|
+
'type': type,
|
479
|
+
'timeInForce': None,
|
480
|
+
'postOnly': None,
|
481
|
+
'side': side,
|
482
|
+
'price': price,
|
483
|
+
'stopPrice': None,
|
484
|
+
'triggerPrice': None,
|
485
|
+
'amount': amount,
|
486
|
+
'cost': None,
|
487
|
+
'average': None,
|
488
|
+
'filled': filled,
|
489
|
+
'remaining': None,
|
490
|
+
'status': status,
|
491
|
+
'fee': None,
|
492
|
+
'trades': None,
|
493
|
+
}, market)
|
494
|
+
else:
|
495
|
+
orderInfo = self.safe_value(order, 'order')
|
496
|
+
marketId = self.safe_string(orderInfo, 'symbol')
|
497
|
+
symbol = self.safe_symbol(marketId, market, '', 'swap')
|
498
|
+
orderId = self.safe_string(orderInfo, 'order_id')
|
499
|
+
timestamp = self.safe_integer(orderInfo, 'create_time')
|
500
|
+
updatedTimestamp = self.safe_integer(orderInfo, 'update_time')
|
501
|
+
lastTrade = self.safe_value(orderInfo, 'last_trade')
|
502
|
+
cachedOrders = self.orders
|
503
|
+
orders = self.safe_value(cachedOrders.hashmap, symbol, {})
|
504
|
+
cachedOrder = self.safe_value(orders, orderId)
|
505
|
+
trades = None
|
506
|
+
if cachedOrder is not None:
|
507
|
+
trades = self.safe_value(order, 'trades')
|
508
|
+
if lastTrade is not None:
|
509
|
+
if trades is None:
|
510
|
+
trades = []
|
511
|
+
trades.append(lastTrade)
|
512
|
+
return self.safe_order({
|
513
|
+
'info': order,
|
514
|
+
'symbol': symbol,
|
515
|
+
'id': orderId,
|
516
|
+
'clientOrderId': self.safe_string(orderInfo, 'client_order_id'),
|
517
|
+
'timestamp': timestamp,
|
518
|
+
'datetime': self.iso8601(timestamp),
|
519
|
+
'lastTradeTimestamp': updatedTimestamp,
|
520
|
+
'type': self.safe_string(orderInfo, 'type'),
|
521
|
+
'timeInForce': None,
|
522
|
+
'postOnly': None,
|
523
|
+
'side': self.parse_ws_order_side(self.safe_string(orderInfo, 'side')),
|
524
|
+
'price': self.safe_string(orderInfo, 'price'),
|
525
|
+
'stopPrice': None,
|
526
|
+
'triggerPrice': None,
|
527
|
+
'amount': self.safe_string(orderInfo, 'size'),
|
528
|
+
'cost': None,
|
529
|
+
'average': self.safe_string(orderInfo, 'deal_avg_price'),
|
530
|
+
'filled': self.safe_string(orderInfo, 'deal_size'),
|
531
|
+
'remaining': None,
|
532
|
+
'status': self.parse_ws_order_status(self.safe_string(order, 'action')),
|
533
|
+
'fee': None,
|
534
|
+
'trades': trades,
|
535
|
+
}, market)
|
536
|
+
|
537
|
+
def parse_ws_order_status(self, statusId):
|
538
|
+
statuses = {
|
539
|
+
'1': 'closed', # match deal
|
540
|
+
'2': 'open', # submit order
|
541
|
+
'3': 'canceled', # cancel order
|
542
|
+
'4': 'closed', # liquidate cancel order
|
543
|
+
'5': 'canceled', # adl cancel order
|
544
|
+
'6': 'open', # part liquidate
|
545
|
+
'7': 'open', # bankrupty order
|
546
|
+
'8': 'closed', # passive adl match deal
|
547
|
+
'9': 'closed', # active adl match deal
|
548
|
+
}
|
549
|
+
return self.safe_string(statuses, statusId, statusId)
|
550
|
+
|
551
|
+
def parse_ws_order_side(self, sideId):
|
552
|
+
sides = {
|
553
|
+
'1': 'buy', # buy_open_long
|
554
|
+
'2': 'buy', # buy_close_short
|
555
|
+
'3': 'sell', # sell_close_long
|
556
|
+
'4': 'sell', # sell_open_short
|
557
|
+
}
|
558
|
+
return self.safe_string(sides, sideId, sideId)
|
559
|
+
|
560
|
+
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}):
|
561
|
+
"""
|
562
|
+
:see: https://developer-pro.bitmart.com/en/futures/#private-position-channel
|
563
|
+
watch all open positions
|
564
|
+
:param str[]|None symbols: list of unified market symbols
|
565
|
+
:param dict params: extra parameters specific to the exchange API endpoint
|
566
|
+
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
|
567
|
+
"""
|
568
|
+
await self.load_markets()
|
569
|
+
type = 'swap'
|
570
|
+
await self.authenticate(type, params)
|
571
|
+
symbols = self.market_symbols(symbols, 'swap', True, True, False)
|
572
|
+
messageHash = 'positions'
|
573
|
+
if symbols is not None:
|
574
|
+
messageHash += '::' + ','.join(symbols)
|
575
|
+
subscriptionHash = 'futures/position'
|
576
|
+
request = {
|
577
|
+
'action': 'subscribe',
|
578
|
+
'args': ['futures/position'],
|
579
|
+
}
|
580
|
+
url = self.implode_hostname(self.urls['api']['ws'][type]['private'])
|
581
|
+
newPositions = await self.watch(url, messageHash, self.deep_extend(request, params), subscriptionHash)
|
582
|
+
if self.newUpdates:
|
583
|
+
return newPositions
|
584
|
+
return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit)
|
585
|
+
|
586
|
+
def handle_positions(self, client: Client, message):
|
587
|
+
#
|
588
|
+
# {
|
589
|
+
# "group":"futures/position",
|
590
|
+
# "data":[
|
591
|
+
# {
|
592
|
+
# "symbol":"LTCUSDT",
|
593
|
+
# "hold_volume":"5",
|
594
|
+
# "position_type":2,
|
595
|
+
# "open_type":2,
|
596
|
+
# "frozen_volume":"0",
|
597
|
+
# "close_volume":"0",
|
598
|
+
# "hold_avg_price":"71.582",
|
599
|
+
# "close_avg_price":"0",
|
600
|
+
# "open_avg_price":"71.582",
|
601
|
+
# "liquidate_price":"0",
|
602
|
+
# "create_time":1701623327513,
|
603
|
+
# "update_time":1701627620439
|
604
|
+
# },
|
605
|
+
# {
|
606
|
+
# "symbol":"LTCUSDT",
|
607
|
+
# "hold_volume":"6",
|
608
|
+
# "position_type":1,
|
609
|
+
# "open_type":2,
|
610
|
+
# "frozen_volume":"0",
|
611
|
+
# "close_volume":"0",
|
612
|
+
# "hold_avg_price":"71.681666666666666667",
|
613
|
+
# "close_avg_price":"0",
|
614
|
+
# "open_avg_price":"71.681666666666666667",
|
615
|
+
# "liquidate_price":"0",
|
616
|
+
# "create_time":1701621167225,
|
617
|
+
# "update_time":1701628152614
|
618
|
+
# }
|
619
|
+
# ]
|
620
|
+
# }
|
621
|
+
#
|
622
|
+
data = self.safe_value(message, 'data', [])
|
623
|
+
cache = self.positions
|
624
|
+
if self.positions is None:
|
625
|
+
self.positions = ArrayCacheBySymbolBySide()
|
626
|
+
newPositions = []
|
627
|
+
for i in range(0, len(data)):
|
628
|
+
rawPosition = data[i]
|
629
|
+
position = self.parse_ws_position(rawPosition)
|
630
|
+
newPositions.append(position)
|
631
|
+
cache.append(position)
|
632
|
+
messageHashes = self.find_message_hashes(client, 'positions::')
|
633
|
+
for i in range(0, len(messageHashes)):
|
634
|
+
messageHash = messageHashes[i]
|
635
|
+
parts = messageHash.split('::')
|
636
|
+
symbolsString = parts[1]
|
637
|
+
symbols = symbolsString.split(',')
|
638
|
+
positions = self.filter_by_array(newPositions, 'symbol', symbols, False)
|
639
|
+
if not self.is_empty(positions):
|
640
|
+
client.resolve(positions, messageHash)
|
641
|
+
client.resolve(newPositions, 'positions')
|
642
|
+
|
643
|
+
def parse_ws_position(self, position, market: Market = None):
|
644
|
+
#
|
645
|
+
# {
|
646
|
+
# "symbol":"LTCUSDT",
|
647
|
+
# "hold_volume":"6",
|
648
|
+
# "position_type":1,
|
649
|
+
# "open_type":2,
|
650
|
+
# "frozen_volume":"0",
|
651
|
+
# "close_volume":"0",
|
652
|
+
# "hold_avg_price":"71.681666666666666667",
|
653
|
+
# "close_avg_price":"0",
|
654
|
+
# "open_avg_price":"71.681666666666666667",
|
655
|
+
# "liquidate_price":"0",
|
656
|
+
# "create_time":1701621167225,
|
657
|
+
# "update_time":1701628152614
|
658
|
+
# }
|
659
|
+
#
|
660
|
+
marketId = self.safe_string(position, 'symbol')
|
661
|
+
market = self.safe_market(marketId, market, '', 'swap')
|
218
662
|
symbol = market['symbol']
|
219
|
-
|
220
|
-
|
221
|
-
|
663
|
+
openTimestamp = self.safe_integer(position, 'create_time')
|
664
|
+
timestamp = self.safe_integer(position, 'update_time')
|
665
|
+
side = self.safe_number(position, 'position_type')
|
666
|
+
marginModeId = self.safe_number(position, 'open_type')
|
667
|
+
return self.safe_position({
|
668
|
+
'info': position,
|
669
|
+
'id': None,
|
222
670
|
'symbol': symbol,
|
223
|
-
'
|
224
|
-
'
|
225
|
-
'
|
226
|
-
'
|
227
|
-
'
|
228
|
-
'
|
229
|
-
'
|
230
|
-
'
|
231
|
-
'
|
232
|
-
'
|
233
|
-
'
|
234
|
-
'
|
235
|
-
'
|
236
|
-
'
|
237
|
-
'
|
238
|
-
'
|
239
|
-
'
|
240
|
-
'
|
241
|
-
'
|
242
|
-
'
|
243
|
-
|
671
|
+
'timestamp': openTimestamp,
|
672
|
+
'datetime': self.iso8601(openTimestamp),
|
673
|
+
'lastUpdateTimestamp': timestamp,
|
674
|
+
'hedged': None,
|
675
|
+
'side': 'long' if (side == 1) else 'short',
|
676
|
+
'contracts': self.safe_number(position, 'hold_volume'),
|
677
|
+
'contractSize': self.safe_number(market, 'contractSize'),
|
678
|
+
'entryPrice': self.safe_number(position, 'open_avg_price'),
|
679
|
+
'markPrice': self.safe_number(position, 'hold_avg_price'),
|
680
|
+
'lastPrice': None,
|
681
|
+
'notional': None,
|
682
|
+
'leverage': None,
|
683
|
+
'collateral': None,
|
684
|
+
'initialMargin': None,
|
685
|
+
'initialMarginPercentage': None,
|
686
|
+
'maintenanceMargin': None,
|
687
|
+
'maintenanceMarginPercentage': None,
|
688
|
+
'unrealizedPnl': None,
|
689
|
+
'realizedPnl': None,
|
690
|
+
'liquidationPrice': self.safe_number(position, 'liquidate_price'),
|
691
|
+
'marginMode': 'isolated' if (marginModeId == 1) else 'cross',
|
692
|
+
'percentage': None,
|
693
|
+
'marginRatio': None,
|
694
|
+
'stopLossPrice': None,
|
695
|
+
'takeProfitPrice': None,
|
696
|
+
})
|
244
697
|
|
245
698
|
def handle_trade(self, client: Client, message):
|
246
699
|
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
255
|
-
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
700
|
+
# spot
|
701
|
+
# {
|
702
|
+
# "table": "spot/trade",
|
703
|
+
# "data": [
|
704
|
+
# {
|
705
|
+
# "price": "52700.50",
|
706
|
+
# "s_t": 1630982050,
|
707
|
+
# "side": "buy",
|
708
|
+
# "size": "0.00112",
|
709
|
+
# "symbol": "BTC_USDT"
|
710
|
+
# },
|
711
|
+
# ]
|
712
|
+
# }
|
259
713
|
#
|
260
|
-
|
261
|
-
|
262
|
-
|
714
|
+
# swap
|
715
|
+
# {
|
716
|
+
# "group":"futures/trade:BTCUSDT",
|
717
|
+
# "data":[
|
718
|
+
# {
|
719
|
+
# "trade_id":6798697637,
|
720
|
+
# "contract_id":1,
|
721
|
+
# "symbol":"BTCUSDT",
|
722
|
+
# "deal_price":"39735.8",
|
723
|
+
# "deal_vol":"2",
|
724
|
+
# "type":0,
|
725
|
+
# "way":1,
|
726
|
+
# "create_time":1701618503,
|
727
|
+
# "create_time_mill":1701618503517,
|
728
|
+
# "created_at":"2023-12-03T15:48:23.517518538Z"
|
729
|
+
# }
|
730
|
+
# ]
|
731
|
+
# }
|
732
|
+
#
|
733
|
+
channel = self.safe_string_2(message, 'table', 'group')
|
734
|
+
isSpot = (channel.find('spot') >= 0)
|
735
|
+
data = self.safe_value(message, 'data')
|
736
|
+
if data is None:
|
737
|
+
return
|
738
|
+
stored = None
|
263
739
|
for i in range(0, len(data)):
|
264
|
-
trade = self.
|
740
|
+
trade = self.parse_ws_trade(data[i])
|
265
741
|
symbol = trade['symbol']
|
266
|
-
|
267
|
-
messageHash = table + ':' + marketId
|
742
|
+
tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
268
743
|
stored = self.safe_value(self.trades, symbol)
|
269
744
|
if stored is None:
|
270
745
|
stored = ArrayCache(tradesLimit)
|
271
746
|
self.trades[symbol] = stored
|
272
747
|
stored.append(trade)
|
273
|
-
|
748
|
+
messageHash = channel
|
749
|
+
if isSpot:
|
750
|
+
messageHash += ':' + self.safe_string(data[0], 'symbol')
|
751
|
+
client.resolve(stored, messageHash)
|
274
752
|
return message
|
275
753
|
|
754
|
+
def parse_ws_trade(self, trade, market: Market = None):
|
755
|
+
# spot
|
756
|
+
# {
|
757
|
+
# "price": "52700.50",
|
758
|
+
# "s_t": 1630982050,
|
759
|
+
# "side": "buy",
|
760
|
+
# "size": "0.00112",
|
761
|
+
# "symbol": "BTC_USDT"
|
762
|
+
# }
|
763
|
+
# swap
|
764
|
+
# {
|
765
|
+
# "trade_id":6798697637,
|
766
|
+
# "contract_id":1,
|
767
|
+
# "symbol":"BTCUSDT",
|
768
|
+
# "deal_price":"39735.8",
|
769
|
+
# "deal_vol":"2",
|
770
|
+
# "type":0,
|
771
|
+
# "way":1,
|
772
|
+
# "create_time":1701618503,
|
773
|
+
# "create_time_mill":1701618503517,
|
774
|
+
# "created_at":"2023-12-03T15:48:23.517518538Z"
|
775
|
+
# }
|
776
|
+
#
|
777
|
+
contractId = self.safe_string(trade, 'contract_id')
|
778
|
+
marketType = 'spot' if (contractId is None) else 'swap'
|
779
|
+
marketDelimiter = '_' if (marketType == 'spot') else ''
|
780
|
+
timestamp = self.safe_integer(trade, 'create_time_mill', self.safe_timestamp(trade, 's_t'))
|
781
|
+
marketId = self.safe_string(trade, 'symbol')
|
782
|
+
return self.safe_trade({
|
783
|
+
'info': trade,
|
784
|
+
'id': self.safe_string(trade, 'trade_id'),
|
785
|
+
'order': None,
|
786
|
+
'timestamp': timestamp,
|
787
|
+
'datetime': self.iso8601(timestamp),
|
788
|
+
'symbol': self.safe_symbol(marketId, market, marketDelimiter, marketType),
|
789
|
+
'type': None,
|
790
|
+
'side': self.safe_string(trade, 'side'),
|
791
|
+
'price': self.safe_string_2(trade, 'price', 'deal_price'),
|
792
|
+
'amount': self.safe_string_2(trade, 'size', 'deal_vol'),
|
793
|
+
'cost': None,
|
794
|
+
'takerOrMaker': None,
|
795
|
+
'fee': None,
|
796
|
+
}, market)
|
797
|
+
|
276
798
|
def handle_ticker(self, client: Client, message):
|
277
799
|
#
|
278
|
-
#
|
279
|
-
#
|
280
|
-
#
|
281
|
-
#
|
282
|
-
#
|
283
|
-
#
|
284
|
-
#
|
285
|
-
#
|
286
|
-
#
|
287
|
-
#
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
800
|
+
# {
|
801
|
+
# "data": [
|
802
|
+
# {
|
803
|
+
# "base_volume_24h": "78615593.81",
|
804
|
+
# "high_24h": "52756.97",
|
805
|
+
# "last_price": "52638.31",
|
806
|
+
# "low_24h": "50991.35",
|
807
|
+
# "open_24h": "51692.03",
|
808
|
+
# "s_t": 1630981727,
|
809
|
+
# "symbol": "BTC_USDT"
|
810
|
+
# }
|
811
|
+
# ],
|
812
|
+
# "table": "spot/ticker"
|
813
|
+
# }
|
814
|
+
# {
|
815
|
+
# "group":"futures/ticker",
|
816
|
+
# "data":{
|
817
|
+
# "symbol":"BTCUSDT",
|
818
|
+
# "volume_24":"117387.58",
|
819
|
+
# "fair_price":"146.24",
|
820
|
+
# "last_price":"146.24",
|
821
|
+
# "range":"147.17",
|
822
|
+
# "ask_price": "147.11",
|
823
|
+
# "ask_vol": "1",
|
824
|
+
# "bid_price": "142.11",
|
825
|
+
# "bid_vol": "1"
|
826
|
+
# }
|
827
|
+
# }
|
292
828
|
#
|
293
829
|
table = self.safe_string(message, 'table')
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
830
|
+
isSpot = (table is not None)
|
831
|
+
data = self.safe_value(message, 'data')
|
832
|
+
if data is None:
|
833
|
+
return
|
834
|
+
if isSpot:
|
835
|
+
for i in range(0, len(data)):
|
836
|
+
ticker = self.parse_ticker(data[i])
|
837
|
+
symbol = ticker['symbol']
|
838
|
+
marketId = self.safe_string(ticker['info'], 'symbol')
|
839
|
+
messageHash = table + ':' + marketId
|
840
|
+
self.tickers[symbol] = ticker
|
841
|
+
client.resolve(ticker, messageHash)
|
842
|
+
else:
|
843
|
+
ticker = self.parse_ws_swap_ticker(data)
|
844
|
+
symbol = self.safe_string(ticker, 'symbol')
|
300
845
|
self.tickers[symbol] = ticker
|
301
|
-
client.resolve(ticker,
|
846
|
+
client.resolve(ticker, 'tickers')
|
847
|
+
self.resolve_promise_if_messagehash_matches(client, 'tickers::', symbol, ticker)
|
302
848
|
return message
|
303
849
|
|
850
|
+
def parse_ws_swap_ticker(self, ticker, market: Market = None):
|
851
|
+
#
|
852
|
+
# {
|
853
|
+
# "symbol":"BTCUSDT",
|
854
|
+
# "volume_24":"117387.58",
|
855
|
+
# "fair_price":"146.24",
|
856
|
+
# "last_price":"146.24",
|
857
|
+
# "range":"147.17",
|
858
|
+
# "ask_price": "147.11",
|
859
|
+
# "ask_vol": "1",
|
860
|
+
# "bid_price": "142.11",
|
861
|
+
# "bid_vol": "1"
|
862
|
+
# }
|
863
|
+
marketId = self.safe_string(ticker, 'symbol')
|
864
|
+
return self.safe_ticker({
|
865
|
+
'symbol': self.safe_symbol(marketId, market, '', 'swap'),
|
866
|
+
'timestamp': None,
|
867
|
+
'datetime': None,
|
868
|
+
'high': None,
|
869
|
+
'low': None,
|
870
|
+
'bid': self.safe_string(ticker, 'bid_price'),
|
871
|
+
'bidVolume': self.safe_string(ticker, 'bid_vol'),
|
872
|
+
'ask': self.safe_string(ticker, 'ask_price'),
|
873
|
+
'askVolume': self.safe_string(ticker, 'ask_vol'),
|
874
|
+
'vwap': None,
|
875
|
+
'open': None,
|
876
|
+
'close': None,
|
877
|
+
'last': self.safe_string(ticker, 'last_price'),
|
878
|
+
'previousClose': None,
|
879
|
+
'change': None,
|
880
|
+
'percentage': None,
|
881
|
+
'average': self.safe_string(ticker, 'fair_price'),
|
882
|
+
'baseVolume': None,
|
883
|
+
'quoteVolume': self.safe_string(ticker, 'volume_24'),
|
884
|
+
'info': ticker,
|
885
|
+
}, market)
|
886
|
+
|
304
887
|
async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
|
305
888
|
"""
|
889
|
+
:see: https://developer-pro.bitmart.com/en/spot/#public-kline-channel
|
890
|
+
:see: https://developer-pro.bitmart.com/en/futures/#public-klinebin-channel
|
306
891
|
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
307
892
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
308
893
|
:param str timeframe: the length of time each candle represents
|
@@ -313,71 +898,125 @@ class bitmart(ccxt.async_support.bitmart):
|
|
313
898
|
"""
|
314
899
|
await self.load_markets()
|
315
900
|
symbol = self.symbol(symbol)
|
901
|
+
market = self.market(symbol)
|
902
|
+
type = 'spot'
|
903
|
+
type, params = self.handle_market_type_and_params('watchOrderBook', market, params)
|
316
904
|
timeframes = self.safe_value(self.options, 'timeframes', {})
|
317
905
|
interval = self.safe_string(timeframes, timeframe)
|
318
|
-
name =
|
319
|
-
|
906
|
+
name = None
|
907
|
+
if type == 'spot':
|
908
|
+
name = 'kline' + interval
|
909
|
+
else:
|
910
|
+
name = 'klineBin' + interval
|
911
|
+
ohlcv = await self.subscribe(name, symbol, type, params)
|
320
912
|
if self.newUpdates:
|
321
913
|
limit = ohlcv.getLimit(symbol, limit)
|
322
914
|
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
|
323
915
|
|
324
916
|
def handle_ohlcv(self, client: Client, message):
|
325
917
|
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
#
|
330
|
-
#
|
331
|
-
#
|
332
|
-
#
|
333
|
-
#
|
334
|
-
#
|
335
|
-
#
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
#
|
341
|
-
#
|
918
|
+
# {
|
919
|
+
# "data": [
|
920
|
+
# {
|
921
|
+
# "candle": [
|
922
|
+
# 1631056350,
|
923
|
+
# "46532.83",
|
924
|
+
# "46555.71",
|
925
|
+
# "46511.41",
|
926
|
+
# "46555.71",
|
927
|
+
# "0.25"
|
928
|
+
# ],
|
929
|
+
# "symbol": "BTC_USDT"
|
930
|
+
# }
|
931
|
+
# ],
|
932
|
+
# "table": "spot/kline1m"
|
933
|
+
# }
|
934
|
+
# swap
|
935
|
+
# {
|
936
|
+
# "group":"futures/klineBin1m:BTCUSDT",
|
937
|
+
# "data":{
|
938
|
+
# "symbol":"BTCUSDT",
|
939
|
+
# "items":[
|
940
|
+
# {
|
941
|
+
# "o":"39635.8",
|
942
|
+
# "h":"39636",
|
943
|
+
# "l":"39614.4",
|
944
|
+
# "c":"39629.7",
|
945
|
+
# "v":"31852",
|
946
|
+
# "ts":1701617761
|
947
|
+
# }
|
948
|
+
# ]
|
949
|
+
# }
|
950
|
+
# }
|
342
951
|
#
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
952
|
+
channel = self.safe_string_2(message, 'table', 'group')
|
953
|
+
isSpot = (channel.find('spot') >= 0)
|
954
|
+
data = self.safe_value(message, 'data')
|
955
|
+
if data is None:
|
956
|
+
return
|
957
|
+
parts = channel.split('/')
|
958
|
+
part1 = self.safe_string(parts, 1, '')
|
347
959
|
interval = part1.replace('kline', '')
|
960
|
+
interval = interval.replace('Bin', '')
|
961
|
+
intervalParts = interval.split(':')
|
962
|
+
interval = self.safe_string(intervalParts, 0)
|
348
963
|
# use a reverse lookup in a static map instead
|
349
964
|
timeframes = self.safe_value(self.options, 'timeframes', {})
|
350
965
|
timeframe = self.find_timeframe(interval, timeframes)
|
351
966
|
duration = self.parse_timeframe(timeframe)
|
352
967
|
durationInMs = duration * 1000
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
968
|
+
if isSpot:
|
969
|
+
for i in range(0, len(data)):
|
970
|
+
marketId = self.safe_string(data[i], 'symbol')
|
971
|
+
market = self.safe_market(marketId)
|
972
|
+
symbol = market['symbol']
|
973
|
+
rawOHLCV = self.safe_value(data[i], 'candle')
|
974
|
+
parsed = self.parse_ohlcv(rawOHLCV, market)
|
975
|
+
parsed[0] = self.parse_to_int(parsed[0] / durationInMs) * durationInMs
|
976
|
+
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
|
977
|
+
stored = self.safe_value(self.ohlcvs[symbol], timeframe)
|
978
|
+
if stored is None:
|
979
|
+
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
980
|
+
stored = ArrayCacheByTimestamp(limit)
|
981
|
+
self.ohlcvs[symbol][timeframe] = stored
|
982
|
+
stored.append(parsed)
|
983
|
+
messageHash = channel + ':' + marketId
|
984
|
+
client.resolve(stored, messageHash)
|
985
|
+
else:
|
986
|
+
marketId = self.safe_string(data, 'symbol')
|
987
|
+
market = self.safe_market(marketId, None, '', 'swap')
|
357
988
|
symbol = market['symbol']
|
358
|
-
|
359
|
-
parsed[0] = self.parse_to_int(parsed[0] / durationInMs) * durationInMs
|
989
|
+
items = self.safe_value(data, 'items', [])
|
360
990
|
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
|
361
991
|
stored = self.safe_value(self.ohlcvs[symbol], timeframe)
|
362
992
|
if stored is None:
|
363
993
|
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
364
994
|
stored = ArrayCacheByTimestamp(limit)
|
365
995
|
self.ohlcvs[symbol][timeframe] = stored
|
366
|
-
|
367
|
-
|
368
|
-
|
996
|
+
for i in range(0, len(items)):
|
997
|
+
candle = items[i]
|
998
|
+
parsed = self.parse_ohlcv(candle, market)
|
999
|
+
stored.append(parsed)
|
1000
|
+
client.resolve(stored, channel)
|
369
1001
|
|
370
1002
|
async def watch_order_book(self, symbol: str, limit: Int = None, params={}):
|
371
1003
|
"""
|
1004
|
+
:see: https://developer-pro.bitmart.com/en/spot/#public-depth-all-channel
|
1005
|
+
:see: https://developer-pro.bitmart.com/en/futures/#public-depth-channel
|
372
1006
|
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
373
1007
|
:param str symbol: unified symbol of the market to fetch the order book for
|
374
1008
|
:param int [limit]: the maximum amount of order book entries to return
|
375
1009
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
376
1010
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
377
1011
|
"""
|
1012
|
+
await self.load_markets()
|
378
1013
|
options = self.safe_value(self.options, 'watchOrderBook', {})
|
379
1014
|
depth = self.safe_string(options, 'depth', 'depth50')
|
380
|
-
|
1015
|
+
symbol = self.symbol(symbol)
|
1016
|
+
market = self.market(symbol)
|
1017
|
+
type = 'spot'
|
1018
|
+
type, params = self.handle_market_type_and_params('watchOrderBook', market, params)
|
1019
|
+
orderbook = await self.subscribe(depth, symbol, type, params)
|
381
1020
|
return orderbook.limit()
|
382
1021
|
|
383
1022
|
def handle_delta(self, bookside, delta):
|
@@ -424,22 +1063,19 @@ class bitmart(ccxt.async_support.bitmart):
|
|
424
1063
|
|
425
1064
|
def handle_order_book(self, client: Client, message):
|
426
1065
|
#
|
1066
|
+
# spot
|
427
1067
|
# {
|
428
1068
|
# "data": [
|
429
1069
|
# {
|
430
1070
|
# "asks": [
|
431
1071
|
# ['46828.38', "0.21847"],
|
432
1072
|
# ['46830.68', "0.08232"],
|
433
|
-
#
|
434
|
-
# ['46837.82', "0.02028"],
|
435
|
-
# ['46839.43', "0.15068"]
|
1073
|
+
# ...
|
436
1074
|
# ],
|
437
1075
|
# "bids": [
|
438
1076
|
# ['46820.78', "0.00444"],
|
439
1077
|
# ['46814.33', "0.00234"],
|
440
|
-
#
|
441
|
-
# ['46808.14', "0.00217"],
|
442
|
-
# ['46808.04', "0.00013"]
|
1078
|
+
# ...
|
443
1079
|
# ],
|
444
1080
|
# "ms_t": 1631044962431,
|
445
1081
|
# "symbol": "BTC_USDT"
|
@@ -447,30 +1083,89 @@ class bitmart(ccxt.async_support.bitmart):
|
|
447
1083
|
# ],
|
448
1084
|
# "table": "spot/depth5"
|
449
1085
|
# }
|
1086
|
+
# swap
|
1087
|
+
# {
|
1088
|
+
# "group":"futures/depth50:BTCUSDT",
|
1089
|
+
# "data":{
|
1090
|
+
# "symbol":"BTCUSDT",
|
1091
|
+
# "way":1,
|
1092
|
+
# "depths":[
|
1093
|
+
# {
|
1094
|
+
# "price":"39509.8",
|
1095
|
+
# "vol":"2379"
|
1096
|
+
# },
|
1097
|
+
# {
|
1098
|
+
# "price":"39509.6",
|
1099
|
+
# "vol":"6815"
|
1100
|
+
# },
|
1101
|
+
# ...
|
1102
|
+
# ],
|
1103
|
+
# "ms_t":1701566021194
|
1104
|
+
# }
|
1105
|
+
# }
|
450
1106
|
#
|
451
|
-
data = self.safe_value(message, 'data'
|
452
|
-
|
1107
|
+
data = self.safe_value(message, 'data')
|
1108
|
+
if data is None:
|
1109
|
+
return
|
1110
|
+
depths = self.safe_value(data, 'depths')
|
1111
|
+
isSpot = (depths is None)
|
1112
|
+
table = self.safe_string_2(message, 'table', 'group')
|
453
1113
|
parts = table.split('/')
|
454
1114
|
lastPart = self.safe_string(parts, 1)
|
455
1115
|
limitString = lastPart.replace('depth', '')
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
1116
|
+
dotsIndex = limitString.find(':')
|
1117
|
+
limitString = limitString[0:dotsIndex]
|
1118
|
+
limit = self.parse_to_int(limitString)
|
1119
|
+
if isSpot:
|
1120
|
+
for i in range(0, len(data)):
|
1121
|
+
update = data[i]
|
1122
|
+
marketId = self.safe_string(update, 'symbol')
|
1123
|
+
symbol = self.safe_symbol(marketId)
|
1124
|
+
orderbook = self.safe_value(self.orderbooks, symbol)
|
1125
|
+
if orderbook is None:
|
1126
|
+
orderbook = self.order_book({}, limit)
|
1127
|
+
orderbook['symbol'] = symbol
|
1128
|
+
self.orderbooks[symbol] = orderbook
|
1129
|
+
orderbook.reset({})
|
1130
|
+
self.handle_order_book_message(client, update, orderbook)
|
1131
|
+
timestamp = self.safe_integer(update, 'ms_t')
|
1132
|
+
orderbook['timestamp'] = timestamp
|
1133
|
+
orderbook['datetime'] = self.iso8601(timestamp)
|
1134
|
+
messageHash = table + ':' + marketId
|
1135
|
+
client.resolve(orderbook, messageHash)
|
1136
|
+
else:
|
1137
|
+
marketId = self.safe_string(data, 'symbol')
|
460
1138
|
symbol = self.safe_symbol(marketId)
|
461
1139
|
orderbook = self.safe_value(self.orderbooks, symbol)
|
462
1140
|
if orderbook is None:
|
463
1141
|
orderbook = self.order_book({}, limit)
|
1142
|
+
orderbook['symbol'] = symbol
|
464
1143
|
self.orderbooks[symbol] = orderbook
|
465
|
-
|
466
|
-
|
467
|
-
|
1144
|
+
way = self.safe_number(data, 'way')
|
1145
|
+
side = 'bids' if (way == 1) else 'asks'
|
1146
|
+
if way == 1:
|
1147
|
+
orderbook[side] = Bids([], limit)
|
1148
|
+
else:
|
1149
|
+
orderbook[side] = Asks([], limit)
|
1150
|
+
for i in range(0, len(depths)):
|
1151
|
+
depth = depths[i]
|
1152
|
+
price = self.safe_number(depth, 'price')
|
1153
|
+
amount = self.safe_number(depth, 'vol')
|
1154
|
+
orderbookSide = self.safe_value(orderbook, side)
|
1155
|
+
orderbookSide.store(price, amount)
|
1156
|
+
bidsLength = len(orderbook['bids'])
|
1157
|
+
asksLength = len(orderbook['asks'])
|
1158
|
+
if (bidsLength == 0) or (asksLength == 0):
|
1159
|
+
return
|
1160
|
+
timestamp = self.safe_integer(data, 'ms_t')
|
1161
|
+
orderbook['timestamp'] = timestamp
|
1162
|
+
orderbook['datetime'] = self.iso8601(timestamp)
|
1163
|
+
messageHash = table
|
468
1164
|
client.resolve(orderbook, messageHash)
|
469
|
-
return message
|
470
1165
|
|
471
|
-
async def authenticate(self, params={}):
|
1166
|
+
async def authenticate(self, type, params={}):
|
472
1167
|
self.check_required_credentials()
|
473
|
-
url = self.implode_hostname(self.urls['api']['ws']['private'])
|
1168
|
+
url = self.implode_hostname(self.urls['api']['ws'][type]['private'])
|
474
1169
|
messageHash = 'authenticated'
|
475
1170
|
client = self.client(url)
|
476
1171
|
future = client.future(messageHash)
|
@@ -481,28 +1176,42 @@ class bitmart(ccxt.async_support.bitmart):
|
|
481
1176
|
path = 'bitmart.WebSocket'
|
482
1177
|
auth = timestamp + '#' + memo + '#' + path
|
483
1178
|
signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
1179
|
+
request = None
|
1180
|
+
if type == 'spot':
|
1181
|
+
request = {
|
1182
|
+
'op': 'login',
|
1183
|
+
'args': [
|
1184
|
+
self.apiKey,
|
1185
|
+
timestamp,
|
1186
|
+
signature,
|
1187
|
+
],
|
1188
|
+
}
|
1189
|
+
else:
|
1190
|
+
request = {
|
1191
|
+
'action': 'access',
|
1192
|
+
'args': [
|
1193
|
+
self.apiKey,
|
1194
|
+
timestamp,
|
1195
|
+
signature,
|
1196
|
+
'web',
|
1197
|
+
],
|
1198
|
+
}
|
493
1199
|
message = self.extend(request, params)
|
494
1200
|
self.watch(url, messageHash, message, messageHash)
|
495
1201
|
return future
|
496
1202
|
|
497
1203
|
def handle_subscription_status(self, client: Client, message):
|
498
1204
|
#
|
499
|
-
#
|
1205
|
+
# {"event":"subscribe","channel":"spot/depth:BTC-USDT"}
|
500
1206
|
#
|
501
1207
|
return message
|
502
1208
|
|
503
1209
|
def handle_authenticate(self, client: Client, message):
|
504
1210
|
#
|
505
|
-
#
|
1211
|
+
# spot
|
1212
|
+
# {event: "login"}
|
1213
|
+
# swap
|
1214
|
+
# {action: 'access', success: True}
|
506
1215
|
#
|
507
1216
|
messageHash = 'authenticated'
|
508
1217
|
future = self.safe_value(client.futures, messageHash)
|
@@ -510,24 +1219,36 @@ class bitmart(ccxt.async_support.bitmart):
|
|
510
1219
|
|
511
1220
|
def handle_error_message(self, client: Client, message):
|
512
1221
|
#
|
513
|
-
#
|
514
|
-
#
|
1222
|
+
# {event: "error", message: "Invalid sign", errorCode: 30013}
|
1223
|
+
# {"event":"error","message":"Unrecognized request: {\"event\":\"subscribe\",\"channel\":\"spot/depth:BTC-USDT\"}","errorCode":30039}
|
1224
|
+
# {
|
1225
|
+
# action: '',
|
1226
|
+
# group: 'futures/trade:BTCUSDT',
|
1227
|
+
# success: False,
|
1228
|
+
# request: {action: '', args: ['futures/trade:BTCUSDT']},
|
1229
|
+
# error: 'Invalid action [] for group [futures/trade:BTCUSDT]'
|
1230
|
+
# }
|
515
1231
|
#
|
516
1232
|
errorCode = self.safe_string(message, 'errorCode')
|
1233
|
+
error = self.safe_string(message, 'error')
|
517
1234
|
try:
|
518
|
-
if errorCode is not None:
|
1235
|
+
if errorCode is not None or error is not None:
|
519
1236
|
feedback = self.id + ' ' + self.json(message)
|
520
1237
|
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
521
|
-
messageString = self.safe_value(message, 'message')
|
522
|
-
|
523
|
-
|
1238
|
+
messageString = self.safe_value(message, 'message', error)
|
1239
|
+
self.throw_broadly_matched_exception(self.exceptions['broad'], messageString, feedback)
|
1240
|
+
action = self.safe_string(message, 'action')
|
1241
|
+
if action == 'access':
|
1242
|
+
raise AuthenticationError(feedback)
|
1243
|
+
raise ExchangeError(feedback)
|
524
1244
|
return False
|
525
1245
|
except Exception as e:
|
526
|
-
if isinstance(e, AuthenticationError):
|
1246
|
+
if (isinstance(e, AuthenticationError)):
|
527
1247
|
messageHash = 'authenticated'
|
528
1248
|
client.reject(e, messageHash)
|
529
1249
|
if messageHash in client.subscriptions:
|
530
1250
|
del client.subscriptions[messageHash]
|
1251
|
+
client.reject(e)
|
531
1252
|
return True
|
532
1253
|
|
533
1254
|
def handle_message(self, client: Client, message):
|
@@ -558,14 +1279,14 @@ class bitmart(ccxt.async_support.bitmart):
|
|
558
1279
|
#
|
559
1280
|
# {data: '', table: "spot/user/order"}
|
560
1281
|
#
|
561
|
-
|
562
|
-
if
|
563
|
-
event = self.
|
1282
|
+
channel = self.safe_string_2(message, 'table', 'group')
|
1283
|
+
if channel is None:
|
1284
|
+
event = self.safe_string_2(message, 'event', 'action')
|
564
1285
|
if event is not None:
|
565
1286
|
methods = {
|
566
1287
|
# 'info': self.handleSystemStatus,
|
567
|
-
# 'book': 'handleOrderBook',
|
568
1288
|
'login': self.handle_authenticate,
|
1289
|
+
'access': self.handle_authenticate,
|
569
1290
|
'subscribe': self.handle_subscription_status,
|
570
1291
|
}
|
571
1292
|
method = self.safe_value(methods, event)
|
@@ -574,24 +1295,21 @@ class bitmart(ccxt.async_support.bitmart):
|
|
574
1295
|
else:
|
575
1296
|
return method(client, message)
|
576
1297
|
else:
|
577
|
-
parts = table.split('/')
|
578
|
-
name = self.safe_string(parts, 1)
|
579
1298
|
methods = {
|
580
|
-
'depth': self.handle_order_book,
|
581
1299
|
'depth5': self.handle_order_book,
|
582
1300
|
'depth20': self.handle_order_book,
|
583
1301
|
'depth50': self.handle_order_book,
|
584
1302
|
'ticker': self.handle_ticker,
|
585
1303
|
'trade': self.handle_trade,
|
586
|
-
|
1304
|
+
'kline': self.handle_ohlcv,
|
1305
|
+
'order': self.handle_orders,
|
1306
|
+
'position': self.handle_positions,
|
1307
|
+
'balance': self.handle_balance,
|
1308
|
+
'asset': self.handle_balance,
|
587
1309
|
}
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
if method is None:
|
595
|
-
return message
|
596
|
-
else:
|
597
|
-
return method(client, message)
|
1310
|
+
keys = list(methods.keys())
|
1311
|
+
for i in range(0, len(keys)):
|
1312
|
+
key = keys[i]
|
1313
|
+
if channel.find(key) >= 0:
|
1314
|
+
method = self.safe_value(methods, key)
|
1315
|
+
return method(client, message)
|