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,413 @@
1
+ import os
2
+ import sys
3
+ from typing import Literal, Dict, List, TypedDict, NotRequired
4
+ from enum import Enum
5
+ from dynaconf import Dynaconf
6
+
7
+ BACKEND_LITERAL = Literal["memory", "redis"]
8
+
9
+
10
+ def is_sphinx_build():
11
+ return "sphinx" in sys.modules
12
+
13
+
14
+ if not os.path.exists(".keys/"):
15
+ os.makedirs(".keys/")
16
+
17
+ _SECRETS_CANDIDATES = [
18
+ ".keys/.secrets.toml",
19
+ ".keys/.secrets.yaml",
20
+ ".keys/.secrets.yml",
21
+ ]
22
+ _SETTINGS_CANDIDATES = [
23
+ ".keys/settings.toml",
24
+ ".keys/settings.yaml",
25
+ ".keys/settings.yml",
26
+ ]
27
+
28
+ _secrets_file = next((f for f in _SECRETS_CANDIDATES if os.path.exists(f)), None)
29
+ _settings_file = next((f for f in _SETTINGS_CANDIDATES if os.path.exists(f)), None)
30
+ _config_files = [f for f in (_settings_file, _secrets_file) if f]
31
+
32
+ if _secrets_file is None and not is_sphinx_build():
33
+ raise FileNotFoundError(
34
+ "Config file not found, please create a secrets file at one of: "
35
+ + ", ".join(_SECRETS_CANDIDATES)
36
+ )
37
+
38
+
39
+ settings = Dynaconf(
40
+ envvar_prefix="NEXUS",
41
+ settings_files=_config_files,
42
+ secrets=_secrets_file,
43
+ environments=False,
44
+ load_dotenv=True,
45
+ )
46
+
47
+
48
+ def get_postgresql_config():
49
+ """Get PostgreSQL configuration from environment variables or settings files."""
50
+ return {
51
+ "host": settings.get("PG_HOST", "localhost"),
52
+ "port": settings.get("PG_PORT", 5432),
53
+ "user": settings.get("PG_USER", "postgres"),
54
+ "password": settings.get("PG_PASSWORD", ""),
55
+ "database": settings.get("PG_DATABASE", "postgres"),
56
+ }
57
+
58
+
59
+ def get_redis_config():
60
+ try:
61
+ return {
62
+ "host": settings.REDIS_HOST,
63
+ "port": settings.REDIS_PORT,
64
+ "db": settings.REDIS_DB,
65
+ "password": settings.REDIS_PASSWORD,
66
+ }
67
+ except Exception as e:
68
+ raise ValueError(f"Failed to get Redis password: {e}")
69
+
70
+
71
+ IntervalType = Literal[
72
+ "1s",
73
+ "1m",
74
+ "3m",
75
+ "5m",
76
+ "15m",
77
+ "30m",
78
+ "1h",
79
+ "2h",
80
+ "4h",
81
+ "6h",
82
+ "8h",
83
+ "12h",
84
+ "1d",
85
+ "3d",
86
+ "1w",
87
+ "1M",
88
+ ]
89
+
90
+
91
+ class BookLevel(Enum):
92
+ L5 = "5"
93
+ L10 = "10"
94
+ L20 = "20"
95
+ L50 = "50"
96
+ L200 = "200"
97
+ L400 = "400"
98
+ L1000 = "1000"
99
+
100
+
101
+ class KlineInterval(Enum):
102
+ SECOND_1 = "1s"
103
+ MINUTE_1 = "1m"
104
+ MINUTE_3 = "3m"
105
+ MINUTE_5 = "5m"
106
+ MINUTE_15 = "15m"
107
+ MINUTE_30 = "30m"
108
+ HOUR_1 = "1h"
109
+ HOUR_2 = "2h"
110
+ HOUR_4 = "4h"
111
+ HOUR_6 = "6h"
112
+ HOUR_8 = "8h"
113
+ HOUR_12 = "12h"
114
+ DAY_1 = "1d"
115
+ DAY_3 = "3d"
116
+ WEEK_1 = "1w"
117
+ MONTH_1 = "1M"
118
+ VOLUME = "volume"
119
+
120
+ @property
121
+ def seconds(self) -> int:
122
+ return {
123
+ KlineInterval.SECOND_1: 1,
124
+ KlineInterval.MINUTE_1: 60,
125
+ KlineInterval.MINUTE_3: 180,
126
+ KlineInterval.MINUTE_5: 300,
127
+ KlineInterval.MINUTE_15: 900,
128
+ KlineInterval.MINUTE_30: 1800,
129
+ KlineInterval.HOUR_1: 3600,
130
+ KlineInterval.HOUR_2: 7200,
131
+ KlineInterval.HOUR_4: 14400,
132
+ KlineInterval.HOUR_6: 21600,
133
+ KlineInterval.HOUR_8: 28800,
134
+ KlineInterval.HOUR_12: 43200,
135
+ KlineInterval.DAY_1: 86400,
136
+ KlineInterval.DAY_3: 259200,
137
+ KlineInterval.WEEK_1: 604800,
138
+ KlineInterval.MONTH_1: 2592000,
139
+ KlineInterval.VOLUME: 0,
140
+ }[self]
141
+
142
+ @property
143
+ def nanoseconds(self) -> int:
144
+ return self.seconds * 1_000_000_000
145
+
146
+ @property
147
+ def microseconds(self) -> int:
148
+ return self.seconds * 1_000_000
149
+
150
+ @property
151
+ def milliseconds(self) -> int:
152
+ return self.seconds * 1_000
153
+
154
+
155
+ class SubmitType(Enum):
156
+ CREATE = 0
157
+ CANCEL = 1
158
+ TWAP = 2
159
+ CANCEL_TWAP = 3
160
+ VWAP = 4
161
+ CANCEL_VWAP = 5
162
+ TAKE_PROFIT_AND_STOP_LOSS = 6
163
+ MODIFY = 7
164
+ CANCEL_ALL = 8
165
+ BATCH = 9
166
+ CREATE_WS = 10
167
+ CANCEL_WS = 11
168
+ CANCEL_BATCH = 12
169
+
170
+ @property
171
+ def priority(self) -> int:
172
+ """Lower value = higher priority. CREATE types return 5 (lowest); call
173
+ ``order_priority()`` instead when the order payload is available."""
174
+ if self in CANCEL_SUBMIT_TYPES:
175
+ return 0
176
+ if self == SubmitType.MODIFY:
177
+ return 1
178
+ return 5 # CREATE / CREATE_WS / BATCH / TP_SL — refined by order_priority()
179
+
180
+
181
+ CANCEL_SUBMIT_TYPES: frozenset = frozenset(
182
+ {
183
+ SubmitType.CANCEL,
184
+ SubmitType.CANCEL_WS,
185
+ SubmitType.CANCEL_ALL,
186
+ SubmitType.CANCEL_BATCH,
187
+ SubmitType.CANCEL_TWAP,
188
+ SubmitType.CANCEL_VWAP,
189
+ }
190
+ )
191
+
192
+
193
+ class EventType(Enum):
194
+ BOOKL1 = 0
195
+ TRADE = 1
196
+ KLINE = 2
197
+ MARK_PRICE = 3
198
+ FUNDING_RATE = 4
199
+ INDEX_PRICE = 5
200
+
201
+
202
+ class OrderStatus(Enum):
203
+ # LOCAL
204
+ INITIALIZED = "INITIALIZED"
205
+ FAILED = "FAILED"
206
+ CANCEL_FAILED = "CANCEL_FAILED"
207
+
208
+ # IN-FLOW
209
+ PENDING = "PENDING"
210
+ CANCELING = "CANCELING"
211
+
212
+ # OPEN
213
+ ACCEPTED = "ACCEPTED"
214
+ PARTIALLY_FILLED = "PARTIALLY_FILLED"
215
+
216
+ # CLOSED
217
+ FILLED = "FILLED"
218
+ CANCELED = "CANCELED"
219
+ EXPIRED = "EXPIRED"
220
+
221
+
222
+ class ExchangeType(Enum):
223
+ BINANCE = "binance"
224
+ OKX = "okx"
225
+ BYBIT = "bybit"
226
+ HYPERLIQUID = "hyperliquid"
227
+ BITGET = "bitget"
228
+
229
+
230
+ class AccountType(Enum):
231
+ pass
232
+
233
+
234
+ class OrderType(Enum):
235
+ MARKET = "MARKET"
236
+ LIMIT = "LIMIT"
237
+ TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET"
238
+ TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT"
239
+ STOP_LOSS_MARKET = "STOP_LOSS_MARKET"
240
+ STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT"
241
+ POST_ONLY = "POST_ONLY"
242
+
243
+ @property
244
+ def is_take_profit(self) -> bool:
245
+ return self in (OrderType.TAKE_PROFIT_MARKET, OrderType.TAKE_PROFIT_LIMIT)
246
+
247
+ @property
248
+ def is_stop_loss(self) -> bool:
249
+ return self in (OrderType.STOP_LOSS_MARKET, OrderType.STOP_LOSS_LIMIT)
250
+
251
+ @property
252
+ def is_market(self) -> bool:
253
+ return self in (
254
+ OrderType.MARKET,
255
+ OrderType.TAKE_PROFIT_MARKET,
256
+ OrderType.STOP_LOSS_MARKET,
257
+ )
258
+
259
+ @property
260
+ def is_limit(self) -> bool:
261
+ return self in (
262
+ OrderType.LIMIT,
263
+ OrderType.TAKE_PROFIT_LIMIT,
264
+ OrderType.STOP_LOSS_LIMIT,
265
+ )
266
+
267
+ @property
268
+ def is_post_only(self) -> bool:
269
+ return self == OrderType.POST_ONLY
270
+
271
+
272
+ class TriggerType(Enum):
273
+ LAST_PRICE = "LAST_PRICE"
274
+ MARK_PRICE = "MARK_PRICE"
275
+ INDEX_PRICE = "INDEX_PRICE"
276
+
277
+
278
+ class OrderSide(Enum):
279
+ BUY = "BUY"
280
+ SELL = "SELL"
281
+
282
+ @property
283
+ def is_buy(self) -> bool:
284
+ return self == OrderSide.BUY
285
+
286
+ @property
287
+ def is_sell(self) -> bool:
288
+ return self == OrderSide.SELL
289
+
290
+
291
+ class TimeInForce(Enum):
292
+ GTC = "GTC"
293
+ IOC = "IOC"
294
+ FOK = "FOK"
295
+
296
+
297
+ class PositionSide(Enum):
298
+ LONG = "LONG"
299
+ SHORT = "SHORT"
300
+ FLAT = "FLAT"
301
+
302
+ @property
303
+ def is_long(self) -> bool:
304
+ return self == PositionSide.LONG
305
+
306
+ @property
307
+ def is_short(self) -> bool:
308
+ return self == PositionSide.SHORT
309
+
310
+ @property
311
+ def is_flat(self) -> bool:
312
+ return self == PositionSide.FLAT
313
+
314
+
315
+ class InstrumentType(Enum):
316
+ SPOT = "spot"
317
+ MARGIN = "margin"
318
+ FUTURE = "future"
319
+ OPTION = "option"
320
+ SWAP = "swap"
321
+ LINEAR = "linear"
322
+ INVERSE = "inverse"
323
+
324
+
325
+ class OptionType(Enum):
326
+ CALL = "call"
327
+ PUT = "put"
328
+
329
+
330
+ STATUS_TRANSITIONS: Dict[OrderStatus, List[OrderStatus]] = {
331
+ OrderStatus.PENDING: [
332
+ OrderStatus.PENDING,
333
+ OrderStatus.CANCELED,
334
+ OrderStatus.CANCELING,
335
+ OrderStatus.ACCEPTED,
336
+ OrderStatus.PARTIALLY_FILLED,
337
+ OrderStatus.FILLED,
338
+ OrderStatus.CANCEL_FAILED,
339
+ OrderStatus.FAILED, # e.g., rejected by exchange we mark as failed
340
+ ],
341
+ OrderStatus.CANCELING: [
342
+ OrderStatus.ACCEPTED,
343
+ OrderStatus.CANCELED,
344
+ OrderStatus.PARTIALLY_FILLED,
345
+ OrderStatus.FILLED,
346
+ OrderStatus.EXPIRED,
347
+ ],
348
+ OrderStatus.ACCEPTED: [
349
+ OrderStatus.ACCEPTED, # for modify order, pending -> accepted -> accepted -> (not allowed) -> pending
350
+ OrderStatus.PARTIALLY_FILLED,
351
+ OrderStatus.FILLED,
352
+ OrderStatus.CANCELING,
353
+ OrderStatus.CANCELED,
354
+ OrderStatus.EXPIRED,
355
+ OrderStatus.CANCEL_FAILED,
356
+ ],
357
+ OrderStatus.PARTIALLY_FILLED: [
358
+ OrderStatus.ACCEPTED, # for modify order, accepted -> partially filled -> accepted -> (not allowed) -> pending
359
+ OrderStatus.PARTIALLY_FILLED,
360
+ OrderStatus.FILLED,
361
+ OrderStatus.CANCELING,
362
+ OrderStatus.CANCELED,
363
+ OrderStatus.EXPIRED,
364
+ OrderStatus.CANCEL_FAILED,
365
+ ],
366
+ OrderStatus.CANCEL_FAILED: [
367
+ OrderStatus.CANCEL_FAILED,
368
+ OrderStatus.ACCEPTED,
369
+ OrderStatus.PARTIALLY_FILLED,
370
+ OrderStatus.CANCELING,
371
+ OrderStatus.CANCELED,
372
+ OrderStatus.FILLED,
373
+ OrderStatus.EXPIRED,
374
+ ],
375
+ OrderStatus.FILLED: [],
376
+ OrderStatus.CANCELED: [],
377
+ OrderStatus.EXPIRED: [],
378
+ OrderStatus.FAILED: [],
379
+ }
380
+
381
+
382
+ class DataType(Enum):
383
+ BOOKL1 = "bookl1"
384
+ BOOKL2 = "bookl2"
385
+ TRADE = "trade"
386
+ KLINE = "kline"
387
+ VOLUME_KLINE = "volume_kline"
388
+ MARK_PRICE = "mark_price"
389
+ FUNDING_RATE = "funding_rate"
390
+ INDEX_PRICE = "index_price"
391
+
392
+
393
+ class StorageType(Enum):
394
+ SQLITE = "sqlite"
395
+ POSTGRESQL = "postgresql"
396
+ MEMORY = "memory"
397
+
398
+
399
+ class ParamBackend(Enum):
400
+ MEMORY = "memory"
401
+ REDIS = "redis"
402
+
403
+
404
+ class RateLimiter:
405
+ pass
406
+
407
+
408
+ class ConfigType(TypedDict):
409
+ apiKey: str
410
+ secret: str
411
+ exchange_id: str
412
+ sandbox: bool
413
+ password: NotRequired[str]
File without changes