hyperquant 0.93__py3-none-any.whl → 0.94__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.
@@ -0,0 +1,508 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import time
5
+ from typing import TYPE_CHECKING, Any, Iterable, Literal, Sequence
6
+
7
+ from pybotters.store import DataStore, DataStoreCollection
8
+
9
+ if TYPE_CHECKING:
10
+ from pybotters.typedefs import Item
11
+ from pybotters.ws import ClientWebSocketResponse
12
+
13
+
14
+ def _maybe_to_dict(payload: Any) -> Any:
15
+ """Convert pydantic models to dict, keeping plain dict/list untouched."""
16
+ if payload is None:
17
+ return None
18
+ if hasattr(payload, "to_dict"):
19
+ return payload.to_dict()
20
+ if hasattr(payload, "model_dump"):
21
+ return payload.model_dump()
22
+ return payload
23
+
24
+
25
+ class Book(DataStore):
26
+ """Order book snapshots sourced from Lighter websocket feeds."""
27
+
28
+ _KEYS = ["s", "S", "p"]
29
+
30
+ def _init(self) -> None:
31
+ self.limit: int | None = None
32
+ self.id_to_symbol: dict[str, str] = {} # broker设置
33
+ self._last_update: float = 0.0
34
+ self._state: dict[str, dict[str, dict[float, float]]] = {}
35
+ self._visible: dict[str, dict[str, dict[float, dict[str, Any]]]] = {}
36
+
37
+ @staticmethod
38
+ def _market_id_from_channel(channel: str | None) -> str | None:
39
+ if not channel:
40
+ return None
41
+ if ":" in channel:
42
+ return channel.split(":", 1)[1]
43
+ if "/" in channel:
44
+ return channel.split("/", 1)[1]
45
+ return channel
46
+
47
+
48
+ @staticmethod
49
+ def _make_entry(symbol: str, side: Literal["a", "b"], price: float, size: float) -> dict[str, Any]:
50
+ return {
51
+ "s": symbol,
52
+ "S": side,
53
+ "p": f"{price}",
54
+ "q": f"{abs(size)}",
55
+ }
56
+
57
+ def _on_message(self, msg: dict[str, Any]) -> None:
58
+ msg_type = msg.get("type")
59
+ if msg_type not in {"subscribed/order_book", "update/order_book"}:
60
+ return
61
+
62
+ market_id = self._market_id_from_channel(msg.get("channel"))
63
+ if market_id is None:
64
+ return
65
+
66
+ order_book = msg.get("order_book")
67
+ if not isinstance(order_book, dict):
68
+ return
69
+
70
+ state = self._state.setdefault(market_id, {"ask": {}, "bid": {}})
71
+ visible = self._visible.setdefault(market_id, {"a": {}, "b": {}})
72
+
73
+ symbol = self.id_to_symbol.get(market_id)
74
+ if symbol is None:
75
+ symbol = market_id
76
+
77
+ for side_name, updates_data in (("ask", order_book.get("asks")), ("bid", order_book.get("bids"))):
78
+ side_state = state[side_name]
79
+ if not updates_data:
80
+ continue
81
+ for level in updates_data:
82
+ price = level.get("price")
83
+ size = level.get("size") or level.get("remaining_base_amount") or level.get("base_amount")
84
+ if price is None or size is None:
85
+ continue
86
+ try:
87
+ price_val = float(price)
88
+ size_val = float(size)
89
+ except (TypeError, ValueError):
90
+ continue
91
+ if size_val <= 0:
92
+ side_state.pop(price_val, None)
93
+ else:
94
+ side_state[price_val] = size_val
95
+
96
+ def build_visible(side_name: Literal["ask", "bid"]) -> dict[float, dict[str, Any]]:
97
+ side_state = state[side_name]
98
+ reverse = side_name == "bid"
99
+ sorted_levels = sorted(side_state.items(), reverse=reverse)
100
+ if self.limit is not None:
101
+ sorted_levels = sorted_levels[: self.limit]
102
+ entry_side = "a" if side_name == "ask" else "b"
103
+ return {
104
+ price: self._make_entry(symbol, entry_side, price, size)
105
+ for price, size in sorted_levels
106
+ }
107
+
108
+ new_visible = {
109
+ "a": build_visible("ask"),
110
+ "b": build_visible("bid"),
111
+ }
112
+
113
+ removals: list[dict[str, Any]] = []
114
+ updates: list[dict[str, Any]] = []
115
+
116
+ for side_key in ("a", "b"):
117
+ prev_side = visible[side_key]
118
+ next_side = new_visible[side_key]
119
+
120
+ for price, entry in prev_side.items():
121
+ if price not in next_side:
122
+ removals.append({k: entry[k] for k in self._KEYS})
123
+
124
+ for price, entry in next_side.items():
125
+ prev_entry = prev_side.get(price)
126
+ if prev_entry is None or prev_entry.get("q") != entry.get("q"):
127
+ updates.append(entry)
128
+
129
+ if removals:
130
+ self._delete(removals)
131
+ if updates:
132
+ self._update(updates)
133
+
134
+ self._visible[market_id] = new_visible
135
+ self._last_update = time.time()
136
+
137
+ def sorted(self, query: Item | None = None, limit: int | None = None) -> dict[str, list[Item]]:
138
+ return self._sorted(
139
+ item_key="S",
140
+ item_asc_key="a",
141
+ item_desc_key="b",
142
+ sort_key="p",
143
+ query=query,
144
+ limit=limit,
145
+ )
146
+
147
+ @property
148
+ def last_update(self) -> float:
149
+ return self._last_update
150
+
151
+
152
+
153
+
154
+ class Detail(DataStore):
155
+ """Market metadata."""
156
+
157
+ _KEYS = ["symbol"]
158
+
159
+ def _onresponse(self, data: Any) -> None:
160
+ payload = _maybe_to_dict(data) or {}
161
+ order_books = payload.get("order_books") or payload.get("order_book_details") or []
162
+
163
+ if isinstance(order_books, dict):
164
+ order_books = list(order_books.values())
165
+
166
+ normalized: list[dict[str, Any]] = []
167
+ for entry in order_books or []:
168
+ if not isinstance(entry, dict):
169
+ continue
170
+ market_id = entry.get("market_id") or entry.get("id")
171
+ symbol = entry.get("symbol")
172
+ if market_id is None and symbol is None:
173
+ continue
174
+ record = dict(entry)
175
+ if market_id is None and symbol is not None:
176
+ record["market_id"] = symbol
177
+ normalized.append(record)
178
+
179
+ self._clear()
180
+ if normalized:
181
+ self._insert(normalized)
182
+
183
+
184
+ class Orders(DataStore):
185
+ """Active orders."""
186
+
187
+ _KEYS = ["order_id"]
188
+
189
+ def _init(self) -> None:
190
+ self.id_to_symbol: dict[str, str] = {} # broker设置
191
+
192
+ @staticmethod
193
+ def _normalize(entry: dict[str, Any]) -> dict[str, Any] | None:
194
+ order_id = entry.get("order_id") or entry.get("orderId")
195
+ if order_id is None:
196
+ return None
197
+ normalized = dict(entry)
198
+ normalized["order_id"] = str(order_id)
199
+ return normalized
200
+
201
+ def _onresponse(self, data: Any) -> None:
202
+ payload = _maybe_to_dict(data) or {}
203
+ orders = payload.get("orders") or []
204
+ items: list[dict[str, Any]] = []
205
+ for entry in orders:
206
+ if not isinstance(entry, dict):
207
+ continue
208
+ normalized = self._normalize(entry)
209
+ if self.id_to_symbol:
210
+ market_id = entry.get("market_index")
211
+ if market_id is not None:
212
+ symbol = self.id_to_symbol.get(str(market_id))
213
+ if symbol is not None and normalized is not None:
214
+ normalized["symbol"] = symbol
215
+
216
+ if normalized:
217
+ items.append(normalized)
218
+
219
+ self._clear()
220
+ if items:
221
+ self._insert(items)
222
+
223
+
224
+
225
+
226
+
227
+ class Accounts(DataStore):
228
+ """Account level balances and metadata."""
229
+
230
+ _KEYS = ["account_index"]
231
+
232
+ def _normalize_account(self, entry: dict[str, Any]) -> dict[str, Any] | None:
233
+ account_index = entry.get("account_index") or entry.get("index")
234
+ if account_index is None:
235
+ return None
236
+ normalized = dict(entry)
237
+ normalized["account_index"] = account_index
238
+ normalized.pop("positions", None)
239
+ return normalized
240
+
241
+ def _on_accounts(self, accounts: Iterable[dict[str, Any]]) -> None:
242
+ normalized: list[dict[str, Any]] = []
243
+ for account in accounts:
244
+ if not isinstance(account, dict):
245
+ continue
246
+ record = self._normalize_account(account)
247
+ if record:
248
+ normalized.append(record)
249
+ if not normalized:
250
+ return
251
+ keys = [{"account_index": record["account_index"]} for record in normalized]
252
+ self._delete(keys)
253
+ self._insert(normalized)
254
+
255
+ def _onresponse(self, data: Any) -> None:
256
+ payload = _maybe_to_dict(data) or {}
257
+ accounts = payload.get("accounts") or []
258
+ self._on_accounts(accounts)
259
+
260
+ def _on_message(self, msg: dict[str, Any]) -> None:
261
+ account = msg.get("account")
262
+ if not isinstance(account, dict):
263
+ return
264
+ self._on_accounts([account])
265
+
266
+
267
+ class Positions(DataStore):
268
+ """Per-market positions grouped by account."""
269
+
270
+ _KEYS = ["account_index", "market_id"]
271
+
272
+ def _init(self) -> None:
273
+ self.id_to_symbol: dict[str, str] = {} # broker设置
274
+
275
+ @staticmethod
276
+ def _normalize(
277
+ account_index: Any,
278
+ position: dict[str, Any],
279
+ ) -> dict[str, Any] | None:
280
+ market_id = position.get("market_id")
281
+ if account_index is None or market_id is None:
282
+ return None
283
+ normalized = dict(position)
284
+ normalized["account_index"] = account_index
285
+ normalized["market_id"] = market_id
286
+ return normalized
287
+
288
+ def _update_positions(
289
+ self,
290
+ account_index: Any,
291
+ positions: Sequence[dict[str, Any]] | None,
292
+ ) -> None:
293
+ if positions is None:
294
+ return
295
+ items: list[dict[str, Any]] = []
296
+ for position in positions:
297
+ if not isinstance(position, dict):
298
+ continue
299
+ record = self._normalize(account_index, position)
300
+ if record:
301
+ items.append(record)
302
+ if not items:
303
+ return
304
+ keys = [{"account_index": item["account_index"], "market_id": item["market_id"]} for item in items]
305
+ self._delete(keys)
306
+ self._insert(items)
307
+
308
+ def _onresponse(self, data: Any) -> None:
309
+ payload = _maybe_to_dict(data) or {}
310
+ accounts = payload.get("accounts") or []
311
+ for account in accounts:
312
+ if not isinstance(account, dict):
313
+ continue
314
+ account_index = account.get("account_index") or account.get("index")
315
+ positions = account.get("positions")
316
+ self._update_positions(account_index, positions)
317
+
318
+ def _on_message(self, msg: dict[str, Any]) -> None:
319
+ account = msg.get("account")
320
+ if not isinstance(account, dict):
321
+ return
322
+ account_index = account.get("account_index") or account.get("index")
323
+ positions = account.get("positions")
324
+ self._update_positions(account_index, positions)
325
+
326
+
327
+ class LighterDataStore(DataStoreCollection):
328
+ """Data store collection for the Lighter exchange."""
329
+
330
+ def _init(self) -> None:
331
+ self._create("book", datastore_class=Book)
332
+ self._create("detail", datastore_class=Detail)
333
+ self._create("orders", datastore_class=Orders)
334
+ self._create("accounts", datastore_class=Accounts)
335
+ self._create("positions", datastore_class=Positions)
336
+
337
+ def set_id_to_symbol(self, id_to_symbol: dict[str, str]) -> None:
338
+ self.id_to_symbol = id_to_symbol
339
+ self.book.id_to_symbol = self.id_to_symbol
340
+ self.orders.id_to_symbol = self.id_to_symbol
341
+ self.positions.id_to_symbol = self.id_to_symbol
342
+
343
+ def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
344
+
345
+ msg_type = msg.get("type")
346
+ if msg_type == "ping":
347
+ asyncio.create_task(ws.send_json({"type": "pong"}))
348
+ return
349
+
350
+ if isinstance(msg, dict):
351
+ msg_type = msg.get("type")
352
+ if msg_type in {"subscribed/order_book", "update/order_book"}:
353
+ self.book._on_message(msg)
354
+ elif msg_type in {"subscribed/account_all", "update/account_all"}:
355
+ self.accounts._on_message(msg)
356
+ self.positions._on_message(msg)
357
+
358
+
359
+ @property
360
+ def book(self) -> Book:
361
+ """
362
+ Lighter 深度快照。
363
+
364
+ .. code:: json
365
+
366
+ {
367
+ "s": "BTC-USD",
368
+ "S": "a", # \"a\"=ask / \"b\"=bid
369
+ "p": "50250.5",
370
+ "q": "0.37"
371
+ }
372
+ """
373
+ return self._get("book")
374
+
375
+ @property
376
+ def detail(self) -> Detail:
377
+ """
378
+ `lighter.models.OrderBookDetail` 元数据。
379
+
380
+ .. code:: json
381
+
382
+ [
383
+ {
384
+ "symbol": "DOLO",
385
+ "market_id": 75,
386
+ "status": "active",
387
+ "taker_fee": "0.0000",
388
+ "maker_fee": "0.0000",
389
+ "liquidation_fee": "1.0000",
390
+ "min_base_amount": "30.0",
391
+ "min_quote_amount": "10.000000",
392
+ "supported_size_decimals": 1,
393
+ "supported_price_decimals": 5,
394
+ "supported_quote_decimals": 6,
395
+ "order_quote_limit": ""
396
+ }
397
+ ]
398
+ """
399
+ return self._get("detail")
400
+
401
+ @property
402
+ def orders(self) -> Orders:
403
+ """
404
+ 活动订单(`lighter.models.Order`)。
405
+
406
+ .. code:: json
407
+
408
+ {
409
+ "order_index": 21673573193817727,
410
+ "client_order_index": 0,
411
+ "order_id": "21673573193817727",
412
+ "client_order_id": "0",
413
+ "market_index": 75,
414
+ "symbol": "DOLO",
415
+ "owner_account_index": 311464,
416
+ "initial_base_amount": "146.7",
417
+ "price": "0.07500",
418
+ "nonce": 281474963807871,
419
+ "remaining_base_amount": "146.7",
420
+ "is_ask": false,
421
+ "base_size": 1467,
422
+ "base_price": 7500,
423
+ "filled_base_amount": "0.0",
424
+ "filled_quote_amount": "0.000000",
425
+ "side": "",
426
+ "type": "limit",
427
+ "time_in_force": "good-till-time",
428
+ "reduce_only": false,
429
+ "trigger_price": "0.00000",
430
+ "order_expiry": 1764082202799,
431
+ "status": "open",
432
+ "trigger_status": "na",
433
+ "trigger_time": 0,
434
+ "parent_order_index": 0,
435
+ "parent_order_id": "0",
436
+ "to_trigger_order_id_0": "0",
437
+ "to_trigger_order_id_1": "0",
438
+ "to_cancel_order_id_0": "0",
439
+ "block_height": 75734444,
440
+ "timestamp": 1761663003,
441
+ "created_at": 1761663003,
442
+ "updated_at": 1761663003
443
+ }
444
+ """
445
+ return self._get("orders")
446
+
447
+
448
+ @property
449
+ def accounts(self) -> Accounts:
450
+ """
451
+ 账户概览(`lighter.models.DetailedAccount`)。
452
+
453
+ .. code:: json
454
+ [
455
+ {
456
+ "code": 0,
457
+ "account_type": 0,
458
+ "index": 311464,
459
+ "l1_address": "0x5B3f0AdDfaf4c1d8729e266b22093545EFaE6c0e",
460
+ "cancel_all_time": 0,
461
+ "total_order_count": 1,
462
+ "total_isolated_order_count": 0,
463
+ "pending_order_count": 0,
464
+ "available_balance": "30.000000",
465
+ "status": 0,
466
+ "collateral": "30.000000",
467
+ "account_index": 311464,
468
+ "name": "",
469
+ "description": "",
470
+ "can_invite": false,
471
+ "referral_points_percentage": "",
472
+ "total_asset_value": "30",
473
+ "cross_asset_value": "30",
474
+ "shares": []
475
+ }
476
+ ]
477
+ """
478
+ return self._get("accounts")
479
+
480
+ @property
481
+ def positions(self) -> Positions:
482
+ """
483
+ 账户持仓(`lighter.models.AccountPosition`)。
484
+
485
+ .. code:: json
486
+
487
+ [
488
+ {
489
+ "market_id": 75,
490
+ "symbol": "DOLO",
491
+ "initial_margin_fraction": "33.33",
492
+ "open_order_count": 1,
493
+ "pending_order_count": 0,
494
+ "position_tied_order_count": 0,
495
+ "sign": 1,
496
+ "position": "129.8",
497
+ "avg_entry_price": "0.08476",
498
+ "position_value": "10.969398",
499
+ "unrealized_pnl": "-0.032450",
500
+ "realized_pnl": "0.000000",
501
+ "liquidation_price": "0",
502
+ "margin_mode": 0,
503
+ "allocated_margin": "0.000000",
504
+ "account_index": 311464
505
+ }
506
+ ]
507
+ """
508
+ return self._get("positions")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.93
3
+ Version: 0.94
4
4
  Summary: A minimal yet hyper-efficient backtesting framework for quantitative trading
