hyperquant 1.21__py3-none-any.whl → 1.24__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.

Potentially problematic release.


This version of hyperquant might be problematic. Click here for more details.

@@ -98,29 +98,11 @@ class Book(DataStore):
98
98
  self._last_update = ms_t
99
99
 
100
100
  def _on_message_api(self, msg: dict[str, Any]) -> None:
101
- # {
102
- # "data": {
103
- # "symbol": "BTCUSDT",
104
- # "asks": [
105
- # {
106
- # "price": "70294.4",
107
- # "vol": "455"
108
- # }
109
- # ],
110
- # "bids": [
111
- # {
112
- # "price": "70293.9",
113
- # "vol": "1856"
114
- # }
115
- # ],
116
- # "ms_t": 1730399750402
117
- # },
118
- # "group": "futures/depthAll20:BTCUSDT@200ms"
119
- # }
120
101
  data = msg.get("data")
121
102
  if not isinstance(data, dict):
122
103
  return
123
- symbol = data.get("symbol")
104
+ # Some callers embed symbol at top-level; prefer msg["symbol"] when present
105
+ symbol = msg.get("symbol") or data.get("symbol")
124
106
  asks = data.get("asks") or []
125
107
  bids = data.get("bids") or []
126
108
  if self.limit:
@@ -128,12 +110,19 @@ class Book(DataStore):
128
110
  bids = bids[: self.limit]
129
111
 
130
112
  self._find_and_delete({'s': symbol})
113
+ # OpenAPI order book arrays are typically [price, size, timestamp]
114
+ def _normalize_level(level: Any) -> tuple[str, str]:
115
+ if isinstance(level, dict):
116
+ return str(level.get("price", "0")), str(level.get("vol", "0"))
117
+ if isinstance(level, (list, tuple)) and len(level) >= 2:
118
+ return str(level[0]), str(level[1])
119
+ return "0", "0"
120
+
131
121
  self._update([
132
122
  self._make_entry(
133
123
  symbol,
134
124
  "a",
135
- entry.get("price", '0'),
136
- entry.get("vol", '0'),
125
+ *_normalize_level(entry),
137
126
  )
138
127
  for entry in asks
139
128
  ])
@@ -141,11 +130,11 @@ class Book(DataStore):
141
130
  self._make_entry(
142
131
  symbol,
143
132
  "b",
144
- entry.get("price", '0'),
145
- entry.get("vol", '0'),
133
+ *_normalize_level(entry),
146
134
  )
147
135
  for entry in bids
148
136
  ])
137
+
149
138
 
150
139
  def sorted(self, query: Item | None = None, limit: int | None = None) -> dict[str, list[Item]]:
