hyperquant 1.35__tar.gz → 1.36__tar.gz

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 (44) hide show
  1. {hyperquant-1.35 → hyperquant-1.36}/PKG-INFO +1 -1
  2. {hyperquant-1.35 → hyperquant-1.36}/pyproject.toml +1 -1
  3. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/polymarket.py +179 -0
  4. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/polymarket.py +4 -3
  5. {hyperquant-1.35 → hyperquant-1.36}/uv.lock +1 -1
  6. {hyperquant-1.35 → hyperquant-1.36}/.gitignore +0 -0
  7. {hyperquant-1.35 → hyperquant-1.36}/README.md +0 -0
  8. {hyperquant-1.35 → hyperquant-1.36}/requirements-dev.lock +0 -0
  9. {hyperquant-1.35 → hyperquant-1.36}/requirements.lock +0 -0
  10. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/__init__.py +0 -0
  11. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/auth.py +0 -0
  12. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/bitget.py +0 -0
  13. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/bitmart.py +0 -0
  14. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/coinw.py +0 -0
  15. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/deepcoin.py +0 -0
  16. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/edgex.py +0 -0
  17. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/hyperliquid.py +0 -0
  18. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/lbank.py +0 -0
  19. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  20. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/lib/hpstore.py +0 -0
  21. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  22. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/lib/util.py +0 -0
  23. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/lighter.py +0 -0
  24. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/apexpro.py +0 -0
  25. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/bitget.py +0 -0
  26. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/bitmart.py +0 -0
  27. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/coinw.py +0 -0
  28. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/deepcoin.py +0 -0
  29. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/edgex.py +0 -0
  30. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  31. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/lbank.py +0 -0
  32. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/lighter.py +0 -0
  33. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/models/ourbit.py +0 -0
  34. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/ourbit.py +0 -0
  35. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/broker/ws.py +0 -0
  36. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/core.py +0 -0
  37. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/datavison/_util.py +0 -0
  38. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/datavison/binance.py +0 -0
  39. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/datavison/coinglass.py +0 -0
  40. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/datavison/okx.py +0 -0
  41. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/db.py +0 -0
  42. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/draw.py +0 -0
  43. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/logkit.py +0 -0
  44. {hyperquant-1.35 → hyperquant-1.36}/src/hyperquant/notikit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 1.35
3
+ Version: 1.36
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "1.35"
3
+ version = "1.36"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ from dataclasses import dataclass, field
5
+ from heapq import heappop, heappush
4
6
  from typing import TYPE_CHECKING, Any, Iterable
5
7
 
6
8
  from pybotters.store import DataStore, DataStoreCollection
@@ -332,6 +334,163 @@ class Book(DataStore):
332
334
  limit=limit,
333
335
  )
334
336
 
