hyperquant 1.3__tar.gz → 1.44__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.3 → hyperquant-1.44}/PKG-INFO +2 -2
  2. {hyperquant-1.3 → hyperquant-1.44}/pyproject.toml +2 -2
  3. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/auth.py +60 -2
  4. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/polymarket.py +397 -12
  5. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/polymarket.py +893 -53
  6. {hyperquant-1.3 → hyperquant-1.44}/uv.lock +2 -2
  7. {hyperquant-1.3 → hyperquant-1.44}/.gitignore +0 -0
  8. {hyperquant-1.3 → hyperquant-1.44}/README.md +0 -0
  9. {hyperquant-1.3 → hyperquant-1.44}/requirements-dev.lock +0 -0
  10. {hyperquant-1.3 → hyperquant-1.44}/requirements.lock +0 -0
  11. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/__init__.py +0 -0
  12. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/bitget.py +0 -0
  13. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/bitmart.py +0 -0
  14. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/coinw.py +0 -0
  15. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/deepcoin.py +0 -0
  16. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/edgex.py +0 -0
  17. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/hyperliquid.py +0 -0
  18. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/lbank.py +0 -0
  19. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  20. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/lib/hpstore.py +0 -0
  21. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  22. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/lib/util.py +0 -0
  23. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/lighter.py +0 -0
  24. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/apexpro.py +0 -0
  25. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/bitget.py +0 -0
  26. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/bitmart.py +0 -0
  27. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/coinw.py +0 -0
  28. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/deepcoin.py +0 -0
  29. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/edgex.py +0 -0
  30. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  31. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/lbank.py +0 -0
  32. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/lighter.py +0 -0
  33. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/models/ourbit.py +0 -0
  34. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/ourbit.py +0 -0
  35. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/broker/ws.py +0 -0
  36. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/core.py +0 -0
  37. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/datavison/_util.py +0 -0
  38. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/datavison/binance.py +0 -0
  39. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/datavison/coinglass.py +0 -0
  40. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/datavison/okx.py +0 -0
  41. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/db.py +0 -0
  42. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/draw.py +0 -0
  43. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/logkit.py +0 -0
  44. {hyperquant-1.3 → hyperquant-1.44}/src/hyperquant/notikit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 1.3
3
+ Version: 1.44
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
@@ -22,7 +22,7 @@ Requires-Dist: lighter-sdk
22
22
  Requires-Dist: numpy>=1.21.0
23
23
  Requires-Dist: pandas>=2.2.3
24
24
  Requires-Dist: py-clob-client>=0.28.0
25
- Requires-Dist: pybotters>=1.9.1
25
+ Requires-Dist: pybotters>=1.10
26
26
  Requires-Dist: pyecharts>=2.0.8
27
27
  Requires-Dist: python-dotenv>=1.2.1
28
28
  Requires-Dist: web3>=7.14.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "1.3"