151
140
  return self._sorted(
@@ -325,7 +314,7 @@ class BitmartDataStore(DataStoreCollection):
325
314
  self._create("positions", datastore_class=Positions)
326
315
  self._create("balances", datastore_class=Balances)
327
316
  self._create("ticker", datastore_class=Ticker)
328
-
317
+
329
318
  def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
330
319
  if isinstance(msg, dict):
331
320
  group = msg.get("group")
@@ -324,6 +324,282 @@ class Positions(DataStore):
324
324
  self._update_positions(account_index, positions)
325
325
 
326
326
 
327
+ class Klines(DataStore):
328
+ """Candlestick/Kline store keyed by (symbol, resolution, timestamp).
329
+
330
+ - Maintains a list of active resolutions in ``_res_list`` (populated by REST updates).
331
+ - Updates candles in real-time by aggregating trade websocket messages.
332
+ """
333
+
334
+ _KEYS = ["symbol", "resolution", "timestamp"]
335
+
336
+ def _init(self) -> None:
337
+ self.id_to_symbol: dict[str, str] = {}
338
+ self._current_symbol: str | None = None
339
+ self._res_list: list[str] = []
340
+ # Track last processed trade_id to deduplicate snapshot trades after reconnect
341
+ self._last_trade_id_by_market: dict[str, int] = {}
342
+ self._last_trade_id_by_symbol: dict[str, int] = {}
343
+
344
+ @staticmethod
345
+ def _resolution_to_ms(resolution: str) -> int | None:
346
+ try:
347
+ res = resolution.strip().lower()
348
+ except Exception:
349
+ return None
350
+ # Common forms: 1m, 5m, 1h, 1d; also allow pure digits => seconds
351
+ unit = res[-1]
352
+ num_part = res[:-1] if unit in {"s", "m", "h", "d", "w"} else res
353
+ try:
354
+ n = int(num_part)
355
+ except Exception:
356
+ return None
357
+ if unit == "s":
358
+ return n * 1000
359
+ if unit == "m" or unit not in {"s", "h", "d", "w"}: # default minutes if no unit
360
+ return n * 60 * 1000
361
+ if unit == "h":
362
+ return n * 60 * 60 * 1000
363
+ if unit == "d":
364
+ return n * 24 * 60 * 60 * 1000
365
+ if unit == "w":
366
+ return n * 7 * 24 * 60 * 60 * 1000
367
+ return None
368
+
369
+ @staticmethod
370
+ def _market_id_from_channel(channel: str | None) -> str | None:
371
+ if not channel:
372
+ return None
373
+ if ":" in channel:
374
+ return channel.split(":", 1)[1]
375
+ if "/" in channel:
376
+ return channel.split("/", 1)[1]
377
+ return channel
378
+
379
+ def _compose_item(
380
+ self,
381
+ *,
382
+ symbol: str,
383
+ resolution: str,
384
+ ts: int,
385
+ price: float,
386
+ size: float,
387
+ last_trade_id: int | None,
388
+ open_price: float | None = None,
389
+ ) -> dict[str, Any]:
390
+ return {
391
+ "symbol": symbol,
392
+ "resolution": resolution,
393
+ "timestamp": ts,
394
+ "open": price if open_price is None else float(open_price),
395
+ "high": price,
396
+ "low": price,
397
+ "close": price,
398
+ "volume0": abs(size),
399
+ "volume1": abs(size) * price,
400
+ "last_trade_id": last_trade_id or 0,
401
+ }
402
+
403
+ def _ensure_backfill(self, *, symbol: str, resolution: str, new_bucket_ts: int) -> None:
404
+ """Backfill missing empty bars up to (but not including) new_bucket_ts.
405
+
406
+ Uses the last known close as O/H/L/C for synthetic bars and zero volume.
407
+ """
408
+ step = self._resolution_to_ms(resolution)
409
+ if not step:
410
+ return
411
+ # find the last existing bar before new_bucket_ts
412
+ rows = self.find({"symbol": symbol, "resolution": resolution})
413
+ prev = None
414
+ prev_ts = None
415
+ for r in rows:
416
+ try:
417
+ ts = int(r.get("timestamp"))
418
+ except Exception:
419
+ continue
420
+ if ts < new_bucket_ts and (prev_ts is None or ts > prev_ts):
421
+ prev = r
422
+ prev_ts = ts
423
+ if prev is None or prev_ts is None:
424
+ return
425
+ expected = prev_ts + step
426
+ while expected < new_bucket_ts:
427
+ prev_close = float(prev.get("close"))
428
+ fill_item = {
429
+ "symbol": symbol,
430
+ "resolution": resolution,
431
+ "timestamp": expected,
432
+ "open": prev_close,
433
+ "high": prev_close,
434
+ "low": prev_close,
435
+ "close": prev_close,
436
+ "volume0": 0.0,
437
+ "volume1": 0.0,
438
+ "last_trade_id": int(prev.get("last_trade_id", 0)) if prev.get("last_trade_id") is not None else 0,
439
+ }
440
+ self._insert([fill_item])
441
+ prev = fill_item
442
+ expected += step
443
+
444
+ def _merge_trade(self, *, symbol: str, trade_ts_ms: int, price: float, size: float, last_trade_id: int | None) -> None:
445
+ # Iterate active resolutions
446
+ for res in list(self._res_list):
447
+ interval_ms = self._resolution_to_ms(res)
448
+ if not interval_ms:
449
+ continue
450
+ bucket_ts = (trade_ts_ms // interval_ms) * interval_ms
451
+ # Upsert logic
452
+ existing = self.get({"symbol": symbol, "resolution": res, "timestamp": bucket_ts})
453
+ if existing is None:
454
+ # backfill any missing empty bars before creating a new bucket
455
+ self._ensure_backfill(symbol=symbol, resolution=res, new_bucket_ts=bucket_ts)
456
+ # open should be previous bar's close if exists; if none, fall back to current price
457
+ prev = None
458
+ rows = self.find({"symbol": symbol, "resolution": res})
459
+ prev_ts = None
460
+ for r in rows:
461
+ try:
462
+ ts = int(r.get("timestamp"))
463
+ except Exception:
464
+ continue
465
+ if ts < bucket_ts and (prev_ts is None or ts > prev_ts):
466
+ prev = r
467
+ prev_ts = ts
468
+ open_px = float(prev.get("close")) if prev is not None else price
469
+ self._insert([
470
+ self._compose_item(
471
+ symbol=symbol,
472
+ resolution=res,
473
+ ts=bucket_ts,
474
+ price=price,
475
+ size=size,
476
+ last_trade_id=last_trade_id,
477
+ open_price=open_px,
478
+ )
479
+ ])
480
+ continue
481
+ # merge into existing
482
+ updated = dict(existing)
483
+ o = float(updated.get("open", price))
484
+ h = float(updated.get("high", price))
485
+ l = float(updated.get("low", price))
486
+ c = float(updated.get("close", price))
487
+ v0 = float(updated.get("volume0", 0.0))
488
+ v1 = float(updated.get("volume1", 0.0))
489
+ p = float(price)
490
+ s = abs(float(size))
491
+ updated["open"] = o
492
+ updated["high"] = max(h, p)
493
+ updated["low"] = min(l, p)
494
+ updated["close"] = p
495
+ updated["volume0"] = v0 + s
496
+ updated["volume1"] = v1 + s * p
497
+ if last_trade_id is not None:
498
+ try:
499
+ updated["last_trade_id"] = max(int(last_trade_id), int(updated.get("last_trade_id", 0)))
500
+ except Exception:
501
+ updated["last_trade_id"] = int(last_trade_id)
502
+ self._update([updated])
503
+
504
+ def _onresponse(self, data: Any, *, symbol: str | None = None, resolution: str | None = None) -> None:
505
+ payload = _maybe_to_dict(data) or {}
506
+ candlesticks = payload.get("candlesticks") or []
507
+ res = payload.get("resolution") or resolution
508
+ if res not in self._res_list and res is not None:
509
+ self._res_list.append(res)
510
+
511
+ sym = symbol or self._current_symbol
512
+
513
+ # Sort incoming bars by timestamp to backfill in order
514
+ items: list[dict[str, Any]] = []
515
+ for c in sorted((candlesticks or []), key=lambda x: x.get("timestamp", 0)):
516
+ if not isinstance(c, dict):
517
+ continue
518
+ entry = dict(c)
519
+ if sym is not None:
520
+ entry["symbol"] = sym
521
+ if res is not None:
522
+ entry["resolution"] = res
523
+ items.append(entry)
524
+
525
+ # Insert or update per bar; backfill gaps before inserting new bars
526
+ for entry in items:
527
+ sym_i = entry.get("symbol")
528
+ res_i = entry.get("resolution")
529
+ ts_i = entry.get("timestamp")
530
+ if sym_i is None or res_i is None or ts_i is None:
531
+ continue
532
+ if self.get({"symbol": sym_i, "resolution": res_i, "timestamp": ts_i}) is None:
533
+ self._ensure_backfill(symbol=sym_i, resolution=res_i, new_bucket_ts=int(ts_i))
534
+ self._insert([entry])
535
+ else:
536
+ self._update([entry])
537
+
538
+ # Update last_trade_id baseline (by symbol) from REST bars if available
539
+ if sym is not None:
540
+ max_tid = 0
541
+ for e in items:
542
+ try:
543
+ tid = int(e.get("last_trade_id", 0))
544
+ except Exception:
545
+ tid = 0
546
+ if tid > max_tid:
547
+ max_tid = tid
548
+ if max_tid:
549
+ prev = self._last_trade_id_by_symbol.get(sym, 0)
550
+ if max_tid > prev:
551
+ self._last_trade_id_by_symbol[sym] = max_tid
552
+
553
+ def _on_message(self, msg: dict[str, Any]) -> None:
554
+ msg_type = msg.get("type")
555
+ if msg_type not in {"subscribed/trade", "update/trade"}:
556
+ return
557
+ market_id = self._market_id_from_channel(msg.get("channel"))
558
+ if market_id is None:
559
+ return
560
+ market_id_str = str(market_id)
561
+ symbol = self.id_to_symbol.get(market_id_str) or market_id_str
562
+ trades = msg.get("trades") or []
563
+ # Baseline last trade_id from market and symbol
564
+ base_last_tid = max(
565
+ self._last_trade_id_by_market.get(market_id_str, 0),
566
+ self._last_trade_id_by_symbol.get(symbol, 0),
567
+ )
568
+ # Process in ascending trade_id order for stability
569
+ try:
570
+ trades_sorted = sorted(trades, key=lambda x: int(x.get("trade_id", 0)))
571
+ except Exception:
572
+ trades_sorted = trades
573
+
574
+ last_tid = base_last_tid
575
+ for t in trades_sorted:
576
+ if not isinstance(t, dict):
577
+ continue
578
+ ts = t.get("timestamp")
579
+ price = t.get("price")
580
+ size = t.get("size")
581
+ trade_id = t.get("trade_id")
582
+ try:
583
+ ts = int(ts)
584
+ p = float(price)
585
+ s = float(size)
586
+ tid = int(trade_id) if trade_id is not None else 0
587
+ except Exception:
588
+ continue
589
+ # Skip stale or duplicate snapshot trades
590
+ if tid and last_tid and tid <= last_tid:
591
+ continue
592
+ self._merge_trade(symbol=symbol, trade_ts_ms=ts, price=p, size=s, last_trade_id=tid)
593
+ if tid > last_tid:
594
+ last_tid = tid
595
+
596
+ # Persist last processed trade_id for this market
597
+ if last_tid and last_tid > base_last_tid:
598
+ self._last_trade_id_by_market[market_id_str] = last_tid
599
+
600
+
601
+
602
+
327
603
  class LighterDataStore(DataStoreCollection):
328
604
  """Data store collection for the Lighter exchange."""
329
605
 
@@ -333,12 +609,14 @@ class LighterDataStore(DataStoreCollection):
333
609
  self._create("orders", datastore_class=Orders)
334
610
  self._create("accounts", datastore_class=Accounts)
335
611
  self._create("positions", datastore_class=Positions)
612
+ self._create("klines", datastore_class=Klines)
336
613
 
337
614
  def set_id_to_symbol(self, id_to_symbol: dict[str, str]) -> None:
338
615
  self.id_to_symbol = id_to_symbol
339
616
  self.book.id_to_symbol = self.id_to_symbol
340
617
  self.orders.id_to_symbol = self.id_to_symbol
341
618
  self.positions.id_to_symbol = self.id_to_symbol
619
+ self.klines.id_to_symbol = self.id_to_symbol
342
620
 
343
621
  def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
344
622
 
@@ -354,6 +632,8 @@ class LighterDataStore(DataStoreCollection):
354
632
  elif msg_type in {"subscribed/account_all", "update/account_all"}:
355
633
  self.accounts._on_message(msg)
356
634
  self.positions._on_message(msg)
635
+ elif msg_type in {"subscribed/trade", "update/trade"}:
636
+ self.klines._on_message(msg)
357
637
 
358
638
 
359
639
  @property
@@ -506,3 +786,24 @@ class LighterDataStore(DataStoreCollection):
506
786
  ]
507
787
  """
508
788
  return self._get("positions")
789
+
790
+ @property
791
+ def klines(self) -> "Klines":
792
+ """
793
+ K线/蜡烛图数据(`lighter.models.Candlesticks` -> `lighter.models.Candlestick`)。
794
+
795
+ .. code:: json
796
+
797
+ {
798
+ "symbol": "BTC",
799
+ "timestamp": 1730612700000,
800
+ "open": 68970.5,
801
+ "high": 69012.3,
802
+ "low": 68890.0,
803
+ "close": 68995.1,
804
+ "volume0": 12.34,
805
+ "volume1": 850000.0,
806
+ "resolution": "1m"
807
+ }
808
+ """
809
+ return self._get("klines")
hyperquant/broker/ws.py CHANGED
@@ -37,12 +37,19 @@ class Heartbeat:
37
37
  while not ws.closed:
38
38
  await ws.send_json({"event": "ping"})
39
39
  await asyncio.sleep(3.0)
40
+
41
+ @staticmethod
42
+ async def lighter(ws: ClientWebSocketResponse):
43
+ while not ws.closed:
44
+ await ws.send_json({"type":"ping"})
45
+ await asyncio.sleep(3)
40
46
 
41
47
  pybotters.ws.HeartbeatHosts.items['futures.ourbit.com'] = Heartbeat.ourbit
42
48
  pybotters.ws.HeartbeatHosts.items['www.ourbit.com'] = Heartbeat.ourbit_spot
43
49
  pybotters.ws.HeartbeatHosts.items['quote.edgex.exchange'] = Heartbeat.edgex
44
50
  pybotters.ws.HeartbeatHosts.items['uuws.rerrkvifj.com'] = Heartbeat.lbank
45
51
  pybotters.ws.HeartbeatHosts.items['ws.futurescw.com'] = Heartbeat.coinw
52
+ pybotters.ws.HeartbeatHosts.items['mainnet.zklighter.elliot.ai'] = Heartbeat.lighter
46
53
 
47
54
  class WssAuth:
48
55
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 1.21
3
+ Version: 1.24
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
@@ -16,14 +16,12 @@ Requires-Python: >=3.11
16
16
  Requires-Dist: aiohttp>=3.10.4
17
17
  Requires-Dist: colorama>=0.4.6
18
18
  Requires-Dist: cryptography>=44.0.2
19
- Requires-Dist: curl-cffi>=0.13.0
20
19
  Requires-Dist: duckdb>=1.2.2
21
- Requires-Dist: lighter-sdk
20
+ Requires-Dist: lighter-sdk>=0.1.4
22
21
  Requires-Dist: numpy>=1.21.0
23
22
  Requires-Dist: pandas>=2.2.3
24
23
  Requires-Dist: pybotters>=1.9.1
25
24
  Requires-Dist: pyecharts>=2.0.8
26
- Requires-Dist: rnet==3.0.0rc10
27
25
  Description-Content-Type: text/markdown
28
26
 
29
27
  # minquant
@@ -4,34 +4,32 @@ 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=C8B5-x8Qcaeafm4ZwPCVFR7GRURmHC3CE4_vdg00Qgw,12139
7
+ hyperquant/broker/auth.py,sha256=3Ws_2iaF2NTCVIbkLzL_0ae3y7f4vgj9zmozvGaUXnk,16029
8
8
  hyperquant/broker/bitget.py,sha256=X_S0LKZ7FZAEb6oEMr1vdGP1fondzK74BhmNTpRDSEA,9488
9
- hyperquant/broker/bitmart.py,sha256=2Yd_Jzn0XYzl3rYN1rmNuwy7Wy74T3bBQ-ue7RvIbMU,22206
10
- hyperquant/broker/coinup.py,sha256=eOr8BTRXiTb5tCU2FDmvBdXXgqiwVmCbP5pdeA1ORJ8,20390
9
+ hyperquant/broker/bitmart.py,sha256=7j_8TU3Dxjj5HCNX7CbSO3nPZcQH1t31A9UOv5tTbg0,25974
11
10
  hyperquant/broker/coinw.py,sha256=SnJU0vASh77rfcpMGWaIfTblQSjQk3vjlW_4juYdbcs,17214
12
11
  hyperquant/broker/edgex.py,sha256=TqUO2KRPLN_UaxvtLL6HnA9dAQXC1sGxOfqTHd6W5k8,18378
13
12
  hyperquant/broker/hyperliquid.py,sha256=7MxbI9OyIBcImDelPJu-8Nd53WXjxPB5TwE6gsjHbto,23252
14
13
  hyperquant/broker/lbank.py,sha256=98M5wmSoeHwbBYMA3rh25zqLb6fQKVaEmwqALF5nOvY,22181
15
- hyperquant/broker/lighter.py,sha256=lW4LgEJHqbiUZPu2BE1aKz_JBIW94KTKnCkD9vuHt-Q,16695
14
+ hyperquant/broker/lighter.py,sha256=mgkdHjXXmOAhVlU8uZJ28fcPqzHM0fnjJXXYMTD4wJs,21918
16
15
  hyperquant/broker/ourbit.py,sha256=NUcDSIttf-HGWzoW1uBTrGLPHlkuemMjYCm91MigTno,18228
17
- hyperquant/broker/ws.py,sha256=NS71Do-62mtVrGcyNE-AtHJkDecsSxdz_KU1yNBr_BQ,4079
16
+ hyperquant/broker/ws.py,sha256=AzyFAHIDF4exxwm_IAEV6ihftwAlu19al8Vla4ygk-A,4354
18
17
  hyperquant/broker/lib/edgex_sign.py,sha256=lLUCmY8HHRLfLKyGrlTJYaBlSHPsIMWg3EZnQJKcmyk,95785
19
18
  hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb84D8,8296
20
19
  hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
21
20
  hyperquant/broker/lib/util.py,sha256=iMU1qF0CHj5zzlIMEQGwjz-qtEVosEe7slXOCuB7Rcw,566
22
21
  hyperquant/broker/models/bitget.py,sha256=0RwDY75KrJb-c-oYoMxbqxWfsILe-n_Npojz4UFUq7c,11389
23
- hyperquant/broker/models/bitmart.py,sha256=hPxSFLmsJif9wm4nTln5G_zCbsoRnM1BF9fnfciZIHo,22061
24
- hyperquant/broker/models/coinup.py,sha256=X_ngB2_sgTOdfAZqTyeWvCN03j-0_inZ6ugZKW6hR7k,11173
22
+ hyperquant/broker/models/bitmart.py,sha256=O9RnU-XBeR9SzicG15jzuzK5oy2kMrRJAyZSqC8DXUw,21938
25
23
  hyperquant/broker/models/coinw.py,sha256=LvLMVP7i-qkkTK1ubw8eBkMK2RQmFoKPxdKqmC4IToY,22157
26
24
  hyperquant/broker/models/edgex.py,sha256=vPAkceal44cjTYKQ_0BoNAskOpmkno_Yo1KxgMLPc6Y,33954
27
25
  hyperquant/broker/models/hyperliquid.py,sha256=c4r5739ibZfnk69RxPjQl902AVuUOwT8RNvKsMtwXBY,9459
28
26
  hyperquant/broker/models/lbank.py,sha256=vHkNKxIMzpoC_EwcZnEOPOupizF92yGWi9GKxvYYFUQ,19181
29
- hyperquant/broker/models/lighter.py,sha256=RpqyMPrXbs4_OY9WSDep4T8pDhxDGaFQ8vdVmLZnfBg,16732
27
+ hyperquant/broker/models/lighter.py,sha256=vpo1xm3S_FEV3GOlbN9_qEhrgj8qXM7jP8jstBP2S0Y,28217
30
28
  hyperquant/broker/models/ourbit.py,sha256=xMcbuCEXd3XOpPBq0RYF2zpTFNnxPtuNJZCexMZVZ1k,41965
31
29
  hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw,533
32
30
  hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
33
31
  hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
34
32
  hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
35
- hyperquant-1.21.dist-info/METADATA,sha256=1ZJFW9uGSQFzXQTBy74LLbaay4lUmc_pTOzSOugj_nU,4409
36
- hyperquant-1.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- hyperquant-1.21.dist-info/RECORD,,
33
+ hyperquant-1.24.dist-info/METADATA,sha256=B2YHKuabMfGfMSx-OVQnCphVOPL5K94nuG5m5PENKAs,4352
34
+ hyperquant-1.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
+ hyperquant-1.24.dist-info/RECORD,,