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.
Files changed (105) hide show
  1. walrasquant/__init__.py +7 -0
  2. walrasquant/aggregation.py +449 -0
  3. walrasquant/backends/__init__.py +5 -0
  4. walrasquant/backends/db.py +109 -0
  5. walrasquant/backends/db_memory.py +61 -0
  6. walrasquant/backends/db_postgresql.py +321 -0
  7. walrasquant/backends/db_sqlite.py +310 -0
  8. walrasquant/base/__init__.py +24 -0
  9. walrasquant/base/api_client.py +46 -0
  10. walrasquant/base/connector.py +863 -0
  11. walrasquant/base/ems.py +794 -0
  12. walrasquant/base/exchange.py +213 -0
  13. walrasquant/base/oms.py +428 -0
  14. walrasquant/base/retry.py +220 -0
  15. walrasquant/base/sms.py +545 -0
  16. walrasquant/base/ws_client.py +408 -0
  17. walrasquant/config.py +284 -0
  18. walrasquant/constants.py +413 -0
  19. walrasquant/core/__init__.py +0 -0
  20. walrasquant/core/cache.py +688 -0
  21. walrasquant/core/clock.py +59 -0
  22. walrasquant/core/connection.py +41 -0
  23. walrasquant/core/entity.py +504 -0
  24. walrasquant/core/nautilius_core.py +103 -0
  25. walrasquant/core/registry.py +41 -0
  26. walrasquant/engine.py +745 -0
  27. walrasquant/error.py +34 -0
  28. walrasquant/exchange/__init__.py +13 -0
  29. walrasquant/exchange/base_factory.py +172 -0
  30. walrasquant/exchange/binance/__init__.py +30 -0
  31. walrasquant/exchange/binance/connector.py +1093 -0
  32. walrasquant/exchange/binance/constants.py +934 -0
  33. walrasquant/exchange/binance/ems.py +140 -0
  34. walrasquant/exchange/binance/error.py +48 -0
  35. walrasquant/exchange/binance/exchange.py +144 -0
  36. walrasquant/exchange/binance/factory.py +115 -0
  37. walrasquant/exchange/binance/oms.py +1807 -0
  38. walrasquant/exchange/binance/rest_api.py +1653 -0
  39. walrasquant/exchange/binance/schema.py +1063 -0
  40. walrasquant/exchange/binance/websockets.py +389 -0
  41. walrasquant/exchange/bitget/__init__.py +28 -0
  42. walrasquant/exchange/bitget/connector.py +578 -0
  43. walrasquant/exchange/bitget/constants.py +392 -0
  44. walrasquant/exchange/bitget/ems.py +202 -0
  45. walrasquant/exchange/bitget/error.py +36 -0
  46. walrasquant/exchange/bitget/exchange.py +128 -0
  47. walrasquant/exchange/bitget/factory.py +135 -0
  48. walrasquant/exchange/bitget/oms.py +1619 -0
  49. walrasquant/exchange/bitget/rest_api.py +610 -0
  50. walrasquant/exchange/bitget/schema.py +885 -0
  51. walrasquant/exchange/bitget/websockets.py +753 -0
  52. walrasquant/exchange/bybit/__init__.py +32 -0
  53. walrasquant/exchange/bybit/connector.py +819 -0
  54. walrasquant/exchange/bybit/constants.py +479 -0
  55. walrasquant/exchange/bybit/ems.py +93 -0
  56. walrasquant/exchange/bybit/error.py +36 -0
  57. walrasquant/exchange/bybit/exchange.py +108 -0
  58. walrasquant/exchange/bybit/factory.py +128 -0
  59. walrasquant/exchange/bybit/oms.py +1195 -0
  60. walrasquant/exchange/bybit/rest_api.py +570 -0
  61. walrasquant/exchange/bybit/schema.py +867 -0
  62. walrasquant/exchange/bybit/websockets.py +307 -0
  63. walrasquant/exchange/hyperliquid/__init__.py +28 -0
  64. walrasquant/exchange/hyperliquid/connector.py +370 -0
  65. walrasquant/exchange/hyperliquid/constants.py +371 -0
  66. walrasquant/exchange/hyperliquid/ems.py +156 -0
  67. walrasquant/exchange/hyperliquid/error.py +48 -0
  68. walrasquant/exchange/hyperliquid/exchange.py +120 -0
  69. walrasquant/exchange/hyperliquid/factory.py +135 -0
  70. walrasquant/exchange/hyperliquid/oms.py +1081 -0
  71. walrasquant/exchange/hyperliquid/rest_api.py +348 -0
  72. walrasquant/exchange/hyperliquid/schema.py +583 -0
  73. walrasquant/exchange/hyperliquid/websockets.py +592 -0
  74. walrasquant/exchange/okx/__init__.py +25 -0
  75. walrasquant/exchange/okx/connector.py +931 -0
  76. walrasquant/exchange/okx/constants.py +518 -0
  77. walrasquant/exchange/okx/ems.py +144 -0
  78. walrasquant/exchange/okx/error.py +66 -0
  79. walrasquant/exchange/okx/exchange.py +102 -0
  80. walrasquant/exchange/okx/factory.py +138 -0
  81. walrasquant/exchange/okx/oms.py +1199 -0
  82. walrasquant/exchange/okx/rest_api.py +799 -0
  83. walrasquant/exchange/okx/schema.py +1449 -0
  84. walrasquant/exchange/okx/websockets.py +420 -0
  85. walrasquant/exchange/registry.py +201 -0
  86. walrasquant/execution/__init__.py +24 -0
  87. walrasquant/execution/algorithm.py +968 -0
  88. walrasquant/execution/algorithms/__init__.py +3 -0
  89. walrasquant/execution/algorithms/twap.py +392 -0
  90. walrasquant/execution/config.py +34 -0
  91. walrasquant/execution/constants.py +27 -0
  92. walrasquant/execution/schema.py +62 -0
  93. walrasquant/indicator.py +382 -0
  94. walrasquant/push.py +77 -0
  95. walrasquant/schema.py +755 -0
  96. walrasquant/strategy.py +1805 -0
  97. walrasquant/tools/__init__.py +0 -0
  98. walrasquant/tools/pm2_wrapper.py +1016 -0
  99. walrasquant/web/__init__.py +26 -0
  100. walrasquant/web/app.py +157 -0
  101. walrasquant/web/server.py +92 -0
  102. walrasquant_lib-0.4.20.dist-info/METADATA +162 -0
  103. walrasquant_lib-0.4.20.dist-info/RECORD +105 -0
  104. walrasquant_lib-0.4.20.dist-info/WHEEL +4 -0
  105. walrasquant_lib-0.4.20.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,389 @@
