hyperquant 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl

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/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from .core import *
2
2
  from .draw import *
3
3
  from .logkit import *
4
-
4
+ from .datavison import *
5
5
  __version__ = "0.1.0"
@@ -0,0 +1,239 @@
1
+ """
2
+ Coinglass API 数据解密与数据获取模块
3
+ 优化:
4
+ - 只保留必要依赖,整理import顺序
5
+ - 增加类型注解和文档
6
+ - 统一异常处理和日志输出
7
+ - 精简冗余代码
8
+ """
9
+ import asyncio
10
+ import base64
11
+ import json
12
+ import struct
13
+ import time
14
+ import zlib
15
+ import hmac
16
+ import hashlib
17
+ from datetime import datetime, timedelta
18
+ from typing import Any, Dict, List, Optional, Union
19
+
20
+ import pandas as pd
21
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
22
+ from cryptography.hazmat.primitives import padding as crypto_padding
23
+ from cryptography.hazmat.backends import default_backend
24
+ import aiohttp
25
+
26
+ # ------------------ 工具函数 ------------------
27
+ class CustomParser:
28
+ @staticmethod
29
+ def parse(data: str) -> Dict[str, Union[List[int], int]]:
30
+ """
31
+ 将字符串转换为数字数组,兼容原始加密逻辑。
32
+ """
33
+ length = len(data)
34
+ n = [0] * ((length + 3) // 4)
35
+ for r in range(length):
36
+ n[r >> 2] |= (ord(data[r]) & 255) << (24 - (r % 4) * 8)
37
+ return {"n": n, "e": length}
38
+
39
+ def convert_words_to_bytes(words: List[int]) -> bytes:
40
+ """
41
+ 将整数数组转换为字节数组。
42
+ """
43
+ return b"".join(struct.pack(">I", word) for word in words)
44
+
45
+ def decrypt_and_clean(t: str, e: Dict[str, Any]) -> str:
46
+ """
47
+ 解密、解压缩并清理输入字符串。
48
+ """
49
+ aes_key = convert_words_to_bytes(e['n'])
50
+ cipher = Cipher(algorithms.AES(aes_key), modes.ECB(), backend=default_backend())
51
+ decryptor = cipher.decryptor()
52
+ encrypted_data = base64.b64decode(t)
53
+ decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
54
+ unpadder = crypto_padding.PKCS7(128).unpadder()
55
+ unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
56
+ decompressed_data = zlib.decompress(unpadded_data, wbits=16 + zlib.MAX_WBITS).decode('utf-8')
57
+ return decompressed_data
58
+
59
+ def generate_totp(secret: str, for_time: int, interval: int = 30, digits: int = 6, digest=hashlib.sha1) -> str:
60
+ """
61
+ 基于标准库的TOTP实现。
62
+ """
63
+ key = base64.b32decode(secret, casefold=True)
64
+ counter = int(for_time // interval)
65
+ msg = counter.to_bytes(8, 'big')
66
+ h = hmac.new(key, msg, digest).digest()
67
+ o = h[-1] & 0x0F
68
+ code = (struct.unpack('>I', h[o:o+4])[0] & 0x7fffffff) % (10 ** digits)
69
+ return str(code).zfill(digits)
70
+
71
+ def generate_encrypted_token() -> str:
72
+ """
73
+ 生成加密token,用于API请求。
74
+ """
75
+ current_time = int(time.time())
76
+ secret_key = "I65VU7K5ZQL7WB4E"
77
+ otp = generate_totp(secret_key, current_time)
78
+ combined_string = f"{current_time},{otp}"
79
+ aes_key = "1f68efd73f8d4921acc0dead41dd39bc"
80
+ aes_key_bytes = CustomParser.parse(aes_key)
81
+ final_key = convert_words_to_bytes(aes_key_bytes['n'])
82
+ cipher = Cipher(algorithms.AES(final_key), modes.ECB(), backend=default_backend())
83
+ encryptor = cipher.encryptor()
84
+ padder = crypto_padding.PKCS7(128).padder()
85
+ padded_data = padder.update(combined_string.encode('utf-8')) + padder.finalize()
86
+ encrypted_bytes = encryptor.update(padded_data) + encryptor.finalize()
87
+ return base64.b64encode(encrypted_bytes).decode('utf-8')
88
+
89
+ def decrypt_coinglass(data: str, user_header: str, url: str) -> str:
90
+ """
91
+ 解密 Coinglass API 的响应数据。
92
+ """
93
+ base_key = base64.b64encode(f"coinglass{url}coinglass".encode("utf-8")).decode("utf-8")[:16]
94
+ processed_key = CustomParser.parse(base_key)
95
+ decrypted_key = decrypt_and_clean(user_header, processed_key)
96
+ session_key = decrypt_and_clean(data, CustomParser.parse(decrypted_key))
97
+ return session_key
98
+
99
+ # ------------------ API类 ------------------
100
+ HEADERS = {
101
+ 'accept': 'application/json',
102
+ 'accept-language': 'en-US,en;q=0.9',
103
+ 'cache-ts': str(int(time.time() * 1000)),
104
+ 'dnt': '1',
105
+ 'encryption': 'true',
106
+ 'language': 'en',
107
+ 'origin': 'https://www.coinglass.com',
108
+ 'priority': 'u=1, i',
109
+ 'referer': 'https://www.coinglass.com/',
110
+ 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
111
+ 'sec-ch-ua-mobile': '?0',
112
+ 'sec-ch-ua-platform': '"Windows"',
113
+ 'sec-fetch-dest': 'empty',
114
+ 'sec-fetch-mode': 'cors',
115
+ 'sec-fetch-site': 'same-site',
116
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
117
+ }
118
+
119
+ class CoinglassApi:
120
+ def __init__(self) -> None:
121
+ self.session = aiohttp.ClientSession()
122
+
123
+ async def connect(self):
124
+ pass
125
+
126
+ async def dec_data(self, response: aiohttp.ClientResponse) -> Optional[Any]:
127
+ try:
128
+ encrypted_data = (await response.json())['data']
129
+ requests_url = response.url.path
130
+ encrypted_user_header = response.headers.get("user", "HEADERNOTFOUND")
131
+ decrypted = decrypt_coinglass(encrypted_data, encrypted_user_header, requests_url)
132
+ return json.loads(decrypted)
133
+ except Exception as e:
134
+ print(f"解密失败: {e}")
135
+ return None
136
+
137
+ async def fetch_base_klines(self, symbol: str, start_time: datetime, end_time: Optional[datetime] = None, ktype: str = '#coin#oi_kline', interval: str = 'm1') -> Any:
138
+ start_ts = int(start_time.timestamp())
139
+ end_ts = int(end_time.timestamp()) if end_time else int(time.time())
140
+ url = 'https://fapi.coinglass.com/api/v2/kline'
141
+ params = {
142
+ 'symbol': f'{symbol}{ktype}',
143
+ 'interval': interval,
144
+ 'endTime': end_ts,
145
+ 'startTime': start_ts,
146
+ 'minLimit': 'false',
147
+ }
148
+ async with self.session.get(url, params=params, headers=HEADERS) as response:
149
+ if response.status == 200:
150
+ return await self.dec_data(response)
151
+ print(f"请求失败,状态码: {response.status}: {await response.text()}")
152
+ return pd.DataFrame()
153
+
154
+ async def fetch_price_klines(self, symbol: str, start_time: datetime, end_time: Optional[datetime] = None, interval: str = 'm5') -> pd.DataFrame:
155
+ data = await self.fetch_base_klines(symbol, start_time, end_time, '#kline', interval)
156
+ df = pd.DataFrame(data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
157
+ df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
158
+ df['timestamp'] = pd.to_datetime(df['timestamp'], unit="s", utc=True)
159
+ df['symbol'] = symbol
160
+ return df
161
+
162
+ async def fetch_top_account_klines(self, symbol: str, start_time: datetime, end_time: Optional[datetime] = None, interval: str = 'm5') -> Optional[pd.DataFrame]:
163
+ end_ts = int(end_time.timestamp()) if end_time else int(time.time())
164
+ start_ts = int(start_time.timestamp())
165
+ url = 'https://fapi.coinglass.com/api/v2/kline'
166
+ params = {
167
+ 'symbol': f'{symbol}#top_account_kline',
168
+ 'interval': interval,
169
+ 'endTime': end_ts,
170
+ 'startTime': start_ts,
171
+ 'minLimit': 'false',
172
+ }
173
+ async with self.session.get(url, params=params, headers=HEADERS) as response:
174
+ if response.status == 200:
175
+ data = await self.dec_data(response)
176
+ columns = ['timestamp', 'ratio', 'long_ratio', 'short_ratio']
177
+ df = pd.DataFrame(data, columns=columns)
178
+ df['timestamp'] = pd.to_datetime(df['timestamp'], unit="s", utc=True)
179
+ df[['ratio', 'long_ratio', 'short_ratio']] = df[['ratio', 'long_ratio', 'short_ratio']].astype(float)
180
+ df['symbol'] = symbol
181
+ return df
182
+ print(f"请求失败,状态码: {response.status}: {await response.text()}")
183
+ return None
184
+
185
+ async def fetch_oi_klines(self, symbol: str, start_time: datetime, end_time: Optional[datetime] = None, interval: str = 'm5') -> pd.DataFrame:
186
+ data = await self.fetch_base_klines(symbol, start_time, end_time, '#coin#oi_kline', interval)
187
+ df = pd.DataFrame(data, columns=['timestamp', 'open', 'high', 'low', 'close'])
188
+ df[['open', 'high', 'low', 'close']] = df[['open', 'high', 'low', 'close']].astype(float)
189
+ df['timestamp'] = pd.to_datetime(df['timestamp'], unit="s", utc=True)
190
+ df['symbol'] = symbol
191
+ return df
192
+
193
+ async def fetch_liq_klines(self, symbol: str, start_time: datetime, end_time: Optional[datetime] = None, interval: str = 'm5') -> pd.DataFrame:
194
+ data = await self.fetch_base_klines(symbol, start_time, end_time, '#aggregated_liq_kline', interval)
195
+ df = pd.DataFrame(data, columns=['timestamp', 'short_amount', 'long_amount'])
196
+ df[['short_amount', 'long_amount']] = df[['short_amount', 'long_amount']].astype(float)
197
+ df['timestamp'] = pd.to_datetime(df['timestamp'], unit="s", utc=True)
198
+ df['symbol'] = symbol
199
+ return df
200
+
201
+ async def fetch_hyperliquid_top_positions(self) -> Optional[Any]:
202
+ url = 'https://capi.coinglass.com/api/hyperliquid/topPosition'
203
+ async with self.session.get(url, headers=HEADERS) as response:
204
+ if response.status == 200:
205
+ return await self.dec_data(response)
206
+ print(f"请求失败,状态码: {response.status}: {await response.text()}")
207
+ return None
208
+
209
+ async def fetch_tickers(self) -> Optional[Any]:
210
+ url = 'https://fapi.coinglass.com/api/select/coins/tickers'
211
+ params = {'exName': 'Binance'}
212
+ async with self.session.get(url, params=params, headers=HEADERS) as response:
213
+ if response.status == 200:
214
+ return await self.dec_data(response)
215
+ print(f"请求失败,状态码: {response.status}: {await response.text()}")
216
+ return None
217
+
218
+ async def fetch_symbols(self, only_usdt: bool = True) -> List[str]:
219
+ tickers = await self.fetch_tickers()
220
+ if tickers:
221
+ symbols = [ticker['instrument']['instrumentId'] for ticker in tickers]
222
+ if only_usdt:
223
+ symbols = [symbol for symbol in symbols if symbol.endswith('USDT')]
224
+ return symbols
225
+ return []
226
+
227
+ async def stop(self):
228
+ await self.session.close()
229
+
230
+ # ------------------ 主程序 ------------------
231
+ async def main():
232
+ api = CoinglassApi()
233
+ await api.connect()
234
+ df = await api.fetch_price_klines('Binance_BTCUSDT', datetime.now() - timedelta(days=1))
235
+ print(df)
236
+ await api.stop()
237
+
238
+ if __name__ == '__main__':
239
+ asyncio.run(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.1.1
3
+ Version: 0.1.2
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
@@ -13,7 +13,9 @@ Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Topic :: Office/Business :: Financial :: Investment
15
15
  Requires-Python: >=3.8
16
+ Requires-Dist: aiohttp>=3.11.16
16
17
  Requires-Dist: colorama>=0.4.6
18
+ Requires-Dist: cryptography>=44.0.2
17
19
  Requires-Dist: pandas>=2.2.3
18
20
  Requires-Dist: pyecharts>=2.0.8
19
21
  Description-Content-Type: text/markdown
@@ -0,0 +1,8 @@
1
+ hyperquant/__init__.py,sha256=ogwPnRZdsCG5VqMpb_BOtmsKW64G9YmZ7t4AMGttLAE,108
2
+ hyperquant/core.py,sha256=vKv8KElo1eGhr_aw0I-j6ZxPOneDx86KqAoOI-wbq0A,18838
3
+ hyperquant/draw.py,sha256=jweENVzf0nWHogzUliwJHI2PgbeQ3V2H749cMJ256k0,54614
4
+ hyperquant/logkit.py,sha256=WALpXpIA3Ywr5DxKKK3k5EKubZ2h-ISGfc5dUReQUBQ,7795
5
+ hyperquant/datavison/coinglass.py,sha256=6kh1S2R1jINFvneUSzXkFksbQcWmJJNT_dzt0yZ-zxk,10492
6
+ hyperquant-0.1.2.dist-info/METADATA,sha256=svaRs3u6gLXQoz9-_ukmrMZBjxSV9e3y_4bcjLVJzes,4229
7
+ hyperquant-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ hyperquant-0.1.2.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- hyperquant/__init__.py,sha256=NRq3MhT7sETNAP-EQ0y5fYgzZl2eAmo9llun1lojYl0,84
2
- hyperquant/core.py,sha256=vKv8KElo1eGhr_aw0I-j6ZxPOneDx86KqAoOI-wbq0A,18838
3
- hyperquant/draw.py,sha256=jweENVzf0nWHogzUliwJHI2PgbeQ3V2H749cMJ256k0,54614
4
- hyperquant/logkit.py,sha256=WALpXpIA3Ywr5DxKKK3k5EKubZ2h-ISGfc5dUReQUBQ,7795
5
- hyperquant-0.1.1.dist-info/METADATA,sha256=Z2NEV0ntAVgsAc-UDwr_5l4I6aEDjlISa-Qhml6kBCg,4161
6
- hyperquant-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- hyperquant-0.1.1.dist-info/RECORD,,