hyperquant 1.3__tar.gz → 1.4__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.4}/PKG-INFO +1 -1
  2. {hyperquant-1.3 → hyperquant-1.4}/pyproject.toml +1 -1
  3. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/auth.py +60 -2
  4. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/polymarket.py +382 -11
  5. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/polymarket.py +492 -47
  6. {hyperquant-1.3 → hyperquant-1.4}/uv.lock +1 -1
  7. {hyperquant-1.3 → hyperquant-1.4}/.gitignore +0 -0
  8. {hyperquant-1.3 → hyperquant-1.4}/README.md +0 -0
  9. {hyperquant-1.3 → hyperquant-1.4}/requirements-dev.lock +0 -0
  10. {hyperquant-1.3 → hyperquant-1.4}/requirements.lock +0 -0
  11. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/__init__.py +0 -0
  12. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/bitget.py +0 -0
  13. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/bitmart.py +0 -0
  14. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/coinw.py +0 -0
  15. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/deepcoin.py +0 -0
  16. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/edgex.py +0 -0
  17. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/hyperliquid.py +0 -0
  18. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/lbank.py +0 -0
  19. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  20. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/lib/hpstore.py +0 -0
  21. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  22. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/lib/util.py +0 -0
  23. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/lighter.py +0 -0
  24. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/apexpro.py +0 -0
  25. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/bitget.py +0 -0
  26. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/bitmart.py +0 -0
  27. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/coinw.py +0 -0
  28. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/deepcoin.py +0 -0
  29. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/edgex.py +0 -0
  30. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  31. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/lbank.py +0 -0
  32. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/lighter.py +0 -0
  33. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/models/ourbit.py +0 -0
  34. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/ourbit.py +0 -0
  35. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/broker/ws.py +0 -0
  36. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/core.py +0 -0
  37. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/datavison/_util.py +0 -0
  38. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/datavison/binance.py +0 -0
  39. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/datavison/coinglass.py +0 -0
  40. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/datavison/okx.py +0 -0
  41. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/db.py +0 -0
  42. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/draw.py +0 -0
  43. {hyperquant-1.3 → hyperquant-1.4}/src/hyperquant/logkit.py +0 -0
  44. {hyperquant-1.3 → hyperquant-1.4}/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.4
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.3"
3
+ version = "1.4"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -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,21 @@ 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 = ["asset_id"]
229
+ _MAXLEN = 500
230
+
231
+
232
+ def _on_message(self, msg: dict[str, Any]) -> None:
233
+ payload = msg or {}
234
+ if payload:
235
+ size = float(payload.get("size"))
236
+ price = float(payload.get("price"))
237
+ # share = size / price
238
+ # payload['shares'] = share
239
+ self._insert([payload])
157
240
 
158
241
  class Book(DataStore):
159
242
  """Full depth order book keyed by Polymarket token id."""
@@ -197,19 +280,36 @@ class Book(DataStore):
197
280
  normalized.append(record)
198
281
  return normalized
199
282
 
283
+ def _purge_missing_levels(
284
+ self, *, symbol: str, side: str, new_levels: list[dict[str, Any]]
285
+ ) -> None:
286
+ """Remove levels no longer present in the latest snapshot."""
287
+ existing = self.find({"s": symbol, "S": side})
288
+ if not existing:
289
+ return
290
+ new_prices = {lvl["p"] for lvl in new_levels}
291
+ stale = [
292
+ {"s": symbol, "S": side, "p": level["p"]}
293
+ for level in existing
294
+ if level.get("p") not in new_prices
295
+ ]
296
+ if stale:
297
+ self._delete(stale)
298
+
200
299
  def _on_message(self, msg: dict[str, Any]) -> None:
201
300
  msg_type = msg.get("event_type")
202
301
  if msg_type not in {"book", "price_change"}:
203
302
  return
204
303
 
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
304
  if msg_type == "book":
305
+ asset_id = msg.get("asset_id") or msg.get("token_id")
306
+ symbol, alias = self._alias(asset_id)
307
+ if symbol is None:
308
+ return
211
309
  bids = self._normalize_levels(msg.get("bids"), side="b", symbol=symbol, alias=alias)
212
310
  asks = self._normalize_levels(msg.get("asks"), side="a", symbol=symbol, alias=alias)
311
+ self._purge_missing_levels(symbol=symbol, side="b", new_levels=bids)
312
+ self._purge_missing_levels(symbol=symbol, side="a", new_levels=asks)
213
313
  if bids:
214
314
  self._insert(bids)
215
315
  if asks:
@@ -220,6 +320,10 @@ class Book(DataStore):
220
320
  updates: list[dict[str, Any]] = []
221
321
  removals: list[dict[str, Any]] = []
