hyperquant 1.33__tar.gz → 1.35__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.33 → hyperquant-1.35}/PKG-INFO +1 -1
  2. {hyperquant-1.33 → hyperquant-1.35}/pyproject.toml +1 -1
  3. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/auth.py +60 -2
  4. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/polymarket.py +82 -21
  5. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/polymarket.py +290 -28
  6. {hyperquant-1.33 → hyperquant-1.35}/uv.lock +1 -1
  7. {hyperquant-1.33 → hyperquant-1.35}/.gitignore +0 -0
  8. {hyperquant-1.33 → hyperquant-1.35}/README.md +0 -0
  9. {hyperquant-1.33 → hyperquant-1.35}/requirements-dev.lock +0 -0
  10. {hyperquant-1.33 → hyperquant-1.35}/requirements.lock +0 -0
  11. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/__init__.py +0 -0
  12. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/bitget.py +0 -0
  13. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/bitmart.py +0 -0
  14. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/coinw.py +0 -0
  15. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/deepcoin.py +0 -0
  16. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/edgex.py +0 -0
  17. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/hyperliquid.py +0 -0
  18. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/lbank.py +0 -0
  19. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  20. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/lib/hpstore.py +0 -0
  21. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  22. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/lib/util.py +0 -0
  23. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/lighter.py +0 -0
  24. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/apexpro.py +0 -0
  25. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/bitget.py +0 -0
  26. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/bitmart.py +0 -0
  27. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/coinw.py +0 -0
  28. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/deepcoin.py +0 -0
  29. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/edgex.py +0 -0
  30. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  31. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/lbank.py +0 -0
  32. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/lighter.py +0 -0
  33. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/models/ourbit.py +0 -0
  34. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/ourbit.py +0 -0
  35. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/broker/ws.py +0 -0
  36. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/core.py +0 -0
  37. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/datavison/_util.py +0 -0
  38. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/datavison/binance.py +0 -0
  39. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/datavison/coinglass.py +0 -0
  40. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/datavison/okx.py +0 -0
  41. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/db.py +0 -0
  42. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/draw.py +0 -0
  43. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/logkit.py +0 -0
  44. {hyperquant-1.33 → hyperquant-1.35}/src/hyperquant/notikit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 1.33
3
+ Version: 1.35
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.33"
3
+ version = "1.35"
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")
@@ -18,6 +18,50 @@ class Position(DataStore):
18
18
  def _on_response(self, msg: Item) -> None:
19
19
  self._update(msg)
20
20
 
21
+ def on_trade(self, trade: Item) -> None:
22
+ status = str(trade.get("status") or "").upper()
23
+ if status not in {"MATCHED"}:
24
+ return
25
+
26
+ asset_id = trade.get("asset_id")
27
+ outcome = trade.get("outcome")
28
+ side = str(trade.get("side") or "").upper()
29
+ size_raw = trade.get("size")
30
+
31
+ if not asset_id or not outcome or side not in {"BUY", "SELL"}:
32
+ return
33
+
34
+ try:
35
+ size = float(size_raw)
36
+ except (TypeError, ValueError):
37
+ return
38
+
39
+ key = {"asset": asset_id, "outcome": outcome}
40
+ existing = self.get(key) or {}
41
+
42
+ cur_size = float(existing.get("size") or 0.0)
43
+ cur_total_bought = float(existing.get("totalBought") or 0.0)
44
+
45
+ if side == "BUY":
46
+ new_size = cur_size + size
47
+ total_bought = cur_total_bought + size
48
+ else: # SELL
49
+ new_size = cur_size - size
50
+ total_bought = cur_total_bought
51
+
52
+ rec: dict[str, Any] = {
53
+ "asset": asset_id,
54
+ "outcome": outcome,
55
+ "side": side,
56
+ "size": new_size,
57
+ "totalBought": total_bought,
58
+ }
59
+
60
+ if existing:
61
+ self._update([rec])
62
+ else:
63
+ self._insert([rec])
64
+
21
65
 