3
+ version = "1.44"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -13,7 +13,7 @@ dependencies = [
13
13
  "cryptography>=44.0.2",
14
14
  "numpy>=1.21.0", # Added numpy as a new dependency
15
15
  "duckdb>=1.2.2",
16
- "pybotters>=1.9.1",
16
+ "pybotters>=1.10",
17
17
  "lighter-sdk",
18
18
  "eth-account>=0.10.0",
19
19
  "web3>=7.14.0",
@@ -499,9 +499,67 @@ class Auth:
499
499
  if not creds:
500
500
  return args
501
501
 
502
+ if isinstance(creds, tuple):
503
+ creds = list(creds)
504
+ session.__dict__["_apis"][api_name] = creds
505
+
502
506
  private_key = creds[0] if len(creds) > 0 and creds[0] else None
503
- chain_id = session.__dict__.get("_polymarket_chain_id", 137)
504
- api_meta = session.__dict__.get("_polymarket_api_creds", {})
507
+ if private_key:
508
+ pk_str = str(private_key)
509
+ if not pk_str.startswith("0x"):
510
+ pk_str = f"0x{pk_str}"
511
+ try:
512
+ session.__dict__["_apis"][api_name][0] = pk_str
513
+ except Exception:
514
+ pass
515
+ private_key = pk_str
516
+
517
+ packed_extra = creds[2] if len(creds) > 2 else None
518
+ packed_api_key = packed_api_secret = packed_passphrase = None
519
+ packed_chain_id = packed_wallet = None
520
+ if isinstance(packed_extra, (list, tuple)):
521
+ def _packed_value(idx: int):
522
+ if idx >= len(packed_extra):
523
+ return None
524
+ value = packed_extra[idx]
525
+ if isinstance(value, str):
526
+ value = value.strip()
527
+ return value or None
528
+
529
+ packed_api_key = _packed_value(0)
530
+ packed_api_secret = _packed_value(1)
531
+ packed_passphrase = _packed_value(2)
532
+ packed_chain_id = _packed_value(3)
533
+ packed_wallet = _packed_value(4)
534
+ elif isinstance(packed_extra, str):
535
+ packed_wallet = packed_extra or None
536
+
537
+ existing_chain_id = session.__dict__.get("_polymarket_chain_id")
538
+ if existing_chain_id is None:
539
+ chain_id = 137
540
+ if packed_chain_id is not None:
541
+ try:
542
+ chain_id = int(packed_chain_id)
543
+ except (TypeError, ValueError):
544
+ chain_id = 137
545
+ session.__dict__["_polymarket_chain_id"] = chain_id
546
+ else:
547
+ chain_id = existing_chain_id
548
+
549
+ api_meta = session.__dict__.get("_polymarket_api_creds") or {}
550
+ if (not api_meta.get("api_key") or not api_meta.get("api_secret") or not api_meta.get("api_passphrase")) and (
551
+ packed_api_key and packed_api_secret and packed_passphrase
552
+ ):
553
+ api_meta = {
554
+ "api_key": packed_api_key,
555
+ "api_secret": packed_api_secret,
556
+ "api_passphrase": packed_passphrase,
557
+ }
558
+ session.__dict__["_polymarket_api_creds"] = api_meta
559
+
560
+ if packed_wallet and len(creds) > 2 and isinstance(creds[2], (list, tuple)):
561
+ creds[2] = packed_wallet
562
+
505
563
  api_key = api_meta.get("api_key")
506
564
  api_secret = api_meta.get("api_secret")
507
565
  api_passphrase = api_meta.get("api_passphrase")
@@ -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
@@ -16,7 +18,73 @@ class Position(DataStore):
16
18
  _KEYS = ["asset", "outcome"]
17
19
 
18
20
  def _on_response(self, msg: Item) -> None:
19
- self._update(msg)
21
+ if msg:
22
+ self._clear()
23
+ self._update(msg)
24
+
25
+ def on_trade(self, trade: Item) -> None:
26
+ status = str(trade.get("status") or "").upper()
27
+ if status not in {"MATCHED"}:
28
+ return
29
+
30
+ asset_id = trade.get("asset_id")
31
+ outcome = trade.get("outcome")
32
+ side = str(trade.get("side") or "").upper()
33
+ size_raw = trade.get("size")
34
+ price_raw = trade.get("price")
35
+
36
+ if not asset_id or not outcome or side not in {"BUY", "SELL"}:
37
+ return
38
+
39
+ try:
40
+ size = float(size_raw)
41
+ except (TypeError, ValueError):
42
+ return
43
+ try:
44
+ price = float(price_raw)
45
+ except (TypeError, ValueError):
46
+ price = None
47
+
48
+ key = {"asset": asset_id, "outcome": outcome}
49
+ existing = self.get(key) or {}
50
+
51
+ cur_size = float(existing.get("size") or 0.0)
52
+ cur_total_bought = float(existing.get("totalBought") or 0.0)
53
+ cur_avg_price = float(existing.get("avgPrice") or 0.0)
54
+ cur_cost = cur_size * cur_avg_price
55
+
56
+ if side == "BUY":
57
+ new_size = cur_size + size
58
+ total_bought = cur_total_bought + size
59
+ # 未拿到成交价时使用当前均价兜底,避免均价被拉低
60
+ effective_price = price if price is not None else cur_avg_price
61
+ new_cost = cur_cost + size * effective_price
62
+ else: # SELL
63
+ new_size = cur_size - size
64
+ total_bought = cur_total_bought
65
+ # 卖出按照当前均价释放成本
66
+ new_cost = cur_cost - min(size, cur_size) * cur_avg_price
67
+
68
+ if new_size <= 0:
69
+ new_size = 0.0
70
+ avg_price = 0.0
71
+ new_cost = 0.0
72
+ else:
73
+ avg_price = max(new_cost, 0.0) / new_size
74
+
75
+ rec: dict[str, Any] = {
76
+ "asset": asset_id,
77
+ "outcome": outcome,
78
+ "side": side,
79
+ "size": new_size,
80
+ "totalBought": total_bought,
81
+ "avgPrice": avg_price,
82
+ }
83
+
84
+ if existing:
85
+ self._update([rec])
86
+ else:
87
+ self._insert([rec])
20
88
 
21
89
 
22
90
  class Fill(DataStore):
@@ -123,7 +191,7 @@ class Order(DataStore):
123
191
  self._insert([norm])
124
192
 
125
193
 
126
- class Trade(DataStore):
194
+ class MyTrade(DataStore):
127
195
  """User trades keyed by trade id."""
128
196
 
129
197
  _KEYS = ["id"]
@@ -154,6 +222,26 @@ class Trade(DataStore):
154
222
  else:
155
223
  self._insert([normalized])
156
224
 
225
+ class Trade(DataStore):
226
+ """User trades keyed by trade id."""
227
+
228
+ _KEYS = ["hash"]
229
+ _MAXLEN = 500
230
+
231
+ def _on_message(self, msg: dict[str, Any]) -> None:
232
+ payload = msg or {}
233
+ if payload:
234
+ if payload.get("event_type") == "last_trade_price":
235
+ transaction_hash = payload.get("transaction_hash")
236
+ if transaction_hash:
237
+ payload.update({"hash": transaction_hash})
238
+ payload.pop("transaction_hash", None)
239
+ else:
240
+ if payload.get("transactionHash"):
241
+ payload.update({"hash": payload.get("transactionHash")})
242
+ payload.pop("transactionHash", None)
243
+
244
+ self._insert([payload])
157
245
 
158
246
  class Book(DataStore):
159
247
  """Full depth order book keyed by Polymarket token id."""
@@ -197,19 +285,36 @@ class Book(DataStore):
197
285
  normalized.append(record)
198
286
  return normalized
199
287
 
288
+ def _purge_missing_levels(
289
+ self, *, symbol: str, side: str, new_levels: list[dict[str, Any]]
290
+ ) -> None:
291
+ """Remove levels no longer present in the latest snapshot."""
292
+ existing = self.find({"s": symbol, "S": side})
293
+ if not existing:
294
+ return
295
+ new_prices = {lvl["p"] for lvl in new_levels}
296
+ stale = [
297
+ {"s": symbol, "S": side, "p": level["p"]}
298
+ for level in existing
299
+ if level.get("p") not in new_prices
300
+ ]
301
+ if stale:
302
+ self._delete(stale)
303
+
200
304
  def _on_message(self, msg: dict[str, Any]) -> None:
201
305
  msg_type = msg.get("event_type")
202
306
  if msg_type not in {"book", "price_change"}:
203
307
  return
204
308
 
205
- asset_id = msg.get("asset_id") or msg.get("token_id")
206
- symbol, alias = self._alias(asset_id)
207
- if symbol is None:
208
- return
209
-
210
309
  if msg_type == "book":
310
+ asset_id = msg.get("asset_id") or msg.get("token_id")
311
+ symbol, alias = self._alias(asset_id)
312
+ if symbol is None:
313
+ return
211
314
  bids = self._normalize_levels(msg.get("bids"), side="b", symbol=symbol, alias=alias)
212
315
  asks = self._normalize_levels(msg.get("asks"), side="a", symbol=symbol, alias=alias)
316
+ self._purge_missing_levels(symbol=symbol, side="b", new_levels=bids)
317
+ self._purge_missing_levels(symbol=symbol, side="a", new_levels=asks)
213
318
  if bids:
214
319
  self._insert(bids)
215
320
  if asks:
@@ -220,6 +325,10 @@ class Book(DataStore):
220
325
  updates: list[dict[str, Any]] = []
221
326
  removals: list[dict[str, Any]] = []
222
327
  for change in price_changes:
328
+ asset_id = change.get("asset_id") or change.get("token_id")
329
+ symbol, alias = self._alias(asset_id)
330
+ if symbol is None:
331
+ continue
223
332
  side = "b" if change.get("side") == "BUY" else "a"
224
333
  try:
225
334
  price = float(change["price"])
@@ -252,6 +361,193 @@ class Book(DataStore):
252
361
  limit=limit,
253
362
  )