5
5
  Project-URL: Homepage, https://github.com/yourusername/hyperquant
6
6
  Project-URL: Issues, https://github.com/yourusername/hyperquant/issues
@@ -18,6 +18,7 @@ Requires-Dist: colorama>=0.4.6
18
18
  Requires-Dist: cryptography>=44.0.2
19
19
  Requires-Dist: curl-cffi>=0.13.0
20
20
  Requires-Dist: duckdb>=1.2.2
21
+ Requires-Dist: lighter-sdk
21
22
  Requires-Dist: numpy>=1.21.0
22
23
  Requires-Dist: pandas>=2.2.3
23
24
  Requires-Dist: pybotters>=1.9.1
@@ -4,13 +4,15 @@ hyperquant/db.py,sha256=i2TjkCbmH4Uxo7UTDvOYBfy973gLcGexdzuT_YcSeIE,6678
4
4
  hyperquant/draw.py,sha256=up_lQ3pHeVLoNOyh9vPjgNwjD0M-6_IetSGviQUgjhY,54624
5
5
  hyperquant/logkit.py,sha256=nUo7nx5eONvK39GOhWwS41zNRL756P2J7-5xGzwXnTY,8462
6
6
  hyperquant/notikit.py,sha256=x5yAZ_tAvLQRXcRbcg-VabCaN45LUhvlTZnUqkIqfAA,3596
