hyperquant 1.21__py3-none-any.whl → 1.22__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.

Potentially problematic release.


This version of hyperquant might be problematic. Click here for more details.

hyperquant/broker/auth.py CHANGED
@@ -249,6 +249,102 @@ class Auth:
249
249
 
250
250
  return args
251
251
 
252
+ @staticmethod
253
+ def bitmart_api(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]:
254
+ """Bitmart OpenAPI (futures v2) signing for api-cloud-v2.bitmart.com.
255
+
256
+ Spec per docs:
257
+ X-BM-SIGN = hex_lower(HMAC_SHA256(secret, timestamp + '#' + memo + '#' + payload_string))
258
+ - For POST: payload_string is the JSON body string
259
+ - For GET: payload_string is the query string (if any), otherwise empty
260
+ Headers required: X-BM-KEY, X-BM-TIMESTAMP, X-BM-SIGN
261
+ """
262
+ method: str = args[0]
263
+ url: URL = args[1]
264
+ headers: CIMultiDict = kwargs["headers"]
265
+
266
+ session = kwargs["session"]
267
+ try:
268
+ api_name = pybotters.auth.Hosts.items[url.host].name
269
+ except (KeyError, AttributeError):
270
+ api_name = url.host
271
+
272
+ creds = session.__dict__.get("_apis", {}).get(api_name)
273
+ if not creds or len(creds) < 3:
274
+ raise RuntimeError("Bitmart API credentials (access_key, secret, memo) are required")
275
+
276
+ access_key = creds[0]
277
+ secret = creds[1]
278
+ memo = creds[2]
279
+ if isinstance(secret, str):
280
+ secret_bytes = secret.encode("utf-8")
281
+ else:
282
+ secret_bytes = secret
283
+ if isinstance(memo, bytes):
284
+ memo = memo.decode("utf-8")
285
+
286
+ timestamp = str(int(time.time() * 1000))
287
+ method_upper = method.upper()
288
+
289
+ # Build query string
290
+ params = kwargs.get("params")
291
+ if isinstance(params, dict) and params:
292
+ query_items = [f"{k}={v}" for k, v in params.items() if v is not None]
293
+ query_string = "&".join(query_items)
294
+ else:
295
+ query_string = url.query_string
296
+
297
+ # Build body string for signing and ensure sending matches signature
298
+ body = None
299
+ body_str = ""
300
+ if method_upper == "GET":
301
+ body_str = query_string or ""
302
+ else:
303
+ # Prefer original JSON object if present for deterministic signing
304
+ if kwargs.get("json") is not None:
305
+ body = kwargs.get("json")
306
+ else:
307
+ body = kwargs.get("data")
308
+
309
+ # If upstream already turned JSON into JsonPayload, extract its value
310
+ if isinstance(body, JsonPayload):
311
+ body_value = getattr(body, "_value", None)
312
+ else:
313
+ body_value = body
314
+
315
+ if isinstance(body_value, (dict, list)):
316
+ # Compact JSON to avoid whitespace discrepancies and sign exact bytes we send
317
+ body_str = pyjson.dumps(body_value, separators=(",", ":"), ensure_ascii=False)
318
+ kwargs["data"] = body_str
319
+ kwargs.pop("json", None)
320
+ elif isinstance(body_value, (str, bytes)):
321
+ # Sign and send exactly this string/bytes
322
+ body_str = body_value.decode("utf-8") if isinstance(body_value, bytes) else body_value
323
+ kwargs["data"] = body_str
324
+ kwargs.pop("json", None)
325
+ elif body_value is None:
326
+ body_str = ""
327
+ else:
328
+ # Fallback: string conversion (should still be JSON-like)
329
+ body_str = str(body_value)
330
+ kwargs["data"] = body_str
331
+ kwargs.pop("json", None)
332
+
333
+ # Prehash format: timestamp#memo#payload
334
+ message = f"{timestamp}#{memo}#{body_str}"
335
+ signature_hex = hmac.new(secret_bytes, message.encode("utf-8"), hashlib.sha256).hexdigest()
336
+
337
+ headers.update(
338
+ {
339
+ "X-BM-KEY": access_key,
340
+ "X-BM-TIMESTAMP": timestamp,
341
+ "X-BM-SIGN": signature_hex,
342
+ "Content-Type": "application/json; charset=UTF-8",
343
+ }
344
+ )
345
+
346
+ return args
347
+
252
348
  @staticmethod
