bitmex-api 0.0.69__py3-none-any.whl → 0.0.71__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.
- bitmex/ccxt/__init__.py +1 -1
- bitmex/ccxt/async_support/__init__.py +1 -1
- bitmex/ccxt/async_support/base/exchange.py +6 -3
- bitmex/ccxt/async_support/base/ws/client.py +173 -64
- bitmex/ccxt/async_support/base/ws/future.py +23 -50
- bitmex/ccxt/async_support/bitmex.py +2 -1
- bitmex/ccxt/base/exchange.py +22 -23
- bitmex/ccxt/base/types.py +1 -0
- bitmex/ccxt/bitmex.py +2 -1
- bitmex/ccxt/pro/__init__.py +1 -1
- {bitmex_api-0.0.69.dist-info → bitmex_api-0.0.71.dist-info}/METADATA +1 -1
- {bitmex_api-0.0.69.dist-info → bitmex_api-0.0.71.dist-info}/RECORD +13 -14
- bitmex/ccxt/async_support/base/ws/aiohttp_client.py +0 -147
- {bitmex_api-0.0.69.dist-info → bitmex_api-0.0.71.dist-info}/WHEEL +0 -0
bitmex/ccxt/__init__.py
CHANGED
@@ -26,7 +26,7 @@ sys.modules['ccxt'] = ccxt_module
|
|
26
26
|
|
27
27
|
# ----------------------------------------------------------------------------
|
28
28
|
|
29
|
-
__version__ = '4.4.
|
29
|
+
__version__ = '4.4.92'
|
30
30
|
|
31
31
|
# ----------------------------------------------------------------------------
|
32
32
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# -----------------------------------------------------------------------------
|
4
4
|
|
5
|
-
__version__ = '4.4.
|
5
|
+
__version__ = '4.4.92'
|
6
6
|
|
7
7
|
# -----------------------------------------------------------------------------
|
8
8
|
|
@@ -34,7 +34,7 @@ from ccxt.base.exchange import Exchange as BaseExchange, ArgumentsRequired
|
|
34
34
|
# -----------------------------------------------------------------------------
|
35
35
|
|
36
36
|
from ccxt.async_support.base.ws.functions import inflate, inflate64, gunzip
|
37
|
-
from ccxt.async_support.base.ws.
|
37
|
+
from ccxt.async_support.base.ws.client import Client
|
38
38
|
from ccxt.async_support.base.ws.future import Future
|
39
39
|
from ccxt.async_support.base.ws.order_book import OrderBook, IndexedOrderBook, CountedOrderBook
|
40
40
|
|
@@ -419,7 +419,7 @@ class Exchange(BaseExchange):
|
|
419
419
|
}, ws_options)
|
420
420
|
# we use aiohttp instead of fastClient now because of this
|
421
421
|
# https://github.com/ccxt/ccxt/pull/25995
|
422
|
-
self.clients[url] =
|
422
|
+
self.clients[url] = Client(url, on_message, on_error, on_close, on_connected, options)
|
423
423
|
# set http/s proxy (socks proxy should be set in other place)
|
424
424
|
httpProxy, httpsProxy, socksProxy = self.check_ws_proxy_settings()
|
425
425
|
if (httpProxy or httpsProxy):
|
@@ -638,6 +638,9 @@ class Exchange(BaseExchange):
|
|
638
638
|
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
|
639
639
|
raise NotSupported(self.id + ' watchTrades() is not supported yet')
|
640
640
|
|
641
|
+
async def un_watch_orders(self, symbol: Str = None, params={}):
|
642
|
+
raise NotSupported(self.id + ' unWatchOrders() is not supported yet')
|
643
|
+
|
641
644
|
async def un_watch_trades(self, symbol: str, params={}):
|
642
645
|
raise NotSupported(self.id + ' unWatchTrades() is not supported yet')
|
643
646
|
|
@@ -1,21 +1,31 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
orjson = None
|
4
|
+
try:
|
5
|
+
import orjson as orjson
|
6
|
+
except ImportError:
|
7
|
+
pass
|
8
|
+
|
9
|
+
import json
|
10
|
+
|
11
|
+
from asyncio import sleep, ensure_future, wait_for, TimeoutError, BaseEventLoop, Future as asyncioFuture
|
12
|
+
from .functions import milliseconds, iso8601, deep_extend, is_json_encoded_object
|
13
|
+
from ccxt import NetworkError, RequestTimeout
|
6
14
|
from ccxt.async_support.base.ws.future import Future
|
7
|
-
from
|
15
|
+
from ccxt.async_support.base.ws.functions import gunzip, inflate
|
16
|
+
from typing import Dict
|
17
|
+
|
18
|
+
from aiohttp import WSMsgType
|
19
|
+
|
8
20
|
|
9
21
|
class Client(object):
|
10
22
|
|
11
23
|
url = None
|
12
24
|
ws = None
|
13
|
-
futures = {}
|
25
|
+
futures: Dict[str, Future] = {}
|
14
26
|
options = {} # ws-specific options
|
15
27
|
subscriptions = {}
|
16
28
|
rejections = {}
|
17
|
-
message_queue = {}
|
18
|
-
useMessageQueue = False
|
19
29
|
on_message_callback = None
|
20
30
|
on_error_callback = None
|
21
31
|
on_close_callback = None
|
@@ -32,14 +42,14 @@ class Client(object):
|
|
32
42
|
maxPingPongMisses = 2.0 # how many missed pongs to raise a timeout
|
33
43
|
lastPong = None
|
34
44
|
ping = None # ping-function if defined
|
45
|
+
proxy = None
|
35
46
|
verbose = False # verbose output
|
36
47
|
gunzip = False
|
37
48
|
inflate = False
|
38
49
|
throttle = None
|
39
50
|
connecting = False
|
40
|
-
asyncio_loop = None
|
51
|
+
asyncio_loop: BaseEventLoop = None
|
41
52
|
ping_looper = None
|
42
|
-
receive_looper = None
|
43
53
|
|
44
54
|
def __init__(self, url, on_message_callback, on_error_callback, on_close_callback, on_connected_callback, config={}):
|
45
55
|
defaults = {
|
@@ -70,37 +80,19 @@ class Client(object):
|
|
70
80
|
if message_hash in self.rejections:
|
71
81
|
future.reject(self.rejections[message_hash])
|
72
82
|
del self.rejections[message_hash]
|
73
|
-
del self.message_queue[message_hash]
|
74
|
-
return future
|
75
|
-
if self.useMessageQueue and message_hash in self.message_queue:
|
76
|
-
queue = self.message_queue[message_hash]
|
77
|
-
if len(queue):
|
78
|
-
future.resolve(queue.popleft())
|
79
|
-
del self.futures[message_hash]
|
80
83
|
return future
|
81
84
|
|
82
85
|
def resolve(self, result, message_hash):
|
83
86
|
if self.verbose and message_hash is None:
|
84
87
|
self.log(iso8601(milliseconds()), 'resolve received None messageHash')
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
queue = self.message_queue[message_hash]
|
90
|
-
queue.append(result)
|
91
|
-
if message_hash in self.futures:
|
92
|
-
future = self.futures[message_hash]
|
93
|
-
future.resolve(queue.popleft())
|
94
|
-
del self.futures[message_hash]
|
95
|
-
else:
|
96
|
-
if message_hash in self.futures:
|
97
|
-
future = self.futures[message_hash]
|
98
|
-
future.resolve(result)
|
99
|
-
del self.futures[message_hash]
|
88
|
+
if message_hash in self.futures:
|
89
|
+
future = self.futures[message_hash]
|
90
|
+
future.resolve(result)
|
91
|
+
del self.futures[message_hash]
|
100
92
|
return result
|
101
93
|
|
102
94
|
def reject(self, result, message_hash=None):
|
103
|
-
if message_hash:
|
95
|
+
if message_hash is not None:
|
104
96
|
if message_hash in self.futures:
|
105
97
|
future = self.futures[message_hash]
|
106
98
|
future.reject(result)
|
@@ -113,19 +105,38 @@ class Client(object):
|
|
113
105
|
self.reject(result, message_hash)
|
114
106
|
return result
|
115
107
|
|
116
|
-
|
108
|
+
def receive_loop(self):
|
117
109
|
if self.verbose:
|
118
110
|
self.log(iso8601(milliseconds()), 'receive loop')
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
self.
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
self.
|
111
|
+
if not self.closed():
|
112
|
+
# let's drain the aiohttp buffer to avoid latency
|
113
|
+
if len(self.buffer) > 1:
|
114
|
+
size_delta = 0
|
115
|
+
while len(self.buffer) > 1:
|
116
|
+
message, size = self.buffer.popleft()
|
117
|
+
size_delta += size
|
118
|
+
self.handle_message(message)
|
119
|
+
# we must update the size of the last message inside WebSocketDataQueue
|
120
|
+
# self.receive() calls WebSocketDataQueue.read() that calls WebSocketDataQueue._read_from_buffer()
|
121
|
+
# which updates the size of the buffer, the _size will overflow and pause the transport
|
122
|
+
# make sure to set the enviroment variable AIOHTTP_NO_EXTENSIONS=Y to check
|
123
|
+
# print(self.connection._conn.protocol._payload._size)
|
124
|
+
self.buffer[0] = (self.buffer[0][0], self.buffer[0][1] + size_delta)
|
125
|
+
|
126
|
+
task = self.asyncio_loop.create_task(self.receive())
|
127
|
+
|
128
|
+
def after_interrupt(resolved: asyncioFuture):
|
129
|
+
exception = resolved.exception()
|
130
|
+
if exception is None:
|
131
|
+
self.handle_message(resolved.result())
|
132
|
+
self.asyncio_loop.call_soon(self.receive_loop)
|
133
|
+
else:
|
134
|
+
error = NetworkError(str(exception))
|
135
|
+
if self.verbose:
|
136
|
+
self.log(iso8601(milliseconds()), 'receive_loop', 'Exception', error)
|
137
|
+
self.reject(error)
|
138
|
+
|
139
|
+
task.add_done_callback(after_interrupt)
|
129
140
|
|
130
141
|
async def open(self, session, backoff_delay=0):
|
131
142
|
# exponential backoff for consequent connections if necessary
|
@@ -146,7 +157,7 @@ class Client(object):
|
|
146
157
|
self.on_connected_callback(self)
|
147
158
|
# run both loops forever
|
148
159
|
self.ping_looper = ensure_future(self.ping_loop(), loop=self.asyncio_loop)
|
149
|
-
self.
|
160
|
+
self.asyncio_loop.call_soon(self.receive_loop)
|
150
161
|
except TimeoutError:
|
151
162
|
# connection timeout
|
152
163
|
error = RequestTimeout('Connection timeout')
|
@@ -160,6 +171,13 @@ class Client(object):
|
|
160
171
|
self.log(iso8601(milliseconds()), 'NetworkError', error)
|
161
172
|
self.on_error(error)
|
162
173
|
|
174
|
+
@property
|
175
|
+
def buffer(self):
|
176
|
+
# looks like they exposed it in C
|
177
|
+
# this means we can bypass it
|
178
|
+
# https://github.com/aio-libs/aiohttp/blob/master/aiohttp/_websocket/reader_c.pxd#L53C24-L53C31
|
179
|
+
return self.connection._conn.protocol._payload._buffer
|
180
|
+
|
163
181
|
def connect(self, session, backoff_delay=0):
|
164
182
|
if not self.connection and not self.connecting:
|
165
183
|
self.connecting = True
|
@@ -170,7 +188,7 @@ class Client(object):
|
|
170
188
|
if self.verbose:
|
171
189
|
self.log(iso8601(milliseconds()), 'on_error', error)
|
172
190
|
self.error = error
|
173
|
-
self.
|
191
|
+
self.reject(error)
|
174
192
|
self.on_error_callback(self, error)
|
175
193
|
if not self.closed():
|
176
194
|
ensure_future(self.close(1006), loop=self.asyncio_loop)
|
@@ -179,36 +197,127 @@ class Client(object):
|
|
179
197
|
if self.verbose:
|
180
198
|
self.log(iso8601(milliseconds()), 'on_close', code)
|
181
199
|
if not self.error:
|
182
|
-
self.
|
200
|
+
self.reject(NetworkError('Connection closed by remote server, closing code ' + str(code)))
|
183
201
|
self.on_close_callback(self, code)
|
184
|
-
|
185
|
-
ensure_future(self.close(code), loop=self.asyncio_loop)
|
202
|
+
ensure_future(self.aiohttp_close(), loop=self.asyncio_loop)
|
186
203
|
|
187
|
-
def
|
188
|
-
|
189
|
-
self.reject(error)
|
204
|
+
def log(self, *args):
|
205
|
+
print(*args)
|
190
206
|
|
191
|
-
|
192
|
-
|
193
|
-
self.log(iso8601(milliseconds()), 'ping loop')
|
207
|
+
def closed(self):
|
208
|
+
return (self.connection is None) or self.connection.closed
|
194
209
|
|
195
210
|
def receive(self):
|
196
|
-
|
211
|
+
return self.connection.receive()
|
212
|
+
|
213
|
+
# helper method for binary and text messages
|
214
|
+
def handle_text_or_binary_message(self, data):
|
215
|
+
if self.verbose:
|
216
|
+
self.log(iso8601(milliseconds()), 'message', data)
|
217
|
+
if isinstance(data, bytes):
|
218
|
+
data = data.decode()
|
219
|
+
# decoded = json.loads(data) if is_json_encoded_object(data) else data
|
220
|
+
decode = None
|
221
|
+
if is_json_encoded_object(data):
|
222
|
+
if orjson is None:
|
223
|
+
decode = json.loads(data)
|
224
|
+
else:
|
225
|
+
decode = orjson.loads(data)
|
226
|
+
else:
|
227
|
+
decode = data
|
228
|
+
self.on_message_callback(self, decode)
|
197
229
|
|
198
230
|
def handle_message(self, message):
|
199
|
-
|
231
|
+
# self.log(iso8601(milliseconds()), message)
|
232
|
+
if message.type == WSMsgType.TEXT:
|
233
|
+
self.handle_text_or_binary_message(message.data)
|
234
|
+
elif message.type == WSMsgType.BINARY:
|
235
|
+
data = message.data
|
236
|
+
if self.gunzip:
|
237
|
+
data = gunzip(data)
|
238
|
+
elif self.inflate:
|
239
|
+
data = inflate(data)
|
240
|
+
self.handle_text_or_binary_message(data)
|
241
|
+
# autoping is responsible for automatically replying with pong
|
242
|
+
# to a ping incoming from a server, we have to disable autoping
|
243
|
+
# with aiohttp's websockets and respond with pong manually
|
244
|
+
# otherwise aiohttp's websockets client won't trigger WSMsgType.PONG
|
245
|
+
elif message.type == WSMsgType.PING:
|
246
|
+
if self.verbose:
|
247
|
+
self.log(iso8601(milliseconds()), 'ping', message)
|
248
|
+
ensure_future(self.connection.pong(message.data), loop=self.asyncio_loop)
|
249
|
+
elif message.type == WSMsgType.PONG:
|
250
|
+
self.lastPong = milliseconds()
|
251
|
+
if self.verbose:
|
252
|
+
self.log(iso8601(milliseconds()), 'pong', message)
|
253
|
+
pass
|
254
|
+
elif message.type == WSMsgType.CLOSE:
|
255
|
+
if self.verbose:
|
256
|
+
self.log(iso8601(milliseconds()), 'close', self.closed(), message)
|
257
|
+
self.on_close(message.data)
|
258
|
+
elif message.type == WSMsgType.ERROR:
|
259
|
+
if self.verbose:
|
260
|
+
self.log(iso8601(milliseconds()), 'error', message)
|
261
|
+
error = NetworkError(str(message))
|
262
|
+
self.on_error(error)
|
200
263
|
|
201
|
-
def
|
202
|
-
|
264
|
+
def create_connection(self, session):
|
265
|
+
# autoping is responsible for automatically replying with pong
|
266
|
+
# to a ping incoming from a server, we have to disable autoping
|
267
|
+
# with aiohttp's websockets and respond with pong manually
|
268
|
+
# otherwise aiohttp's websockets client won't trigger WSMsgType.PONG
|
269
|
+
# call aenter here to simulate async with otherwise we get the error "await not called with future"
|
270
|
+
# if connecting to a non-existent endpoint
|
271
|
+
if (self.proxy):
|
272
|
+
return session.ws_connect(self.url, autoping=False, autoclose=False, headers=self.options.get('headers'), proxy=self.proxy, max_msg_size=10485760).__aenter__()
|
273
|
+
return session.ws_connect(self.url, autoping=False, autoclose=False, headers=self.options.get('headers'), max_msg_size=10485760).__aenter__()
|
203
274
|
|
204
275
|
async def send(self, message):
|
205
|
-
|
276
|
+
if self.verbose:
|
277
|
+
self.log(iso8601(milliseconds()), 'sending', message)
|
278
|
+
send_msg = None
|
279
|
+
if isinstance(message, str):
|
280
|
+
send_msg = message
|
281
|
+
else:
|
282
|
+
if orjson is None:
|
283
|
+
send_msg = json.dumps(message, separators=(',', ':'))
|
284
|
+
else:
|
285
|
+
send_msg = orjson.dumps(message).decode('utf-8')
|
286
|
+
return await self.connection.send_str(send_msg)
|
206
287
|
|
207
288
|
async def close(self, code=1000):
|
208
|
-
|
289
|
+
if self.verbose:
|
290
|
+
self.log(iso8601(milliseconds()), 'closing', code)
|
291
|
+
for future in self.futures.values():
|
292
|
+
future.cancel()
|
293
|
+
await self.aiohttp_close()
|
209
294
|
|
210
|
-
def
|
211
|
-
|
295
|
+
async def aiohttp_close(self):
|
296
|
+
if not self.closed():
|
297
|
+
await self.connection.close()
|
298
|
+
# these will end automatically once self.closed() = True
|
299
|
+
# so we don't need to cancel them
|
300
|
+
if self.ping_looper:
|
301
|
+
self.ping_looper.cancel()
|
212
302
|
|
213
|
-
def
|
214
|
-
|
303
|
+
async def ping_loop(self):
|
304
|
+
if self.verbose:
|
305
|
+
self.log(iso8601(milliseconds()), 'ping loop')
|
306
|
+
while self.keepAlive and not self.closed():
|
307
|
+
now = milliseconds()
|
308
|
+
self.lastPong = now if self.lastPong is None else self.lastPong
|
309
|
+
if (self.lastPong + self.keepAlive * self.maxPingPongMisses) < now:
|
310
|
+
self.on_error(RequestTimeout('Connection to ' + self.url + ' timed out due to a ping-pong keepalive missing on time'))
|
311
|
+
# the following ping-clause is not necessary with aiohttp's built-in ws
|
312
|
+
# since it has a heartbeat option (see create_connection above)
|
313
|
+
# however some exchanges require a text-type ping message
|
314
|
+
# therefore we need this clause anyway
|
315
|
+
else:
|
316
|
+
if self.ping:
|
317
|
+
try:
|
318
|
+
await self.send(self.ping(self))
|
319
|
+
except Exception as e:
|
320
|
+
self.on_error(e)
|
321
|
+
else:
|
322
|
+
await self.connection.ping()
|
323
|
+
await sleep(self.keepAlive / 1000)
|
@@ -1,69 +1,42 @@
|
|
1
1
|
import asyncio
|
2
|
-
from ccxt import ExchangeClosedByUser
|
3
2
|
|
4
|
-
class Future(asyncio.Future):
|
5
3
|
|
6
|
-
|
4
|
+
class Future(asyncio.Future):
|
7
5
|
|
8
6
|
def resolve(self, result=None):
|
9
7
|
if not self.done():
|
10
|
-
|
11
|
-
self.set_result(result)
|
12
|
-
except BaseException as e:
|
13
|
-
print("Error in Future.resolve")
|
14
|
-
raise e
|
8
|
+
self.set_result(result)
|
15
9
|
|
16
10
|
def reject(self, error=None):
|
17
11
|
if not self.done():
|
18
|
-
|
19
|
-
if not isinstance(error, BaseException):
|
20
|
-
error = Exception(error)
|
21
|
-
try:
|
22
|
-
self.set_exception(error)
|
23
|
-
except BaseException as e:
|
24
|
-
print("Error in Future.reject")
|
25
|
-
raise e
|
12
|
+
self.set_exception(error)
|
26
13
|
|
27
14
|
@classmethod
|
28
15
|
def race(cls, futures):
|
29
16
|
future = Future()
|
30
|
-
for f in futures:
|
31
|
-
f.is_race_future = True
|
32
17
|
coro = asyncio.wait(futures, return_when=asyncio.FIRST_COMPLETED)
|
33
18
|
task = asyncio.create_task(coro)
|
34
19
|
|
35
20
|
def callback(done):
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if are_all_canceled:
|
57
|
-
future.reject(ExchangeClosedByUser('Connection closed by the user'))
|
58
|
-
return
|
59
|
-
|
60
|
-
first = futures_list[0]
|
61
|
-
|
62
|
-
first_result = first.result()
|
63
|
-
future.resolve(first_result)
|
64
|
-
except asyncio.CancelledError as e:
|
65
|
-
future.reject(e)
|
66
|
-
except Exception as e:
|
67
|
-
future.reject(e)
|
21
|
+
complete, _ = done.result()
|
22
|
+
# check for exceptions
|
23
|
+
exceptions = []
|
24
|
+
cancelled = False
|
25
|
+
for f in complete:
|
26
|
+
if f.cancelled():
|
27
|
+
cancelled = True
|
28
|
+
else:
|
29
|
+
err = f.exception()
|
30
|
+
if err:
|
31
|
+
exceptions.append(err)
|
32
|
+
# if any exceptions return with first exception
|
33
|
+
if len(exceptions) > 0:
|
34
|
+
future.set_exception(exceptions[0])
|
35
|
+
# else return first result
|
36
|
+
elif cancelled:
|
37
|
+
future.cancel()
|
38
|
+
else:
|
39
|
+
first_result = list(complete)[0].result()
|
40
|
+
future.set_result(first_result)
|
68
41
|
task.add_done_callback(callback)
|
69
42
|
return future
|
@@ -101,6 +101,7 @@ class bitmex(Exchange, ImplicitAPI):
|
|
101
101
|
'fetchTransactions': 'emulated',
|
102
102
|
'fetchTransfer': False,
|
103
103
|
'fetchTransfers': False,
|
104
|
+
'index': True,
|
104
105
|
'reduceMargin': None,
|
105
106
|
'sandbox': True,
|
106
107
|
'setLeverage': True,
|
@@ -427,8 +428,8 @@ class bitmex(Exchange, ImplicitAPI):
|
|
427
428
|
# # "mediumPrecision": "8",
|
428
429
|
# # "shorterPrecision": "4",
|
429
430
|
# # "symbol": "₿",
|
430
|
-
# # "weight": "1",
|
431
431
|
# # "tickLog": "0",
|
432
|
+
# # "weight": "1",
|
432
433
|
# "enabled": True,
|
433
434
|
# "isMarginCurrency": True,
|
434
435
|
# "minDepositAmount": "10000",
|
bitmex/ccxt/base/exchange.py
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
# -----------------------------------------------------------------------------
|
6
6
|
|
7
|
-
__version__ = '4.4.
|
7
|
+
__version__ = '4.4.92'
|
8
8
|
|
9
9
|
# -----------------------------------------------------------------------------
|
10
10
|
|
@@ -1515,6 +1515,12 @@ class Exchange(object):
|
|
1515
1515
|
parts = re.sub(r'0+$', '', str).split('.')
|
1516
1516
|
return len(parts[1]) if len(parts) > 1 else 0
|
1517
1517
|
|
1518
|
+
def map_to_safe_map(self, dictionary):
|
1519
|
+
return dictionary # wrapper for go
|
1520
|
+
|
1521
|
+
def safe_map_to_map(self, dictionary):
|
1522
|
+
return dictionary # wrapper for go
|
1523
|
+
|
1518
1524
|
def load_markets(self, reload=False, params={}):
|
1519
1525
|
"""
|
1520
1526
|
Loads and prepares the markets for trading.
|
@@ -2580,6 +2586,9 @@ class Exchange(object):
|
|
2580
2586
|
def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
|
2581
2587
|
raise NotSupported(self.id + ' watchTrades() is not supported yet')
|
2582
2588
|
|
2589
|
+
def un_watch_orders(self, symbol: Str = None, params={}):
|
2590
|
+
raise NotSupported(self.id + ' unWatchOrders() is not supported yet')
|
2591
|
+
|
2583
2592
|
def un_watch_trades(self, symbol: str, params={}):
|
2584
2593
|
raise NotSupported(self.id + ' unWatchTrades() is not supported yet')
|
2585
2594
|
|
@@ -3219,14 +3228,14 @@ class Exchange(object):
|
|
3219
3228
|
else:
|
3220
3229
|
market['subType'] = None
|
3221
3230
|
values.append(market)
|
3222
|
-
self.markets = self.index_by(values, 'symbol')
|
3231
|
+
self.markets = self.map_to_safe_map(self.index_by(values, 'symbol'))
|
3223
3232
|
marketsSortedBySymbol = self.keysort(self.markets)
|
3224
3233
|
marketsSortedById = self.keysort(self.markets_by_id)
|
3225
3234
|
self.symbols = list(marketsSortedBySymbol.keys())
|
3226
3235
|
self.ids = list(marketsSortedById.keys())
|
3227
3236
|
if currencies is not None:
|
3228
3237
|
# currencies is always None when called in constructor but not when called from loadMarkets
|
3229
|
-
self.currencies = self.deep_extend(self.currencies, currencies)
|
3238
|
+
self.currencies = self.map_to_safe_map(self.deep_extend(self.currencies, currencies))
|
3230
3239
|
else:
|
3231
3240
|
baseCurrencies = []
|
3232
3241
|
quoteCurrencies = []
|
@@ -3252,8 +3261,8 @@ class Exchange(object):
|
|
3252
3261
|
quoteCurrencies.append(currency)
|
3253
3262
|
baseCurrencies = self.sort_by(baseCurrencies, 'code', False, '')
|
3254
3263
|
quoteCurrencies = self.sort_by(quoteCurrencies, 'code', False, '')
|
3255
|
-
self.baseCurrencies = self.index_by(baseCurrencies, 'code')
|
3256
|
-
self.quoteCurrencies = self.index_by(quoteCurrencies, 'code')
|
3264
|
+
self.baseCurrencies = self.map_to_safe_map(self.index_by(baseCurrencies, 'code'))
|
3265
|
+
self.quoteCurrencies = self.map_to_safe_map(self.index_by(quoteCurrencies, 'code'))
|
3257
3266
|
allCurrencies = self.array_concat(baseCurrencies, quoteCurrencies)
|
3258
3267
|
groupedCurrencies = self.group_by(allCurrencies, 'code')
|
3259
3268
|
codes = list(groupedCurrencies.keys())
|
@@ -3270,7 +3279,7 @@ class Exchange(object):
|
|
3270
3279
|
highestPrecisionCurrency = currentCurrency if (currentCurrency['precision'] > highestPrecisionCurrency['precision']) else highestPrecisionCurrency
|
3271
3280
|
resultingCurrencies.append(highestPrecisionCurrency)
|
3272
3281
|
sortedCurrencies = self.sort_by(resultingCurrencies, 'code')
|
3273
|
-
self.currencies = self.deep_extend(self.currencies, self.index_by(sortedCurrencies, 'code'))
|
3282
|
+
self.currencies = self.map_to_safe_map(self.deep_extend(self.currencies, self.index_by(sortedCurrencies, 'code')))
|
3274
3283
|
self.currencies_by_id = self.index_by_safe(self.currencies, 'id')
|
3275
3284
|
currenciesSortedByCode = self.keysort(self.currencies)
|
3276
3285
|
self.codes = list(currenciesSortedByCode.keys())
|
@@ -6789,7 +6798,7 @@ class Exchange(object):
|
|
6789
6798
|
return reconstructedDate
|
6790
6799
|
|
6791
6800
|
def convert_market_id_expire_date(self, date: str):
|
6792
|
-
# parse 03JAN24 to 240103
|
6801
|
+
# parse 03JAN24 to 240103.
|
6793
6802
|
monthMappping = {
|
6794
6803
|
'JAN': '01',
|
6795
6804
|
'FEB': '02',
|
@@ -6894,7 +6903,7 @@ class Exchange(object):
|
|
6894
6903
|
symbolAndTimeFrame = symbolsAndTimeFrames[i]
|
6895
6904
|
symbol = self.safe_string(symbolAndTimeFrame, 0)
|
6896
6905
|
timeframe = self.safe_string(symbolAndTimeFrame, 1)
|
6897
|
-
if symbol in self.ohlcvs:
|
6906
|
+
if (self.ohlcvs is not None) and (symbol in self.ohlcvs):
|
6898
6907
|
if timeframe in self.ohlcvs[symbol]:
|
6899
6908
|
del self.ohlcvs[symbol][timeframe]
|
6900
6909
|
elif symbolsLength > 0:
|
@@ -6910,21 +6919,11 @@ class Exchange(object):
|
|
6910
6919
|
if symbol in self.tickers:
|
6911
6920
|
del self.tickers[symbol]
|
6912
6921
|
else:
|
6913
|
-
if topic == 'myTrades':
|
6914
|
-
|
6915
|
-
|
6916
|
-
|
6917
|
-
|
6918
|
-
key = keys[i]
|
6919
|
-
if key in self.myTrades:
|
6920
|
-
del self.myTrades[key]
|
6921
|
-
elif topic == 'orders':
|
6922
|
-
orderSymbols = list(self.orders.keys())
|
6923
|
-
for i in range(0, len(orderSymbols)):
|
6924
|
-
orderSymbol = orderSymbols[i]
|
6925
|
-
if orderSymbol in self.orders:
|
6926
|
-
del self.orders[orderSymbol]
|
6927
|
-
elif topic == 'ticker':
|
6922
|
+
if topic == 'myTrades' and (self.myTrades is not None):
|
6923
|
+
self.myTrades = None
|
6924
|
+
elif topic == 'orders' and (self.orders is not None):
|
6925
|
+
self.orders = None
|
6926
|
+
elif topic == 'ticker' and (self.tickers is not None):
|
6928
6927
|
tickerSymbols = list(self.tickers.keys())
|
6929
6928
|
for i in range(0, len(tickerSymbols)):
|
6930
6929
|
tickerSymbol = tickerSymbols[i]
|
bitmex/ccxt/base/types.py
CHANGED
bitmex/ccxt/bitmex.py
CHANGED
@@ -101,6 +101,7 @@ class bitmex(Exchange, ImplicitAPI):
|
|
101
101
|
'fetchTransactions': 'emulated',
|
102
102
|
'fetchTransfer': False,
|
103
103
|
'fetchTransfers': False,
|
104
|
+
'index': True,
|
104
105
|
'reduceMargin': None,
|
105
106
|
'sandbox': True,
|
106
107
|
'setLeverage': True,
|
@@ -427,8 +428,8 @@ class bitmex(Exchange, ImplicitAPI):
|
|
427
428
|
# # "mediumPrecision": "8",
|
428
429
|
# # "shorterPrecision": "4",
|
429
430
|
# # "symbol": "₿",
|
430
|
-
# # "weight": "1",
|
431
431
|
# # "tickLog": "0",
|
432
|
+
# # "weight": "1",
|
432
433
|
# "enabled": True,
|
433
434
|
# "isMarginCurrency": True,
|
434
435
|
# "minDepositAmount": "10000",
|
bitmex/ccxt/pro/__init__.py
CHANGED
@@ -1,27 +1,26 @@
|
|
1
1
|
bitmex/__init__.py,sha256=YoXAuxG_VioYinNV_H2dBOxO7R1bhKl6aL_6s6TfuWg,246
|
2
|
-
bitmex/ccxt/__init__.py,sha256=
|
3
|
-
bitmex/ccxt/bitmex.py,sha256=
|
2
|
+
bitmex/ccxt/__init__.py,sha256=aEJyISXdcfVqLr5aEV8As2cwXDxAzseaaQzKY8py4vI,6048
|
3
|
+
bitmex/ccxt/bitmex.py,sha256=yfYWTE6n3KxcWIGrbEwus9Q4eOm5xGlmjapaBOjQfek,131832
|
4
4
|
bitmex/ccxt/abstract/bitmex.py,sha256=v15OP-vSO_eotD6KVf1BgKrbPxCPl2eXXYIuzWF1dl4,10774
|
5
|
-
bitmex/ccxt/async_support/__init__.py,sha256=
|
6
|
-
bitmex/ccxt/async_support/bitmex.py,sha256=
|
5
|
+
bitmex/ccxt/async_support/__init__.py,sha256=8F3ZBrf8hi2jBcKKKDe_XEpWrp6jl9ebmV_Dpp0l7KE,4781
|
6
|
+
bitmex/ccxt/async_support/bitmex.py,sha256=Nol7iGiAzcCom877VUrfsL0cobZ5DXPRmYmq6noBsoM,132410
|
7
7
|
bitmex/ccxt/async_support/base/__init__.py,sha256=aVYSsFi--b4InRs9zDN_wtCpj8odosAB726JdUHavrk,67
|
8
|
-
bitmex/ccxt/async_support/base/exchange.py,sha256=
|
8
|
+
bitmex/ccxt/async_support/base/exchange.py,sha256=FQynGv0nHLJXfqiWThG1GFXUYqFMvMBdRkMYAxVGrqY,119286
|
9
9
|
bitmex/ccxt/async_support/base/throttler.py,sha256=tvDVcdRUVYi8fZRlEcnqtgzcgB_KMUMRs5Pu8tuU-tU,1847
|
10
10
|
bitmex/ccxt/async_support/base/ws/__init__.py,sha256=uockzpLuwntKGZbs5EOWFe-Zg-k6Cj7GhNJLc_RX0so,1791
|
11
|
-
bitmex/ccxt/async_support/base/ws/aiohttp_client.py,sha256=Y5HxAVXyyYduj6b6SbbUZETlq3GrVMzrkW1r-TMgpb8,6329
|
12
11
|
bitmex/ccxt/async_support/base/ws/cache.py,sha256=xf2VOtfUwloxSlIQ39M1RGZHWQzyS9IGhB5NX6cDcAc,8370
|
13
|
-
bitmex/ccxt/async_support/base/ws/client.py,sha256=
|
12
|
+
bitmex/ccxt/async_support/base/ws/client.py,sha256=1N_ji0ekjViecIHdcT7a8DVbq8aB-ZVgjK6ccJZntMU,13476
|
14
13
|
bitmex/ccxt/async_support/base/ws/functions.py,sha256=qwvEnjtINWL5ZU-dbbeIunjyBxzFqbGWHfVhxqAcKug,1499
|
15
|
-
bitmex/ccxt/async_support/base/ws/future.py,sha256=
|
14
|
+
bitmex/ccxt/async_support/base/ws/future.py,sha256=9yFyxqT7cl-7ZFM6LM4b6UPXyO2FGIbAhs5uoJ3-Smo,1271
|
16
15
|
bitmex/ccxt/async_support/base/ws/order_book.py,sha256=uBUaIHhzMRykpmo4BCsdJ-t_HozS6VxhEs8x-Kbj-NI,2894
|
17
16
|
bitmex/ccxt/async_support/base/ws/order_book_side.py,sha256=GhnGUt78pJ-AYL_Dq9produGjmBJLCI5FHIRdMz1O-g,6551
|
18
17
|
bitmex/ccxt/base/__init__.py,sha256=eTx1OE3HJjspFUQjGm6LBhaQiMKJnXjkdP-JUXknyQ0,1320
|
19
18
|
bitmex/ccxt/base/decimal_to_precision.py,sha256=fgWRBzRTtsf3r2INyS4f7WHlzgjB5YM1ekiwqD21aac,6634
|
20
19
|
bitmex/ccxt/base/errors.py,sha256=MvCrL_sAM3de616T6RE0PSxiF2xV6Qqz5b1y1ghidbk,4888
|
21
|
-
bitmex/ccxt/base/exchange.py,sha256=
|
20
|
+
bitmex/ccxt/base/exchange.py,sha256=_2FOqAIq1yfzBic5QI4JMQseWYPIesZpubuSde7jxYk,328428
|
22
21
|
bitmex/ccxt/base/precise.py,sha256=koce64Yrp6vFbGijJtUt-QQ6XhJgeGTCksZ871FPp_A,8886
|
23
|
-
bitmex/ccxt/base/types.py,sha256=
|
24
|
-
bitmex/ccxt/pro/__init__.py,sha256=
|
22
|
+
bitmex/ccxt/base/types.py,sha256=vMQfFDVntED4YHrRJt0Q98YaM7OtGhK-DkbkqXFTYHc,11485
|
23
|
+
bitmex/ccxt/pro/__init__.py,sha256=3KZOyC7YKnTsJEMTTiRvKjLX8-vWw6l1SwI5Jh65UAc,4095
|
25
24
|
bitmex/ccxt/pro/bitmex.py,sha256=5gpicgex_am9Km9ZsZpMKBSKX-SFFAD3PPuv9r-nNdA,74693
|
26
25
|
bitmex/ccxt/static_dependencies/README.md,sha256=3TCvhhn09_Cqf9BDDpao1V7EfKHDpQ6k9oWRsLFixpU,18
|
27
26
|
bitmex/ccxt/static_dependencies/__init__.py,sha256=tzFje8cloqmiIE6kola3EaYC0SnD1izWnri69hzHsSw,168
|
@@ -282,6 +281,6 @@ bitmex/ccxt/static_dependencies/toolz/curried/exceptions.py,sha256=gKFOHDIayAWnX
|
|
282
281
|
bitmex/ccxt/static_dependencies/toolz/curried/operator.py,sha256=ML92mknkAwzBl2NCm-4werSUmJEtSHNY9NSzhseNM9s,525
|
283
282
|
bitmex/ccxt/static_dependencies/typing_inspect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
284
283
|
bitmex/ccxt/static_dependencies/typing_inspect/typing_inspect.py,sha256=5gIWomLPfuDpgd3gX1GlnX0MuXM3VorR4j2W2qXORiQ,28269
|
285
|
-
bitmex_api-0.0.
|
286
|
-
bitmex_api-0.0.
|
287
|
-
bitmex_api-0.0.
|
284
|
+
bitmex_api-0.0.71.dist-info/METADATA,sha256=JzM5mSY1O5atdt_4CH7nc_ufMMdRVG5r1tmMWbsNx1k,10822
|
285
|
+
bitmex_api-0.0.71.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
286
|
+
bitmex_api-0.0.71.dist-info/RECORD,,
|
@@ -1,147 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
|
-
orjson = None
|
4
|
-
try:
|
5
|
-
import orjson as orjson
|
6
|
-
except ImportError:
|
7
|
-
pass
|
8
|
-
|
9
|
-
import json
|
10
|
-
from asyncio import sleep, ensure_future
|
11
|
-
from aiohttp import WSMsgType
|
12
|
-
from .functions import milliseconds, iso8601, is_json_encoded_object
|
13
|
-
from ccxt.async_support.base.ws.client import Client
|
14
|
-
from ccxt.async_support.base.ws.functions import gunzip, inflate
|
15
|
-
from ccxt import NetworkError, RequestTimeout, ExchangeClosedByUser
|
16
|
-
|
17
|
-
|
18
|
-
class AiohttpClient(Client):
|
19
|
-
|
20
|
-
proxy = None
|
21
|
-
|
22
|
-
def closed(self):
|
23
|
-
return (self.connection is None) or self.connection.closed
|
24
|
-
|
25
|
-
def receive(self):
|
26
|
-
return self.connection.receive()
|
27
|
-
|
28
|
-
# helper method for binary and text messages
|
29
|
-
def handle_text_or_binary_message(self, data):
|
30
|
-
if self.verbose:
|
31
|
-
self.log(iso8601(milliseconds()), 'message', data)
|
32
|
-
if isinstance(data, bytes):
|
33
|
-
data = data.decode()
|
34
|
-
# decoded = json.loads(data) if is_json_encoded_object(data) else data
|
35
|
-
decode = None
|
36
|
-
if is_json_encoded_object(data):
|
37
|
-
if orjson is None:
|
38
|
-
decode = json.loads(data)
|
39
|
-
else:
|
40
|
-
decode = orjson.loads(data)
|
41
|
-
else:
|
42
|
-
decode = data
|
43
|
-
self.on_message_callback(self, decode)
|
44
|
-
|
45
|
-
def handle_message(self, message):
|
46
|
-
# self.log(iso8601(milliseconds()), message)
|
47
|
-
if message.type == WSMsgType.TEXT:
|
48
|
-
self.handle_text_or_binary_message(message.data)
|
49
|
-
elif message.type == WSMsgType.BINARY:
|
50
|
-
data = message.data
|
51
|
-
if self.gunzip:
|
52
|
-
data = gunzip(data)
|
53
|
-
elif self.inflate:
|
54
|
-
data = inflate(data)
|
55
|
-
self.handle_text_or_binary_message(data)
|
56
|
-
# autoping is responsible for automatically replying with pong
|
57
|
-
# to a ping incoming from a server, we have to disable autoping
|
58
|
-
# with aiohttp's websockets and respond with pong manually
|
59
|
-
# otherwise aiohttp's websockets client won't trigger WSMsgType.PONG
|
60
|
-
elif message.type == WSMsgType.PING:
|
61
|
-
if self.verbose:
|
62
|
-
self.log(iso8601(milliseconds()), 'ping', message)
|
63
|
-
ensure_future(self.connection.pong(message.data), loop=self.asyncio_loop)
|
64
|
-
elif message.type == WSMsgType.PONG:
|
65
|
-
self.lastPong = milliseconds()
|
66
|
-
if self.verbose:
|
67
|
-
self.log(iso8601(milliseconds()), 'pong', message)
|
68
|
-
pass
|
69
|
-
elif message.type == WSMsgType.CLOSE:
|
70
|
-
if self.verbose:
|
71
|
-
self.log(iso8601(milliseconds()), 'close', self.closed(), message)
|
72
|
-
self.on_close(message.data)
|
73
|
-
elif message.type == WSMsgType.CLOSED:
|
74
|
-
if self.verbose:
|
75
|
-
self.log(iso8601(milliseconds()), 'closed', self.closed(), message)
|
76
|
-
self.on_close(1000)
|
77
|
-
elif message.type == WSMsgType.ERROR:
|
78
|
-
if self.verbose:
|
79
|
-
self.log(iso8601(milliseconds()), 'error', message)
|
80
|
-
error = NetworkError(str(message))
|
81
|
-
self.on_error(error)
|
82
|
-
|
83
|
-
def create_connection(self, session):
|
84
|
-
# autoping is responsible for automatically replying with pong
|
85
|
-
# to a ping incoming from a server, we have to disable autoping
|
86
|
-
# with aiohttp's websockets and respond with pong manually
|
87
|
-
# otherwise aiohttp's websockets client won't trigger WSMsgType.PONG
|
88
|
-
# call aenter here to simulate async with otherwise we get the error "await not called with future"
|
89
|
-
# if connecting to a non-existent endpoint
|
90
|
-
if (self.proxy):
|
91
|
-
return session.ws_connect(self.url, autoping=False, autoclose=False, headers=self.options.get('headers'), proxy=self.proxy, max_msg_size=10485760).__aenter__()
|
92
|
-
return session.ws_connect(self.url, autoping=False, autoclose=False, headers=self.options.get('headers'), max_msg_size=10485760).__aenter__()
|
93
|
-
|
94
|
-
async def send(self, message):
|
95
|
-
if self.verbose:
|
96
|
-
self.log(iso8601(milliseconds()), 'sending', message)
|
97
|
-
send_msg = None
|
98
|
-
if isinstance(message, str):
|
99
|
-
send_msg = message
|
100
|
-
else:
|
101
|
-
if orjson is None:
|
102
|
-
send_msg = json.dumps(message, separators=(',', ':'))
|
103
|
-
else:
|
104
|
-
send_msg = orjson.dumps(message).decode('utf-8')
|
105
|
-
return await self.connection.send_str(send_msg)
|
106
|
-
|
107
|
-
async def close(self, code=1000):
|
108
|
-
if self.verbose:
|
109
|
-
self.log(iso8601(milliseconds()), 'closing', code)
|
110
|
-
if not self.closed():
|
111
|
-
await self.connection.close()
|
112
|
-
# these will end automatically once self.closed() = True
|
113
|
-
# so we don't need to cancel them
|
114
|
-
if self.ping_looper:
|
115
|
-
self.ping_looper.cancel()
|
116
|
-
if self.receive_looper:
|
117
|
-
self.receive_looper.cancel() # cancel all pending futures stored in self.futures
|
118
|
-
for key in self.futures:
|
119
|
-
future = self.futures[key]
|
120
|
-
if not future.done():
|
121
|
-
if future.is_race_future:
|
122
|
-
future.cancel() # this is an "internal" future so we want to cancel it silently
|
123
|
-
else:
|
124
|
-
future.reject(ExchangeClosedByUser('Connection closed by the user'))
|
125
|
-
|
126
|
-
|
127
|
-
async def ping_loop(self):
|
128
|
-
if self.verbose:
|
129
|
-
self.log(iso8601(milliseconds()), 'ping loop')
|
130
|
-
while self.keepAlive and not self.closed():
|
131
|
-
now = milliseconds()
|
132
|
-
self.lastPong = now if self.lastPong is None else self.lastPong
|
133
|
-
if (self.lastPong + self.keepAlive * self.maxPingPongMisses) < now:
|
134
|
-
self.on_error(RequestTimeout('Connection to ' + self.url + ' timed out due to a ping-pong keepalive missing on time'))
|
135
|
-
# the following ping-clause is not necessary with aiohttp's built-in ws
|
136
|
-
# since it has a heartbeat option (see create_connection above)
|
137
|
-
# however some exchanges require a text-type ping message
|
138
|
-
# therefore we need this clause anyway
|
139
|
-
else:
|
140
|
-
if self.ping:
|
141
|
-
try:
|
142
|
-
await self.send(self.ping(self))
|
143
|
-
except Exception as e:
|
144
|
-
self.on_error(e)
|
145
|
-
else:
|
146
|
-
await self.connection.ping()
|
147
|
-
await sleep(self.keepAlive / 1000)
|
File without changes
|