7
- hyperquant/broker/auth.py,sha256=xNZEQP0LRRV9BkT2uXBJ-vFfeahUnRVq1bjIT6YbQu8,10089
7
+ hyperquant/broker/auth.py,sha256=C8B5-x8Qcaeafm4ZwPCVFR7GRURmHC3CE4_vdg00Qgw,12139
8
8
  hyperquant/broker/bitget.py,sha256=X_S0LKZ7FZAEb6oEMr1vdGP1fondzK74BhmNTpRDSEA,9488
9
+ hyperquant/broker/bitmart.py,sha256=ish5SXenO1f1MCDnuRbkatPApHvUiyer8fmSGylIk2A,15914
9
10
  hyperquant/broker/coinup.py,sha256=eOr8BTRXiTb5tCU2FDmvBdXXgqiwVmCbP5pdeA1ORJ8,20390
10
11
  hyperquant/broker/coinw.py,sha256=SnJU0vASh77rfcpMGWaIfTblQSjQk3vjlW_4juYdbcs,17214
11
12
  hyperquant/broker/edgex.py,sha256=TqUO2KRPLN_UaxvtLL6HnA9dAQXC1sGxOfqTHd6W5k8,18378
12
13
  hyperquant/broker/hyperliquid.py,sha256=7MxbI9OyIBcImDelPJu-8Nd53WXjxPB5TwE6gsjHbto,23252
