exchanges-wrapper 2.1.21rc2__tar.gz → 2.1.22__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.21rc2 → exchanges_wrapper-2.1.22}/LICENSE.md +0 -0
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/PKG-INFO +9 -12
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/README.md +4 -7
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/__init__.py +4 -3
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/client.py +33 -43
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/definitions.py +0 -0
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/errors.py +0 -0
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/events.py +0 -0
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/exch_srv.py +20 -12
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/exch_srv_cfg.toml.template +4 -10
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/http_client.py +80 -121
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/martin/__init__.py +56 -23
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/parsers/bitfinex.py +0 -0
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/parsers/bybit.py +4 -2
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/parsers/huobi.py +0 -0
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/parsers/okx.py +0 -0
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/proto/martin.proto +3 -1
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/web_sockets.py +1 -16
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/pyproject.toml +3 -3
- exchanges-wrapper-2.1.21rc2/.deepsource.toml +0 -13
- exchanges-wrapper-2.1.21rc2/.dockerignore +0 -3
- exchanges-wrapper-2.1.21rc2/.github/FUNDING.yml +0 -1
- exchanges-wrapper-2.1.21rc2/.github/dependabot.yml +0 -11
- exchanges-wrapper-2.1.21rc2/.github/workflows/docker-image.yml +0 -32
- exchanges-wrapper-2.1.21rc2/.github/workflows/python-publish.yml +0 -40
- exchanges-wrapper-2.1.21rc2/.run/exchanges-wrapper-image.run.xml +0 -15
- exchanges-wrapper-2.1.21rc2/.run/exchanges-wrapper.run.xml +0 -32
- exchanges-wrapper-2.1.21rc2/CHANGELOG.md +0 -605
- exchanges-wrapper-2.1.21rc2/Dockerfile +0 -23
- exchanges-wrapper-2.1.21rc2/example/exch_client.py +0 -436
- exchanges-wrapper-2.1.21rc2/example/ms_cfg.toml +0 -5
- exchanges-wrapper-2.1.21rc2/requirements.txt +0 -10
- {exchanges-wrapper-2.1.21rc2 → exchanges_wrapper-2.1.22}/exchanges_wrapper/lib.py +0 -0
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: exchanges-wrapper
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.22
|
|
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
|
|
@@ -11,15 +11,15 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Operating System :: Unix
|
|
12
12
|
Classifier: Operating System :: Microsoft :: Windows
|
|
13
13
|
Classifier: Operating System :: MacOS
|
|
14
|
-
Requires-Dist: crypto-ws-api==2.0.
|
|
14
|
+
Requires-Dist: crypto-ws-api==2.0.15
|
|
15
15
|
Requires-Dist: pyotp==2.9.0
|
|
16
16
|
Requires-Dist: simplejson==3.19.3
|
|
17
17
|
Requires-Dist: toml~=0.10.2
|
|
18
|
-
Requires-Dist: aiohttp~=3.11.
|
|
18
|
+
Requires-Dist: aiohttp~=3.11.11
|
|
19
19
|
Requires-Dist: websockets==14.1
|
|
20
20
|
Requires-Dist: expiringdict~=1.2.2
|
|
21
21
|
Requires-Dist: ujson~=5.10.0
|
|
22
|
-
Requires-Dist: betterproto==2.0.
|
|
22
|
+
Requires-Dist: betterproto==2.0.0b7
|
|
23
23
|
Requires-Dist: grpclib~=0.4.7
|
|
24
24
|
Project-URL: Source, https://github.com/DogsTailFarmer/exchanges-wrapper
|
|
25
25
|
|
|
@@ -37,10 +37,10 @@ Project-URL: Source, https://github.com/DogsTailFarmer/exchanges-wrapper
|
|
|
37
37
|
<a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_exchanges-wrapper" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_exchanges-wrapper&metric=alert_status"/></a>
|
|
38
38
|
<a href="https://pepy.tech/project/exchanges-wrapper" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/exchanges-wrapper/month"/></a>
|
|
39
39
|
***
|
|
40
|
-
From `
|
|
40
|
+
From `2.1.21` must be updated `exch_srv_cfg.toml` from `exchanges_wrapper/exch_srv_cfg.toml.template`
|
|
41
41
|
***
|
|
42
42
|
|
|
43
|
-
## exchanges-wrapper vs binance.py
|
|
43
|
+
## exchanges-wrapper vs [binance.py](https://github.com/Th0rgal/binance.py)
|
|
44
44
|
The main difference is the development of the project for trading with many exchanges.
|
|
45
45
|
|
|
46
46
|
Next is adding a new module ```exchanges_wrapper/exch_srv.py``` as a multiplexer layer, providing simultaneous async interaction for many accounts
|
|
@@ -65,11 +65,8 @@ Served methods describes into ```example/exch_client.py```
|
|
|
65
65
|
- Adaptive algorithm to ensure maximum performance and avoid exceeding the rates limits
|
|
66
66
|
- Passthrough logging
|
|
67
67
|
- WSS keepalive
|
|
68
|
-
- Reuse session for new client
|
|
69
|
-
- Utilizing Websocket API (bidirectional) for the most commonly used methods:
|
|
70
|
-
+ Binance ws-api/v3
|
|
71
|
-
+ Bitfinex Websocket Authenticated Inputs v2
|
|
72
|
-
+ OKX WS v5
|
|
68
|
+
- Reuse session for new client connections
|
|
69
|
+
- [Utilizing Websocket API (bidirectional) for the most commonly used methods:](https://github.com/DogsTailFarmer/crypto-ws-api)
|
|
73
70
|
|
|
74
71
|
## Extra exchanges implementation features
|
|
75
72
|
- Binance REST API and WSS are accepted as basic, connection of other exchanges wrapped their API to Binance compatible
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
<a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_exchanges-wrapper" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_exchanges-wrapper&metric=alert_status"/></a>
|
|
13
13
|
<a href="https://pepy.tech/project/exchanges-wrapper" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/exchanges-wrapper/month"/></a>
|
|
14
14
|
***
|
|
15
|
-
From `
|
|
15
|
+
From `2.1.21` must be updated `exch_srv_cfg.toml` from `exchanges_wrapper/exch_srv_cfg.toml.template`
|
|
16
16
|
***
|
|
17
17
|
|
|
18
|
-
## exchanges-wrapper vs binance.py
|
|
18
|
+
## exchanges-wrapper vs [binance.py](https://github.com/Th0rgal/binance.py)
|
|
19
19
|
The main difference is the development of the project for trading with many exchanges.
|
|
20
20
|
|
|
21
21
|
Next is adding a new module ```exchanges_wrapper/exch_srv.py``` as a multiplexer layer, providing simultaneous async interaction for many accounts
|
|
@@ -40,11 +40,8 @@ Served methods describes into ```example/exch_client.py```
|
|
|
40
40
|
- Adaptive algorithm to ensure maximum performance and avoid exceeding the rates limits
|
|
41
41
|
- Passthrough logging
|
|
42
42
|
- WSS keepalive
|
|
43
|
-
- Reuse session for new client
|
|
44
|
-
- Utilizing Websocket API (bidirectional) for the most commonly used methods:
|
|
45
|
-
+ Binance ws-api/v3
|
|
46
|
-
+ Bitfinex Websocket Authenticated Inputs v2
|
|
47
|
-
+ OKX WS v5
|
|
43
|
+
- Reuse session for new client connections
|
|
44
|
+
- [Utilizing Websocket API (bidirectional) for the most commonly used methods:](https://github.com/DogsTailFarmer/crypto-ws-api)
|
|
48
45
|
|
|
49
46
|
## Extra exchanges implementation features
|
|
50
47
|
- Binance REST API and WSS are accepted as basic, connection of other exchanges wrapped their API to Binance compatible
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
4
|
REST API and WebSocket asyncio wrapper with grpc powered multiplexer server
|
|
5
|
-
for crypto exchanges (Binance, Bitfinex, OKX, Huobi)
|
|
5
|
+
for crypto exchanges (Binance, Bitfinex, OKX, Huobi, ByBit)
|
|
6
6
|
Utilizes one connection for many accounts and trading pairs.
|
|
7
7
|
For SPOT market only
|
|
8
8
|
"""
|
|
@@ -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.22"
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import shutil
|
|
@@ -20,9 +20,10 @@ import shutil
|
|
|
20
20
|
from grpclib.server import Server, GRPCError, Status
|
|
21
21
|
from grpclib.client import Channel
|
|
22
22
|
from grpclib.utils import graceful_exit
|
|
23
|
+
from grpclib import exceptions
|
|
23
24
|
|
|
24
25
|
__all__ = [
|
|
25
|
-
Server, GRPCError, Status, Channel, graceful_exit
|
|
26
|
+
Server, GRPCError, Status, Channel, graceful_exit, exceptions
|
|
26
27
|
]
|
|
27
28
|
|
|
28
29
|
WORK_PATH = Path(Path.home(), ".MartinBinance")
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import aiohttp
|
|
2
1
|
from enum import Enum
|
|
3
2
|
from typing import Union
|
|
4
3
|
import decimal
|
|
@@ -12,7 +11,7 @@ from expiringdict import ExpiringDict
|
|
|
12
11
|
import uuid
|
|
13
12
|
from decimal import Decimal, ROUND_HALF_DOWN
|
|
14
13
|
|
|
15
|
-
from exchanges_wrapper.http_client import
|
|
14
|
+
from exchanges_wrapper.http_client import HttpClient
|
|
16
15
|
from exchanges_wrapper.errors import ExchangePyError
|
|
17
16
|
from exchanges_wrapper.web_sockets import UserEventsDataStream, \
|
|
18
17
|
MarketEventsDataStream, \
|
|
@@ -31,7 +30,10 @@ logger = logging.getLogger(__name__)
|
|
|
31
30
|
|
|
32
31
|
STATUS_TIMEOUT = 5 # sec, also use for lifetime limit for inactive order (Bitfinex) as 60 * STATUS_TIMEOUT
|
|
33
32
|
USER_DATA_STREAM = "/api/v3/userDataStream"
|
|
34
|
-
|
|
33
|
+
ORDER_ENDPOINT = "/api/v3/order"
|
|
34
|
+
|
|
35
|
+
def fallback_warning(exchange, symbol=None):
|
|
36
|
+
logger.warning(f"{exchange}:{symbol}: Fallback to HTTP API call")
|
|
35
37
|
|
|
36
38
|
def truncate(f, n):
|
|
37
39
|
return math.floor(f * 10 ** n) / 10 ** n
|
|
@@ -59,29 +61,16 @@ class Client:
|
|
|
59
61
|
self.master_name = acc['master_name']
|
|
60
62
|
self.two_fa = acc['two_fa']
|
|
61
63
|
#
|
|
62
|
-
self.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
if self.exchange == 'binance':
|
|
74
|
-
self.http = ClientBinance(client_init_params)
|
|
75
|
-
elif self.exchange == 'bitfinex':
|
|
76
|
-
self.http = ClientBFX(client_init_params)
|
|
77
|
-
elif self.exchange == 'huobi':
|
|
78
|
-
self.http = ClientHBP(client_init_params)
|
|
79
|
-
elif self.exchange == 'okx':
|
|
80
|
-
self.http = ClientOKX(client_init_params)
|
|
81
|
-
elif self.exchange == 'bybit':
|
|
82
|
-
self.http = ClientBybit(client_init_params)
|
|
83
|
-
else:
|
|
84
|
-
raise UserWarning(f"Exchange {self.exchange} not yet connected")
|
|
64
|
+
self.http = HttpClient(
|
|
65
|
+
{
|
|
66
|
+
'api_key': self.api_key,
|
|
67
|
+
'api_secret': self.api_secret,
|
|
68
|
+
'passphrase': self.passphrase,
|
|
69
|
+
'endpoint': self.endpoint_api_auth,
|
|
70
|
+
'exchange': self.exchange,
|
|
71
|
+
'test_net': self.test_net
|
|
72
|
+
}
|
|
73
|
+
)
|
|
85
74
|
|
|
86
75
|
if self.exchange in ('binance', 'okx', 'bitfinex', 'huobi'):
|
|
87
76
|
self.user_session = UserWSSession(
|
|
@@ -157,7 +146,8 @@ class Client:
|
|
|
157
146
|
logger.info(f"Info for {self.exchange}:{symbol} loaded successfully")
|
|
158
147
|
|
|
159
148
|
async def close(self):
|
|
160
|
-
|
|
149
|
+
if self.http.session:
|
|
150
|
+
await self.http.session.close()
|
|
161
151
|
|
|
162
152
|
@property
|
|
163
153
|
def events(self):
|
|
@@ -825,8 +815,8 @@ class Client:
|
|
|
825
815
|
_signed=True
|
|
826
816
|
)
|
|
827
817
|
if binance_res is None:
|
|
828
|
-
|
|
829
|
-
route = "/api/v3/order/test" if test else
|
|
818
|
+
fallback_warning(self.exchange, symbol)
|
|
819
|
+
route = "/api/v3/order/test" if test else ORDER_ENDPOINT
|
|
830
820
|
binance_res = await self.http.send_api_call(route, "POST", data=params, signed=True)
|
|
831
821
|
elif self.exchange == 'bitfinex':
|
|
832
822
|
params = {
|
|
@@ -843,7 +833,7 @@ class Client:
|
|
|
843
833
|
res = await self.user_session.handle_request(trade_id, "on", _params=params)
|
|
844
834
|
|
|
845
835
|
if not res or (res and isinstance(res, list) and res[6] != 'SUCCESS'):
|
|
846
|
-
|
|
836
|
+
fallback_warning(self.exchange, symbol)
|
|
847
837
|
logger.debug(f"create_order.bitfinex {new_client_order_id}: {res}")
|
|
848
838
|
res = await self.http.send_api_call(
|
|
849
839
|
"v2/auth/w/order/submit",
|
|
@@ -871,7 +861,7 @@ class Client:
|
|
|
871
861
|
res = await self.user_session.handle_request(trade_id, "create-order", _params=params)
|
|
872
862
|
|
|
873
863
|
if res is None:
|
|
874
|
-
|
|
864
|
+
fallback_warning(self.exchange, symbol)
|
|
875
865
|
res = await self.http.send_api_call(
|
|
876
866
|
"v1/order/orders/place",
|
|
877
867
|
method="POST",
|
|
@@ -898,7 +888,7 @@ class Client:
|
|
|
898
888
|
}
|
|
899
889
|
res = await self.user_session.handle_request(trade_id, "order", _params=params)
|
|
900
890
|
if res is None:
|
|
901
|
-
|
|
891
|
+
fallback_warning(self.exchange, symbol)
|
|
902
892
|
res = await self.http.send_api_call(
|
|
903
893
|
"/api/v5/trade/order",
|
|
904
894
|
method="POST",
|
|
@@ -956,9 +946,9 @@ class Client:
|
|
|
956
946
|
_signed=True,
|
|
957
947
|
)
|
|
958
948
|
if b_res is None:
|
|
959
|
-
|
|
949
|
+
fallback_warning(self.exchange, symbol)
|
|
960
950
|
b_res = await self.http.send_api_call(
|
|
961
|
-
|
|
951
|
+
ORDER_ENDPOINT,
|
|
962
952
|
params=params,
|
|
963
953
|
signed=True,
|
|
964
954
|
)
|
|
@@ -1048,9 +1038,9 @@ class Client:
|
|
|
1048
1038
|
_signed=True
|
|
1049
1039
|
)
|
|
1050
1040
|
if binance_res is None:
|
|
1051
|
-
|
|
1041
|
+
fallback_warning(self.exchange, symbol)
|
|
1052
1042
|
binance_res = await self.http.send_api_call(
|
|
1053
|
-
|
|
1043
|
+
ORDER_ENDPOINT,
|
|
1054
1044
|
"DELETE",
|
|
1055
1045
|
params=params,
|
|
1056
1046
|
signed=True,
|
|
@@ -1063,7 +1053,7 @@ class Client:
|
|
|
1063
1053
|
params = {'id': order_id}
|
|
1064
1054
|
res = await self.user_session.handle_request(trade_id, "oc", _params=params)
|
|
1065
1055
|
if res is None or (res and isinstance(res, list) and res[6] == 'ERROR'):
|
|
1066
|
-
|
|
1056
|
+
fallback_warning(self.exchange, symbol)
|
|
1067
1057
|
logger.debug(f"cancel_order.bitfinex {order_id}: res1: {res}")
|
|
1068
1058
|
res = await self.http.send_api_call(
|
|
1069
1059
|
"v2/auth/w/order/cancel",
|
|
@@ -1150,7 +1140,7 @@ class Client:
|
|
|
1150
1140
|
_signed=True
|
|
1151
1141
|
)
|
|
1152
1142
|
if binance_res is None:
|
|
1153
|
-
|
|
1143
|
+
fallback_warning(self.exchange, symbol)
|
|
1154
1144
|
binance_res = await self.http.send_api_call(
|
|
1155
1145
|
"/api/v3/openOrders",
|
|
1156
1146
|
"DELETE",
|
|
@@ -1163,7 +1153,7 @@ class Client:
|
|
|
1163
1153
|
params = {'id': orders_id}
|
|
1164
1154
|
res = await self.user_session.handle_request(trade_id, "oc_multi", _params=params)
|
|
1165
1155
|
if not res or (res and isinstance(res, list) and res[6] != 'SUCCESS'):
|
|
1166
|
-
|
|
1156
|
+
fallback_warning(self.exchange, symbol)
|
|
1167
1157
|
logger.debug(f"cancel_all_orders.bitfinex {orders_id}: res1: {res}")
|
|
1168
1158
|
res = await self.http.send_api_call(
|
|
1169
1159
|
"v2/auth/w/order/cancel/multi",
|
|
@@ -1183,7 +1173,7 @@ class Client:
|
|
|
1183
1173
|
res = await self.user_session.handle_request(trade_id, "cancel", _params=params)
|
|
1184
1174
|
|
|
1185
1175
|
if res is None:
|
|
1186
|
-
|
|
1176
|
+
fallback_warning(self.exchange, symbol)
|
|
1187
1177
|
res = await self.http.send_api_call(
|
|
1188
1178
|
"v1/order/orders/batchcancel",
|
|
1189
1179
|
method="POST",
|
|
@@ -1273,7 +1263,7 @@ class Client:
|
|
|
1273
1263
|
_signed=True
|
|
1274
1264
|
)
|
|
1275
1265
|
if binance_res is None:
|
|
1276
|
-
|
|
1266
|
+
fallback_warning(self.exchange, symbol)
|
|
1277
1267
|
binance_res = await self.http.send_api_call(
|
|
1278
1268
|
"/api/v3/openOrders",
|
|
1279
1269
|
params=params,
|
|
@@ -1518,7 +1508,7 @@ class Client:
|
|
|
1518
1508
|
_signed=True
|
|
1519
1509
|
)
|
|
1520
1510
|
if binance_res is None:
|
|
1521
|
-
|
|
1511
|
+
fallback_warning(self.exchange)
|
|
1522
1512
|
binance_res = await self.http.send_api_call(
|
|
1523
1513
|
"/api/v3/account",
|
|
1524
1514
|
params=params,
|
|
@@ -1740,7 +1730,7 @@ class Client:
|
|
|
1740
1730
|
_signed=True
|
|
1741
1731
|
)
|
|
1742
1732
|
if binance_res is None:
|
|
1743
|
-
|
|
1733
|
+
fallback_warning(self.exchange, symbol)
|
|
1744
1734
|
binance_res = await self.http.send_api_call(
|
|
1745
1735
|
"/api/v3/myTrades",
|
|
1746
1736
|
params=params,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -25,17 +25,17 @@ from exchanges_wrapper.lib import (
|
|
|
25
25
|
REST_RATE_LIMIT_INTERVAL,
|
|
26
26
|
FILTER_TYPE_MAP,
|
|
27
27
|
)
|
|
28
|
-
from exchanges_wrapper.errors import ExchangeError
|
|
29
28
|
#
|
|
30
|
-
HEARTBEAT = 1 #
|
|
29
|
+
HEARTBEAT = 1 # sec
|
|
31
30
|
MAX_QUEUE_SIZE = 100
|
|
31
|
+
WSS_TICKER_TIMEOUT = 300 # sec
|
|
32
32
|
#
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
34
34
|
formatter = logging.Formatter(fmt="[%(asctime)s: %(levelname)s] %(message)s")
|
|
35
35
|
#
|
|
36
36
|
fh = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=1000000, backupCount=10)
|
|
37
37
|
fh.setFormatter(formatter)
|
|
38
|
-
fh.setLevel(logging.
|
|
38
|
+
fh.setLevel(logging.DEBUG)
|
|
39
39
|
#
|
|
40
40
|
sh = logging.StreamHandler()
|
|
41
41
|
sh.setFormatter(formatter)
|
|
@@ -91,6 +91,7 @@ class OpenClient:
|
|
|
91
91
|
class Martin(mr.MartinBase):
|
|
92
92
|
rate_limit_reached_time = None
|
|
93
93
|
rate_limiter = None
|
|
94
|
+
ticker_update_time = {}
|
|
94
95
|
|
|
95
96
|
async def rate_limit_control(self, exchange, ts_rlc, _call_from='default'):
|
|
96
97
|
if exchange == 'bitfinex':
|
|
@@ -133,7 +134,7 @@ class Martin(mr.MartinBase):
|
|
|
133
134
|
except asyncio.CancelledError:
|
|
134
135
|
pass # Task cancellation should not be logged as an error
|
|
135
136
|
except asyncio.exceptions.TimeoutError:
|
|
136
|
-
await OpenClient.get_client(client_id).client.session.close()
|
|
137
|
+
await OpenClient.get_client(client_id).client.http.session.close()
|
|
137
138
|
OpenClient.remove_client(request.account_name)
|
|
138
139
|
raise GRPCError(status=Status.UNAVAILABLE, message=f"'{open_client.name}' timeout error")
|
|
139
140
|
except Exception as ex:
|
|
@@ -188,7 +189,7 @@ class Martin(mr.MartinBase):
|
|
|
188
189
|
self.log_and_raise_grpc_error(msg_header, Status.RESOURCE_EXHAUSTED, f"RateLimitReached: {ex}")
|
|
189
190
|
except errors.HTTPError as ex:
|
|
190
191
|
self.log_and_raise_grpc_error(msg_header, Status.FAILED_PRECONDITION, f"HTTPError: {ex}")
|
|
191
|
-
except ExchangeError as ex:
|
|
192
|
+
except errors.ExchangeError as ex:
|
|
192
193
|
self.log_and_raise_grpc_error(msg_header, Status.OUT_OF_RANGE, str(ex))
|
|
193
194
|
except Exception as ex:
|
|
194
195
|
logger.error(f"{msg_header} exception: {ex}")
|
|
@@ -531,6 +532,7 @@ class Martin(mr.MartinBase):
|
|
|
531
532
|
logger.info(f"OnTickerUpdate: Stop loop for {open_client.name}: {request.symbol}")
|
|
532
533
|
return
|
|
533
534
|
else:
|
|
535
|
+
Martin.ticker_update_time[request.trade_id] = time.time()
|
|
534
536
|
response.from_pydict(
|
|
535
537
|
{
|
|
536
538
|
'openPrice': _event.open_price,
|
|
@@ -775,14 +777,19 @@ class Martin(mr.MartinBase):
|
|
|
775
777
|
|
|
776
778
|
async def check_stream(self, request: mr.MarketRequest) -> mr.SimpleResponse:
|
|
777
779
|
response = mr.SimpleResponse()
|
|
778
|
-
|
|
779
|
-
if
|
|
780
|
-
|
|
781
|
-
response.success = bool(client.data_streams.get(request.trade_id))
|
|
780
|
+
check_time = time.time() - Martin.ticker_update_time.get(request.trade_id, time.time() - WSS_TICKER_TIMEOUT - 1)
|
|
781
|
+
if check_time < WSS_TICKER_TIMEOUT:
|
|
782
|
+
response.success = True
|
|
782
783
|
else:
|
|
783
|
-
|
|
784
|
+
response.success = False
|
|
785
|
+
logger.warning(f"CheckStream request failed for {request.trade_id}")
|
|
784
786
|
return response
|
|
785
787
|
|
|
788
|
+
async def client_restart(self, request: mr.MarketRequest) -> mr.SimpleResponse:
|
|
789
|
+
if session:=OpenClient.get_client(request.client_id).client.http.session:
|
|
790
|
+
await session.close()
|
|
791
|
+
OpenClient.remove_client(request.account_name)
|
|
792
|
+
return mr.SimpleResponse(success=True)
|
|
786
793
|
|
|
787
794
|
async def stop_stream(client, trade_id):
|
|
788
795
|
await client.stop_events_listener(trade_id)
|
|
@@ -820,7 +827,8 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
820
827
|
await server.wait_closed()
|
|
821
828
|
|
|
822
829
|
for oc in OpenClient.open_clients:
|
|
823
|
-
|
|
830
|
+
if oc.client.http.session:
|
|
831
|
+
await oc.client.http.session.close()
|
|
824
832
|
|
|
825
833
|
[task.cancel() for task in asyncio.all_tasks() if not task.done() and task is not asyncio.current_task()]
|
|
826
834
|
|
|
@@ -829,7 +837,7 @@ def main():
|
|
|
829
837
|
try:
|
|
830
838
|
asyncio.run(amain())
|
|
831
839
|
except (asyncio.exceptions.CancelledError, grpclib.exceptions.StreamTerminatedError):
|
|
832
|
-
pass #
|
|
840
|
+
pass # Task cancellation should not be logged as an error
|
|
833
841
|
except Exception as ex:
|
|
834
842
|
print(f"Exception: {ex}")
|
|
835
843
|
print(traceback.format_exc())
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Parameters for exchanges-wrapper REST API Server exch_srv.py
|
|
2
|
-
# Copyright © 2021 Jerry Fedorenko aka VM
|
|
2
|
+
# Copyright © 2021-2025 Jerry Fedorenko aka VM
|
|
3
3
|
# __version__ = "2.1.21"
|
|
4
|
-
# TODO update from vertex
|
|
5
4
|
# region endpoint
|
|
6
5
|
[endpoint]
|
|
7
6
|
[endpoint.binance]
|
|
@@ -24,12 +23,6 @@
|
|
|
24
23
|
ws_api = 'wss://ws-api.binance.us:443/ws-api/v3'
|
|
25
24
|
ws_api_test = 'wss://testnet.binance.vision/ws-api/v3'
|
|
26
25
|
|
|
27
|
-
[endpoint.ftx]
|
|
28
|
-
api_public = 'https://ftx.com/api'
|
|
29
|
-
ws_public = 'wss://ftx.com/ws/'
|
|
30
|
-
api_auth = 'https://ftx.com/api'
|
|
31
|
-
ws_auth = 'wss://ftx.com/ws/'
|
|
32
|
-
|
|
33
26
|
[endpoint.bitfinex]
|
|
34
27
|
api_public = 'https://api-pub.bitfinex.com'
|
|
35
28
|
api_auth = 'https://api.bitfinex.com'
|
|
@@ -97,8 +90,9 @@
|
|
|
97
90
|
# endregion
|
|
98
91
|
|
|
99
92
|
# region Bitfinex accounts
|
|
100
|
-
# Set up a
|
|
101
|
-
# for transfer funds from
|
|
93
|
+
# Set up a subaccount two-fa (2FA) key and master_email (Main account EMail)
|
|
94
|
+
# for transfer funds from subaccount to master account
|
|
95
|
+
|
|
102
96
|
[[accounts]]
|
|
103
97
|
exchange = 'bitfinex'
|
|
104
98
|
name = 'Demo - Bitfinex'
|