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,592 @@
1
+ import msgspec
2
+ import picows
3
+ import eth_account
4
+ from eth_account.signers.local import LocalAccount
5
+ from eth_account.messages import encode_typed_data
6
+ from Crypto.Hash import keccak
7
+ from typing import Any, Callable, List, Dict, Literal
8
+
9
+ from walrasquant.base import WSClient
10
+ from walrasquant.core.entity import TaskManager
11
+ from walrasquant.exchange.hyperliquid.schema import HyperLiquidWsMessageGeneral
12
+ from walrasquant.core.nautilius_core import LiveClock
13
+ from walrasquant.exchange.hyperliquid.constants import (
14
+ HyperLiquidAccountType,
15
+ HyperLiquidKlineInterval,
16
+ HyperLiquidRateLimiter,
17
+ HyperLiquidOrderRequest,
18
+ HyperLiquidOrderCancelRequest,
19
+ HyperLiquidCloidCancelRequest,
20
+ )
21
+
22
+
23
+ def user_api_pong_callback(self, frame: picows.WSFrame) -> bool:
24
+ if frame.msg_type != picows.WSMsgType.TEXT:
25
+ return False
26
+
27
+ raw = frame.get_payload_as_bytes()
28
+ try:
29
+ message = msgspec.json.decode(raw, type=HyperLiquidWsMessageGeneral)
30
+ return message.channel == "pong"
31
+ except msgspec.DecodeError:
32
+ return False
33
+
34
+
35
+ class HyperLiquidWSClient(WSClient):
36
+ _api_key: str | None
37
+
38
+ def __init__(
39
+ self,
40
+ account_type: HyperLiquidAccountType,
41
+ handler: Callable[..., Any],
42
+ task_manager: TaskManager,
43
+ clock: LiveClock,
44
+ api_key: str | None = None, # in HyperLiquid, api_key is the wallet address
45
+ custom_url: str | None = None,
46
+ max_subscriptions_per_client: int | None = None,
47
+ max_clients: int | None = None,
48
+ ):
49
+ self._account_type = account_type
50
+
51
+ if custom_url:
52
+ url = custom_url
53
+ else:
54
+ url = account_type.ws_url
55
+
56
+ self._api_key = api_key
57
+
58
+ super().__init__(
59
+ url=url,
60
+ handler=handler,
61
+ task_manager=task_manager,
62
+ clock=clock,
63
+ ping_idle_timeout=30,
64
+ ping_reply_timeout=5,
65
+ specific_ping_msg=msgspec.json.encode({"method": "ping"}),
66
+ auto_ping_strategy="ping_when_idle",
67
+ user_pong_callback=user_api_pong_callback,
68
+ max_subscriptions_per_client=max_subscriptions_per_client,
69
+ max_clients=max_clients,
70
+ )
71
+
72
+ def _send_msg(
73
+ self,
74
+ msg: Dict[str, str],
75
+ method: str = "subscribe",
76
+ client_id: int | None = None,
77
+ ):
78
+ self.send(
79
+ {
80
+ "method": method,
81
+ "subscription": msg,
82
+ },
83
+ client_id=client_id,
84
+ )
85
+
86
+ def _subscribe(self, msgs: List[Dict[str, str]]):
87
+ assigned = self._register_subscriptions(msgs)
88
+ if not assigned:
89
+ return
90
+ for client_id, client_msgs in assigned.items():
91
+ for msg in client_msgs:
92
+ format_msg = ".".join(msg.values())
93
+ self._log.debug(f"Subscribing to {format_msg}...")
94
+ if self._is_client_connected(client_id):
95
+ for msg in client_msgs:
96
+ self._send_msg(msg, client_id=client_id)
97
+
98
+ def _unsubscribe(self, msgs: List[Dict[str, str]]):
99
+ removed = self._unregister_subscriptions(msgs)
100
+ if not removed:
101
+ return
102
+ for client_id, client_msgs in removed.items():
103
+ for msg in client_msgs:
104
+ format_msg = ".".join(msg.values())
105
+ self._log.debug(f"Unsubscribing from {format_msg}...")
106
+ self._send_msg(msg, method="unsubscribe", client_id=client_id)
107
+
108
+ async def _resubscribe_for_client(
109
+ self, client_id: int, subscriptions: List[Dict[str, str]]
110
+ ):
111
+ if not subscriptions:
112
+ return
113
+ for msg in subscriptions:
114
+ self._send_msg(msg, client_id=client_id)
115
+
116
+ def subscribe_trades(self, symbols: List[str]):
117
+ msgs = [{"type": "trades", "coin": symbol} for symbol in symbols]
118
+ self._subscribe(msgs)
119
+
120
+ def unsubscribe_trades(self, symbols: List[str]):
121
+ msgs = [{"type": "trades", "coin": symbol} for symbol in symbols]
122
+ self._unsubscribe(msgs)
123
+
124
+ def subscribe_bbo(self, symbols: List[str]):
125
+ msgs = [{"type": "bbo", "coin": symbol} for symbol in symbols]
126
+ self._subscribe(msgs)
127
+
128
+ def unsubscribe_bbo(self, symbols: List[str]):
129
+ msgs = [{"type": "bbo", "coin": symbol} for symbol in symbols]
130
+ self._unsubscribe(msgs)
131
+
132
+ def subscribe_l2book(self, symbols: List[str]):
133
+ msgs = [{"type": "l2Book", "coin": symbol} for symbol in symbols]
134
+ self._subscribe(msgs)
135
+
136
+ def unsubscribe_l2book(self, symbols: List[str]):
137
+ msgs = [{"type": "l2Book", "coin": symbol} for symbol in symbols]
138
+ self._unsubscribe(msgs)
139
+
140
+ def subscribe_candle(self, symbols: List[str], interval: HyperLiquidKlineInterval):
141
+ msgs = [
142
+ {"type": "candle", "coin": symbol, "interval": interval.value}
143
+ for symbol in symbols
144
+ ]
145
+ self._subscribe(msgs)
146
+
147
+ def unsubscribe_candle(
148
+ self, symbols: List[str], interval: HyperLiquidKlineInterval
149
+ ):
150
+ msgs = [
151
+ {"type": "candle", "coin": symbol, "interval": interval.value}
152
+ for symbol in symbols
153
+ ]
154
+ self._unsubscribe(msgs)
155
+
156
+ def subscribe_order_updates(self):
157
+ if self._api_key is None:
158
+ raise ValueError("api_key is required for user subscriptions")
159
+ msg = {
160
+ "type": "orderUpdates",
161
+ "user": self._api_key,
162
+ }
163
+ self._subscribe([msg])
164
+
165
+ def subscribe_user_events(self):
166
+ if self._api_key is None:
167
+ raise ValueError("api_key is required for user subscriptions")
168
+ msg = {
169
+ "type": "userEvents",
170
+ "user": self._api_key,
171
+ }
172
+ self._subscribe([msg])
173
+
174
+ def subscribe_user_fills(self):
175
+ if self._api_key is None:
176
+ raise ValueError("api_key is required for user subscriptions")
177
+ msg = {
178
+ "type": "userFills",
179
+ "user": self._api_key,
180
+ }
181
+ self._subscribe([msg])
182
+
183
+ def subscribe_user_fundings(self):
184
+ if self._api_key is None:
185
+ raise ValueError("api_key is required for user subscriptions")
186
+ msg = {
187
+ "type": "userFundings",
188
+ "user": self._api_key,
189
+ }
190
+ self._subscribe([msg])
191
+
192
+ def subscribe_user_non_funding_ledger_updates(self):
193
+ if self._api_key is None:
194
+ raise ValueError("api_key is required for user subscriptions")
195
+ msg = {
196
+ "type": "userNonFundingLedgerUpdates",
197
+ "user": self._api_key,
198
+ }
199
+ self._subscribe([msg])
200
+
201
+ def subscribe_web_data2(self):
202
+ if self._api_key is None:
203
+ raise ValueError("api_key is required for user subscriptions")
204
+ msg = {
205
+ "type": "webData2",
206
+ "user": self._api_key,
207
+ }
208
+ self._subscribe([msg])
209
+
210
+ def subscribe_notification(self):
211
+ if self._api_key is None:
212
+ raise ValueError("api_key is required for user subscriptions")
213
+ msg = {
214
+ "type": "notification",
215
+ "user": self._api_key,
216
+ }
217
+ self._subscribe([msg])
218
+
219
+
220
+ class HyperLiquidWSApiClient(WSClient):
221
+ """WebSocket API client for HyperLiquid order operations"""
222
+
223
+ def __init__(
224
+ self,
225
+ account_type: HyperLiquidAccountType,
226
+ api_key: str,
227
+ secret: str,
228
+ handler: Callable[..., Any],
229
+ task_manager: TaskManager,
230
+ clock: LiveClock,
231
+ enable_rate_limit: bool = True,
232
+ ):
233
+ self._api_key = api_key
234
+ self._secret = secret
235
+ self._account_type = account_type
236
+ self._testnet = account_type.is_testnet
237
+
238
+ if secret:
239
+ self._eth_account: LocalAccount = eth_account.Account.from_key(secret)
240
+
241
+ url = account_type.ws_url
242
+ self._limiter = HyperLiquidRateLimiter(enable_rate_limit=enable_rate_limit)
243
+
244
+ # UUID to integer mapping for HyperLiquid API
245
+ self._oid_to_id: Dict[str, int] = {}
246
+ self._id_to_oid: Dict[int, str] = {}
247
+ self._next_id = 1
248
+
249
+ super().__init__(
250
+ url=url,
251
+ handler=handler,
252
+ task_manager=task_manager,
253
+ clock=clock,
254
+ ping_idle_timeout=30,
255
+ ping_reply_timeout=5,
256
+ specific_ping_msg=msgspec.json.encode({"method": "ping"}),
257
+ auto_ping_strategy="ping_when_idle",
258
+ user_pong_callback=user_api_pong_callback,
259
+ )
260
+
261
+ async def _resubscribe_for_client(self, client_id: int, subscriptions: List[Any]):
262
+ return
263
+
264
+ def _get_rate_limit_cost(self, length: int, cost: int = 1) -> int:
265
+ """Get rate limit cost for an operation
266
+
267
+ Please refer to https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/rate-limits-and-user-limits
268
+ """
269
+ return cost + length // 40
270
+
271
+ def _oid_to_int(self, oid_str: str) -> int:
272
+ """Convert oid to integer for HyperLiquid API compatibility"""
273
+ if oid_str not in self._oid_to_id:
274
+ self._oid_to_id[oid_str] = self._next_id
275
+ self._id_to_oid[self._next_id] = oid_str
276
+ self._next_id += 1
277
+ return self._oid_to_id[oid_str]
278
+
279
+ def _int_to_oid(self, id_int: int) -> str:
280
+ """Convert integer back to oid"""
281
+ return self._id_to_oid.get(id_int, str(id_int))
282
+
283
+ def _construct_phantom_agent(self, hash_bytes: bytes) -> Dict[str, Any]:
284
+ """Construct phantom agent for signature"""
285
+ return {"source": "b" if self._testnet else "a", "connectionId": hash_bytes}
286
+
287
+ def _action_hash(
288
+ self, action: Dict[str, Any], nonce: int, vault_address: str | None = None
289
+ ) -> bytes:
290
+ """Generate action hash for signature"""
291
+ data = msgspec.msgpack.encode(action)
292
+ data += nonce.to_bytes(8, "big")
293
+ if vault_address is None:
294
+ data += b"\x00"
295
+ else:
296
+ data += b"\x01"
297
+ data += bytes.fromhex(
298
+ vault_address[2:] if vault_address.startswith("0x") else vault_address
299
+ )
300
+ return keccak.new(digest_bits=256, data=data).digest()
301
+
302
+ def _sign_l1_action(
303
+ self, action: Dict[str, Any], nonce: int, vault_address: str | None = None
304
+ ) -> Dict[str, Any]:
305
+ """Sign L1 action for authentication"""
306
+ hash_bytes = self._action_hash(action, nonce, vault_address)
307
+ phantom_agent = self._construct_phantom_agent(hash_bytes)
308
+ encoded_data = encode_typed_data(
309
+ full_message={
310
+ "domain": {
311
+ "chainId": 1337,
312
+ "name": "Exchange",
313
+ "verifyingContract": "0x0000000000000000000000000000000000000000",
314
+ "version": "1",
315
+ },
316
+ "types": {
317
+ "Agent": [
318
+ {"name": "source", "type": "string"},
319
+ {"name": "connectionId", "type": "bytes32"},
320
+ ],
321
+ "EIP712Domain": [
322
+ {"name": "name", "type": "string"},
323
+ {"name": "version", "type": "string"},
324
+ {"name": "chainId", "type": "uint256"},
325
+ {"name": "verifyingContract", "type": "address"},
326
+ ],
327
+ },
328
+ "primaryType": "Agent",
329
+ "message": phantom_agent,
330
+ }
331
+ )
332
+ signed = self._eth_account.sign_message(encoded_data)
333
+ return {
334
+ "r": f"{signed.r:#x}",
335
+ "s": f"{signed.s:#x}",
336
+ "v": signed.v,
337
+ }
338
+
339
+ def _submit(
340
+ self,
341
+ oid: str,
342
+ request_type: Literal["info", "action"],
343
+ payload: Dict[str, Any],
344
+ ):
345
+ """Submit request to HyperLiquid WebSocket API"""
346
+ message_id = self._oid_to_int(oid)
347
+ message = {
348
+ "method": "post",
349
+ "id": message_id,
350
+ "request": {"type": request_type, "payload": payload},
351
+ }
352
+ self.send(message)
353
+
354
+ async def place_order(
355
+ self,
356
+ id: str,
357
+ orders: List[HyperLiquidOrderRequest],
358
+ grouping: Literal["na", "normalTpsl", "positionTpsl"] = "na",
359
+ ):
360
+ """Place orders via WebSocket"""
361
+ nonce = self._clock.timestamp_ms()
362
+ order_action = {
363
+ "type": "order",
364
+ "orders": orders,
365
+ "grouping": grouping,
366
+ }
367
+ signature = self._sign_l1_action(order_action, nonce, vault_address=None)
368
+
369
+ payload = {
370
+ "action": order_action,
371
+ "nonce": nonce,
372
+ "signature": signature,
373
+ }
374
+ cost = self._get_rate_limit_cost(length=len(orders), cost=1)
375
+ await self._limiter.exchange_limit(cost=cost)
376
+ self._submit(oid=id, request_type="action", payload=payload)
377
+
378
+ async def cancel_order(
379
+ self,
380
+ id: str,
381
+ cancels: List[HyperLiquidOrderCancelRequest],
382
+ ):
383
+ """Cancel orders via WebSocket"""
384
+ nonce = self._clock.timestamp_ms()
385
+ cancel_action = {
386
+ "type": "cancel",
387
+ "cancels": cancels,
388
+ }
389
+ signature = self._sign_l1_action(cancel_action, nonce, vault_address=None)
390
+
391
+ payload = {
392
+ "action": cancel_action,
393
+ "nonce": nonce,
394
+ "signature": signature,
395
+ }
396
+ # cost = self._get_rate_limit_cost(length=len(cancels), cost=1)
397
+ # await self._limiter.exchange_limit(cost=cost)
398
+ self._submit(oid=id, request_type="action", payload=payload)
399
+
400
+ async def cancel_orders_by_cloid(
401
+ self,
402
+ id: str,
403
+ cancels: List[HyperLiquidCloidCancelRequest],
404
+ ):
405
+ nounce = self._clock.timestamp_ms()
406
+ orderAction = {
407
+ "type": "cancelByCloid",
408
+ "cancels": cancels,
409
+ }
410
+ signature = self._sign_l1_action(orderAction, nounce, vault_address=None)
411
+ payload = {
412
+ "action": orderAction,
413
+ "nonce": nounce,
414
+ "signature": signature,
415
+ }
416
+ # cost = self._get_rate_limit_cost(length=len(cancels), cost=1)
417
+ # await self._limiter.exchange_limit(cost=cost)
418
+ self._submit(oid=id, request_type="action", payload=payload)
419
+
420
+
421
+ # import asyncio # noqa
422
+
423
+
424
+ # async def main():
425
+ # from walrasquant.constants import settings
426
+ # from walrasquant.exchange.hyperliquid.constants import oid_to_cloid_hex
427
+ # from walrasquant.core.entity import TaskManager, OidGen
428
+ # from walrasquant.core.nautilius_core import LiveClock, setup_nexus_core
429
+
430
+ # HYPER_API_KEY = settings.HYPER.TESTNET.API_KEY
431
+ # HYPER_SECRET = settings.HYPER.TESTNET.SECRET
432
+
433
+ # log_guard, _, clock = setup_nexus_core( # noqa
434
+ # trader_id="hyper-test",
435
+ # level_stdout="DEBUG",
436
+ # )
437
+
438
+ # oidgen = OidGen(clock)
439
+
440
+ # task_manager = TaskManager(
441
+ # loop=asyncio.get_event_loop(),
442
+ # )
443
+
444
+ # ws_api_client = HyperLiquidWSApiClient(
445
+ # account_type=HyperLiquidAccountType.TESTNET,
446
+ # api_key=HYPER_API_KEY,
447
+ # secret=HYPER_SECRET,
448
+ # handler=lambda msg: print(msg),
449
+ # task_manager=task_manager,
450
+ # clock=LiveClock(),
451
+ # enable_rate_limit=True,
452
+ # )
453
+ # oid = oid_to_cloid_hex(oidgen.oid)
454
+ # await ws_api_client.connect()
455
+ # await ws_api_client.place_order(
456
+ # id=oid,
457
+ # orders=[
458
+ # {
459
+ # "a": 4,
460
+ # "b": True,
461
+ # "p": "4500",
462
+ # "s": "0.003",
463
+ # "r": False,
464
+ # "t": {
465
+ # "limit": {
466
+ # "tif": "Gtc"
467
+ # }
468
+ # },
469
+ # "c": oid,
470
+ # }
471
+ # ]
472
+ # )
473
+ # await ws_api_client.cancel_order(
474
+ # id=UUID4().value, cancels=[{"a": 4, "o": 38086199157}]
475
+ # )
476
+ # await ws_api_client.cancel_orders_by_cloid(
477
+ # id="0x0000000000000000f3f0b3863b5d5b86",
478
+ # cancels=[{"asset": 4, "cloid": "0x0000000000000000f3f0b3863b5d5b86"}],
479
+ # )
480
+ # await task_manager.wait()
481
+
482
+
483
+ # place order success
484
+ # {
485
+ # "channel": "post",
486
+ # "data": {
487
+ # "id": 1,
488
+ # "response": {
489
+ # "type": "action",
490
+ # "payload": {
491
+ # "status": "ok",
492
+ # "response": {"type": "cancel", "data": {"statuses": ["success"]}},
493
+ # },
494
+ # },
495
+ # },
496
+ # }
497
+ # # cancel order success
498
+ # {
499
+ # "channel": "post",
500
+ # "data": {
501
+ # "id": 1,
502
+ # "response": {
503
+ # "type": "action",
504
+ # "payload": {
505
+ # "status": "ok",
506
+ # "response": {
507
+ # "type": "order",
508
+ # "data": {
509
+ # "statuses": [
510
+ # {"error": "Order must have minimum value of $10. asset=4"}
511
+ # ]
512
+ # },
513
+ # },
514
+ # },
515
+ # },
516
+ # },
517
+ # }
518
+ # # place order failed
519
+ # {
520
+ # "channel": "post",
521
+ # "data": {
522
+ # "id": 1,
523
+ # "response": {
524
+ # "type": "action",
525
+ # "payload": {
526
+ # "status": "ok",
527
+ # "response": {
528
+ # "type": "order",
529
+ # "data": {
530
+ # "statuses": [
531
+ # {"error": "Order must have minimum value of $10. asset=4"}
532
+ # ]
533
+ # },
534
+ # },
535
+ # },
536
+ # },
537
+ # },
538
+ # }
539
+ # # place order success, you only need to get oid
540
+ # {
541
+ # "channel": "post",
542
+ # "data": {
543
+ # "id": 1,
544
+ # "response": {
545
+ # "type": "action",
546
+ # "payload": {
547
+ # "status": "ok",
548
+ # "response": {
549
+ # "type": "order",
550
+ # "data": {
551
+ # "statuses": [
552
+ # {
553
+ # "filled": {
554
+ # "totalSz": "0.003",
555
+ # "avgPx": "4432.9",
556
+ # "oid": 38086199157,
557
+ # }
558
+ # }
559
+ # ]
560
+ # },
561
+ # },
562
+ # },
563
+ # },
564
+ # },
565
+ # }
566
+ # # cancel order failed
567
+ # {
568
+ # "channel": "post",
569
+ # "data": {
570
+ # "id": 1,
571
+ # "response": {
572
+ # "type": "action",
573
+ # "payload": {
574
+ # "status": "ok",
575
+ # "response": {
576
+ # "type": "cancel",
577
+ # "data": {
578
+ # "statuses": [
579
+ # {
580
+ # "error": "Order was never placed, already canceled, or filled. asset=4"
581
+ # }
582
+ # ]
583
+ # },
584
+ # },
585
+ # },
586
+ # },
587
+ # },
588
+ # }
589
+
590
+
591
+ # if __name__ == "__main__":
592
+ # asyncio.run(main())
@@ -0,0 +1,25 @@
1
+ from walrasquant.exchange.okx.constants import OkxAccountType
2
+ from walrasquant.exchange.okx.exchange import OkxExchangeManager
3
+ from walrasquant.exchange.okx.connector import OkxPublicConnector, OkxPrivateConnector
4
+ from walrasquant.exchange.okx.ems import OkxExecutionManagementSystem
5
+ from walrasquant.exchange.okx.oms import OkxOrderManagementSystem
6
+ from walrasquant.exchange.okx.factory import OkxFactory
7
+
8
+ # Auto-register factory on import
9
+ try:
10
+ from walrasquant.exchange.registry import register_factory
11
+
12
+ register_factory(OkxFactory())
13
+ except ImportError:
14
+ # Registry not available yet during bootstrap
15
+ pass
16
+
17
+ __all__ = [
18
+ "OkxAccountType",
19
+ "OkxExchangeManager",
20
+ "OkxPublicConnector",
21
+ "OkxPrivateConnector",
22
+ "OkxExecutionManagementSystem",
23
+ "OkxOrderManagementSystem",
24
+ "OkxFactory",
25
+ ]