254
363
 
364
+ @dataclass
365
+ class _SideBook:
366
+ is_ask: bool
367
+ levels: dict[float, tuple[str, str]] = field(default_factory=dict)
368
+ heap: list[tuple[float, float]] = field(default_factory=list)
369
+
370
+ def clear(self) -> None:
371
+ self.levels.clear()
372
+ self.heap.clear()
373
+
374
+ def update_levels(
375
+ self, updates: Iterable[dict[str, Any]] | None, *, snapshot: bool
376
+ ) -> None:
377
+ if updates is None:
378
+ return
379
+
380
+ if snapshot:
381
+ self.clear()
382
+
383
+ for entry in updates:
384
+ price, size = self._extract(entry)
385
+ price_val = self._to_float(price)
386
+ size_val = self._to_float(size)
387
+ if price_val is None or size_val is None:
388
+ continue
389
+
390
+ if size_val <= 0:
391
+ self.levels.pop(price_val, None)
392
+ continue
393
+
394
+ self.levels[price_val] = (str(price), str(size))
395
+ priority = price_val if self.is_ask else -price_val
396
+ heappush(self.heap, (priority, price_val))
397
+
398
+ def best(self) -> tuple[str, str] | None:
399
+ while self.heap:
400
+ _, price = self.heap[0]
401
+ level = self.levels.get(price)
402
+ if level is not None:
403
+ return level
404
+ heappop(self.heap)
405
+ return None
406
+
407
+ @staticmethod
408
+ def _extract(entry: Any) -> tuple[Any, Any]:
409
+ if isinstance(entry, dict):
410
+ price = entry.get("price", entry.get("p"))
411
+ size = entry.get("size", entry.get("q"))
412
+ return price, size
413
+ if isinstance(entry, (list, tuple)) and len(entry) >= 2:
414
+ return entry[0], entry[1]
415
+ return None, None
416
+
417
+ @staticmethod
418
+ def _to_float(value: Any) -> float | None:
419
+ try:
420
+ return float(value)
421
+ except (TypeError, ValueError):
422
+ return None
423
+
424
+ class Price(DataStore):
425
+ _KEYS = ["s"]
426
+
427
+ def _on_message(self, msg: dict[str, Any]) -> None:
428
+ payload = msg.get('payload') or {}
429
+ data = payload.get('data') or {}
430
+ symbol = payload.get('symbol')
431
+
432
+ if not symbol:
433
+ return
434
+
435
+ _next = self.get({'s': symbol}) or {}
436
+ _next_price = _next.get('p')
437
+ last_price = None
438
+
439
+ if data and isinstance(data, list):
440
+ last_price = data[-1].get('value')
441
+ if 'value' in payload:
442
+ last_price = payload.get('value')
443
+
444
+ if last_price is None:
445
+ return
446
+
447
+ record = {'s': symbol, 'p': last_price}
448
+ key = {'s': symbol}
449
+ if self.get(key):
450
+ self._update([record])
451
+ else:
452
+ self._insert([record])
453
+
454
+
455
+ class BBO(DataStore):
456
+ _KEYS = ["s", "S"]
457
+
458
+ def _init(self) -> None:
459
+ self._book: dict[str, dict[str, _SideBook]] = {}
460
+ self.id_to_alias: dict[str, str] = {}
461
+
462
+ def update_aliases(self, mapping: dict[str, str]) -> None:
463
+ if not mapping:
464
+ return
465
+ self.id_to_alias.update(mapping)
466
+
467
+ def _alias(self, asset_id: str | None) -> tuple[str, str | None] | tuple[None, None]:
468
+ if asset_id is None:
469
+ return None, None
470
+ alias = self.id_to_alias.get(asset_id)
471
+ return asset_id, alias
472
+
473
+ def _side(self, symbol: str, side: str) -> _SideBook:
474
+ symbol_book = self._book.setdefault(symbol, {})
475
+ side_book = symbol_book.get(side)
476
+ if side_book is None:
477
+ side_book = _SideBook(is_ask=(side == "a"))
478
+ symbol_book[side] = side_book
479
+ return side_book
480
+
481
+ def _sync_side(
482
+ self, symbol: str, side: str, best: tuple[str, str] | None, alias: str | None
483
+ ) -> None:
484
+ key = {"s": symbol, "S": side}
485
+ current = self.get(key)
486
+
487
+ if best is None:
488
+ if current:
489
+ self._delete([key])
490
+ return
491
+
492
+ price, size = best
493
+ payload = {"s": symbol, "S": side, "p": price, "q": size}
494
+ if alias is not None:
495
+ payload["alias"] = alias
496
+
497
+ if current:
498
+ cur_price = current.get("p")
499
+ cur_size = current.get("q")
500
+ cur_alias = current.get("alias")
501
+
502
+ if cur_price == price:
503
+ # price unchanged -> only update quantities / alias changes
504
+ if cur_size != size or (alias is not None and cur_alias != alias):
505
+ self._update([payload])
506
+ return
507
+
508
+ # price changed -> delete old then insert new level to trigger change watchers
509
+ self._delete([key])
510
+
511
+ self._insert([payload])
512
+
513
+ def _from_price_changes(self, msg: dict[str, Any]) -> None:
514
+ price_changes = msg.get("price_changes") or []
515
+ touched: dict[str, str | None] = {}
516
+ for change in price_changes:
517
+ asset_id = change.get("asset_id") or change.get("token_id")
518
+ symbol, alias = self._alias(asset_id)
519
+ if symbol is None:
520
+ continue
521
+ side = "b" if str(change.get("side") or "").upper() == "BUY" else "a"
522
+ side_book = self._side(symbol, side)
523
+ side_book.update_levels([change], snapshot=False)
524
+ touched[symbol] = alias
525
+
526
+ for symbol, alias in touched.items():
527
+ asks = self._side(symbol, "a")
528
+ bids = self._side(symbol, "b")
529
+ self._sync_side(symbol, "a", asks.best(), alias)
530
+ self._sync_side(symbol, "b", bids.best(), alias)
531
+
532
+ def _from_snapshot(self, msg: dict[str, Any]) -> None:
533
+ asset_id = msg.get("asset_id") or msg.get("token_id")
534
+ symbol, alias = self._alias(asset_id)
535
+ if symbol is None:
536
+ return
537
+ asks = self._side(symbol, "a")
538
+ bids = self._side(symbol, "b")
539
+ asks.update_levels(msg.get("asks"), snapshot=True)
540
+ bids.update_levels(msg.get("bids"), snapshot=True)
541
+ self._sync_side(symbol, "a", asks.best(), alias)
542
+ self._sync_side(symbol, "b", bids.best(), alias)
543
+
544
+ def _on_message(self, msg: dict[str, Any]) -> None:
545
+ msg_type = (msg.get("event_type") or msg.get("type") or "").lower()
546
+ if msg_type == "book":
547
+ self._from_snapshot(msg)
548
+ elif msg_type == "price_change":
549
+ self._from_price_changes(msg)
550
+
255
551
 