222
322
  for change in price_changes:
323
+ asset_id = change.get("asset_id") or change.get("token_id")
324
+ symbol, alias = self._alias(asset_id)
325
+ if symbol is None:
326
+ continue
223
327
  side = "b" if change.get("side") == "BUY" else "a"
224
328
  try:
225
329
  price = float(change["price"])
@@ -252,6 +356,193 @@ class Book(DataStore):
252
356
  limit=limit,
253
357
  )
254
358
 
359
+ @dataclass
360
+ class _SideBook:
361
+ is_ask: bool
362
+ levels: dict[float, tuple[str, str]] = field(default_factory=dict)
363
+ heap: list[tuple[float, float]] = field(default_factory=list)
364
+
365
+ def clear(self) -> None:
366
+ self.levels.clear()
367
+ self.heap.clear()
368
+
369
+ def update_levels(
370
+ self, updates: Iterable[dict[str, Any]] | None, *, snapshot: bool
371
+ ) -> None:
372
+ if updates is None:
373
+ return
374
+
375
+ if snapshot:
376
+ self.clear()
377
+
378
+ for entry in updates:
379
+ price, size = self._extract(entry)
380
+ price_val = self._to_float(price)
381
+ size_val = self._to_float(size)
382
+ if price_val is None or size_val is None:
383
+ continue
384
+
385
+ if size_val <= 0:
386
+ self.levels.pop(price_val, None)
387
+ continue
388
+
389
+ self.levels[price_val] = (str(price), str(size))
390
+ priority = price_val if self.is_ask else -price_val
391
+ heappush(self.heap, (priority, price_val))
392
+
393
+ def best(self) -> tuple[str, str] | None:
394
+ while self.heap:
395
+ _, price = self.heap[0]
396
+ level = self.levels.get(price)
397
+ if level is not None:
398
+ return level
399
+ heappop(self.heap)
400
+ return None
401
+
402
+ @staticmethod
403
+ def _extract(entry: Any) -> tuple[Any, Any]:
404
+ if isinstance(entry, dict):
405
+ price = entry.get("price", entry.get("p"))
406
+ size = entry.get("size", entry.get("q"))
407
+ return price, size
408
+ if isinstance(entry, (list, tuple)) and len(entry) >= 2:
409
+ return entry[0], entry[1]
410
+ return None, None
411
+
412
+ @staticmethod
413
+ def _to_float(value: Any) -> float | None:
414
+ try:
415
+ return float(value)
416
+ except (TypeError, ValueError):
417
+ return None
418
+
419
+ class Price(DataStore):
420
+ _KEYS = ["s"]
421
+
422
+ def _on_message(self, msg: dict[str, Any]) -> None:
423
+ payload = msg.get('payload') or {}
424
+ data = payload.get('data') or {}
425
+ symbol = payload.get('symbol')
426
+
427
+ if not symbol:
428
+ return
429
+
430
+ _next = self.get({'s': symbol}) or {}
431
+ _next_price = _next.get('p')
432
+ last_price = None
433
+
434
+ if data and isinstance(data, list):
435
+ last_price = data[-1].get('value')
436
+ if 'value' in payload:
437
+ last_price = payload.get('value')
438
+
439
+ if last_price is None:
440
+ return
441
+
442
+ record = {'s': symbol, 'p': last_price}
443
+ key = {'s': symbol}
444
+ if self.get(key):
445
+ self._update([record])
446
+ else:
447
+ self._insert([record])
448
+
449
+
450
+ class BBO(DataStore):
451
+ _KEYS = ["s", "S"]
452
+
453
+ def _init(self) -> None:
454
+ self._book: dict[str, dict[str, _SideBook]] = {}
455
+ self.id_to_alias: dict[str, str] = {}
456
+
457
+ def update_aliases(self, mapping: dict[str, str]) -> None:
458
+ if not mapping:
459
+ return
460
+ self.id_to_alias.update(mapping)
461
+
462
+ def _alias(self, asset_id: str | None) -> tuple[str, str | None] | tuple[None, None]:
463
+ if asset_id is None:
464
+ return None, None
465
+ alias = self.id_to_alias.get(asset_id)
466
+ return asset_id, alias
467
+
468
+ def _side(self, symbol: str, side: str) -> _SideBook:
469
+ symbol_book = self._book.setdefault(symbol, {})
470
+ side_book = symbol_book.get(side)
471
+ if side_book is None:
472
+ side_book = _SideBook(is_ask=(side == "a"))
473
+ symbol_book[side] = side_book
474
+ return side_book
475
+
476
+ def _sync_side(
477
+ self, symbol: str, side: str, best: tuple[str, str] | None, alias: str | None
478
+ ) -> None:
479
+ key = {"s": symbol, "S": side}
480
+ current = self.get(key)
481
+
482
+ if best is None:
483
+ if current:
484
+ self._delete([key])
485
+ return
486
+
487
+ price, size = best
488
+ payload = {"s": symbol, "S": side, "p": price, "q": size}
489
+ if alias is not None:
490
+ payload["alias"] = alias
491
+
492
+ if current:
493
+ cur_price = current.get("p")
494
+ cur_size = current.get("q")
495
+ cur_alias = current.get("alias")
496
+
497
+ if cur_price == price:
498
+ # price unchanged -> only update quantities / alias changes
499
+ if cur_size != size or (alias is not None and cur_alias != alias):
500
+ self._update([payload])
501
+ return
502
+
503
+ # price changed -> delete old then insert new level to trigger change watchers
504
+ self._delete([key])
505
+
506
+ self._insert([payload])
507
+
508
+ def _from_price_changes(self, msg: dict[str, Any]) -> None:
509
+ price_changes = msg.get("price_changes") or []
510
+ touched: dict[str, str | None] = {}
511
+ for change in price_changes:
512
+ asset_id = change.get("asset_id") or change.get("token_id")
513
+ symbol, alias = self._alias(asset_id)
514
+ if symbol is None:
515
+ continue
516
+ side = "b" if str(change.get("side") or "").upper() == "BUY" else "a"
517
+ side_book = self._side(symbol, side)
518
+ side_book.update_levels([change], snapshot=False)
519
+ touched[symbol] = alias
520
+
521
+ for symbol, alias in touched.items():
522
+ asks = self._side(symbol, "a")
523
+ bids = self._side(symbol, "b")
524
+ self._sync_side(symbol, "a", asks.best(), alias)
525
+ self._sync_side(symbol, "b", bids.best(), alias)
526
+
527
+ def _from_snapshot(self, msg: dict[str, Any]) -> None:
528
+ asset_id = msg.get("asset_id") or msg.get("token_id")
529
+ symbol, alias = self._alias(asset_id)
530
+ if symbol is None:
531
+ return
532
+ asks = self._side(symbol, "a")
533
+ bids = self._side(symbol, "b")
534
+ asks.update_levels(msg.get("asks"), snapshot=True)
535
+ bids.update_levels(msg.get("bids"), snapshot=True)
536
+ self._sync_side(symbol, "a", asks.best(), alias)
537
+ self._sync_side(symbol, "b", bids.best(), alias)
538
+
539
+ def _on_message(self, msg: dict[str, Any]) -> None:
540
+ msg_type = (msg.get("event_type") or msg.get("type") or "").lower()
541
+ if msg_type == "book":
542
+ self._from_snapshot(msg)
543
+ elif msg_type == "price_change":
544
+ self._from_price_changes(msg)
545
+
255
546
 