337
+ @dataclass
338
+ class _SideBook:
339
+ is_ask: bool
340
+ levels: dict[float, tuple[str, str]] = field(default_factory=dict)
341
+ heap: list[tuple[float, float]] = field(default_factory=list)
342
+
343
+ def clear(self) -> None:
344
+ self.levels.clear()
345
+ self.heap.clear()
346
+
347
+ def update_levels(
348
+ self, updates: Iterable[dict[str, Any]] | None, *, snapshot: bool
349
+ ) -> None:
350
+ if updates is None:
351
+ return
352
+
353
+ if snapshot:
354
+ self.clear()
355
+
356
+ for entry in updates:
357
+ price, size = self._extract(entry)
358
+ price_val = self._to_float(price)
359
+ size_val = self._to_float(size)
360
+ if price_val is None or size_val is None:
361
+ continue
362
+
363
+ if size_val <= 0:
364
+ self.levels.pop(price_val, None)
365
+ continue
366
+
367
+ self.levels[price_val] = (str(price), str(size))
368
+ priority = price_val if self.is_ask else -price_val
369
+ heappush(self.heap, (priority, price_val))
370
+
371
+ def best(self) -> tuple[str, str] | None:
372
+ while self.heap:
373
+ _, price = self.heap[0]
374
+ level = self.levels.get(price)
375
+ if level is not None:
376
+ return level
377
+ heappop(self.heap)
378
+ return None
379
+
380
+ @staticmethod
381
+ def _extract(entry: Any) -> tuple[Any, Any]:
382
+ if isinstance(entry, dict):
383
+ price = entry.get("price", entry.get("p"))
384
+ size = entry.get("size", entry.get("q"))
385
+ return price, size
386
+ if isinstance(entry, (list, tuple)) and len(entry) >= 2:
387
+ return entry[0], entry[1]
388
+ return None, None
389
+
390
+ @staticmethod
391
+ def _to_float(value: Any) -> float | None:
392
+ try:
393
+ return float(value)
394
+ except (TypeError, ValueError):
395
+ return None
396
+
397
+
398
+ class BBO(DataStore):
399
+ _KEYS = ["s", "S"]
400
+
401
+ def _init(self) -> None:
402
+ self._book: dict[str, dict[str, _SideBook]] = {}
403
+ self.id_to_alias: dict[str, str] = {}
404
+
405
+ def update_aliases(self, mapping: dict[str, str]) -> None:
406
+ if not mapping:
407
+ return
408
+ self.id_to_alias.update(mapping)
409
+
410
+ def _alias(self, asset_id: str | None) -> tuple[str, str | None] | tuple[None, None]:
411
+ if asset_id is None:
412
+ return None, None
413
+ alias = self.id_to_alias.get(asset_id)
414
+ return asset_id, alias
415
+
416
+ def _side(self, symbol: str, side: str) -> _SideBook:
417
+ symbol_book = self._book.setdefault(symbol, {})
418
+ side_book = symbol_book.get(side)
419
+ if side_book is None:
420
+ side_book = _SideBook(is_ask=(side == "a"))
421
+ symbol_book[side] = side_book
422
+ return side_book
423
+
424
+ def _sync_side(
425
+ self, symbol: str, side: str, best: tuple[str, str] | None, alias: str | None
426
+ ) -> None:
427
+ key = {"s": symbol, "S": side}
428
+ current = self.get(key)
429
+
430
+ if best is None:
431
+ if current:
432
+ self._delete([key])
433
+ return
434
+
435
+ price, size = best
436
+ payload = {"s": symbol, "S": side, "p": price, "q": size}
437
+ if alias is not None:
438
+ payload["alias"] = alias
439
+
440
+ if current:
441
+ cur_price = current.get("p")
442
+ cur_size = current.get("q")
443
+ cur_alias = current.get("alias")
444
+
445
+ if cur_price == price:
446
+ # price unchanged -> only update quantities / alias changes
447
+ if cur_size != size or (alias is not None and cur_alias != alias):
448
+ self._update([payload])
449
+ return
450
+
451
+ # price changed -> delete old then insert new level to trigger change watchers
452
+ self._delete([key])
453
+
454
+ self._insert([payload])
455
+
456
+ def _from_price_changes(self, msg: dict[str, Any]) -> None:
457
+ price_changes = msg.get("price_changes") or []
458
+ touched: dict[str, str | None] = {}
459
+ for change in price_changes:
460
+ asset_id = change.get("asset_id") or change.get("token_id")
461
+ symbol, alias = self._alias(asset_id)
462
+ if symbol is None:
463
+ continue
464
+ side = "b" if str(change.get("side") or "").upper() == "BUY" else "a"
465
+ side_book = self._side(symbol, side)
466
+ side_book.update_levels([change], snapshot=False)
467
+ touched[symbol] = alias
468
+
469
+ for symbol, alias in touched.items():
470
+ asks = self._side(symbol, "a")
471
+ bids = self._side(symbol, "b")
472
+ self._sync_side(symbol, "a", asks.best(), alias)
473
+ self._sync_side(symbol, "b", bids.best(), alias)
474
+
475
+ def _from_snapshot(self, msg: dict[str, Any]) -> None:
476
+ asset_id = msg.get("asset_id") or msg.get("token_id")
477
+ symbol, alias = self._alias(asset_id)
478
+ if symbol is None:
479
+ return
480
+ asks = self._side(symbol, "a")
481
+ bids = self._side(symbol, "b")
482
+ asks.update_levels(msg.get("asks"), snapshot=True)
483
+ bids.update_levels(msg.get("bids"), snapshot=True)
484
+ self._sync_side(symbol, "a", asks.best(), alias)
485
+ self._sync_side(symbol, "b", bids.best(), alias)
486
+
487
+ def _on_message(self, msg: dict[str, Any]) -> None:
488
+ msg_type = (msg.get("event_type") or msg.get("type") or "").lower()
489
+ if msg_type == "book":
490
+ self._from_snapshot(msg)
491
+ elif msg_type == "price_change":
492
+ self._from_price_changes(msg)
493
+
335
494
 