22
66
  class Fill(DataStore):
23
67
  """Fill records keyed by maker order id."""
@@ -157,13 +201,17 @@ class MyTrade(DataStore):
157
201
  class Trade(DataStore):
158
202
  """User trades keyed by trade id."""
159
203
 
160
- _KEYS = ["asset"]
204
+ _KEYS = ["asset_id"]
161
205
  _MAXLEN = 500
162
206
 
163
207
 
164
208
  def _on_message(self, msg: dict[str, Any]) -> None:
165
- payload = msg.get("payload") or {}
209
+ payload = msg or {}
166
210
  if payload:
211
+ size = float(payload.get("size"))
212
+ price = float(payload.get("price"))
213
+ # share = size / price
214
+ # payload['shares'] = share
167
215
  self._insert([payload])
168
216
 
169
217
  class Book(DataStore):
@@ -364,10 +412,14 @@ class Detail(DataStore):
364
412
 
365
413
  for token in tokens:
366
414
  normalized = self._normalize_entry(market, token)
415
+ slug: str = market.get("slug")
416
+ # 取最后一个'-'之前部分
417
+ base_slug = slug.rsplit("-", 1)[0] if slug else slug
367
418
  # Add or update additional fields from market
368
419
  normalized.update({
369
420
  "condition_id": market.get("conditionId"),
370
421
  "slug": market.get("slug"),
422
+ "base_slug": base_slug,
371
423
  "end_date": market.get("endDate"),
372
424
  "start_date": market.get("startDate"),
373
425
  "icon": market.get("icon"),
@@ -579,27 +631,35 @@ class PolymarketDataStore(DataStoreCollection):
579
631
  def trade(self) -> Trade:
580
632
  """
581
633
  _key asset
582
-
634
+ MATCHED进行快速捕捉
583
635
  .. code:: json
584
636
  {
585
- "asset": "28343900911859986062638672238388649192722581036873866155064320792236495661418",
586
- "bio": "",
587
- "conditionId": "0x730a593d5b63a39d316139a7f4bc1dd9fd05d858f6d96f659629c371bc81cd4d",
588
- "eventSlug": "btc-updown-15m-1762951500",
589
- "icon": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png",
590
- "name": "",
591
- "outcome": "Down",
592
- "outcomeIndex": 1,
593
- "price": 0.11,
594
- "profileImage": "",
595
- "proxyWallet": "0x7459f51304037Da66c47CeA58F078D7f603fD011",
596
- "pseudonym": "",
597
- "side": "SELL",
598
- "size": 84,
599
- "slug": "btc-updown-15m-1762951500",
600
- "timestamp": 1762952057,
601
- "title": "Bitcoin Up or Down - November 12, 7:45AM-8:00AM ET",
602
- "transactionHash": "0x6853cab8901779c494fe15f0eb3350aebfb182125347ca67b8bd9706babbddca"
637
+ "asset_id": "52114319501245915516055106046884209969926127482827954674443846427813813222426",
638
+ "event_type": "trade",
639
+ "id": "28c4d2eb-bbea-40e7-a9f0-b2fdb56b2c2e",
640
+ "last_update": "1672290701",
641
+ "maker_orders": [
642
+ {
643
+ "asset_id": "52114319501245915516055106046884209969926127482827954674443846427813813222426",
644
+ "matched_amount": "10",
645
+ "order_id": "0xff354cd7ca7539dfa9c28d90943ab5779a4eac34b9b37a757d7b32bdfb11790b",
646
+ "outcome": "YES",
647
+ "owner": "9180014b-33c8-9240-a14b-bdca11c0a465",
648
+ "price": "0.57"
649
+ }
650
+ ],
651
+ "market": "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af",
652
+ "matchtime": "1672290701",
653
+ "outcome": "YES",
654
+ "owner": "9180014b-33c8-9240-a14b-bdca11c0a465",
655
+ "price": "0.57",
656
+ "side": "BUY",
657
+ "size": "10",
658
+ "status": "MATCHED",
659
+ "taker_order_id": "0x06bc63e346ed4ceddce9efd6b3af37c8f8f440c92fe7da6b2d0f9e4ccbc50c42",
660
+ "timestamp": "1672290701",
661
+ "trade_owner": "9180014b-33c8-9240-a14b-bdca11c0a465",
662
+ "type": "TRADE"
603
663
  }
604
664
  """
605
665
  return self._get("trade")
@@ -620,5 +680,6 @@ class PolymarketDataStore(DataStoreCollection):
620
680
  elif msg_type == "trade":
621
681
  self.trade._on_message(m)
622
682
  self.fill._on_trade(m)
683
+ self.position.on_trade(m)
623
684
  elif msg_type == 'orders_matched':
624
685
  self.trade._on_message(m)
@@ -4,6 +4,7 @@ import asyncio
4
4
  from contextlib import suppress
5
5
  from datetime import UTC, datetime
6
6
  from functools import lru_cache
7
+ import os
7
8
  from typing import Any, Iterable, Iterator, Literal, Mapping, Sequence
8
9
 
9
10
  import json
@@ -38,6 +39,14 @@ DEFAULT_POLYGON_RPCS = (
38
39
  "https://rpc.ankr.com/polygon",
39
40
  )
40
41
 
42
+ def parse_field(value):
43
+ """尝试将字符串 JSON 转为对象,否则原样返回"""
44
+ if isinstance(value, str):
45
+ try:
46
+ return json.loads(value)
47
+ except json.JSONDecodeError:
48
+ return value
49
+ return value
41
50
 
42
51
  def _iter_offsets(window: int) -> Iterator[int]:
43
52
  yield 0
@@ -94,7 +103,7 @@ class Polymarket:
94
103
  self._ws_personal: pybotters.ws.WebSocketApp | None = None
95
104
  self.auth = False
96
105
 
97
- self._ensure_session_entry(private_key=private_key, funder=funder)
106
+ self._ensure_session_entry(private_key=private_key, funder=funder, chain_id=chain_id)
98
107
 
99
108
  async def __aenter__(self) -> "Polymarket":
100
109
  if self.auth:
@@ -266,6 +275,7 @@ class Polymarket:
266
275
  self._ws_personal = self.client.ws_connect(
267
276
  "wss://ws-subscriptions-clob.polymarket.com/ws/user",
268
277
  hdlr_json=effective_cb,
278
+ heartbeat=30,
269
279
  auth=None,
270
280
  )
271
281
  await self._ws_personal._event.wait()
@@ -298,7 +308,7 @@ class Polymarket:
298
308
  wsapp = self.client.ws_connect(
299
309
  RTS_DATA_ENDPOINT,
300
310
  hdlr_str=callback,
301
- heartbeat=5
311
+ heartbeat=30
302
312
  )
303
313
  await wsapp._event.wait()
304
314
  await wsapp.current_ws.send_json(payload)
@@ -314,9 +324,12 @@ class Polymarket:
314
324
  return await self._rest("GET", f"/markets/{market_id}")
315
325
 
316
326
  async def get_market_by_slug(self, slug: str) -> Any:
317
- """Fetch a market using its human-readable slug."""
318
- # https://gamma-api.polymarket.com/markets/slug/{slug}
319
- return await self._rest("GET", f"/slug/{slug}", host='https://gamma-api.polymarket.com/markets')
327
+ """Fetch a market using its human-readable slug.
328
+ https://docs.polymarket.com/api-reference/markets/get-market-by-slug
329
+ """
330
+ market:dict = await self._rest("GET", f"/slug/{slug}", host='https://gamma-api.polymarket.com/markets')
331
+ market = {k: parse_field(v) for k, v in market.items()}
332
+ return market
320
333
 
321
334
  async def get_order_book(self, token_id: str) -> Any:
322
335
  return await self._rest("GET", "/book", params={"token_id": token_id})
@@ -498,7 +511,7 @@ class Polymarket:
498
511
  side: str,
499
512
  price: float,
500
513
  size: float,
501
- order_type: str = "GTC",
514
+ order_type: Literal["GTC", 'FOK'] = "GTC",
502
515
  tick_size: str | float | None = None,
503
516
  fee_rate_bps: int | None = None,
504
517
  expiration: int | None = None,
@@ -523,6 +536,9 @@ class Polymarket:
523
536
  - nonce: onchain nonce, default 0
524
537
  """
525
538
 
539
+ if price <= 0:
540
+ raise ValueError("price must be positive; use place_market_order for market orders")
541
+
526
542
  # Ensure L2 creds exist
527
543
  if not self._api_creds():
528
544
  raise RuntimeError("Polymarket API credentials missing; call create_or_derive_api_creds first")
@@ -552,6 +568,117 @@ class Polymarket:
552
568
  }
553
569
  return await self._signed_request_via_session("POST", "/order", payload)
554
570
 
571
+ async def place_market_order(
572
+ self,
573
+ *,
574
+ token_id: str,
575
+ side: str,
576
+ amount: float,
577
+ order_type: Literal["FOK", "GTC", "FAK", "GTD"] = "FOK",
578
+ price: float | None = None,
579
+ tick_size: str | float | None = None,
580
+ fee_rate_bps: int | None = None,
581
+ taker: str = ZERO_ADDRESS,
582
+ neg_risk: bool = False,
583
+ owner: str | None = None,
584
+ nonce: int | None = None,
585
+ ) -> Any:
586
+ """Create, sign and submit a market order similar to ``py_clob_client``.
587
+
588
+ BUY orders treat ``amount`` as collateral (USDC); SELL orders treat it as shares.
589
+ """
590
+
591
+ if amount <= 0:
592
+ raise ValueError("amount must be greater than 0 for market orders")
593
+
594
+ if not self._api_creds():
595
+ raise RuntimeError("Polymarket API credentials missing; call create_or_derive_api_creds first")
596
+
597
+ private_key, maker_addr, signer_addr = self._get_signing_context()
598
+ owner_key = self._owner_key(owner)
599
+ order_type_str = (order_type or "FOK").upper()
600
+
601
+ if price is None or price <= 0:
602
+ price = await self._calculate_market_price(
603
+ token_id=token_id,
604
+ side=side,
605
+ amount=amount,
606
+ order_type=order_type_str,
607
+ )
608
+
609
+ signed_dict = await self._build_signed_market_order(
610
+ private_key=private_key,
611
+ maker_addr=maker_addr,
612
+ signer_addr=signer_addr,
613
+ token_id=token_id,
614
+ side=side,
615
+ amount=amount,
616
+ price=price,
617
+ tick_size=tick_size,
618
+ fee_rate_bps=fee_rate_bps,
619
+ taker=taker,
620
+ neg_risk=neg_risk,
621
+ nonce=nonce,
622
+ )
623
+
624
+ payload = {
625
+ "order": signed_dict,
626
+ "owner": owner_key,
627
+ "orderType": order_type_str,
628
+ }
629
+ return await self._signed_request_via_session("POST", "/order", payload)
630
+
631
+ async def _calculate_market_price(
632
+ self,
633
+ *,
634
+ token_id: str,
635
+ side: str,
636
+ amount: float,
637
+ order_type: str,
638
+ ) -> float:
639
+ side_flag = side.upper()
640
+ if side_flag not in {"BUY", "SELL"}:
641
+ raise ValueError("side must be 'BUY' or 'SELL'")
642
+ if amount <= 0:
643
+ raise ValueError("amount must be greater than 0 for market pricing")
644
+
645
+ book = await self.get_order_book(token_id)
646
+ if not isinstance(book, Mapping):
647
+ raise RuntimeError("Polymarket order book unavailable for market order")
648
+
649
+ key = "asks" if side_flag == "BUY" else "bids"
650
+ raw_levels = book.get(key) or []
651
+ levels: list[tuple[float, float]] = []
652
+ for lvl in raw_levels:
653
+ try:
654
+ price = float(lvl.get("price"))
655
+ size = float(lvl.get("size"))
656
+ except (TypeError, ValueError):
657
+ continue
658
+ if price is None or size is None:
659
+ continue
660
+ levels.append((price, size))
661
+
662
+ if not levels:
663
+ raise RuntimeError(f"Polymarket market order has no {key} liquidity")
664
+
665
+ total = 0.0
666
+ if side_flag == "BUY":
667
+ for price, size in reversed(levels):
668
+ total += price * size
669
+ if total >= amount:
670
+ return price
671
+ else:
672
+ for price, size in reversed(levels):
673
+ total += size
674
+ if total >= amount:
675
+ return price
676
+
677
+ if (order_type or "FOK").upper() == "FOK":
678
+ raise RuntimeError("Polymarket market order exceeds available liquidity")
679
+
680
+ return levels[0][0]
681
+
555
682
  async def _signed_request_via_session(
556
683
  self, method: str, path: str, body: Mapping[str, Any] | list[Any] | None
557
684
  ) -> Any:
@@ -686,6 +813,76 @@ class Polymarket:
686
813
  },
687
814
  )
688
815
 
816
+ async def _build_signed_market_order(
817
+ self,
818
+ *,
819
+ private_key: str,
820
+ maker_addr: str,
821
+ signer_addr: str,
822
+ token_id: str,
823
+ side: str,
824
+ amount: float,
825
+ price: float,
826
+ tick_size: str | float | None,
827
+ fee_rate_bps: int | None,
828
+ taker: str,
829
+ neg_risk: bool,
830
+ nonce: int | None,
831
+ ) -> dict[str, Any]:
832
+ side = side.upper()
833
+ tick = await self._resolve_tick_size(token_id, tick_size)
834
+ fee_bps = await self._resolve_fee_rate(token_id, fee_rate_bps)
835
+
836
+ price_d, size_d, amt_d = self._rounding_for_tick(tick)
837
+ price = float(self._round_normal(price, price_d))
838
+ if price <= 0:
839
+ raise ValueError("market price must be positive")
840
+
841
+ amt = float(amount)
842
+ if amt <= 0:
843
+ raise ValueError("amount must be greater than 0")
844
+
845
+ maker_amt_raw = self._round_down(amt, size_d)
846
+ if maker_amt_raw <= 0:
847
+ raise ValueError("amount too small for current tick size")
848
+
849
+ if side == "BUY":
850
+ taker_amt_raw = maker_amt_raw / price
851
+ elif side == "SELL":
852
+ taker_amt_raw = maker_amt_raw * price
853
+ else:
854
+ raise ValueError("side must be 'BUY' or 'SELL'")
855
+
856
+ if self._decimal_places(taker_amt_raw) > amt_d:
857
+ tmp = self._round_up(taker_amt_raw, amt_d + 4)
858
+ taker_amt_raw = tmp if self._decimal_places(tmp) <= amt_d else self._round_down(tmp, amt_d)
859
+
860
+ maker_amount = self._to_token_decimals(maker_amt_raw)
861
+ taker_amount = self._to_token_decimals(taker_amt_raw)
862
+
863
+ contract = self._contracts(self.chain_id, neg_risk)
864
+ side_flag = 0 if side == "BUY" else 1
865
+ sig_type = int(self.signature_type)
866
+
867
+ return Auth.sign_polymarket_order(
868
+ private_key=private_key,
869
+ chain_id=self.chain_id,
870
+ exchange_address=contract["exchange"],
871
+ order={
872
+ "maker": maker_addr,
873
+ "signer": signer_addr,
874
+ "taker": taker or ZERO_ADDRESS,
875
+ "tokenId": str(token_id),
876
+ "makerAmount": int(maker_amount),
877
+ "takerAmount": int(taker_amount),
878
+ "expiration": 0,
879
+ "nonce": int(nonce or 0),
880
+ "feeRateBps": int(fee_bps or 0),
881
+ "side": side_flag,
882
+ "signatureType": sig_type,
883
+ },
884
+ )
885
+
689
886
  async def _resolve_tick_size(self, token_id: str, tick_size: str | float | None) -> str:
690
887
  if tick_size is not None:
691
888
  return str(tick_size)
@@ -878,6 +1075,7 @@ class Polymarket:
878
1075
 
879
1076
  async def get_trades(self, params: Mapping[str, Any] | None = None) -> list[Any]:
880
1077
  return await self._paginate("/data/trades", params)
1078
+
881
1079
 
882
1080
  async def get_notifications(self, signature_type: int | None = None) -> Any:
883
1081
  sig = signature_type if signature_type is not None else self.signature_type
@@ -889,12 +1087,30 @@ class Polymarket:
889
1087
  return await self._rest("DELETE", "/notifications", params=params)
890
1088
 
891
1089
  async def get_balance_allowance(self, **params: Any) -> Any:
892
- return await self._rest("GET", "/balance-allowance", params=params or None)
1090
+ query = dict(params or {})
1091
+ query.setdefault("signature_type", self.signature_type)
1092
+ return await self._rest("GET", "/balance-allowance", params=query or None)
893
1093
 
894
1094
  async def update_balance_allowance(self, **params: Any) -> Any:
895
- return await self._rest("POST", "/balance-allowance/update", json=params or None)
896
-
897
- async def get_usdc(
1095
+ body = dict(params or {})
1096
+ body.setdefault("signature_type", self.signature_type)
1097
+ return await self._rest("POST", "/balance-allowance/update", json=body or None)
1098
+
1099
+ async def get_usdc(self):
1100
+ data = await self.get_balance_allowance(asset_type='COLLATERAL')
1101
+ balance = float(data.get('balance', 0.0))
1102
+ if balance > 0:
1103
+ balance = balance / 1e6
1104
+ return balance
1105
+
1106
+ async def get_position(self, token_id: str) -> Any:
1107
+ data = await self.get_balance_allowance(asset_type='CONDITIONAL', token_id=token_id)
1108
+ position = float(data.get('balance', 0.0))
1109
+ if position > 0:
1110
+ position = position / 1e6
1111
+ return position
1112
+
1113
+ async def get_usdc_web3(
898
1114
  self,
899
1115
  wallet: str = None,
900
1116
  rpc_urls: Sequence[str] | None = None,
@@ -966,14 +1182,7 @@ class Polymarket:
966
1182
  if not event:
967
1183
  continue
968
1184
 
969
- def parse_field(value):
970
- """尝试将字符串 JSON 转为对象,否则原样返回"""
971
- if isinstance(value, str):
972
- try:
973
- return json.loads(value)
974
- except json.JSONDecodeError:
975
- return value
976
- return value
1185
+
977
1186
 
978
1187
  event = {k: parse_field(v) for k, v in event.items()}
979
1188
 
@@ -1113,6 +1322,7 @@ class Polymarket:
1113
1322
  *,
1114
1323
  private_key: str | None,
1115
1324
  funder: str | None,
1325
+ chain_id: int | None,
1116
1326
  ) -> None:
1117
1327
  session = getattr(self.client, "_session", None)
1118
1328
  if session is None:
@@ -1125,27 +1335,66 @@ class Polymarket:
1125
1335
  if not entry and not private_key:
1126
1336
  return
1127
1337
 
1338
+ packed = entry[2] if len(entry) > 2 else None
1339
+ if not isinstance(packed, (list, tuple)):
1340
+ packed = None
1341
+
1342
+ def _packed_value(idx: int) -> Any | None:
1343
+ if packed is None:
1344
+ return None
1345
+ if idx >= len(packed):
1346
+ return None
1347
+ value = packed[idx]
1348
+ if isinstance(value, str):
1349
+ value = value.strip()
1350
+ return value or None
1351
+
1352
+ packed_api_key = _packed_value(0)
1353
+ packed_api_secret = _packed_value(1)
1354
+ packed_passphrase = _packed_value(2)
1355
+ packed_chain_id = _packed_value(3)
1356
+ packed_wallet = _packed_value(4)
1357
+
1128
1358
  while len(entry) < 3:
1129
1359
  entry.append("")
1130
1360
 
1131
- existing_pk = entry[0]
1361
+ existing_pk = entry[0] if entry else None
1132
1362
  normalized_pk: str | None = None
1133
- if private_key:
1363
+ candidate_pk = private_key or existing_pk
1364
+ if candidate_pk:
1365
+ candidate_pk = str(candidate_pk)
1134
1366
  normalized_pk = (
1135
- private_key if private_key.startswith("0x") else f"0x{private_key}"
1136
- )
1137
- elif existing_pk:
1138
- normalized_pk = (
1139
- existing_pk if existing_pk.startswith("0x") else f"0x{existing_pk}"
1367
+ candidate_pk if candidate_pk.startswith("0x") else f"0x{candidate_pk}"
1140
1368
  )
1141
1369
 
1142
1370
  if not normalized_pk:
1143
1371
  raise RuntimeError("Polymarket需要钱包私钥 (apis['polymarket'][0])")
1144
1372
 
1145
1373
  entry[0] = normalized_pk
1146
- if funder:
1147
- entry[2] = funder
1148
- self.funder = entry[2] or self.funder
1374
+
1375
+ existing_wallet = entry[2] if isinstance(entry[2], str) and entry[2] else None
1376
+ effective_wallet = funder or packed_wallet or existing_wallet or self.funder
1377
+ if effective_wallet:
1378
+ entry[2] = effective_wallet
1379
+ self.funder = effective_wallet
1380
+ else:
1381
+ entry[2] = ""
1382
+
1383
+ derived_chain_id: int | None = None
1384
+ if packed_chain_id is not None:
1385
+ try:
1386
+ derived_chain_id = int(packed_chain_id)
1387
+ except (TypeError, ValueError):
1388
+ derived_chain_id = None
1389
+ if chain_id is None and derived_chain_id is not None:
1390
+ self.chain_id = derived_chain_id
1391
+
1392
+ if packed_api_key and packed_api_secret and packed_passphrase:
1393
+ session.__dict__["_polymarket_api_creds"] = {
1394
+ "api_key": packed_api_key,
1395
+ "api_secret": packed_api_secret,
1396
+ "api_passphrase": packed_passphrase,
1397
+ }
1149
1398
 
1150
1399
  apis[API_NAME] = entry
1151
1400
  session.__dict__["_apis"] = apis
@@ -1153,6 +1402,19 @@ class Polymarket:
1153
1402
  session.__dict__["_polymarket_signature_type"] = self.signature_type
1154
1403
  self.auth = True
1155
1404
 
1405
+ @staticmethod
1406
+ def load_poly_api():
1407
+ from dotenv import load_dotenv
1408
+
1409
+ load_dotenv()
1410
+ pk = os.getenv("PK")
1411
+ api_key = os.getenv("CLOB_API_KEY")
1412
+ api_secret = os.getenv("CLOB_API_SECRET")
1413
+ passphrase = os.getenv("CLOB_API_PASSPHRASE")
1414
+ chain_id = os.getenv("CHAIN_ID") or 137
1415
+ wallet_address = os.getenv("POLY_WALLET_ADDRESS")
1416
+ return [pk, "", (api_key, api_secret, passphrase, chain_id, wallet_address)]
1417
+
1156
1418
  @lru_cache(maxsize=8)
1157
1419
  def _get_web3(rpc_url: str):
1158
1420
  from web3 import Web3
@@ -945,7 +945,7 @@ wheels = [
945
945
 
946
946
  [[package]]
947
947
  name = "hyperquant"
948
- version = "1.32"
948
+ version = "1.34"
949
949
  source = { editable = "." }
950
950
  dependencies = [
951
951
  { name = "aiohttp" },
File without changes
File without changes
File without changes