hyperquant 0.21__py3-none-any.whl → 0.23__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
@@ -2,4 +2,5 @@ from .core import *
2
2
  from .draw import *
3
3
  from .logkit import *
4
4
  from .datavison import *
5
+ from .notikit import *
5
6
  __version__ = "0.1.0"
@@ -0,0 +1,252 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from aiohttp import ClientWebSocketResponse
5
+ import aiohttp
6
+
7
+ from pybotters.store import DataStore
8
+ from pybotters.models.hyperliquid import HyperliquidDataStore
9
+ from typing import TYPE_CHECKING, Awaitable
10
+
11
+ if TYPE_CHECKING:
12
+ from pybotters.typedefs import Item
13
+ from pybotters.ws import ClientWebSocketResponse
14
+
15
+
16
+
17
+ # {'channel': 'orderUpdates', 'data': [{'order': {'coin': 'HYPE', 'side': 'A', 'limitPx': '22.887', 'sz': '1.12', 'oid': 29641480516, 'timestamp': 1746766108031, 'origSz': '1.12', 'reduceOnly': True}, 'status': 'rejected', 'statusTimestamp': 1746766108031}]}
18
+ class OrderStore(DataStore):
19
+ _KEYS = ["oid"]
20
+
21
+ def _onmessage(self, msg: Item) -> None:
22
+
23
+ for rec in msg:
24
+ order = rec["order"]
25
+ item = {
26
+ **order,
27
+ "status": rec.get("status"),
28
+ "px": None,
29
+ 'fee': None,
30
+ "statusTimestamp": rec.get("statusTimestamp"),
31
+ }
32
+
33
+ if item["status"] == "open":
34
+ self._update([item])
35
+ else:
36
+ self._delete([item])
37
+
38
+ class FillStore(DataStore):
39
+ _KEYS = ["oid"]
40
+
41
+ def _onmessage(self, msg: Item) -> None:
42
+ for fill in msg:
43
+ self._update([fill])
44
+
45
+
46
+
47
+ class Account(DataStore):
48
+ _KEYS = ["marginCoin", "value"]
49
+
50
+ def _onmessage(self, data: list[Item]) -> None:
51
+ self._update(
52
+ [
53
+ {
54
+ "marginCoin": 'USDC',
55
+ 'value': float(item['accountValue']),
56
+ 'frozen': float(item['totalMarginUsed']),
57
+ 'available': float(item['accountValue']) - float(item['totalMarginUsed']),
58
+ }
59
+ for item in data
60
+ ]
61
+ )
62
+
63
+ class SpotAccount(DataStore):
64
+
65
+ _KEYS = ["coin"]
66
+
67
+ def _onmessage(self, data: list[Item]) -> None:
68
+ self._update(
69
+ [
70
+ {
71
+ "coin": item['coin'],
72
+ "total": float(item['total']),
73
+ "frozen": float(item['hold']),
74
+ "available": float(item['total']) - float(item['hold']),
75
+ "entryNtl": float(item['entryNtl']),
76
+ }
77
+ for item in data
78
+ ]
79
+ )
80
+
81
+ class PositionStore(DataStore):
82
+ _KEYS = ["coin"]
83
+
84
+ def _onmessage(self, data: list[Item]) -> None:
85
+
86
+ if len(data) == 0 and self.__len__() > 0:
87
+ self._clear()
88
+ elif len(data) > 0:
89
+ self._update([
90
+
91
+ {
92
+ "coin": item['position']['coin'],
93
+ "sz": float(item['position']['szi']),
94
+ "px": float(item['position']['entryPx']),
95
+ 'unpnl': float(item['position']['unrealizedPnl']),
96
+ 'rt': float(item['position']['returnOnEquity']),
97
+ 'lv': int(item['position']['leverage']['value']),
98
+ }
99
+ for item in data
100
+ ])
101
+
102
+
103
+ class MyHyperStore(HyperliquidDataStore):
104
+ ORDER_TYPE = 'orderUpdates'
105
+ WEBDATA2_TYPE = 'webData2'
106
+ ORDER_FILL_TYPE = 'userFills'
107
+
108
+ def _init(self) -> None:
109
+ self._create("orders", datastore_class=OrderStore)
110
+ self._create("account", datastore_class=Account)
111
+ self._create("positions", datastore_class=PositionStore)
112
+ self._create("spot_account", datastore_class=SpotAccount)
113
+ self._create("fills", datastore_class=FillStore)
114
+ super()._init()
115
+
116
+ def _onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
117
+
118
+ if msg.get("channel") == self.ORDER_TYPE:
119
+ self.orders._onmessage(msg.get('data', []))
120
+ elif msg.get("channel") == self.WEBDATA2_TYPE:
121
+ # print(msg.get('data', {}).get('clearinghouseState', {}))
122
+ act_data = msg.get('data', {}).get('clearinghouseState', {}).get('crossMarginSummary', [])
123
+ if act_data:
124
+ self.account._onmessage([act_data])
125
+
126
+ pos_data = msg.get('data', {}).get('clearinghouseState', {}).get('assetPositions', [])
127
+ self.positions._onmessage(pos_data)
128
+
129
+ spot_act_data = msg.get('data', {}).get('spotState', {}).get('balances', [])
130
+ self.spot_account._onmessage(spot_act_data)
131
+
132
+ elif msg.get("channel") == self.ORDER_FILL_TYPE:
133
+ fills = msg.get('data', {}).get('fills', [])
134
+ is_snap = msg.get('data', {}).get('isSnapshot', False)
135
+ if not is_snap:
136
+ self.fills._onmessage(fills)
137
+
138
+ super()._onmessage(msg, ws)
139
+
140
+ async def initialize(self, *aws: tuple[str, Awaitable[aiohttp.ClientResponse]]) -> None:
141
+
142
+ for a in aws:
143
+ method, f = a
144
+ resp = await f
145
+ data = await resp.json()
146
+ if method == "orders":
147
+
148
+ self.orders._onmessage(
149
+ [
150
+ {
151
+ 'order': o,
152
+ 'status': "open",
153
+ 'statusTimestamp': int(time.time() * 1000)
154
+ } for o in data
155
+ ]
156
+ )
157
+
158
+ pass
159
+
160
+ @property
161
+ def orders(self) -> OrderStore:
162
+ """``orders`` data stream.
163
+
164
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions
165
+
166
+ Data structure:
167
+
168
+ .. code:: python
169
+ [
170
+ {
171
+ "coin": "HYPE",
172
+ "side": "A",
173
+ "limitPx": "22.887",
174
+ "sz": "1.12",
175
+ "oid": 29641480516,
176
+ "timestamp": 1746766108031,
177
+ "origSz": "1.12",
178
+ "reduceOnly": True
179
+ "status": "open",
180
+ "statusTimestamp": 1746766108031
181
+ }...
182
+ ]
183
+ """
184
+ return self._get("orders", OrderStore)
185
+ @property
186
+ def account(self) -> Account:
187
+ """``account`` data stream.
188
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions
189
+ Data structure:
190
+ .. code:: python
191
+ [
192
+ {
193
+ "marginCoin": 'USDC',
194
+ 'value': float(item['accountValue']),
195
+ 'frozen': float(item['totalMarginUsed']),
196
+ 'available': float(item['accountValue']) - float(item['totalMarginUsed']),
197
+ }...
198
+ ]
199
+ """
200
+ return self._get("account", Account)
201
+
202
+ @property
203
+ def positions(self) -> PositionStore:
204
+ return self._get("positions", PositionStore)
205
+
206
+ @property
207
+ def spot_account(self) -> SpotAccount:
208
+ """``spot_account`` data stream.
209
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions
210
+ Data structure:
211
+ .. code:: python
212
+ [
213
+ {
214
+ "coin": 'FEUSD',
215
+ "sz": "21.0",
216
+ "px": "0.9719",
217
+ "unpnl": "0.0",
218
+ "rt": "0.0",
219
+ "lv": 1,
220
+ }...
221
+ ]
222
+ """
223
+ return self._get("spot_account", SpotAccount)
224
+
225
+ @property
226
+ def fills(self) -> FillStore:
227
+ """``fills`` data stream.
228
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions
229
+ Data structure:
230
+ .. code:: python
231
+ [
232
+ {
233
+ "coin": 'FEUSD',
234
+ "px": "0.9719",
235
+ "sz": "21.0",
236
+ "side": 'buy',
237
+ "time": 1679999999999,
238
+ "startPosition": '0.0',
239
+ "dir": 'buy',
240
+ "closedPnl": '0.0',
241
+ "hash": '0x123456789abcdef',
242
+ "oid": 123456789,
243
+ "crossed": True,
244
+ "fee": '-0.0001',
245
+ "tid": 987654321,
246
+ "liquidation": None,
247
+ "feeToken": 'USDC',
248
+ }...
249
+ ]
250
+ """
251
+ return self._get("fills", FillStore)
252
+
@@ -0,0 +1,48 @@
1
+ from typing import TypedDict, Dict
2
+
3
+ class Leverage(TypedDict):
4
+ rawUsd: str
5
+ type: str
6
+ value: int
7
+
8
+ class CumFunding(TypedDict):
9
+ allTime: str
10
+ sinceChange: str
11
+ sinceOpen: str
12
+
13
+ class Position(TypedDict):
14
+ coin: str
15
+ cumFunding: CumFunding
16
+ entryPx: str
17
+ leverage: Leverage
18
+ liquidationPx: str
19
+ marginUsed: str
20
+ maxLeverage: int
21
+ positionValue: str
22
+ returnOnEquity: str
23
+ szi: str
24
+ unrealizedPnl: str
25
+
26
+ class AssetPosition(TypedDict):
27
+ position: Position
28
+ type: str
29
+
30
+ class CrossMarginSummary(TypedDict):
31
+ accountValue: str
32
+ totalMarginUsed: str
33
+ totalNtlPos: str
34
+ totalRawUsd: str
35
+
36
+ class MarginSummary(TypedDict):
37
+ accountValue: str
38
+ totalMarginUsed: str
39
+ totalNtlPos: str
40
+ totalRawUsd: str
41
+
42
+ class AccountBalance(TypedDict):
43
+ assetPositions: list[AssetPosition]
44
+ crossMaintenanceMarginUsed: str
45
+ crossMarginSummary: CrossMarginSummary
46
+ marginSummary: MarginSummary
47
+ time: int
48
+ withdrawable: str
@@ -0,0 +1,177 @@
1
+ import aiohttp
2
+ import asyncio
3
+ import pandas as pd
4
+ import time
5
+ from datetime import datetime, timezone
6
+ from ._util import _to_milliseconds # 确保时间转换函数可用
7
+
8
+ class OKX:
9
+ def __init__(self) -> None:
10
+ self.session = aiohttp.ClientSession()
11
+ self.base_url = "https://www.okx.com/api/v5/market"
12
+
13
+ async def get_klines(self, symbol: str, interval: str, start_time, end_time=None, limit: int = 100):
14
+ """
15
+ 获取 OKX 永续合约 K 线数据,带时间过滤,从 end_time 向 start_time 方向翻页。
16
+
17
+ :param symbol: 交易对, 如 'BTC-USDT'
18
+ :param interval: K 线间隔, 如 '1m', '15m', '1H', '4H', '1D'
19
+ :param start_time: 开始时间(datetime 或 毫秒)
20
+ :param end_time: 结束时间(datetime 或 毫秒), 可选
21
+ :param limit: 每次请求最大数量(OKX 最大 300)
22
+ :return: DataFrame 格式的 K 线数据,按时间升序
23
+ """
24
+ if 'h' in interval or 'd' in interval:
25
+ interval = interval.upper() # 确保间隔是大写格式
26
+
27
+ url = f"{self.base_url}/history-candles"
28
+ all_rows = []
29
+ # 转换 start_time 和 end_time 到毫秒时间戳
30
+ if isinstance(start_time, (int, float)):
31
+ start_ts = int(start_time)
32
+ else:
33
+ # 处理 datetime 对象
34
+ start_ts = int(start_time.timestamp() * 1000)
35
+ if end_time:
36
+ if isinstance(end_time, (int, float)):
37
+ end_ts = int(end_time)
38
+ else:
39
+ end_ts = int(end_time.timestamp() * 1000)
40
+ else:
41
+ # 如果没有指定结束时间,就用当前时间戳
42
+ end_ts = int(time.time() * 1000)
43
+
44
+ # 每次请求最多返回 limit=300
45
+ batch_limit = min(limit, 300)
46
+ # 初始 after 参数为 end_ts,向过去翻页
47
+ current_after = end_ts
48
+
49
+ while True:
50
+ params = {
51
+ "instId": symbol,
52
+ "bar": interval,
53
+ "limit": str(batch_limit),
54
+ "after": str(current_after)
55
+ }
56
+ # 发送请求
57
+ async with self.session.get(url, params=params) as resp:
58
+ data = await resp.json()
59
+ if not data or data.get("code") != "0" or not data.get("data"):
60
+ # 返回错误或无数据,结束循环
61
+ break
62
+ buf = data["data"] # 每条是 [ts, o, h, l, c, vol, volCcy, volCcyQuote, confirm]
63
+
64
+ # 本批数据按时间从新到旧排列, 最后一条是最旧的
65
+ rows_this_batch = []
66
+ for item in buf:
67
+ ts = int(item[0])
68
+ # 如果已经早于 start_ts,就跳过,并认为后面更旧,也可以结束循环
69
+ if ts < start_ts:
70
+ continue
71
+ # 如果某些条目时间超出 end_ts,也跳过
72
+ if ts > end_ts:
73
+ continue
74
+ # 解析数值字段
75
+ dt = pd.to_datetime(ts, unit='ms', utc=True)
76
+ o = float(item[1]); h = float(item[2]); l = float(item[3]); c = float(item[4]); vol = float(item[5])
77
+ # 按需把每个 K 线封装为字典,后续转换为 DataFrame
78
+ rows_this_batch.append({
79
+ "symbol": symbol,
80
+ "open_time": dt,
81
+ "open": o,
82
+ "high": h,
83
+ "low": l,
84
+ "close": c,
85
+ "volume": vol,
86
+ "interval": interval,
87
+ "confirm": item[8]
88
+ })
89
+
90
+ if not rows_this_batch:
91
+ # 本批没有符合时间范围的数据,直接结束
92
+ break
93
+
94
+ # 累积本批符合条件的行
95
+ all_rows.extend(rows_this_batch)
96
+
97
+ # 检查是否到达 start_ts 之前:buf 最后一项是最旧
98
+ oldest_ts = int(buf[-1][0])
99
+ if oldest_ts < start_ts:
100
+ # 已经翻到 start_time 范围之前,结束循环
101
+ break
102
+
103
+ # 否则,更新 after = oldest_ts,继续向过去翻页
104
+ current_after = oldest_ts
105
+ # 为了不触发速率限制,稍做休眠(根据需要可以调整或删除)
106
+
107
+ # 如果累积到数据,则转换为 DataFrame;否则返回空 DataFrame
108
+ if all_rows:
109
+ df = pd.DataFrame(all_rows)
110
+ # 去重、按时间排序
111
+ df.drop_duplicates(subset=["open_time"], inplace=True)
112
+ df.sort_values("open_time", inplace=True)
113
+ df.reset_index(drop=True, inplace=True)
114
+ return df
115
+ else:
116
+ return pd.DataFrame()
117
+
118
+ async def get_index_klines(self, pair: str, interval: str, start_time, end_time=None, limit: int = 100):
119
+ """
120
+ 获取OKX指数K线数据(自动分批)
121
+
122
+ :param pair: 指数名称, 如 'BTC-USD'
123
+ :param interval: K线间隔, 如 '1m', '1H', '1D'
124
+ :param start_time: 开始时间(毫秒时间戳/datetime/date)
125
+ :param end_time: 结束时间(毫秒时间戳/datetime/date)
126
+ :param limit: 每次请求最大数量(OKX最大300)
127
+ :return: DataFrame格式的指数K线
128
+ """
129
+ url = f"{self.base_url}/index-candles"
130
+ all_klines = []
131
+ ms_start = _to_milliseconds(start_time)
132
+ ms_end = _to_milliseconds(end_time) if end_time else None
133
+
134
+ params = {
135
+ "instId": pair,
136
+ "bar": interval,
137
+ "limit": min(limit, 300),
138
+ "after": ms_start
139
+ }
140
+ if ms_end:
141
+ params["before"] = ms_end
142
+
143
+ while True:
144
+ async with self.session.get(url, params=params) as resp:
145
+ data = await resp.json()
146
+ if data['code'] != "0":
147
+ raise Exception(f"OKX API Error: {data['msg']} (Code {data['code']})")
148
+
149
+ klines = data['data']
150
+ if not klines:
151
+ break
152
+
153
+ all_klines.extend(klines)
154
+
155
+ if len(klines) < params["limit"]:
156
+ break
157
+
158
+ last_ts = int(klines[-1][0])
159
+ params["after"] = last_ts
160
+
161
+ # 数据转换
162
+ columns = ["open_time", "open", "high", "low", "close", "confirm"]
163
+ df = pd.DataFrame(all_klines, columns=columns)
164
+
165
+ df["open_time"] = pd.to_datetime(df["open_time"].astype(int), unit="ms")
166
+ num_cols = ["open", "high", "low", "close"]
167
+ df[num_cols] = df[num_cols].apply(pd.to_numeric, errors="coerce")
168
+
169
+ return df.sort_values("open_time").reset_index(drop=True)
170
+
171
+ async def close(self):
172
+ """关闭会话"""
173
+ await self.session.close()
174
+
175
+ # 使用示例
176
+ # async with OKXSwap() as okx:
177
+ # df = await okx.get_klines("BTC-USDT", "1H", datetime(2023,1,1))
hyperquant/notikit.py ADDED
@@ -0,0 +1,104 @@
1
+ """
2
+ 通知类的功能简单封装,非必要别修改 :)
3
+ 只要知道怎么使用以下函数:
4
+ - send_wecom_msg
5
+ - send_wecom_img
6
+
7
+ Binance期现套利 | 邢不行 | 2024分享会
8
+ author: 邢不行
9
+ 微信: xbx6660
10
+ """
11
+ import base64
12
+ import hashlib
13
+ import os.path
14
+ import requests
15
+ import json
16
+ import traceback
17
+ from datetime import datetime
18
+
19
+ from hyperquant.logkit import get_logger
20
+ logger = get_logger('notikit', './data/logs/notikit.log', show_time=True)
21
+
22
+
23
+
24
+ proxy = {}
25
+
26
+
27
+ def handle_exception(e: Exception, msg: str = '') -> None:
28
+ logger.error(f"{msg}:{e}")
29
+ logger.error(e)
30
+ logger.error(traceback.format_exc())
31
+
32
+
33
+ # 企业微信通知
34
+ def send_wecom_msg(content, webhook_url):
35
+ if not webhook_url:
36
+ logger.warning('未配置wecom_webhook_url,不发送信息')
37
+ return
38
+ if not content:
39
+ logger.warning('未配置content,不发送信息')
40
+ return
41
+ try:
42
+ data = {
43
+ "msgtype": "text",
44
+ "text": {
45
+ "content": content + '\n' + datetime.now().strftime("%Y-%m-%d %H:%M:%S")
46
+ }
47
+ }
48
+ r = requests.post(webhook_url, data=json.dumps(data), timeout=10, proxies=proxy)
49
+ logger.info(f'调用企业微信接口返回: {r.text}')
50
+ logger.ok('成功发送企业微信')
51
+ except Exception as e:
52
+ handle_exception(e, '发送企业微信失败')
53
+
54
+
55
+ # 上传图片,解析bytes
56
+ class MyEncoder(json.JSONEncoder):
57
+ def default(self, obj):
58
+ """
59
+ 只要检查到了是bytes类型的数据就把它转为str类型
60
+ :param obj:
61
+ :return:
62
+ """
63
+ if isinstance(obj, bytes):
64
+ return str(obj, encoding='utf-8')
65
+ return json.JSONEncoder.default(self, obj)
66
+
67
+
68
+ # 企业微信发送图片
69
+ def send_wecom_img(file_path, webhook_url):
70
+ """
71
+ 企业微信发送图片
72
+ :param file_path: 图片地址
73
+ :param webhook_url: 企业微信webhook网址
74
+ :return:
75
+ """
76
+ if not os.path.exists(file_path):
77
+ logger.warning('找不到图片')
78
+ return
79
+ if not webhook_url:
80
+ logger.warning('未配置wecom_webhook_url,不发送信息')
81
+ return
82
+ try:
83
+ with open(file_path, 'rb') as f:
84
+ image_content = f.read()
85
+ image_base64 = base64.b64encode(image_content).decode('utf-8')
86
+ md5 = hashlib.md5()
87
+ md5.update(image_content)
88
+ image_md5 = md5.hexdigest()
89
+ data = {
90
+ 'msgtype': 'image',
91
+ 'image': {
92
+ 'base64': image_base64,
93
+ 'md5': image_md5
94
+ }
95
+ }
96
+ # 服务器上传bytes图片的时候,json.dumps解析会出错,需要自己手动去转一下
97
+ r = requests.post(webhook_url, data=json.dumps(data, cls=MyEncoder, indent=4), timeout=10, proxies=proxy)
98
+ logger.info(f'调用企业微信接口返回: {r.text}')
99
+ logger.ok('成功发送企业微信图片')
100
+ except Exception as e:
101
+ handle_exception(e, '发送企业微信图片失败')
102
+ finally:
103
+ if os.path.exists(file_path):
104
+ os.remove(file_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.21
3
+ Version: 0.23
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,12 +1,16 @@
1
- hyperquant/__init__.py,sha256=ogwPnRZdsCG5VqMpb_BOtmsKW64G9YmZ7t4AMGttLAE,108
1
+ hyperquant/__init__.py,sha256=gUuAPVpg5k8X_dpda5OpqmMyZ-ZXNQq-xwx-6JR5Jr4,131
2
2
  hyperquant/core.py,sha256=vKv8KElo1eGhr_aw0I-j6ZxPOneDx86KqAoOI-wbq0A,18838
3
3
  hyperquant/db.py,sha256=i2TjkCbmH4Uxo7UTDvOYBfy973gLcGexdzuT_YcSeIE,6678
4
4
  hyperquant/draw.py,sha256=up_lQ3pHeVLoNOyh9vPjgNwjD0M-6_IetSGviQUgjhY,54624
5
5
  hyperquant/logkit.py,sha256=WALpXpIA3Ywr5DxKKK3k5EKubZ2h-ISGfc5dUReQUBQ,7795
6
+ hyperquant/notikit.py,sha256=IztRn_qSBPf9IbDeQaruW_QSqs7TMB3h-V6obIuA1Mo,3090
6
7
  hyperquant/broker/hyperliquid.py,sha256=mSBVfmjcv6ciI1vWrmHYwBOTHrg-NQrwcyVFUXYEgVw,21998
8
+ hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb84D8,8296
9
+ hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
7
10
  hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw,533
8
11
  hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
9
12
  hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
10
- hyperquant-0.21.dist-info/METADATA,sha256=DlfI1zKdTkYLMfrdrrJrXGWCD-8hMxm5U3bKUZH3zhg,4317
11
- hyperquant-0.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
- hyperquant-0.21.dist-info/RECORD,,
13
+ hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
14
+ hyperquant-0.23.dist-info/METADATA,sha256=A3Z2liUIAYJPZZOimZryq41namP4dZbYlPPLbdiv1BU,4317
15
+ hyperquant-0.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ hyperquant-0.23.dist-info/RECORD,,