exchanges-wrapper 2.1.0__tar.gz → 2.1.3__tar.gz
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.
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/PKG-INFO +1 -1
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/bybit_parser.py +18 -25
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/client.py +11 -9
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/exch_srv.py +35 -20
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/http_client.py +6 -5
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/lib.py +1 -1
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/web_sockets.py +2 -2
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/README.md +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/bitfinex_parser.py +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/events.py +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/huobi_parser.py +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/okx_parser.py +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/proto/martin.proto +0 -0
- {exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: exchanges-wrapper
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: REST API and WebSocket asyncio wrapper with grpc powered multiplexer server
|
|
5
5
|
Author-email: Thomas Marchand <thomas.marchand@tuta.io>, Jerry Fedorenko <jerry.fedorenko@yahoo.com>
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -12,7 +12,7 @@ __maintainer__ = "Jerry Fedorenko"
|
|
|
12
12
|
__contact__ = "https://github.com/DogsTailFarmer"
|
|
13
13
|
__email__ = "jerry.fedorenko@yahoo.com"
|
|
14
14
|
__credits__ = ["https://github.com/DanyaSWorlD"]
|
|
15
|
-
__version__ = "2.1.
|
|
15
|
+
__version__ = "2.1.3"
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import shutil
|
|
@@ -14,6 +14,7 @@ class OrderBook:
|
|
|
14
14
|
self.asks = {i[0]: i[1] for i in snapshot["a"]}
|
|
15
15
|
self.bids = {i[0]: i[1] for i in snapshot["b"]}
|
|
16
16
|
|
|
17
|
+
# noinspection PyTypeChecker
|
|
17
18
|
def get_book(self) -> dict:
|
|
18
19
|
return {
|
|
19
20
|
'stream': f"{self.symbol}@depth5",
|
|
@@ -498,32 +499,24 @@ def funding_wallet(res: []) -> []:
|
|
|
498
499
|
return balances
|
|
499
500
|
|
|
500
501
|
|
|
501
|
-
def order_trade_list(res: []
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
for trade in order_trades:
|
|
507
|
-
trade_id = trade['tradeId']
|
|
508
|
-
qty = Decimal(trade['cashFlow'])
|
|
509
|
-
fee = Decimal(trade['fee'])
|
|
510
|
-
if (trade['side'] == 'Buy' and qty > 0) or (trade['side'] == 'Sell' and qty < 0):
|
|
511
|
-
price = trade['tradePrice']
|
|
512
|
-
qty = abs(qty)
|
|
513
|
-
quote_qty = str(Decimal(price) * qty)
|
|
514
|
-
trade_rows[trade_id] = {
|
|
502
|
+
def order_trade_list(res: []) -> []:
|
|
503
|
+
trades = []
|
|
504
|
+
for trade in reversed(res):
|
|
505
|
+
trades.append(
|
|
506
|
+
{
|
|
515
507
|
"symbol": trade['symbol'],
|
|
516
|
-
"id": int(trade['
|
|
517
|
-
"orderId": int(
|
|
508
|
+
"id": int(trade['execId']),
|
|
509
|
+
"orderId": int(trade['orderId']),
|
|
518
510
|
"orderListId": -1,
|
|
519
|
-
"price":
|
|
520
|
-
"qty":
|
|
521
|
-
"quoteQty":
|
|
522
|
-
"commission":
|
|
523
|
-
"commissionAsset": trade['
|
|
524
|
-
"time": int(trade['
|
|
511
|
+
"price": trade['execPrice'],
|
|
512
|
+
"qty": trade['execQty'],
|
|
513
|
+
"quoteQty": trade['execValue'],
|
|
514
|
+
"commission": trade['execFee'],
|
|
515
|
+
"commissionAsset": trade['feeCurrency'],
|
|
516
|
+
"time": int(trade['execTime']),
|
|
525
517
|
"isBuyer": trade['side'] == 'Buy',
|
|
526
|
-
"isMaker":
|
|
527
|
-
"isBestMatch": True
|
|
518
|
+
"isMaker": trade['isMaker'],
|
|
519
|
+
"isBestMatch": True
|
|
528
520
|
}
|
|
529
|
-
|
|
521
|
+
)
|
|
522
|
+
return trades
|
|
@@ -1029,7 +1029,6 @@ class Client:
|
|
|
1029
1029
|
res, _ = await self.http.send_api_call("/v5/order/history", signed=True, **params)
|
|
1030
1030
|
if res["list"]:
|
|
1031
1031
|
b_res = bbt.order(res["list"][0], response_type=response_type)
|
|
1032
|
-
logger.debug(f"fetch_order.b_res: {b_res}")
|
|
1033
1032
|
return b_res
|
|
1034
1033
|
|
|
1035
1034
|
# https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#cancel-order-trade
|
|
@@ -1151,7 +1150,6 @@ class Client:
|
|
|
1151
1150
|
except asyncio.TimeoutError:
|
|
1152
1151
|
logger.warning(f"WSS CancelOrder for ByBit:{symbol}:{order_id} timeout exception")
|
|
1153
1152
|
|
|
1154
|
-
logger.debug(f"cancel_order.binance_res: {binance_res}")
|
|
1155
1153
|
return binance_res
|
|
1156
1154
|
|
|
1157
1155
|
# https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#cancel-all-open-orders-on-a-symbol-trade
|
|
@@ -1791,10 +1789,9 @@ class Client:
|
|
|
1791
1789
|
params["end"] = str(end_time)
|
|
1792
1790
|
res = await self.http.send_api_call("/api/v5/trade/fills-history", signed=True, **params)
|
|
1793
1791
|
binance_res = okx.order_trade_list(res)
|
|
1794
|
-
logger.debug(f"fetch_account_trade_list.binance_res: {binance_res}")
|
|
1795
1792
|
return binance_res
|
|
1796
1793
|
|
|
1797
|
-
async def fetch_order_trade_list(self, trade_id, symbol, order_id
|
|
1794
|
+
async def fetch_order_trade_list(self, trade_id, symbol, order_id):
|
|
1798
1795
|
if not order_id:
|
|
1799
1796
|
raise ValueError("This query (fetch_order_trade_list) requires an order_id")
|
|
1800
1797
|
self.assert_symbol(symbol)
|
|
@@ -1821,14 +1818,19 @@ class Client:
|
|
|
1821
1818
|
}
|
|
1822
1819
|
res = await self.http.send_api_call("/api/v5/trade/fills", signed=True, **params)
|
|
1823
1820
|
b_res = okx.order_trade_list(res)
|
|
1824
|
-
elif self.exchange == 'bybit'
|
|
1821
|
+
elif self.exchange == 'bybit':
|
|
1822
|
+
res_list = []
|
|
1825
1823
|
params = {
|
|
1826
|
-
'accountType': "UNIFIED",
|
|
1827
1824
|
'category': "spot",
|
|
1828
|
-
'
|
|
1825
|
+
'execType': 'Trade',
|
|
1826
|
+
'orderId': str(order_id)
|
|
1829
1827
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1828
|
+
next_page_cursor = 1
|
|
1829
|
+
while next_page_cursor:
|
|
1830
|
+
res, _ = await self.http.send_api_call("/v5/execution/list", signed=True, **params)
|
|
1831
|
+
next_page_cursor = params['cursor'] = res['nextPageCursor']
|
|
1832
|
+
res_list.extend(res['list'])
|
|
1833
|
+
b_res = bbt.order_trade_list(res_list)
|
|
1832
1834
|
return b_res
|
|
1833
1835
|
|
|
1834
1836
|
# endregion
|
|
@@ -18,6 +18,7 @@ from exchanges_wrapper import WORK_PATH, CONFIG_FILE, LOG_FILE, errors, Server,
|
|
|
18
18
|
from exchanges_wrapper.client import Client
|
|
19
19
|
from exchanges_wrapper.definitions import Side, OrderType, TimeInForce, ResponseType
|
|
20
20
|
from exchanges_wrapper.lib import OrderTradesEvent, REST_RATE_LIMIT_INTERVAL, FILTER_TYPE_MAP
|
|
21
|
+
from exchanges_wrapper.errors import ExchangeError
|
|
21
22
|
#
|
|
22
23
|
HEARTBEAT = 1 # Sec
|
|
23
24
|
MAX_QUEUE_SIZE = 100
|
|
@@ -217,7 +218,7 @@ class Martin(mr.MartinBase):
|
|
|
217
218
|
async def send_request(self, client_method_name, request, rate_limit=False, **kwargs):
|
|
218
219
|
open_client = OpenClient.get_client(request.client_id)
|
|
219
220
|
client = open_client.client
|
|
220
|
-
msg_header = f"Send request
|
|
221
|
+
msg_header = f"Send request: {client_method_name}:{open_client.name}:"
|
|
221
222
|
if hasattr(request, 'symbol'):
|
|
222
223
|
msg_header += f"{request.symbol}:"
|
|
223
224
|
if hasattr(request, 'order_id'):
|
|
@@ -239,6 +240,10 @@ class Martin(mr.MartinBase):
|
|
|
239
240
|
msg = f"{msg_header} HTTPError: {ex}"
|
|
240
241
|
logger.error(msg)
|
|
241
242
|
raise GRPCError(status=Status.FAILED_PRECONDITION, message=msg)
|
|
243
|
+
except ExchangeError as ex:
|
|
244
|
+
msg = f"{msg_header}: {ex}"
|
|
245
|
+
logger.warning(msg)
|
|
246
|
+
raise GRPCError(status=Status.OUT_OF_RANGE, message=msg)
|
|
242
247
|
except Exception as ex:
|
|
243
248
|
msg = f"{msg_header} exception: {ex}"
|
|
244
249
|
logger.error(msg)
|
|
@@ -283,7 +288,7 @@ class Martin(mr.MartinBase):
|
|
|
283
288
|
|
|
284
289
|
async def fetch_order(self, request: mr.FetchOrderRequest) -> mr.FetchOrderResponse:
|
|
285
290
|
response = mr.FetchOrderResponse()
|
|
286
|
-
res,
|
|
291
|
+
res, _, msg_header = await self.send_request(
|
|
287
292
|
'fetch_order',
|
|
288
293
|
request,
|
|
289
294
|
rate_limit=True,
|
|
@@ -293,26 +298,27 @@ class Martin(mr.MartinBase):
|
|
|
293
298
|
origin_client_order_id=request.client_order_id,
|
|
294
299
|
receive_window=None
|
|
295
300
|
)
|
|
301
|
+
logger.debug(f"{msg_header}: {res}")
|
|
296
302
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
await self.create_trade_stream_event(_queue, request, res)
|
|
303
|
+
if res and request.filled_update_call and Decimal(res['executedQty']):
|
|
304
|
+
request.order_id = res['orderId']
|
|
305
|
+
await self.create_trade_stream_event(request, res)
|
|
301
306
|
response.from_pydict(res)
|
|
302
307
|
return response
|
|
303
308
|
|
|
304
|
-
async def create_trade_stream_event(self,
|
|
305
|
-
trades,
|
|
309
|
+
async def create_trade_stream_event(self, request, order):
|
|
310
|
+
trades, client, msg_header = await self.send_request(
|
|
306
311
|
'fetch_order_trade_list',
|
|
307
312
|
request,
|
|
308
313
|
trade_id=request.trade_id,
|
|
309
314
|
symbol=request.symbol,
|
|
310
|
-
order_id=request.order_id
|
|
311
|
-
order=order
|
|
315
|
+
order_id=request.order_id
|
|
312
316
|
)
|
|
313
317
|
|
|
314
|
-
|
|
318
|
+
_queue = client.on_order_update_queues.get(request.trade_id)
|
|
319
|
+
|
|
315
320
|
for trade in trades:
|
|
321
|
+
trade['updateTime'] = trade.pop('time')
|
|
316
322
|
trade |= {
|
|
317
323
|
'clientOrderId': order['clientOrderId'],
|
|
318
324
|
'orderPrice': order['price'],
|
|
@@ -320,9 +326,11 @@ class Martin(mr.MartinBase):
|
|
|
320
326
|
'executedQty': order['executedQty'],
|
|
321
327
|
'cummulativeQuoteQty': order['cummulativeQuoteQty'],
|
|
322
328
|
'status': order['status'],
|
|
329
|
+
"time": order['time']
|
|
323
330
|
}
|
|
324
331
|
event = OrderTradesEvent(trade)
|
|
325
332
|
await _queue.put(weakref.ref(event)())
|
|
333
|
+
logger.debug(f"{msg_header}: {trades}")
|
|
326
334
|
|
|
327
335
|
async def cancel_all_orders(self, request: mr.MarketRequest) -> mr.SimpleResponse():
|
|
328
336
|
response = mr.SimpleResponse()
|
|
@@ -692,7 +700,7 @@ class Martin(mr.MartinBase):
|
|
|
692
700
|
else:
|
|
693
701
|
event = vars(_event)
|
|
694
702
|
event.pop('handlers', None)
|
|
695
|
-
logger.debug(f"OnOrderUpdate: {open_client.name}: {event}")
|
|
703
|
+
# logger.debug(f"OnOrderUpdate: {open_client.name}: {event}")
|
|
696
704
|
response.success = True
|
|
697
705
|
response.result = json.dumps(event)
|
|
698
706
|
yield response
|
|
@@ -701,7 +709,7 @@ class Martin(mr.MartinBase):
|
|
|
701
709
|
async def create_limit_order(self, request: mr.CreateLimitOrderRequest) -> mr.CreateLimitOrderResponse:
|
|
702
710
|
response = mr.CreateLimitOrderResponse()
|
|
703
711
|
|
|
704
|
-
res, _,
|
|
712
|
+
res, _, _ = await self.send_request(
|
|
705
713
|
'create_order',
|
|
706
714
|
request,
|
|
707
715
|
rate_limit=True,
|
|
@@ -722,13 +730,12 @@ class Martin(mr.MartinBase):
|
|
|
722
730
|
)
|
|
723
731
|
|
|
724
732
|
response.from_pydict(res)
|
|
725
|
-
logger.debug(f"{msg_header}: order created: {res.get('orderId')}")
|
|
726
733
|
return response
|
|
727
734
|
|
|
728
735
|
async def cancel_order(self, request: mr.CancelOrderRequest) -> mr.CancelOrderResponse:
|
|
729
736
|
response = mr.CancelOrderResponse()
|
|
730
737
|
|
|
731
|
-
res,
|
|
738
|
+
res, _, _ = await self.send_request(
|
|
732
739
|
'cancel_order',
|
|
733
740
|
request,
|
|
734
741
|
rate_limit=True,
|
|
@@ -740,10 +747,7 @@ class Martin(mr.MartinBase):
|
|
|
740
747
|
receive_window=None
|
|
741
748
|
)
|
|
742
749
|
|
|
743
|
-
|
|
744
|
-
if Decimal(res.get('executedQty', '0')):
|
|
745
|
-
await self.create_trade_stream_event(_queue, request, res)
|
|
746
|
-
response.from_pydict(res)
|
|
750
|
+
response.from_pydict(res)
|
|
747
751
|
return response
|
|
748
752
|
|
|
749
753
|
async def transfer_to_master(self, request: mr.MarketRequest) -> mr.SimpleResponse:
|
|
@@ -840,9 +844,20 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
840
844
|
logger.info(f"Starting server v:{__version__} on {host}:{port}")
|
|
841
845
|
await server.wait_closed()
|
|
842
846
|
|
|
847
|
+
for oc in OpenClient.open_clients:
|
|
848
|
+
await oc.client.session.close()
|
|
849
|
+
|
|
850
|
+
[task.cancel() for task in asyncio.all_tasks() if not task.done()]
|
|
851
|
+
|
|
843
852
|
|
|
844
853
|
def main():
|
|
845
|
-
|
|
854
|
+
try:
|
|
855
|
+
asyncio.run(amain())
|
|
856
|
+
except asyncio.exceptions.CancelledError:
|
|
857
|
+
pass # # Task cancellation should not be logged as an error
|
|
858
|
+
except Exception as ex:
|
|
859
|
+
print(f"Exception: {ex}")
|
|
860
|
+
print(traceback.format_exc())
|
|
846
861
|
|
|
847
862
|
|
|
848
863
|
if __name__ == '__main__':
|
|
@@ -5,7 +5,7 @@ from urllib.parse import urlencode, urlparse
|
|
|
5
5
|
import aiohttp
|
|
6
6
|
import logging
|
|
7
7
|
import time
|
|
8
|
-
from datetime import datetime
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
9
|
from crypto_ws_api.ws_session import generate_signature
|
|
10
10
|
from exchanges_wrapper.errors import (
|
|
11
11
|
RateLimitReached,
|
|
@@ -48,15 +48,16 @@ class HttpClient:
|
|
|
48
48
|
payload = None
|
|
49
49
|
|
|
50
50
|
if response.status >= 400:
|
|
51
|
-
logger.debug(f"handle_errors.response: {response.text}")
|
|
52
51
|
if response.status == 400 and payload and payload.get("error", str()) == "ERR_RATE_LIMIT":
|
|
53
52
|
raise RateLimitReached(RateLimitReached.message)
|
|
53
|
+
elif response.status == 400 and response.reason != "Bad Request":
|
|
54
|
+
raise ExchangeError(f"ExchangeError: {payload}:{response.reason}:{response.text}")
|
|
54
55
|
elif response.status == 403 and self.exchange != 'okx':
|
|
55
56
|
raise WAFLimitViolated(WAFLimitViolated.message)
|
|
56
57
|
elif response.status == 418:
|
|
57
58
|
raise IPAddressBanned(IPAddressBanned.message)
|
|
58
59
|
else:
|
|
59
|
-
raise HTTPError(f"Malformed request:
|
|
60
|
+
raise HTTPError(f"Malformed request: {payload}:{response.reason}:{response.text}")
|
|
60
61
|
|
|
61
62
|
if self.exchange == 'bybit' and payload and payload.get('retCode') == 0:
|
|
62
63
|
return payload.get('result'), payload.get('time')
|
|
@@ -224,7 +225,7 @@ class ClientHBP(HttpClient):
|
|
|
224
225
|
_params = {}
|
|
225
226
|
url = f"{_endpoint}/{path}?"
|
|
226
227
|
if signed:
|
|
227
|
-
ts = datetime.
|
|
228
|
+
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
|
|
228
229
|
_params = {
|
|
229
230
|
"AccessKeyId": self.api_key,
|
|
230
231
|
"SignatureMethod": 'HmacSHA256',
|
|
@@ -267,7 +268,7 @@ class ClientOKX(HttpClient):
|
|
|
267
268
|
path += f"?{urlencode(kwargs)}"
|
|
268
269
|
url = f'{_endpoint}{path}'
|
|
269
270
|
if signed:
|
|
270
|
-
ts = f"{datetime.
|
|
271
|
+
ts = f"{datetime.now(timezone.utc).replace(tzinfo=None).isoformat('T', 'milliseconds')}Z"
|
|
271
272
|
if method == 'POST' and kwargs:
|
|
272
273
|
query_kwargs = json.dumps(kwargs.get('data') if 'data' in kwargs else kwargs)
|
|
273
274
|
signature_payload = f"{ts}{method}{path}{query_kwargs}"
|
|
@@ -43,7 +43,7 @@ class OrderTradesEvent:
|
|
|
43
43
|
self.last_executed_price = event_data["price"]
|
|
44
44
|
self.commission_amount = event_data["commission"]
|
|
45
45
|
self.commission_asset = event_data["commissionAsset"]
|
|
46
|
-
self.transaction_time = event_data["
|
|
46
|
+
self.transaction_time = event_data["updateTime"]
|
|
47
47
|
self.trade_id = event_data["id"]
|
|
48
48
|
self.ignore_a = int()
|
|
49
49
|
self.in_order_book = True
|
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
import time
|
|
7
7
|
from decimal import Decimal
|
|
8
8
|
import gzip
|
|
9
|
-
from datetime import datetime
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
10
|
from urllib.parse import urlencode, urlparse
|
|
11
11
|
import websockets.client
|
|
12
12
|
from websockets import ConnectionClosed
|
|
@@ -352,7 +352,7 @@ class HbpPrivateEventsDataStream(EventsDataStream):
|
|
|
352
352
|
self.symbol = symbol
|
|
353
353
|
|
|
354
354
|
async def start_wss(self):
|
|
355
|
-
ts = datetime.
|
|
355
|
+
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
|
|
356
356
|
_params = {
|
|
357
357
|
"accessKey": self.client.api_key,
|
|
358
358
|
"signatureMethod": "HmacSHA256",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.0 → exchanges_wrapper-2.1.3}/exchanges_wrapper/exch_srv_cfg.toml.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|