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.
- {hyperquant-1.3 → hyperquant-1.5}/PKG-INFO +5 -6
- {hyperquant-1.3 → hyperquant-1.5}/pyproject.toml +5 -6
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/auth.py +198 -5
- hyperquant-1.5/src/hyperquant/broker/lib/polymarket/ctfAbi.py +721 -0
- hyperquant-1.5/src/hyperquant/broker/lib/polymarket/safeAbi.py +1138 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/polymarket.py +542 -26
- hyperquant-1.5/src/hyperquant/broker/polymarket.py +2432 -0
- {hyperquant-1.3 → hyperquant-1.5}/uv.lock +24 -701
- hyperquant-1.3/src/hyperquant/broker/polymarket.py +0 -1126
- {hyperquant-1.3 → hyperquant-1.5}/.gitignore +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/README.md +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/requirements-dev.lock +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/requirements.lock +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/__init__.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/bitget.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/bitmart.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/coinw.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/deepcoin.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/edgex.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/hyperliquid.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lbank.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lib/hpstore.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lib/hyper_types.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lib/util.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/lighter.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/apexpro.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/bitget.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/bitmart.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/coinw.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/deepcoin.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/edgex.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/hyperliquid.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/lbank.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/lighter.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/models/ourbit.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/ourbit.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/broker/ws.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/core.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/datavison/_util.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/datavison/binance.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/datavison/coinglass.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/datavison/okx.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/db.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/draw.py +0 -0
- {hyperquant-1.3 → hyperquant-1.5}/src/hyperquant/logkit.py +0 -0
- {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
|
+
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.
|
|
16
|
-
Requires-Dist: aiohttp>=3.
|
|
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:
|
|
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
|
+
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.
|
|
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.
|
|
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
|
-
"
|
|
20
|
+
"coincurve>=21.0.0"
|
|
22
21
|
]
|
|
23
22
|
readme = "README.md"
|
|
24
|
-
requires-python = ">=3.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
504
|
-
|
|
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
|
)
|