13
14
  hyperquant/broker/lbank.py,sha256=98M5wmSoeHwbBYMA3rh25zqLb6fQKVaEmwqALF5nOvY,22181
15
+ hyperquant/broker/lighter.py,sha256=lW4LgEJHqbiUZPu2BE1aKz_JBIW94KTKnCkD9vuHt-Q,16695
14
16
  hyperquant/broker/ourbit.py,sha256=NUcDSIttf-HGWzoW1uBTrGLPHlkuemMjYCm91MigTno,18228
15
17
  hyperquant/broker/ws.py,sha256=NS71Do-62mtVrGcyNE-AtHJkDecsSxdz_KU1yNBr_BQ,4079
16
18
  hyperquant/broker/lib/edgex_sign.py,sha256=lLUCmY8HHRLfLKyGrlTJYaBlSHPsIMWg3EZnQJKcmyk,95785
@@ -18,16 +20,18 @@ hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb8
18
20
  hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
19
21
  hyperquant/broker/lib/util.py,sha256=iMU1qF0CHj5zzlIMEQGwjz-qtEVosEe7slXOCuB7Rcw,566
20
22
  hyperquant/broker/models/bitget.py,sha256=0RwDY75KrJb-c-oYoMxbqxWfsILe-n_Npojz4UFUq7c,11389