1
+ from typing import Callable, List, Dict
2
+ from typing import Any
3
+ from urllib.parse import urlencode
4
+ from walrasquant.base import WSClient
5
+ from walrasquant.exchange.binance.constants import (
6
+ BinanceAccountType,
7
+ BinanceKlineInterval,
8
+ BinanceRateLimiter,
9
+ )
10
+ from walrasquant.core.entity import TaskManager
11
+ from walrasquant.core.nautilius_core import hmac_signature, LiveClock
12
+
13
+
14
+ class BinanceWSClient(WSClient):
15
+ def __init__(
16
+ self,
17
+ account_type: BinanceAccountType,
18
+ handler: Callable[..., Any],
19
+ task_manager: TaskManager,
20
+ clock: LiveClock,
21
+ ws_suffix: str = "/ws",
22
+ custom_url: str | None = None,
23
+ max_subscriptions_per_client: int | None = None,
24
+ max_clients: int | None = None,
25
+ ):
26
+ self._account_type = account_type
27
+ url = account_type.ws_url
28
+
29
+ if ws_suffix not in ["/ws", "/stream"]:
30
+ raise ValueError(f"Invalid ws_suffix: {ws_suffix}")
31
+
32
+ url += ws_suffix
33
+
34
+ if custom_url is not None:
35
+ url = custom_url
36
+
37
+ super().__init__(
38
+ url,
39
+ handler=handler,
40
+ task_manager=task_manager,
41
+ clock=clock,
42
+ enable_auto_ping=False,
43
+ max_subscriptions_per_client=max_subscriptions_per_client,
44
+ max_clients=max_clients,
45
+ )
46
+
47
+ def _send_payload(
48
+ self,
49
+ params: List[str],
50
+ method: str = "SUBSCRIBE",
51
+ chunk_size: int = 50,
52
+ client_id: int | None = None,
53
+ ):
54
+ # Split params into chunks of 100 if length exceeds 100
55
+ params_chunks = [
56
+ params[i : i + chunk_size] for i in range(0, len(params), chunk_size)
57
+ ]
58
+
59
+ for chunk in params_chunks:
60
+ payload = {
61
+ "method": method,
62
+ "params": chunk,
63
+ "id": self._clock.timestamp_ms(),
64
+ }
65
+ self.send(payload, client_id=client_id)
66
+
67
+ def _subscribe(self, params: List[str]):
68
+ assigned = self._register_subscriptions(params)
69
+ if not assigned:
70
+ return
71
+ for client_id, client_params in assigned.items():
72
+ for param in client_params:
73
+ self._log.debug(f"Subscribing to {param}...")
74
+ if self._is_client_connected(client_id):
75
+ self._send_payload(
76
+ client_params, method="SUBSCRIBE", client_id=client_id
77
+ )
78
+
79
+ def _unsubscribe(self, params: List[str]):
80
+ removed = self._unregister_subscriptions(params)
81
+ if not removed:
82
+ return
83
+ for client_id, client_params in removed.items():
84
+ for param in client_params:
85
+ self._log.debug(f"Unsubscribing from {param}...")
86
+ self._send_payload(client_params, method="UNSUBSCRIBE", client_id=client_id)
87
+
88
+ def subscribe_agg_trade(self, symbols: List[str]):
89
+ if (
90
+ self._account_type.is_isolated_margin_or_margin
91
+ or self._account_type.is_portfolio_margin
92
+ ):
93
+ raise ValueError(
94
+ "Not Supported for `Margin Account` or `Portfolio Margin Account`"
95
+ )
96
+ params = [f"{symbol.lower()}@aggTrade" for symbol in symbols]
97
+ self._subscribe(params)
98
+
99
+ def subscribe_trade(self, symbols: List[str]):
100
+ if (
101
+ self._account_type.is_isolated_margin_or_margin
102
+ or self._account_type.is_portfolio_margin
103
+ ):
104
+ raise ValueError(
105
+ "Not Supported for `Margin Account` or `Portfolio Margin Account`"
106
+ )
107
+ params = [f"{symbol.lower()}@trade" for symbol in symbols]
108
+ self._subscribe(params)
109
+
110
+ def subscribe_book_ticker(self, symbols: List[str]):
111
+ if (
112
+ self._account_type.is_isolated_margin_or_margin
113
+ or self._account_type.is_portfolio_margin
114
+ ):
115
+ raise ValueError(
116
+ "Not Supported for `Margin Account` or `Portfolio Margin Account`"
117
+ )
118
+ params = [f"{symbol.lower()}@bookTicker" for symbol in symbols]
119
+ self._subscribe(params)
120
+
121
+ def subscribe_partial_book_depth(self, symbols: List[str], level: int):
122
+ if level not in (5, 10, 20):
123
+ raise ValueError("Level must be 5, 10, or 20")
124
+ params = [f"{symbol.lower()}@depth{level}@100ms" for symbol in symbols]
125
+ self._subscribe(params)
126
+
127
+ def subscribe_mark_price(self, symbols: List[str]):
128
+ if not self._account_type.is_future:
129
+ raise ValueError("Only Supported for `Future Account`")
130
+ params = [f"{symbol.lower()}@markPrice@1s" for symbol in symbols]
131
+ self._subscribe(params)
132
+
133
+ def subscribe_user_data_stream(self, listen_key: str):
134
+ self._subscribe([listen_key])
135
+
136
+ def subscribe_kline(
137
+ self,
138
+ symbols: List[str],
139
+ interval: BinanceKlineInterval,
140
+ ):
141
+ if (
142
+ self._account_type.is_isolated_margin_or_margin
143
+ or self._account_type.is_portfolio_margin
144
+ ):
145
+ raise ValueError(
146
+ "Not Supported for `Margin Account` or `Portfolio Margin Account`"
147
+ )
148
+ params = [f"{symbol.lower()}@kline_{interval.value}" for symbol in symbols]
149
+ self._subscribe(params)
150
+
151
+ def unsubscribe_agg_trade(self, symbols: List[str]):
152
+ if (
153
+ self._account_type.is_isolated_margin_or_margin
154
+ or self._account_type.is_portfolio_margin
155
+ ):
156
+ raise ValueError(
157
+ "Not Supported for `Margin Account` or `Portfolio Margin Account`"
158
+ )
159
+ params = [f"{symbol.lower()}@aggTrade" for symbol in symbols]
160
+ self._unsubscribe(params)
161
+
162
+ def unsubscribe_trade(self, symbols: List[str]):
163
+ if (
164
+ self._account_type.is_isolated_margin_or_margin
165
+ or self._account_type.is_portfolio_margin
166
+ ):
167
+ raise ValueError(
168
+ "Not Supported for `Margin Account` or `Portfolio Margin Account`"
169
+ )
170
+ params = [f"{symbol.lower()}@trade" for symbol in symbols]
171
+ self._unsubscribe(params)
172
+
173
+ def unsubscribe_book_ticker(self, symbols: List[str]):
174
+ if (
175
+ self._account_type.is_isolated_margin_or_margin
176
+ or self._account_type.is_portfolio_margin
177
+ ):
178
+ raise ValueError(
179
+ "Not Supported for `Margin Account` or `Portfolio Margin Account`"
180
+ )
181
+ params = [f"{symbol.lower()}@bookTicker" for symbol in symbols]
182
+ self._unsubscribe(params)
183
+
184
+ def unsubscribe_partial_book_depth(self, symbols: List[str], level: int):
185
+ if level not in (5, 10, 20):
186
+ raise ValueError("Level must be 5, 10, or 20")
187
+ params = [f"{symbol.lower()}@depth{level}@100ms" for symbol in symbols]
188
+ self._unsubscribe(params)
189
+
190
+ def subscribe_diff_depth(self, symbols: List[str]):
191
+ params = [f"{symbol.lower()}@depth@100ms" for symbol in symbols]
192
+ self._subscribe(params)
193
+
194
+ def unsubscribe_diff_depth(self, symbols: List[str]):
195
+ params = [f"{symbol.lower()}@depth@100ms" for symbol in symbols]
196
+ self._unsubscribe(params)
197
+
198
+ def unsubscribe_mark_price(self, symbols: List[str]):
199
+ if not self._account_type.is_future:
200
+ raise ValueError("Only Supported for `Future Account`")
201
+ params = [f"{symbol.lower()}@markPrice@1s" for symbol in symbols]
202
+ self._unsubscribe(params)
203
+
204
+ def unsubscribe_user_data_stream(self, listen_key: str):
205
+ self._unsubscribe([listen_key])
206
+
207
+ def unsubscribe_kline(
208
+ self,
209
+ symbols: List[str],
210
+ interval: BinanceKlineInterval,
211
+ ):
212
+ if (
213
+ self._account_type.is_isolated_margin_or_margin
214
+ or self._account_type.is_portfolio_margin
215
+ ):
216
+ raise ValueError(
217
+ "Not Supported for `Margin Account` or `Portfolio Margin Account`"
218
+ )
219
+ params = [f"{symbol.lower()}@kline_{interval.value}" for symbol in symbols]
220
+ self._unsubscribe(params)
221
+
222
+ async def _resubscribe_for_client(self, client_id: int, subscriptions: List[str]):
223
+ if not subscriptions:
224
+ return
225
+ self._send_payload(subscriptions, client_id=client_id)
226
+
227
+
228
+ class BinanceWSApiClient(WSClient):
229
+ def __init__(
230
+ self,
231
+ account_type: BinanceAccountType,
232
+ api_key: str,
233
+ secret: str,
234
+ handler: Callable[..., Any],
235
+ task_manager: TaskManager,
236
+ clock: LiveClock,
237
+ enable_rate_limit: bool,
238
+ ):
239
+ self._account_type = account_type
240
+ self._api_key = api_key
241
+ self._secret = secret
242
+ self._limiter = BinanceRateLimiter(enable_rate_limit)
243
+
244
+ url = account_type.ws_order_url
245
+
246
+ if not url:
247
+ raise ValueError(f"WebSocket URL not supported for {account_type}")
248
+
249
+ super().__init__(
250
+ url=url,
251
+ handler=handler,
252
+ task_manager=task_manager,
253
+ clock=clock,
254
+ enable_auto_ping=False,
255
+ )
256
+
257
+ def _generate_signature_v2(self, query: str) -> str:
258
+ signature = hmac_signature(self._secret, query)
259
+ return signature
260
+
261
+ def _send_payload(
262
+ self,
263
+ id: str,
264
+ method: str,
265
+ params: Dict[str, Any],
266
+ required_ts: bool = True,
267
+ auth: bool = True,
268
+ ):
269
+ if required_ts:
270
+ params["timestamp"] = self._clock.timestamp_ms()
271
+
272
+ if auth:
273
+ params["apiKey"] = self._api_key
274
+ query = urlencode(sorted(params.items()))
275
+ signature = self._generate_signature_v2(query)
276
+ params["signature"] = signature
277
+
278
+ payload = {
279
+ "method": method,
280
+ "id": id,
281
+ "params": params,
282
+ }
283
+ self.send(payload)
284
+
285
+ async def spot_new_order(
286
+ self, oid: str, symbol: str, side: str, type: str, quantity: str, **kwargs: Any
287
+ ):
288
+ params = {
289
+ "symbol": symbol,
290
+ "side": side,
291
+ "type": type,
292
+ "quantity": quantity,
293
+ **kwargs,
294
+ }
295
+ await self._limiter.api_order_limit(cost=1)
296
+ self._send_payload(id=f"n{oid}", method="order.place", params=params)
297
+
298
+ async def spot_cancel_order(
299
+ self, oid: str, symbol: str, origClientOrderId: int, **kwargs: Any
300
+ ):
301
+ params = {
302
+ "symbol": symbol,
303
+ "origClientOrderId": origClientOrderId,
304
+ **kwargs,
305
+ }
306
+ self._send_payload(id=f"c{oid}", method="order.cancel", params=params)
307
+
308
+ async def usdm_new_order(
309
+ self, oid: str, symbol: str, side: str, type: str, quantity: str, **kwargs: Any
310
+ ):
311
+ params = {
312
+ "symbol": symbol,
313
+ "side": side,
314
+ "type": type,
315
+ "quantity": quantity,
316
+ **kwargs,
317
+ }
318
+ await self._limiter.fapi_order_limit(cost=0)
319
+ self._send_payload(id=f"n{oid}", method="order.place", params=params)
320
+
321
+ async def usdm_cancel_order(
322
+ self, oid: str, symbol: str, origClientOrderId: int, **kwargs: Any
323
+ ):
324
+ params = {
325
+ "symbol": symbol,
326
+ "origClientOrderId": origClientOrderId,
327
+ **kwargs,
328
+ }
329
+ self._send_payload(id=f"c{oid}", method="order.cancel", params=params)
330
+
331
+ async def coinm_new_order(
332
+ self, oid: str, symbol: str, side: str, type: str, quantity: str, **kwargs: Any
333
+ ):
334
+ params = {
335
+ "symbol": symbol,
336
+ "side": side,
337
+ "type": type,
338
+ "quantity": quantity,
339
+ **kwargs,
340
+ }
341
+ await self._limiter.dapi_order_limit(cost=0)
342
+ self._send_payload(id=f"n{oid}", method="order.place", params=params)
343
+
344
+ async def coinm_cancel_order(
345
+ self, oid: str, symbol: str, origClientOrderId: int, **kwargs: Any
346
+ ):
347
+ params = {
348
+ "symbol": symbol,
349
+ "origClientOrderId": origClientOrderId,
350
+ **kwargs,
351
+ }
352
+ self._send_payload(id=f"c{oid}", method="order.cancel", params=params)
353
+
354
+ def _subscribe(self, subscriptions: List[tuple[Dict[str, Any], str]]):
355
+ assigned = self._register_subscriptions(subscriptions)
356
+ if not assigned:
357
+ return
358
+ for client_id, client_subs in assigned.items():
359
+ for params, method in client_subs:
360
+ self._log.debug(f"Subscribing to {method} with {params}...")
361
+ if self._is_client_connected(client_id):
362
+ for params, method in client_subs:
363
+ request_id = f"{self._clock.timestamp_ms()}"
364
+ self._send_payload(
365
+ id=request_id,
366
+ method=method,
367
+ params=dict(params),
368
+ auth=True,
369
+ )
370
+
371
+ async def _resubscribe_for_client(
372
+ self, client_id: int, subscriptions: List[tuple[Dict[str, Any], str]]
373
+ ):
374
+ if not subscriptions:
375
+ return
376
+ for params, method in subscriptions:
377
+ request_id = f"s{self._clock.timestamp_ms()}"
378
+ self._send_payload(
379
+ id=request_id,
380
+ method=method,
381
+ params=dict(params),
382
+ auth=True,
383
+ )
384
+
385
+ def subscribe_spot_user_data_stream(self):
386
+ if not self._account_type.is_spot:
387
+ raise ValueError("Only Supported for `Spot Account`")
388
+ params: Dict[str, Any] = {}
389
+ self._subscribe([(params, "userDataStream.subscribe.signature")])
@@ -0,0 +1,28 @@
1
+ from walrasquant.exchange.bitget.exchange import BitgetExchangeManager
2
+ from walrasquant.exchange.bitget.connector import (
3
+ BitgetPublicConnector,
4
+ BitgetPrivateConnector,
5
+ )
6
+ from walrasquant.exchange.bitget.constants import BitgetAccountType
7
+ from walrasquant.exchange.bitget.ems import BitgetExecutionManagementSystem
8
+ from walrasquant.exchange.bitget.oms import BitgetOrderManagementSystem
9
+ from walrasquant.exchange.bitget.factory import BitgetFactory
10
+
11
+ # Auto-register factory on import
12
+ try:
13
+ from walrasquant.exchange.registry import register_factory
14
+
15
+ register_factory(BitgetFactory())
16
+ except ImportError:
17
+ # Registry not available yet during bootstrap
18
+ pass
19
+
20
+ __all__ = [
21
+ "BitgetExchangeManager",
22
+ "BitgetPublicConnector",
23
+ "BitgetPrivateConnector",
24
+ "BitgetAccountType",
25
+ "BitgetExecutionManagementSystem",
26
+ "BitgetOrderManagementSystem",
27
+ "BitgetFactory",
28
+ ]