256
552
  class Detail(DataStore):
257
553
  """Market metadata keyed by Polymarket token id."""
@@ -260,7 +556,7 @@ class Detail(DataStore):
260
556
 
261
557
  @staticmethod
262
558
  def _normalize_entry(market: dict[str, Any], token: dict[str, Any]) -> dict[str, Any]:
263
- slug = market.get("market_slug") or market.get("question") or market.get("id")
559
+ slug = market.get("slug")
264
560
  outcome = token.get("outcome")
265
561
  alias = slug if outcome is None else f"{slug}:{outcome}"
266
562
 
@@ -332,10 +628,14 @@ class Detail(DataStore):
332
628
 
333
629
  for token in tokens:
334
630
  normalized = self._normalize_entry(market, token)
631
+ slug: str = market.get("slug")
632
+ # 取最后一个'-'之前部分
633
+ base_slug = slug.rsplit("-", 1)[0] if slug else slug
335
634
  # Add or update additional fields from market
336
635
  normalized.update({
337
636
  "condition_id": market.get("conditionId"),
338
637
  "slug": market.get("slug"),
638
+ "base_slug": base_slug,
339
639
  "end_date": market.get("endDate"),
340
640
  "start_date": market.get("startDate"),
341
641
  "icon": market.get("icon"),
@@ -362,11 +662,14 @@ class PolymarketDataStore(DataStoreCollection):
362
662
 
363
663
  def _init(self) -> None:
364
664
  self._create("book", datastore_class=Book)
665
+ self._create("bbo", datastore_class=BBO)
365
666
  self._create("detail", datastore_class=Detail)
366
667
  self._create("position", datastore_class=Position)
367
668
  self._create("order", datastore_class=Order)
368
- self._create("trade", datastore_class=Trade)
669
+ self._create("mytrade", datastore_class=MyTrade)
369
670
  self._create("fill", datastore_class=Fill)
671
+ self._create("trade", datastore_class=Trade)
672
+ self._create("price", datastore_class=Price)
370
673
 
371
674
  @property
372
675
  def book(self) -> Book:
@@ -497,7 +800,7 @@ class PolymarketDataStore(DataStoreCollection):
497
800
  return self._get("order")
498
801
 
499
802
  @property
500
- def trade(self) -> Trade:
803
+ def mytrade(self) -> MyTrade:
501
804
  """User trade stream keyed by trade id.
502
805
 
503
806
  Columns include Polymarket websocket ``trade`` payloads, e.g.
@@ -517,6 +820,13 @@ class PolymarketDataStore(DataStoreCollection):
517
820
  """
518
821
 
519
822
  return self._get("trade")
823
+
824
+ @property
825
+ def price(self) -> Price:
826
+ """Price DataStore
827
+ _key: s
828
+ """
829
+ return self._get("price")
520
830
 
521
831
  @property
522
832
  def fill(self) -> Fill:
@@ -541,11 +851,55 @@ class PolymarketDataStore(DataStoreCollection):
541
851
  """
542
852
 
543
853
  return self._get("fill")
854
+
855
+ @property
856
+ def bbo(self) -> BBO:
857
+ """Best Bid and Offer DataStore
858
+ _key: s (asset_id), S (side)
859
+
860
+ """
861
+ return self._get("bbo")
862
+
863
+ @property
864
+ def trade(self) -> Trade:
865
+ """
866
+ _key asset
867
+ MATCHED进行快速捕捉
868
+ .. code:: json
869
+ {
870
+ "asset": "12819879685513143002408869746992985182419696851931617234615358342350852997413",
871
+ "bio": "",
872
+ "conditionId": "0xea609d2c6bc2cb20e328be7c89f258b84b35bbe119b44e0a2cfc5f15e6642b3b",
873
+ "eventSlug": "btc-updown-15m-1763865000",
874
+ "icon": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png",
875
+ "name": "infusion",
876
+ "outcome": "Up",
877
+ "outcomeIndex": 0,
878
+ "price": 0.7,
879
+ "profileImage": "",
880
+ "proxyWallet": "0x2C060830B6F6B43174b1Cf8B4475db07703c1543",
881
+ "pseudonym": "Frizzy-Graduate",
882
+ "side": "BUY",
883
+ "size": 5,
884
+ "slug": "btc-updown-15m-1763865000",
885
+ "timestamp": 1763865085,
886
+ "title": "Bitcoin Up or Down - November 22, 9:30PM-9:45PM ET",
887
+ "hash": "0xddea11d695e811686f83379d9269accf1be581fbcb542809c6c67a3cc3002488"
888
+ }
889
+ """
890
+ return self._get("trade")
891
+
544
892
 
545
893
  def onmessage(self, msg: Any, ws: ClientWebSocketResponse | None = None) -> None:
546
894
  # 判定msg是否为list
547
895
  lst_msg = msg if isinstance(msg, list) else [msg]
548
896
  for m in lst_msg:
897
+ if m == '':
898
+ continue
899
+ topic = m.get("topic") or ""
900
+ if topic in {'crypto_prices_chainlink', 'crypto_prices'}:
901
+ self.price._on_message(m)
902
+ continue
549
903
  raw_type = m.get("event_type") or m.get("type")
550
904
  if not raw_type:
551
905
  continue
@@ -553,7 +907,38 @@ class PolymarketDataStore(DataStoreCollection):
553
907
  if msg_type in {"book", "price_change"}:
554
908
  self.book._on_message(m)
555
909
  elif msg_type == "order":
556
- self.order._on_message(m)
910
+ self.orders._on_message(m)
557
911
  elif msg_type == "trade":
558
- self.trade._on_message(m)
912
+ self.mytrade._on_message(m)
559
913
  self.fill._on_trade(m)
914
+ self.position.on_trade(m)
915
+ elif msg_type == 'orders_matched':
916
+ payload = m.get("payload") or {}
917
+ if not payload:
918
+ continue
919
+ trade_msg = dict(payload)
920
+ if "asset_id" not in trade_msg and "asset" in trade_msg:
921
+ trade_msg["asset_id"] = trade_msg["asset"]
922
+ self.trade._on_message(trade_msg)
923
+
924
+ def onmessage_for_bbo(self, msg: Any, ws: ClientWebSocketResponse | None = None) -> None:
925
+ # 判定msg是否为list
926
+ lst_msg = msg if isinstance(msg, list) else [msg]
927
+ for m in lst_msg:
928
+ raw_type = m.get("event_type") or m.get("type")
929
+ if not raw_type:
930
+ continue
931
+ msg_type = str(raw_type).lower()
932
+ if msg_type in {"book", "price_change"}:
933
+ self.bbo._on_message(m)
934
+
935
+ def onmessage_for_last_trade(self, msg, ws = None):
936
+ # 判定msg是否为list
937
+ lst_msg = msg if isinstance(msg, list) else [msg]
938
+ for m in lst_msg:
939
+ raw_type = m.get("event_type") or m.get("type")
940
+ if not raw_type:
941
+ continue
942
+ msg_type = str(raw_type).lower()
943
+ if msg_type == "last_trade_price":
944
+ self.trade._on_message(m)