23
+ hyperquant/broker/models/bitmart.py,sha256=7DgnuDlD-ZneDpyilW-V-mEwVSniwBTre1xlFaJiK6o,16578
21
24
  hyperquant/broker/models/coinup.py,sha256=X_ngB2_sgTOdfAZqTyeWvCN03j-0_inZ6ugZKW6hR7k,11173
22
25
  hyperquant/broker/models/coinw.py,sha256=LvLMVP7i-qkkTK1ubw8eBkMK2RQmFoKPxdKqmC4IToY,22157
23
26
  hyperquant/broker/models/edgex.py,sha256=vPAkceal44cjTYKQ_0BoNAskOpmkno_Yo1KxgMLPc6Y,33954
24
27
  hyperquant/broker/models/hyperliquid.py,sha256=c4r5739ibZfnk69RxPjQl902AVuUOwT8RNvKsMtwXBY,9459
25
28
  hyperquant/broker/models/lbank.py,sha256=vHkNKxIMzpoC_EwcZnEOPOupizF92yGWi9GKxvYYFUQ,19181
29
+ hyperquant/broker/models/lighter.py,sha256=RpqyMPrXbs4_OY9WSDep4T8pDhxDGaFQ8vdVmLZnfBg,16732
26
30
  hyperquant/broker/models/ourbit.py,sha256=xMcbuCEXd3XOpPBq0RYF2zpTFNnxPtuNJZCexMZVZ1k,41965
27
31
  hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw,533
28
32
  hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
29
33
  hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
30
34
  hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
31
- hyperquant-0.93.dist-info/METADATA,sha256=L9HIZUQL-xV3tZTqgvHD9p1Vin2e7M-XPuimVVmt3F8,4382
32
- hyperquant-0.93.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
- hyperquant-0.93.dist-info/RECORD,,
35
+ hyperquant-0.94.dist-info/METADATA,sha256=UofH3N5NelHoWQnfrfAdEnAbtwesIsXerHl_ExVoMSU,4409
36
+ hyperquant-0.94.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ hyperquant-0.94.dist-info/RECORD,,