walrasquant-lib 0.4.20__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.
- walrasquant/__init__.py +7 -0
- walrasquant/aggregation.py +449 -0
- walrasquant/backends/__init__.py +5 -0
- walrasquant/backends/db.py +109 -0
- walrasquant/backends/db_memory.py +61 -0
- walrasquant/backends/db_postgresql.py +321 -0
- walrasquant/backends/db_sqlite.py +310 -0
- walrasquant/base/__init__.py +24 -0
- walrasquant/base/api_client.py +46 -0
- walrasquant/base/connector.py +863 -0
- walrasquant/base/ems.py +794 -0
- walrasquant/base/exchange.py +213 -0
- walrasquant/base/oms.py +428 -0
- walrasquant/base/retry.py +220 -0
- walrasquant/base/sms.py +545 -0
- walrasquant/base/ws_client.py +408 -0
- walrasquant/config.py +284 -0
- walrasquant/constants.py +413 -0
- walrasquant/core/__init__.py +0 -0
- walrasquant/core/cache.py +688 -0
- walrasquant/core/clock.py +59 -0
- walrasquant/core/connection.py +41 -0
- walrasquant/core/entity.py +504 -0
- walrasquant/core/nautilius_core.py +103 -0
- walrasquant/core/registry.py +41 -0
- walrasquant/engine.py +745 -0
- walrasquant/error.py +34 -0
- walrasquant/exchange/__init__.py +13 -0
- walrasquant/exchange/base_factory.py +172 -0
- walrasquant/exchange/binance/__init__.py +30 -0
- walrasquant/exchange/binance/connector.py +1093 -0
- walrasquant/exchange/binance/constants.py +934 -0
- walrasquant/exchange/binance/ems.py +140 -0
- walrasquant/exchange/binance/error.py +48 -0
- walrasquant/exchange/binance/exchange.py +144 -0
- walrasquant/exchange/binance/factory.py +115 -0
- walrasquant/exchange/binance/oms.py +1807 -0
- walrasquant/exchange/binance/rest_api.py +1653 -0
- walrasquant/exchange/binance/schema.py +1063 -0
- walrasquant/exchange/binance/websockets.py +389 -0
- walrasquant/exchange/bitget/__init__.py +28 -0
- walrasquant/exchange/bitget/connector.py +578 -0
- walrasquant/exchange/bitget/constants.py +392 -0
- walrasquant/exchange/bitget/ems.py +202 -0
- walrasquant/exchange/bitget/error.py +36 -0
- walrasquant/exchange/bitget/exchange.py +128 -0
- walrasquant/exchange/bitget/factory.py +135 -0
- walrasquant/exchange/bitget/oms.py +1619 -0
- walrasquant/exchange/bitget/rest_api.py +610 -0
- walrasquant/exchange/bitget/schema.py +885 -0
- walrasquant/exchange/bitget/websockets.py +753 -0
- walrasquant/exchange/bybit/__init__.py +32 -0
- walrasquant/exchange/bybit/connector.py +819 -0
- walrasquant/exchange/bybit/constants.py +479 -0
- walrasquant/exchange/bybit/ems.py +93 -0
- walrasquant/exchange/bybit/error.py +36 -0
- walrasquant/exchange/bybit/exchange.py +108 -0
- walrasquant/exchange/bybit/factory.py +128 -0
- walrasquant/exchange/bybit/oms.py +1195 -0
- walrasquant/exchange/bybit/rest_api.py +570 -0
- walrasquant/exchange/bybit/schema.py +867 -0
- walrasquant/exchange/bybit/websockets.py +307 -0
- walrasquant/exchange/hyperliquid/__init__.py +28 -0
- walrasquant/exchange/hyperliquid/connector.py +370 -0
- walrasquant/exchange/hyperliquid/constants.py +371 -0
- walrasquant/exchange/hyperliquid/ems.py +156 -0
- walrasquant/exchange/hyperliquid/error.py +48 -0
- walrasquant/exchange/hyperliquid/exchange.py +120 -0
- walrasquant/exchange/hyperliquid/factory.py +135 -0
- walrasquant/exchange/hyperliquid/oms.py +1081 -0
- walrasquant/exchange/hyperliquid/rest_api.py +348 -0
- walrasquant/exchange/hyperliquid/schema.py +583 -0
- walrasquant/exchange/hyperliquid/websockets.py +592 -0
- walrasquant/exchange/okx/__init__.py +25 -0
- walrasquant/exchange/okx/connector.py +931 -0
- walrasquant/exchange/okx/constants.py +518 -0
- walrasquant/exchange/okx/ems.py +144 -0
- walrasquant/exchange/okx/error.py +66 -0
- walrasquant/exchange/okx/exchange.py +102 -0
- walrasquant/exchange/okx/factory.py +138 -0
- walrasquant/exchange/okx/oms.py +1199 -0
- walrasquant/exchange/okx/rest_api.py +799 -0
- walrasquant/exchange/okx/schema.py +1449 -0
- walrasquant/exchange/okx/websockets.py +420 -0
- walrasquant/exchange/registry.py +201 -0
- walrasquant/execution/__init__.py +24 -0
- walrasquant/execution/algorithm.py +968 -0
- walrasquant/execution/algorithms/__init__.py +3 -0
- walrasquant/execution/algorithms/twap.py +392 -0
- walrasquant/execution/config.py +34 -0
- walrasquant/execution/constants.py +27 -0
- walrasquant/execution/schema.py +62 -0
- walrasquant/indicator.py +382 -0
- walrasquant/push.py +77 -0
- walrasquant/schema.py +755 -0
- walrasquant/strategy.py +1805 -0
- walrasquant/tools/__init__.py +0 -0
- walrasquant/tools/pm2_wrapper.py +1016 -0
- walrasquant/web/__init__.py +26 -0
- walrasquant/web/app.py +157 -0
- walrasquant/web/server.py +92 -0
- walrasquant_lib-0.4.20.dist-info/METADATA +162 -0
- walrasquant_lib-0.4.20.dist-info/RECORD +105 -0
- walrasquant_lib-0.4.20.dist-info/WHEEL +4 -0
- walrasquant_lib-0.4.20.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import nexuslog as logging
|
|
3
|
+
|
|
4
|
+
from typing import Callable, Awaitable, Generic, TypeVar
|
|
5
|
+
from random import randint
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_exponential_backoff(
|
|
11
|
+
num_attempts: int,
|
|
12
|
+
delay_initial_ms: int = 500,
|
|
13
|
+
delay_max_ms: int = 2_000,
|
|
14
|
+
backoff_factor: int = 2,
|
|
15
|
+
jitter: bool = True,
|
|
16
|
+
) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Compute the backoff using exponential backoff and jitter.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
num_attempts : int, default 1
|
|
23
|
+
The number of attempts that have already been made.
|
|
24
|
+
delay_initial_ms : int, default 500
|
|
25
|
+
The time to sleep in the first attempt.
|
|
26
|
+
delay_max_ms : int, default 2_000
|
|
27
|
+
The maximum delay.
|
|
28
|
+
backoff_factor : int, default 2
|
|
29
|
+
The exponential backoff factor for delays.
|
|
30
|
+
jitter : bool, default True
|
|
31
|
+
Whether or not to apply jitter.
|
|
32
|
+
|
|
33
|
+
Notes
|
|
34
|
+
-----
|
|
35
|
+
https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
int
|
|
40
|
+
Delay in milliseconds.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
delay = min(delay_max_ms, delay_initial_ms * backoff_factor ** (num_attempts - 1))
|
|
44
|
+
|
|
45
|
+
if jitter:
|
|
46
|
+
return randint(delay_initial_ms, delay) # noqa: S311
|
|
47
|
+
|
|
48
|
+
return delay
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RetryManager(Generic[T]):
|
|
52
|
+
"""
|
|
53
|
+
Provides retry state management for an HTTP request.
|
|
54
|
+
|
|
55
|
+
This class is generic over `T`, where `T` is the return type of the
|
|
56
|
+
function passed to the `run` method.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
max_retries : int
|
|
61
|
+
The maximum number of retries before failure.
|
|
62
|
+
delay_initial_ms : int
|
|
63
|
+
The initial delay (milliseconds) for retries.
|
|
64
|
+
delay_max_ms : int
|
|
65
|
+
The maximum delay (milliseconds) for exponential backoff.
|
|
66
|
+
backoff_factor : int
|
|
67
|
+
The exponential backoff factor for retry delays.
|
|
68
|
+
exc_types : tuple[Type[BaseException], ...]
|
|
69
|
+
The exception types to handle for retries.
|
|
70
|
+
retry_check : Callable[[BaseException], None], optional
|
|
71
|
+
A function that performs additional checks on the exception.
|
|
72
|
+
If the function returns `False`, a retry will not be attempted.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
max_retries: int,
|
|
79
|
+
delay_initial_ms: int,
|
|
80
|
+
delay_max_ms: int,
|
|
81
|
+
backoff_factor: int,
|
|
82
|
+
exc_types: tuple[type[BaseException], ...],
|
|
83
|
+
retry_check: Callable[[BaseException], bool] | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
self.max_retries = max_retries
|
|
86
|
+
self.delay_initial_ms = delay_initial_ms
|
|
87
|
+
self.delay_max_ms = delay_max_ms
|
|
88
|
+
self.backoff_factor = backoff_factor
|
|
89
|
+
self.exc_types = exc_types
|
|
90
|
+
self.retry_check = retry_check
|
|
91
|
+
self.cancel_event = asyncio.Event()
|
|
92
|
+
self._log = logging.getLogger(name=type(self).__name__)
|
|
93
|
+
|
|
94
|
+
self.name: str | None = None
|
|
95
|
+
self.details: list[object] | None = None
|
|
96
|
+
self.details_str: str | None = None
|
|
97
|
+
self.result: bool = False
|
|
98
|
+
self.message: str | None = None
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
return f"<{type(self).__name__}(name='{self.name}', details={self.details}) at {hex(id(self))}>"
|
|
102
|
+
|
|
103
|
+
async def run(
|
|
104
|
+
self,
|
|
105
|
+
name: str,
|
|
106
|
+
func: Callable[..., Awaitable[T]],
|
|
107
|
+
details: list[object] | None = None,
|
|
108
|
+
jitter: bool = True,
|
|
109
|
+
*args,
|
|
110
|
+
**kwargs,
|
|
111
|
+
) -> T | None:
|
|
112
|
+
"""
|
|
113
|
+
Execute the given `func` with retry management.
|
|
114
|
+
|
|
115
|
+
If an exception in `self.exc_types` is raised, a warning is logged, and the function is
|
|
116
|
+
retried after a delay until the maximum retries are reached, at which point an error is logged.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
name : str
|
|
121
|
+
The name of the operation to run.
|
|
122
|
+
details : list[object], optional
|
|
123
|
+
The operation details such as identifiers.
|
|
124
|
+
func : Callable[..., Awaitable[T]]
|
|
125
|
+
The function to execute.
|
|
126
|
+
jitter : bool, default True
|
|
127
|
+
Whether to apply jitter to the retry delay.
|
|
128
|
+
args : Any
|
|
129
|
+
Positional arguments to pass to the function `func`.
|
|
130
|
+
kwargs : Any
|
|
131
|
+
Keyword arguments to pass to the function `func`.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
T | None
|
|
136
|
+
The result of the executed function, or ``None`` if the retries fail.
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
retries = 0
|
|
140
|
+
self.name = name
|
|
141
|
+
self.details = details
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
while True:
|
|
145
|
+
if self.cancel_event.is_set():
|
|
146
|
+
self._cancel()
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
response = await func(*args, **kwargs)
|
|
151
|
+
self.result = True
|
|
152
|
+
|
|
153
|
+
return response # Successful request
|
|
154
|
+
except self.exc_types as e:
|
|
155
|
+
self._log.warning(repr(e))
|
|
156
|
+
if (
|
|
157
|
+
(self.retry_check and not self.retry_check(e))
|
|
158
|
+
or not self.max_retries
|
|
159
|
+
or retries >= self.max_retries
|
|
160
|
+
):
|
|
161
|
+
self._log_error()
|
|
162
|
+
self.result = False
|
|
163
|
+
self.message = str(e)
|
|
164
|
+
raise e
|
|
165
|
+
|
|
166
|
+
retries += 1
|
|
167
|
+
retry_delay_ms = get_exponential_backoff(
|
|
168
|
+
delay_initial_ms=self.delay_initial_ms,
|
|
169
|
+
delay_max_ms=self.delay_max_ms,
|
|
170
|
+
backoff_factor=self.backoff_factor,
|
|
171
|
+
num_attempts=retries,
|
|
172
|
+
jitter=jitter,
|
|
173
|
+
)
|
|
174
|
+
self._log_retry(retries, retry_delay_ms=retry_delay_ms)
|
|
175
|
+
await asyncio.sleep(retry_delay_ms / 1000)
|
|
176
|
+
except asyncio.CancelledError:
|
|
177
|
+
self._cancel()
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def cancel(self) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Cancel the retry operation.
|
|
183
|
+
"""
|
|
184
|
+
self._log.debug(f"Canceling {self!r}")
|
|
185
|
+
self.cancel_event.set()
|
|
186
|
+
|
|
187
|
+
def clear(self) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Clear all state from this retry manager.
|
|
190
|
+
"""
|
|
191
|
+
self.name = None
|
|
192
|
+
self.details = None
|
|
193
|
+
self.details_str = None
|
|
194
|
+
self.result = False
|
|
195
|
+
self.message = None
|
|
196
|
+
|
|
197
|
+
def _cancel(self) -> None:
|
|
198
|
+
self._log.warning(f"Canceled retry for '{self.name}'")
|
|
199
|
+
self.result = False
|
|
200
|
+
self.message = "Canceled retry"
|
|
201
|
+
|
|
202
|
+
def _log_retry(self, retry: int, retry_delay_ms: int) -> None:
|
|
203
|
+
self._log.warning(
|
|
204
|
+
f"Retrying {retry}/{self.max_retries} for '{self.name}' "
|
|
205
|
+
f"in {retry_delay_ms / 1000}s{self._details_str()}",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def _log_error(self) -> None:
|
|
209
|
+
self._log.error(
|
|
210
|
+
f"Failed on '{self.name}'{self._details_str()}",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def _details_str(self) -> str:
|
|
214
|
+
if not self.details:
|
|
215
|
+
return ""
|
|
216
|
+
|
|
217
|
+
if not self.details_str:
|
|
218
|
+
self.details_str = ": " + ", ".join([repr(x) for x in self.details])
|
|
219
|
+
|
|
220
|
+
return self.details_str
|