256
547
  class Detail(DataStore):
257
548
  """Market metadata keyed by Polymarket token id."""
@@ -260,7 +551,7 @@ class Detail(DataStore):
260
551
 
261
552
  @staticmethod
262
553
  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")
554
+ slug = market.get("slug")
264
555
  outcome = token.get("outcome")
265
556
  alias = slug if outcome is None else f"{slug}:{outcome}"
266
557
 
@@ -332,10 +623,14 @@ class Detail(DataStore):
332
623
 
333
624
  for token in tokens:
334
625
  normalized = self._normalize_entry(market, token)
626
+ slug: str = market.get("slug")
627
+ # 取最后一个'-'之前部分
628
+ base_slug = slug.rsplit("-", 1)[0] if slug else slug
335
629
  # Add or update additional fields from market
336
630
  normalized.update({
337
631
  "condition_id": market.get("conditionId"),
338
632
  "slug": market.get("slug"),
633
+ "base_slug": base_slug,
339
634
  "end_date": market.get("endDate"),
340
635
  "start_date": market.get("startDate"),
341
636
  "icon": market.get("icon"),
@@ -362,11 +657,14 @@ class PolymarketDataStore(DataStoreCollection):
362
657
 
363
658
  def _init(self) -> None:
364
659
  self._create("book", datastore_class=Book)
660
+ self._create("bbo", datastore_class=BBO)
365
661
  self._create("detail", datastore_class=Detail)
366
662
  self._create("position", datastore_class=Position)
367
663
  self._create("order", datastore_class=Order)
368
- self._create("trade", datastore_class=Trade)
664
+ self._create("mytrade", datastore_class=MyTrade)
369
665
  self._create("fill", datastore_class=Fill)
666
+ self._create("trade", datastore_class=Trade)
667
+ self._create("price", datastore_class=Price)
370
668
 
371
669
  @property
372
670
  def book(self) -> Book:
@@ -497,7 +795,7 @@ class PolymarketDataStore(DataStoreCollection):
497
795
  return self._get("order")
498
796
 
499
797
  @property
500
- def trade(self) -> Trade:
798
+ def mytrade(self) -> MyTrade:
501
799
  """User trade stream keyed by trade id.
502
800
 
503
801
  Columns include Polymarket websocket ``trade`` payloads, e.g.
@@ -517,6 +815,13 @@ class PolymarketDataStore(DataStoreCollection):
517
815
  """
518
816
 
519
817
  return self._get("trade")
818
+
819
+ @property
820
+ def price(self) -> Price:
821
+ """Price DataStore
822
+ _key: s
823
+ """
824
+ return self._get("price")
520
825
 
521
826
  @property
522
827
  def fill(self) -> Fill:
@@ -541,11 +846,63 @@ class PolymarketDataStore(DataStoreCollection):
541
846
  """
542
847
 
543
848
  return self._get("fill")
849
+
850
+ @property
851
+ def bbo(self) -> BBO:
852
+ """Best Bid and Offer DataStore
853
+ _key: s (asset_id), S (side)
854
+
855
+ """
856
+ return self._get("bbo")
857
+
858
+ @property
859
+ def trade(self) -> Trade:
860
+ """
861
+ _key asset
862
+ MATCHED进行快速捕捉
863
+ .. code:: json
864
+ {
865
+ "asset_id": "52114319501245915516055106046884209969926127482827954674443846427813813222426",
866
+ "event_type": "trade",
867
+ "id": "28c4d2eb-bbea-40e7-a9f0-b2fdb56b2c2e",
868
+ "last_update": "1672290701",
869
+ "maker_orders": [
870
+ {
871
+ "asset_id": "52114319501245915516055106046884209969926127482827954674443846427813813222426",
872
+ "matched_amount": "10",
873
+ "order_id": "0xff354cd7ca7539dfa9c28d90943ab5779a4eac34b9b37a757d7b32bdfb11790b",
874
+ "outcome": "YES",
875
+ "owner": "9180014b-33c8-9240-a14b-bdca11c0a465",
876
+ "price": "0.57"
877
+ }
878
+ ],
879
+ "market": "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af",
880
+ "matchtime": "1672290701",
881
+ "outcome": "YES",
882
+ "owner": "9180014b-33c8-9240-a14b-bdca11c0a465",
883
+ "price": "0.57",
884
+ "side": "BUY",
885
+ "size": "10",
886
+ "status": "MATCHED",
887
+ "taker_order_id": "0x06bc63e346ed4ceddce9efd6b3af37c8f8f440c92fe7da6b2d0f9e4ccbc50c42",
888
+ "timestamp": "1672290701",
889
+ "trade_owner": "9180014b-33c8-9240-a14b-bdca11c0a465",
890
+ "type": "TRADE"
891
+ }
892
+ """
893
+ return self._get("trade")
894
+
544
895
 
545
896
  def onmessage(self, msg: Any, ws: ClientWebSocketResponse | None = None) -> None:
546
897
  # 判定msg是否为list
547
898
  lst_msg = msg if isinstance(msg, list) else [msg]
548
899
  for m in lst_msg:
900
+ if m == '':
901
+ continue
902
+ topic = m.get("topic") or ""
903
+ if topic in {'crypto_prices_chainlink', 'crypto_prices'}:
904
+ self.price._on_message(m)
905
+ continue
549
906
  raw_type = m.get("event_type") or m.get("type")
550
907
  if not raw_type:
551
908
  continue
@@ -553,7 +910,21 @@ class PolymarketDataStore(DataStoreCollection):
553
910
  if msg_type in {"book", "price_change"}:
554
911
  self.book._on_message(m)
555
912
  elif msg_type == "order":
556
- self.order._on_message(m)
913
+ self.orders._on_message(m)
557
914
  elif msg_type == "trade":
558
915
  self.trade._on_message(m)
559
916
  self.fill._on_trade(m)
917
+ self.position.on_trade(m)
918
+ elif msg_type == 'orders_matched':
919
+ self.trade._on_message(m)
920
+
921
+ def onmessage_for_bbo(self, msg: Any, ws: ClientWebSocketResponse | None = None) -> None:
922
+ # 判定msg是否为list
923
+ lst_msg = msg if isinstance(msg, list) else [msg]
924
+ for m in lst_msg:
925
+ raw_type = m.get("event_type") or m.get("type")
926
+ if not raw_type:
927
+ continue
928
+ msg_type = str(raw_type).lower()
929
+ if msg_type in {"book", "price_change"}:
930
+ self.bbo._on_message(m)