hyperquant 1.3__tar.gz → 1.5__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 (47) hide show
  1. {hyperquant-1.3 → hyperquant-1.5}/PKG-INFO +5 -6
  2. {hyperquant-1.3 → hyperquant-1.5}/pyproject.toml +5 -6
  3. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/auth.py +198 -5
  4. hyperquant-1.5/src/hyperquant/broker/lib/polymarket/ctfAbi.py +721 -0
  5. hyperquant-1.5/src/hyperquant/broker/lib/polymarket/safeAbi.py +1138 -0
  6. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/polymarket.py +542 -26
  7. hyperquant-1.5/src/hyperquant/broker/polymarket.py +2432 -0
  8. {hyperquant-1.3 → hyperquant-1.5}/uv.lock +24 -701
  9. hyperquant-1.3/src/hyperquant/broker/polymarket.py +0 -1126
  10. {hyperquant-1.3 → hyperquant-1.5}/.gitignore +0 -0
  11. {hyperquant-1.3 → hyperquant-1.5}/README.md +0 -0
  12. {hyperquant-1.3 → hyperquant-1.5}/requirements-dev.lock +0 -0
  13. {hyperquant-1.3 → hyperquant-1.5}/requirements.lock +0 -0
  14. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/__init__.py +0 -0
  15. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/bitget.py +0 -0
  16. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/bitmart.py +0 -0
  17. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/coinw.py +0 -0
  18. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/deepcoin.py +0 -0
  19. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/edgex.py +0 -0
  20. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/hyperliquid.py +0 -0
  21. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lbank.py +0 -0
  22. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  23. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lib/hpstore.py +0 -0
  24. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  25. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lib/util.py +0 -0
  26. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lighter.py +0 -0
  27. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/apexpro.py +0 -0
  28. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/bitget.py +0 -0
  29. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/bitmart.py +0 -0
  30. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/coinw.py +0 -0
  31. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/deepcoin.py +0 -0
  32. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/edgex.py +0 -0
  33. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  34. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/lbank.py +0 -0
  35. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/lighter.py +0 -0
  36. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/ourbit.py +0 -0
  37. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/ourbit.py +0 -0
  38. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/ws.py +0 -0
  39. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/core.py +0 -0
  40. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/datavison/_util.py +0 -0
  41. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/datavison/binance.py +0 -0
  42. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/datavison/coinglass.py +0 -0
  43. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/datavison/okx.py +0 -0
  44. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/db.py +0 -0
  45. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/draw.py +0 -0
  46. {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/logkit.py +0 -0
  47. {hyperquant-1.3 → hyperquant-1.5}/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.5
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
@@ -12,17 +12,16 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Topic :: Office/Business :: Financial :: Investment
15
- Requires-Python: >=3.11
16
- Requires-Dist: aiohttp>=3.10.4
15
+ Requires-Python: >=3.13
16
+ Requires-Dist: aiohttp>=3.13
17
+ Requires-Dist: coincurve>=21.0.0
17
18
  Requires-Dist: colorama>=0.4.6
18
19
  Requires-Dist: cryptography>=44.0.2
19
20
  Requires-Dist: duckdb>=1.2.2
20
21
  Requires-Dist: eth-account>=0.10.0
21
- Requires-Dist: lighter-sdk
22
22
  Requires-Dist: numpy>=1.21.0
23
23
  Requires-Dist: pandas>=2.2.3
24
- Requires-Dist: py-clob-client>=0.28.0
25
- Requires-Dist: pybotters>=1.9.1
24
+ Requires-Dist: pybotters>=1.10
26
25
  Requires-Dist: pyecharts>=2.0.8
27
26
  Requires-Dist: python-dotenv>=1.2.1
28
27
  Requires-Dist: web3>=7.14.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "1.3"
3
+ version = "1.5"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -9,19 +9,18 @@ dependencies = [
9
9
  "pyecharts>=2.0.8",
10
10
  "pandas>=2.2.3",
11
11
  "colorama>=0.4.6",
12
- "aiohttp>=3.10.4",
12
+ "aiohttp>=3.13",
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",
17
- "lighter-sdk",
16
+ "pybotters>=1.10",
18
17
  "eth-account>=0.10.0",
19
18
  "web3>=7.14.0",
20
19
  "python-dotenv>=1.2.1",
21
- "py-clob-client>=0.28.0",
20
+ "coincurve>=21.0.0"
22
21
  ]
23
22
  readme = "README.md"
24
- requires-python = ">=3.11"
23
+ requires-python = ">=3.13"
25
24
 
26
25
 
27
26
  keywords = ["quant", "backtesting", "trading", "hyperquant"]
@@ -3,6 +3,7 @@ import hmac
3
3
  import urllib.parse
4
4
  import time
5
5
  import hashlib
6
+ from functools import lru_cache
6
7
  from typing import Any
7
8
  from aiohttp import ClientWebSocketResponse, FormData, JsonPayload
8
9
  from multidict import CIMultiDict
@@ -12,11 +13,18 @@ import json as pyjson
12
13
  from urllib.parse import urlencode
13
14
  from datetime import datetime, timezone
14
15
  from eth_account import Account
15
- from eth_account.messages import encode_typed_data
16
+ try:
17
+ from eth_account.messages import encode_typed_data
18
+ except ImportError:
19
+ from eth_account.messages import encode_structured_data as encode_typed_data
20
+ from eth_utils import keccak, to_checksum_address
16
21
  import secrets
17
22
  from random import random
18
- from datetime import datetime, timezone
19
23
 
24
+ try:
25
+ from coincurve import PrivateKey as _CoincurvePrivateKey
26
+ except Exception: # pragma: no cover - optional dependency
27
+ _CoincurvePrivateKey = None
20
28
 
21
29
  POLY_ADDRESS = "POLY_ADDRESS"
22
30
  POLY_SIGNATURE = "POLY_SIGNATURE"
@@ -27,6 +35,53 @@ POLY_PASSPHRASE = "POLY_PASSPHRASE"
27
35
  CLOB_AUTH_DOMAIN = {"name": "ClobAuthDomain", "version": "1"}
28
36
  CLOB_AUTH_MESSAGE = "This message attests that I control the given wallet"
29
37
 
38
+ _PM_DOMAIN_TYPEHASH = keccak(b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
39
+ _PM_ORDER_TYPEHASH = keccak(
40
+ b"Order(uint256 salt,address maker,address signer,address taker,uint256 tokenId,uint256 makerAmount,uint256 takerAmount,uint256 expiration,uint256 nonce,uint256 feeRateBps,uint8 side,uint8 signatureType)"
41
+ )
42
+ _PM_DOMAIN_NAME_HASH = keccak(text="Polymarket CTF Exchange")
43
+ _PM_DOMAIN_VERSION_HASH = keccak(text="1")
44
+
45
+
46
+ def _int_to_32(val: int) -> bytes:
47
+ return int(val).to_bytes(32, "big", signed=False)
48
+
49
+
50
+ def _addr_to_32(addr: str) -> bytes:
51
+ # Normalize to checksum to match eth signing behavior
52
+ normalized = to_checksum_address(addr)
53
+ return int(normalized, 16).to_bytes(32, "big", signed=False)
54
+
55
+
56
+ @lru_cache(maxsize=32)
57
+ def _pm_domain_separator(chain_id: int, verifying_contract: str) -> bytes:
58
+ return keccak(
59
+ _PM_DOMAIN_TYPEHASH
60
+ + _PM_DOMAIN_NAME_HASH
61
+ + _PM_DOMAIN_VERSION_HASH
62
+ + _int_to_32(chain_id)
63
+ + _addr_to_32(verifying_contract),
64
+ )
65
+
66
+
67
+ def _pm_order_hash(message: dict[str, Any]) -> bytes:
68
+ """Manually hash the Polymarket Order struct for EIP-712."""
69
+ return keccak(
70
+ _PM_ORDER_TYPEHASH
71
+ + _int_to_32(message["salt"])
72
+ + _addr_to_32(message["maker"])
73
+ + _addr_to_32(message["signer"])
74
+ + _addr_to_32(message["taker"])
75
+ + _int_to_32(message["tokenId"])
76
+ + _int_to_32(message["makerAmount"])
77
+ + _int_to_32(message["takerAmount"])
78
+ + _int_to_32(message["expiration"])
79
+ + _int_to_32(message["nonce"])
80
+ + _int_to_32(message["feeRateBps"])
81
+ + _int_to_32(message["side"])
82
+ + _int_to_32(message["signatureType"]),
83
+ )
84
+
30
85
 
31
86
  def md5_hex(s: str) -> str:
32
87
  return hashlib.md5(s.encode("utf-8")).hexdigest()
@@ -484,7 +539,7 @@ class Auth:
484
539
  return args
485
540
 
486
541
  @staticmethod
487
- def polymarket(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]:
542
+ def polymarket_back(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]:
488
543
  method: str = args[0].upper()
489
544
  url: URL = args[1]
490
545
  headers: CIMultiDict = kwargs["headers"]
@@ -499,9 +554,67 @@ class Auth:
499
554
  if not creds:
500
555
  return args
501
556
 
557
+ if isinstance(creds, tuple):
558
+ creds = list(creds)
559
+ session.__dict__["_apis"][api_name] = creds
560
+
502
561
  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", {})
562
+ if private_key:
563
+ pk_str = str(private_key)
564
+ if not pk_str.startswith("0x"):
565
+ pk_str = f"0x{pk_str}"
566
+ try:
567
+ session.__dict__["_apis"][api_name][0] = pk_str
568
+ except Exception:
569
+ pass
570
+ private_key = pk_str
571
+
572
+ packed_extra = creds[2] if len(creds) > 2 else None
573
+ packed_api_key = packed_api_secret = packed_passphrase = None
574
+ packed_chain_id = packed_wallet = None
575
+ if isinstance(packed_extra, (list, tuple)):
576
+ def _packed_value(idx: int):
577
+ if idx >= len(packed_extra):
578
+ return None
579
+ value = packed_extra[idx]
580
+ if isinstance(value, str):
581
+ value = value.strip()
582
+ return value or None
583
+
584
+ packed_api_key = _packed_value(0)
585
+ packed_api_secret = _packed_value(1)
586
+ packed_passphrase = _packed_value(2)
587
+ packed_chain_id = _packed_value(3)
588
+ packed_wallet = _packed_value(4)
589
+ elif isinstance(packed_extra, str):
590
+ packed_wallet = packed_extra or None
591
+
592
+ existing_chain_id = session.__dict__.get("_polymarket_chain_id")
593
+ if existing_chain_id is None:
594
+ chain_id = 137
595
+ if packed_chain_id is not None:
596
+ try:
597
+ chain_id = int(packed_chain_id)
598
+ except (TypeError, ValueError):
599
+ chain_id = 137
600
+ session.__dict__["_polymarket_chain_id"] = chain_id
601
+ else:
602
+ chain_id = existing_chain_id
603
+
604
+ api_meta = session.__dict__.get("_polymarket_api_creds") or {}
605
+ if (not api_meta.get("api_key") or not api_meta.get("api_secret") or not api_meta.get("api_passphrase")) and (
606
+ packed_api_key and packed_api_secret and packed_passphrase
607
+ ):
608
+ api_meta = {
609
+ "api_key": packed_api_key,
610
+ "api_secret": packed_api_secret,
611
+ "api_passphrase": packed_passphrase,
612
+ }
613
+ session.__dict__["_polymarket_api_creds"] = api_meta
614
+
615
+ if packed_wallet and len(creds) > 2 and isinstance(creds[2], (list, tuple)):
616
+ creds[2] = packed_wallet
617
+
505
618
  api_key = api_meta.get("api_key")
506
619
  api_secret = api_meta.get("api_secret")
507
620
  api_passphrase = api_meta.get("api_passphrase")
@@ -621,6 +734,11 @@ class Auth:
621
734
 
622
735
  return args
623
736
 
737
+ @staticmethod
738
+ def polymarket(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]:
739
+ """Alias kept for backward compatibility with pybotters host registration."""
740
+ return Auth.polymarket_back(args, kwargs)
741
+
624
742
  # --------------------------
625
743
  # Polymarket order signing
626
744
  # --------------------------
@@ -734,6 +852,81 @@ class Auth:
734
852
  }
735
853
  return out
736
854
 
855
+ @staticmethod
856
+ def sign_polymarket_order2(
857
+ *,
858
+ private_key: str,
859
+ chain_id: int,
860
+ exchange_address: str,
861
+ order: dict[str, Any],
862
+ ) -> dict[str, Any]:
863
+ """
864
+ Fast EIP-712 signer using coincurve + manual hashing.
865
+
866
+ Avoids heavy typed-data helpers by caching type/domain hashes and
867
+ signing the final digest directly.
868
+ """
869
+ if _CoincurvePrivateKey is None:
870
+ raise RuntimeError("coincurve is required for sign_polymarket_order2")
871
+
872
+ try:
873
+ now_ts = datetime.now().replace(tzinfo=timezone.utc).timestamp()
874
+ generated_salt = round(now_ts * random())
875
+ except Exception:
876
+ generated_salt = int(time.time())
877
+
878
+ side = int(order.get("side", 0))
879
+ signature_type = int(order.get("signatureType", 1))
880
+ signer_addr = order.get("signer") or Account.from_key(private_key).address
881
+ taker_addr = order.get("taker") or "0x0000000000000000000000000000000000000000"
882
+
883
+ message = {
884
+ "salt": int(order.get("salt") or generated_salt),
885
+ "maker": order.get("maker"),
886
+ "signer": signer_addr,
887
+ "taker": taker_addr,
888
+ "tokenId": int(order["tokenId"]),
889
+ "makerAmount": int(order["makerAmount"]),
890
+ "takerAmount": int(order["takerAmount"]),
891
+ "expiration": int(order.get("expiration", 0)),
892
+ "nonce": int(order.get("nonce", 0)),
893
+ "feeRateBps": int(order.get("feeRateBps", 0)),
894
+ "side": side,
895
+ "signatureType": signature_type,
896
+ }
897
+
898
+ # Normalize addresses for hashing
899
+ exchange_addr = to_checksum_address(exchange_address)
900
+ message["maker"] = to_checksum_address(message["maker"])
901
+ message["signer"] = to_checksum_address(message["signer"])
902
+ message["taker"] = to_checksum_address(message["taker"])
903
+
904
+ domain_sep = _pm_domain_separator(int(chain_id), exchange_addr)
905
+ msg_hash = _pm_order_hash(message)
906
+ typed_hash = keccak(b"\x19\x01" + domain_sep + msg_hash)
907
+
908
+ pk_bytes = bytes.fromhex(private_key[2:] if private_key.startswith("0x") else private_key)
909
+ sig65 = _CoincurvePrivateKey(pk_bytes).sign_recoverable(typed_hash, hasher=None)
910
+ r, s, rec_id = sig65[:32], sig65[32:64], sig65[64]
911
+ v = rec_id + 27 # align with eth_account v
912
+ signature = "0x" + (r + s + bytes([v])).hex()
913
+
914
+ return {
915
+ "salt": int(message["salt"]),
916
+ "maker": message["maker"],
917
+ "signer": message["signer"],
918
+ "taker": message["taker"],
919
+ "tokenId": str(message["tokenId"]),
920
+ "makerAmount": str(message["makerAmount"]),
921
+ "takerAmount": str(message["takerAmount"]),
922
+ "expiration": str(message["expiration"]),
923
+ "nonce": str(message["nonce"]),
924
+ "feeRateBps": str(message["feeRateBps"]),
925
+ "side": "BUY" if side == 0 else "SELL",
926
+ "signatureType": int(signature_type),
927
+ "signature": signature,
928
+ }
929
+
737
930
  pybotters.auth.Hosts.items["futures.ourbit.com"] = pybotters.auth.Item(
738
931
  "ourbit", Auth.ourbit
739
932
  )