253
349
  def coinw(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]:
254
350
  method: str = args[0]
@@ -348,4 +444,8 @@ pybotters.auth.Hosts.items["api.coinw.com"] = pybotters.auth.Item(
348
444
 
349
445
  pybotters.auth.Hosts.items["derivatives.bitmart.com"] = pybotters.auth.Item(
350
446
  "bitmart", Auth.bitmart
351
- )
447
+ )
448
+
449
+ pybotters.auth.Hosts.items["api-cloud-v2.bitmart.com"] = pybotters.auth.Item(
450
+ "bitmart_api", Auth.bitmart_api
451
+ )
@@ -10,6 +10,41 @@ import pybotters
10
10
 
11
11
  from .models.bitmart import BitmartDataStore
12
12
 
13
+
14
+ class Book():
15
+ def __init__(self):
16
+ self.limit: int | None = None
17
+ self.store = {}
18
+
19
+ def on_message(self, msg: dict[str, Any], ws=None) -> None:
20
+ data = msg.get("data")
21
+ if not isinstance(data, dict):
22
+ return
23
+ symbol = data.get("symbol")
24
+ self.store[symbol] = data
25
+
26
+ def find(self, query: dict[str, Any]) -> dict[str, Any] | None:
27
+ s = query.get("s")
28
+ S = query.get("S")
29
+ item = self.store.get(s)
30
+ if item:
31
+ if S == "a":
32
+ return [{"s": s, "S": "a", "p": item["asks"][0]['price'], "q": item["asks"][0]['vol']}]
33
+ elif S == "b":
34
+ return [{"s": s, "S": "b", "p": item["bids"][0]['price'], "q": item["bids"][0]['vol']}]
35
+ else:
36
+ return []
37
+
38
+ class BitmartDataStore2(BitmartDataStore):
39
+ def _init(self):
40
+ self.bk = Book()
41
+ return super()._init()
42
+
43
+ @property
44
+ def book(self) -> Book:
45
+ return self.bk
46
+
47
+
13
48
  class Bitmart:
14
49
  """Bitmart 合约交易(REST + WebSocket)。"""
15
50
 
@@ -24,17 +59,18 @@ class Bitmart:
24
59
  apis: str = None
25
60
  ) -> None:
26
61
  self.client = client
27
- self.store = BitmartDataStore()
62
+ self.store = BitmartDataStore2()
28
63
 
29
64
  self.public_api = public_api or "https://contract-v2.bitmart.com"
30
65
  self.private_api = "https://derivatives.bitmart.com"
31
66
  self.forward_api = f'{self.private_api}/gw-api/contract-tiger/forward'
32
67
  self.ws_url = ws_url or "wss://contract-ws-v2.bitmart.com/v1/ifcontract/realTime"
33
68
  self.api_ws_url = "wss://openapi-ws-v2.bitmart.com/api?protocol=1.1"
34
-
69
+ self.api_url = "https://api-cloud-v2.bitmart.com"
35
70
  self.account_index = account_index
36
71
  self.apis = apis
37
72
  self.symbol_to_contract_id: dict[str, str] = {}
73
+ self.book = Book()
38
74
 
39
75
  async def __aenter__(self) -> "Bitmart":
40
76
  await self.update("detail")
@@ -244,7 +280,6 @@ class Bitmart:
244
280
  symbol = entry.get("name") or entry.get("display_name")
245
281
  if contract_id is None or symbol is None:
246
282
  continue
247
- self.store.book.id_to_symbol[str(contract_id)] = str(symbol)
248
283
 
249
284
  if "orders" in results:
250
285
  resp = results["orders"]
@@ -280,69 +315,41 @@ class Bitmart:
280
315
  self,
281
316
  symbols: Sequence[str] | str,
282
317
  *,
283
- depth: str = "Depth",
284
318
  depth_limit: int | None = None,
285
- use_api_ws: bool = True,
286
319
  ) -> pybotters.ws.WebSocketApp:
287
320
  """Subscribe order book channel(s)."""
288
321
 
289
322
  if isinstance(symbols, str):
290
323
  symbols = [symbols]
324
+
291
325
 
292
326
  if not symbols:
293
327
  raise ValueError("symbols must not be empty")
294
328
  if depth_limit is not None:
295
329
  self.store.book.limit = depth_limit
296
- if not use_api_ws:
297
- missing = [sym for sym in symbols if self.get_contract_id(sym) is None]
298
- if missing:
299
- await self.update("detail")
300
- still_missing = [sym for sym in missing if self.get_contract_id(sym) is None]
301
- if still_missing:
302
- raise ValueError(f"Unknown symbols: {', '.join(still_missing)}")
303
-
304
-
305
-
306
- channels: list[str] = []
307
- for symbol in symbols:
308
- contract_id = self.get_contract_id(symbol)
309
- if contract_id is None:
310
- continue
311
- self.store.book.id_to_symbol[str(contract_id)] = symbol
312
- channels.append(f"{depth}:{contract_id}")
313
-
314
- if not channels:
315
- raise ValueError("No channels resolved for subscription")
316
-
317
- payload = {"action": "subscribe", "args": channels}
318
- # print(payload)
330
+
331
+ hdlr_json = self.store.book.on_message
319
332
 
320
- ws_app = self.client.ws_connect(
321
- self.api_ws_url,
322
- send_json=payload,
323
- hdlr_json=self.store.onmessage,
324
- )
325
- else:
326
- channels: list[str] = []
327
- for symbol in symbols:
328
- channels.append(f"futures/depthAll5:{symbol}@100ms")
333
+ channels: list[str] = []
334
+ for symbol in symbols:
335
+ channels.append(f"futures/depthAll5:{symbol}@100ms")
329
336
 
330
- if not channels:
331
- raise ValueError("No channels resolved for subscription")
337
+ if not channels:
338
+ raise ValueError("No channels resolved for subscription")
332
339
 
333
- payload = {"action": "subscribe", "args": channels}
334
- # print(payload)
340
+ payload = {"action": "subscribe", "args": channels}
335
341
 
336
- ws_app = self.client.ws_connect(
337
- self.api_ws_url,
338
- send_json=payload,
339
- hdlr_json=self.store.onmessage,
340
- autoping=False,
341
- )
342
+ ws_app = self.client.ws_connect(
343
+ self.api_ws_url,
344
+ send_json=payload,
345
+ hdlr_json=hdlr_json,
346
+ autoping=False,
347
+ )
342
348
 
343
349
  await ws_app._event.wait()
344
350
  return ws_app
345
351
 
352
+
346
353
  def gen_order_id(self):
347
354
  ts = int(time.time() * 1000) # 13位毫秒时间戳
348
355
  rand = random.randint(100000, 999999) # 6位随机数
@@ -364,6 +371,7 @@ class Bitmart:
364
371
  trigger_price: float | None = None,
365
372
  custom_id: int | str | None = None,
366
373
  extra_params: dict[str, Any] | None = None,
374
+ use_api: bool = False,
367
375
  ) -> int:
368
376
  """Submit an order via ``submitOrder``.
369
377
  返回值: order_id (int)
@@ -462,42 +470,124 @@ class Bitmart:
462
470
  "open_type",
463
471
  )
464
472
 
465
- payload: dict[str, Any] = {
466
- "place_all_order": False,
467
- "contract_id": contract_id_int,
468
- "category": category,
469
- "price": price_fmt,
470
- "vol": contracts_int,
471
- "way": way_value,
472
- "mode": mode_value,
473
- "open_type": open_type_value,
474
- "leverage": leverage,
475
- "reverse_vol": reverse_vol,
476
- }
473
+ if use_api:
474
+ # Official API path
475
+ order_type_str = "limit" if category == 1 else "market"
476
+ open_type_str = "cross" if open_type_value == 1 else "isolated"
477
+ client_oid = str(custom_id or self.gen_order_id())
478
+ api_payload: dict[str, Any] = {
479
+ "symbol": symbol,
480
+ "client_order_id": client_oid,
481
+ "side": way_value,
482
+ "type": order_type_str,
483
+ "mode": mode_value,
484
+ "leverage": str(leverage),
485
+ "open_type": open_type_str,
486
+ "size": int(contracts_int),
487
+ }
488
+ if order_type_str == "limit":
489
+ api_payload["price"] = price_fmt
490
+ if extra_params:
491
+ api_payload.update(extra_params)
492
+ # Ensure leverage is synchronized via official API before placing order
493
+ try:
494
+ lev_payload = {
495
+ "symbol": symbol,
496
+ "leverage": str(leverage),
497
+ "open_type": open_type_str,
498
+ }
499
+ res_lev = await self.client.post(
500
+ f"{self.api_url}/contract/private/submit-leverage",
501
+ json=lev_payload,
502
+ )
503
+ txt_lev = await res_lev.text()
504
+ try:
505
+ resp_lev = json.loads(txt_lev)
506
+ if resp_lev.get("code") != 1000:
507
+ # ignore and proceed; order may still pass
508
+ pass
509
+ except json.JSONDecodeError:
510
+ pass
511
+ await asyncio.sleep(0.05)
512
+ except Exception:
513
+ pass
514
+
515
+ res = await self.client.post(
516
+ f"{self.api_url}/contract/private/submit-order",
517
+ json=api_payload,
518
+ )
519
+ # Parse response (some errors may return text/plain containing JSON)
520
+ text = await res.text()
521
+ try:
522
+ resp = json.loads(text)
523
+ except json.JSONDecodeError:
524
+ raise ValueError(f"Bitmart API submit-order non-json response: {text[:200]}")
525
+ if resp.get("code") != 1000:
526
+ # Auto-sync leverage once if required, then retry once
527
+ if resp.get("code") in (40012,):
528
+ try:
529
+ # Retry leverage sync via official API then retry the order
530
+ lev_payload = {
531
+ "symbol": symbol,
532
+ "leverage": str(leverage),
533
+ "open_type": open_type_str,
534
+ }
535
+ await self.client.post(
536
+ f"{self.api_url}/contract/private/submit-leverage",
537
+ json=lev_payload,
538
+ )
539
+ await asyncio.sleep(0.05)
540
+ res2 = await self.client.post(
541
+ f"{self.api_url}/contract/private/submit-order",
542
+ json=api_payload,
543
+ )
544
+ text2 = await res2.text()
545
+ try:
546
+ resp2 = json.loads(text2)
547
+ except json.JSONDecodeError:
548
+ raise ValueError(
549
+ f"Bitmart API submit-order non-json response: {text2[:200]}"
550
+ )
551
+ if resp2.get("code") == 1000:
552
+ return resp2.get("data", {}).get("order_id")
553
+ else:
554
+ raise ValueError(f"Bitmart API submit-order error: {resp2}")
555
+ except Exception:
556
+ # Fall through to raise original error if sync failed
557
+ pass
558
+ raise ValueError(f"Bitmart API submit-order error: {resp}")
559
+ return resp.get("data", {}).get("order_id")
560
+ else:
561
+ payload: dict[str, Any] = {
562
+ "place_all_order": False,
563
+ "contract_id": contract_id_int,
564
+ "category": category,
565
+ "price": price_fmt,
566
+ "vol": contracts_int,
567
+ "way": way_value,
568
+ "mode": mode_value,
569
+ "open_type": open_type_value,
570
+ "leverage": leverage,
571
+ "reverse_vol": reverse_vol,
572
+ }
477
573
 
478
- if trigger_price is not None:
479
- payload["trigger_price"] = trigger_price
574
+ if trigger_price is not None:
575
+ payload["trigger_price"] = trigger_price
480
576
 
481
- payload["custom_id"] = custom_id or self.gen_order_id()
577
+ payload["custom_id"] = custom_id or self.gen_order_id()
482
578
 
483
- if extra_params:
484
- payload.update(extra_params)
485
-
486
- # print(payload)
487
- # exit()
488
-
489
- res = await self.client.post(
490
- f"{self.forward_api}/v1/ifcontract/submitOrder",
491
- json=payload,
492
- )
493
- resp = await res.json()
494
-
495
- if resp.get("success") is False or resp.get("errno") not in (None, "OK"):
496
- raise ValueError(f"Bitmart submitOrder error: {resp}")
497
-
498
- # {"errno":"OK","message":"Success","data":{"order_id":3000236525013551},"success":true}
499
- # 直接取order_id返回
500
- return resp.get("data", {}).get("order_id")
579
+ if extra_params:
580
+ payload.update(extra_params)
581
+
582
+ res = await self.client.post(
583
+ f"{self.forward_api}/v1/ifcontract/submitOrder",
584
+ json=payload,
585
+ )
586
+ resp = await res.json()
587
+
588
+ if resp.get("success") is False or resp.get("errno") not in (None, "OK"):
589
+ raise ValueError(f"Bitmart submitOrder error: {resp}")
590
+ return resp.get("data", {}).get("order_id")
501
591
 
502
592
  async def cancel_order(
503
593
  self,
@@ -98,29 +98,11 @@ class Book(DataStore):
98
98
  self._last_update = ms_t
99
99
 
100
100
  def _on_message_api(self, msg: dict[str, Any]) -> None:
101
- # {
102
- # "data": {
103
- # "symbol": "BTCUSDT",
104
- # "asks": [
105
- # {
106
- # "price": "70294.4",
107
- # "vol": "455"
108
- # }
109
- # ],
110
- # "bids": [
111
- # {
112
- # "price": "70293.9",
113
- # "vol": "1856"
114
- # }
115
- # ],
116
- # "ms_t": 1730399750402
117
- # },
118
- # "group": "futures/depthAll20:BTCUSDT@200ms"
119
- # }
120
101
  data = msg.get("data")
121
102
  if not isinstance(data, dict):
122
103
  return
123
- symbol = data.get("symbol")
104
+ # Some callers embed symbol at top-level; prefer msg["symbol"] when present
105
+ symbol = msg.get("symbol") or data.get("symbol")
124
106
  asks = data.get("asks") or []
125
107
  bids = data.get("bids") or []
126
108
  if self.limit:
@@ -128,12 +110,19 @@ class Book(DataStore):
128
110
  bids = bids[: self.limit]
129
111
 
130
112
  self._find_and_delete({'s': symbol})
113
+ # OpenAPI order book arrays are typically [price, size, timestamp]
114
+ def _normalize_level(level: Any) -> tuple[str, str]:
115
+ if isinstance(level, dict):
116
+ return str(level.get("price", "0")), str(level.get("vol", "0"))
117
+ if isinstance(level, (list, tuple)) and len(level) >= 2:
118
+ return str(level[0]), str(level[1])
119
+ return "0", "0"
120
+
131
121
  self._update([
132
122
  self._make_entry(
133
123
  symbol,
134
124
  "a",
135
- entry.get("price", '0'),
136
- entry.get("vol", '0'),
125
+ *_normalize_level(entry),
137
126
  )
138
127
  for entry in asks
139
128
  ])
@@ -141,11 +130,11 @@ class Book(DataStore):
141
130
  self._make_entry(
142
131
  symbol,
143
132
  "b",
144
- entry.get("price", '0'),
145
- entry.get("vol", '0'),
133
+ *_normalize_level(entry),
146
134
  )
147
135
  for entry in bids
148
136
  ])
137
+
149
138
 
150
139
  def sorted(self, query: Item | None = None, limit: int | None = None) -> dict[str, list[Item]]:
151
140
  return self._sorted(
@@ -325,7 +314,7 @@ class BitmartDataStore(DataStoreCollection):
325
314
  self._create("positions", datastore_class=Positions)
326
315
  self._create("balances", datastore_class=Balances)
327
316
  self._create("ticker", datastore_class=Ticker)
328
-
317
+
329
318
  def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
330
319
  if isinstance(msg, dict):
331
320
  group = msg.get("group")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 1.21
3
+ Version: 1.22
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
@@ -19,6 +19,7 @@ Requires-Dist: cryptography>=44.0.2
19
19
  Requires-Dist: curl-cffi>=0.13.0
20
20
  Requires-Dist: duckdb>=1.2.2
21
21
  Requires-Dist: lighter-sdk
22
+ Requires-Dist: numba>=0.62.1
22
23
  Requires-Dist: numpy>=1.21.0
23
24
  Requires-Dist: pandas>=2.2.3
24
25
  Requires-Dist: pybotters>=1.9.1
@@ -4,9 +4,9 @@ hyperquant/db.py,sha256=i2TjkCbmH4Uxo7UTDvOYBfy973gLcGexdzuT_YcSeIE,6678
4
4
  hyperquant/draw.py,sha256=up_lQ3pHeVLoNOyh9vPjgNwjD0M-6_IetSGviQUgjhY,54624
5
5
  hyperquant/logkit.py,sha256=nUo7nx5eONvK39GOhWwS41zNRL756P2J7-5xGzwXnTY,8462
6
6
  hyperquant/notikit.py,sha256=x5yAZ_tAvLQRXcRbcg-VabCaN45LUhvlTZnUqkIqfAA,3596
7
- hyperquant/broker/auth.py,sha256=C8B5-x8Qcaeafm4ZwPCVFR7GRURmHC3CE4_vdg00Qgw,12139
7
+ hyperquant/broker/auth.py,sha256=3Ws_2iaF2NTCVIbkLzL_0ae3y7f4vgj9zmozvGaUXnk,16029
8
8
  hyperquant/broker/bitget.py,sha256=X_S0LKZ7FZAEb6oEMr1vdGP1fondzK74BhmNTpRDSEA,9488
9
- hyperquant/broker/bitmart.py,sha256=2Yd_Jzn0XYzl3rYN1rmNuwy7Wy74T3bBQ-ue7RvIbMU,22206
9
+ hyperquant/broker/bitmart.py,sha256=7j_8TU3Dxjj5HCNX7CbSO3nPZcQH1t31A9UOv5tTbg0,25974
10
10
  hyperquant/broker/coinup.py,sha256=eOr8BTRXiTb5tCU2FDmvBdXXgqiwVmCbP5pdeA1ORJ8,20390
11
11
  hyperquant/broker/coinw.py,sha256=SnJU0vASh77rfcpMGWaIfTblQSjQk3vjlW_4juYdbcs,17214
12
12
  hyperquant/broker/edgex.py,sha256=TqUO2KRPLN_UaxvtLL6HnA9dAQXC1sGxOfqTHd6W5k8,18378
@@ -20,7 +20,7 @@ hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb8
20
20
  hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
21
21
  hyperquant/broker/lib/util.py,sha256=iMU1qF0CHj5zzlIMEQGwjz-qtEVosEe7slXOCuB7Rcw,566
22
22
  hyperquant/broker/models/bitget.py,sha256=0RwDY75KrJb-c-oYoMxbqxWfsILe-n_Npojz4UFUq7c,11389
23
- hyperquant/broker/models/bitmart.py,sha256=hPxSFLmsJif9wm4nTln5G_zCbsoRnM1BF9fnfciZIHo,22061
23
+ hyperquant/broker/models/bitmart.py,sha256=O9RnU-XBeR9SzicG15jzuzK5oy2kMrRJAyZSqC8DXUw,21938
24
24
  hyperquant/broker/models/coinup.py,sha256=X_ngB2_sgTOdfAZqTyeWvCN03j-0_inZ6ugZKW6hR7k,11173
25
25
  hyperquant/broker/models/coinw.py,sha256=LvLMVP7i-qkkTK1ubw8eBkMK2RQmFoKPxdKqmC4IToY,22157
26
26
  hyperquant/broker/models/edgex.py,sha256=vPAkceal44cjTYKQ_0BoNAskOpmkno_Yo1KxgMLPc6Y,33954
@@ -32,6 +32,6 @@ hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw
32
32
  hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
33
33
  hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
34
34
  hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
35
- hyperquant-1.21.dist-info/METADATA,sha256=1ZJFW9uGSQFzXQTBy74LLbaay4lUmc_pTOzSOugj_nU,4409
36
- hyperquant-1.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- hyperquant-1.21.dist-info/RECORD,,
35
+ hyperquant-1.22.dist-info/METADATA,sha256=dH0APcdAjkH7pyeQiyPyTd3tl1E2tQVYxMd5YlbzjrE,4438
36
+ hyperquant-1.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ hyperquant-1.22.dist-info/RECORD,,