336
495
  class Detail(DataStore):
337
496
  """Market metadata keyed by Polymarket token id."""
@@ -446,6 +605,7 @@ class PolymarketDataStore(DataStoreCollection):
446
605
 
447
606
  def _init(self) -> None:
448
607
  self._create("book", datastore_class=Book)
608
+ self._create("bbo", datastore_class=BBO)
449
609
  self._create("detail", datastore_class=Detail)
450
610
  self._create("position", datastore_class=Position)
451
611
  self._create("order", datastore_class=Order)
@@ -627,6 +787,14 @@ class PolymarketDataStore(DataStoreCollection):
627
787
 
628
788
  return self._get("fill")
629
789
 
790
+ @property
791
+ def bbo(self) -> BBO:
792
+ """Best Bid and Offer DataStore
793
+ _key: s (asset_id), S (side)
794
+
795
+ """
796
+ return self._get("bbo")
797
+
630
798
  @property
631
799
  def trade(self) -> Trade:
632
800
  """
@@ -683,3 +851,14 @@ class PolymarketDataStore(DataStoreCollection):
683
851
  self.position.on_trade(m)
684
852
  elif msg_type == 'orders_matched':
685
853
  self.trade._on_message(m)
854
+
855
+ def onmessage_for_bbo(self, msg: Any, ws: ClientWebSocketResponse | None = None) -> None:
856
+ # 判定msg是否为list
857
+ lst_msg = msg if isinstance(msg, list) else [msg]
858
+ for m in lst_msg:
859
+ raw_type = m.get("event_type") or m.get("type")
860
+ if not raw_type:
861
+ continue
862
+ msg_type = str(raw_type).lower()
863
+ if msg_type in {"book", "price_change"}:
864
+ self.bbo._on_message(m)
@@ -223,12 +223,12 @@ class Polymarket:
223
223
  self.store.orders._on_response(orders)
224
224
 
225
225
 
226
-
227
226
 
228
227
  async def sub_books(
229
228
  self,
230
229
  token_ids: Sequence[str] | str,
231
230
  wsapp: pybotters.ws.WebSocketApp | None = None,
231
+ only_bbo: bool = False,
232
232
  ) -> pybotters.ws.WebSocketApp:
233
233
  """Subscribe to public order-book updates for the provided token ids."""
234
234
 
@@ -236,11 +236,12 @@ class Polymarket:
236
236
  payload = {"type": "market", "assets_ids": tokens}
237
237
  if wsapp:
238
238
  await wsapp.current_ws.send_json(payload)
239
-
239
+ hdrl_json = self.store.onmessage_for_bbo if only_bbo else self.store.onmessage
240
+
240
241
  self._ws_public = self.client.ws_connect(
241
242
  self.ws_public,
242
243
  send_json=payload,
243
- hdlr_json=self.store.onmessage,
244
+ hdlr_json=hdrl_json,
244
245
  )
245
246
  await self._ws_public._event.wait()
246
247
  return self._ws_public
@@ -945,7 +945,7 @@ wheels = [
945
945
 
946
946
  [[package]]
947
947
  name = "hyperquant"
948
- version = "1.34"
948
+ version = "1.35"
949
949
  source = { editable = "." }
950
950
  dependencies = [
951
951
  { name = "aiohttp" },
File without changes
File without changes
File without changes