hyperquant 1.1__py3-none-any.whl → 1.21__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.
@@ -30,6 +30,7 @@ class Bitmart:
30
30
  self.private_api = "https://derivatives.bitmart.com"
31
31
  self.forward_api = f'{self.private_api}/gw-api/contract-tiger/forward'
32
32
  self.ws_url = ws_url or "wss://contract-ws-v2.bitmart.com/v1/ifcontract/realTime"
33
+ self.api_ws_url = "wss://openapi-ws-v2.bitmart.com/api?protocol=1.1"
33
34
 
34
35
  self.account_index = account_index
35
36
  self.apis = apis
@@ -281,6 +282,7 @@ class Bitmart:
281
282
  *,
282
283
  depth: str = "Depth",
283
284
  depth_limit: int | None = None,
285
+ use_api_ws: bool = True,
284
286
  ) -> pybotters.ws.WebSocketApp:
285
287
  """Subscribe order book channel(s)."""
286
288
 
@@ -289,37 +291,55 @@ class Bitmart:
289
291
 
290
292
  if not symbols:
291
293
  raise ValueError("symbols must not be empty")
292
-
293
- missing = [sym for sym in symbols if self.get_contract_id(sym) is None]
294
- if missing:
295
- await self.update("detail")
296
- still_missing = [sym for sym in missing if self.get_contract_id(sym) is None]
297
- if still_missing:
298
- raise ValueError(f"Unknown symbols: {', '.join(still_missing)}")
299
-
300
294
  if depth_limit is not None:
301
295
  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)}")
302
303
 
303
- channels: list[str] = []
304
- for symbol in symbols:
305
- contract_id = self.get_contract_id(symbol)
306
- if contract_id is None:
307
- continue
308
- self.store.book.id_to_symbol[str(contract_id)] = symbol
309
- channels.append(f"{depth}:{contract_id}")
310
304
 
311
- if not channels:
312
- raise ValueError("No channels resolved for subscription")
313
305
 
314
- payload = {"action": "subscribe", "args": channels}
315
- # print(payload)
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)
319
+
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")
329
+
330
+ if not channels:
331
+ raise ValueError("No channels resolved for subscription")
332
+
333
+ payload = {"action": "subscribe", "args": channels}
334
+ # print(payload)
335
+
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
+ )
316
342
 
317
- ws_app = self.client.ws_connect(
318
- self.ws_url,
319
- send_json=payload,
320
- hdlr_json=self.store.onmessage,
321
- autoping=False,
322
- )
323
343
  await ws_app._event.wait()
324
344
  return ws_app
325
345
 
@@ -510,3 +530,101 @@ class Bitmart:
510
530
  if resp.get("success") is False or resp.get("errno") not in (None, "OK"):
511
531
  raise ValueError(f"Bitmart cancelOrders error: {resp}")
512
532
  return resp
533
+
534
+ async def get_leverage(
535
+ self,
536
+ *,
537
+ symbol: str | None = None,
538
+ contract_id: int | str | None = None,
539
+ ) -> dict[str, Any]:
540
+ """
541
+ 获取指定合约的杠杆信息(可通过 contract_id 或 symbol 查询)。
542
+
543
+ 参数:
544
+ symbol (str | None): 合约符号,例如 "BTCUSDT"。如果未传入 contract_id,则会自动解析。
545
+ contract_id (int | str | None): 合约 ID,可直接指定。
546
+
547
+ 返回:
548
+ dict[str, Any]: 杠杆信息字典,典型返回结构如下:
549
+ {
550
+ "contract_id": 1,
551
+ "leverage": 96, # 当前杠杆倍数
552
+ "open_type": 2, # 开仓类型 (1=全仓, 2=逐仓)
553
+ "max_leverage": {
554
+ "contract_id": 1,
555
+ "leverage": "200", # 最大可用杠杆倍数
556
+ "open_type": 0,
557
+ "imr": "0.005", # 初始保证金率
558
+ "mmr": "0.0025", # 维持保证金率
559
+ "value": "0"
560
+ }
561
+ }
562
+
563
+ 异常:
564
+ ValueError: 当未提供 symbol 或 contract_id,或接口返回错误时抛出。
565
+
566
+ 示例:
567
+ data = await bitmart.get_leverage(symbol="BTCUSDT")
568
+ print(data["leverage"]) # 输出当前杠杆倍数
569
+ """
570
+ if contract_id is None:
571
+ if symbol is not None:
572
+ contract_id = self.get_contract_id(symbol)
573
+ if contract_id is None:
574
+ raise ValueError("Either contract_id or a valid symbol must be provided to get leverage info.")
575
+ res = await self.client.get(
576
+ f"{self.forward_api}/v1/ifcontract/getLeverage",
577
+ params={"contract_id": contract_id},
578
+ )
579
+ resp = await res.json()
580
+ if resp.get("success") is False or resp.get("errno") not in (None, "OK"):
581
+ raise ValueError(f"Bitmart getLeverage error: {resp}")
582
+ return resp.get("data")
583
+
584
+ async def bind_leverage(
585
+ self,
586
+ *,
587
+ symbol: str | None = None,
588
+ contract_id: int | str | None = None,
589
+ leverage: int | str,
590
+ open_type: Literal[1, 2] = 2,
591
+ ) -> None:
592
+ """
593
+ 绑定(设置)指定合约的杠杆倍数。
594
+
595
+ 参数:
596
+ symbol (str | None): 合约符号,例如 "BTCUSDT"。若未传入 contract_id,会自动解析。
597
+ contract_id (int | str | None): 合约 ID,可直接指定。
598
+ leverage (int | str): 要设置的杠杆倍数,如 20、50、100。
599
+ open_type (int): 开仓模式,1=全仓(Cross),2=逐仓(Isolated)。
600
+
601
+ 返回:
602
+ None — 如果接口调用成功,不返回任何内容。
603
+ 若失败则抛出 ValueError。
604
+
605
+ 异常:
606
+ ValueError: 当未提供 symbol 或 contract_id,或接口返回错误时抛出。
607
+
608
+ 示例:
609
+ await bitmart.bind_leverage(symbol="BTCUSDT", leverage=50, open_type=2)
610
+ """
611
+ if contract_id is None:
612
+ if symbol is not None:
613
+ contract_id = self.get_contract_id(symbol)
614
+ if contract_id is None:
615
+ raise ValueError("Either contract_id or a valid symbol must be provided to bind leverage.")
616
+
617
+ payload = {
618
+ "contract_id": int(contract_id),
619
+ "leverage": leverage,
620
+ "open_type": open_type,
621
+ }
622
+
623
+ res = await self.client.post(
624
+ f"{self.forward_api}/v1/ifcontract/bindLeverage",
625
+ json=payload,
626
+ )
627
+ resp = await res.json()
628
+ if resp.get("success") is False or resp.get("errno") not in (None, "OK"):
629
+ raise ValueError(f"Bitmart bindLeverage error: {resp}")
630
+ return None
@@ -62,6 +62,7 @@ class Book(DataStore):
62
62
  def _on_message(self, msg: dict[str, Any]) -> None:
63
63
  data = msg.get("data")
64
64
  group = msg.get("group")
65
+ ms_t = msg.get("ms_t")
65
66
  if not isinstance(data, dict) or not isinstance(group, str):
66
67
  return
67
68
 
@@ -94,7 +95,57 @@ class Book(DataStore):
94
95
  for entry in depths
95
96
  ])
96
97
 
97
- self._last_update = time.time()
98
+ self._last_update = ms_t
99
+
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
+ data = msg.get("data")
121
+ if not isinstance(data, dict):
122
+ return
123
+ symbol = data.get("symbol")
124
+ asks = data.get("asks") or []
125
+ bids = data.get("bids") or []
126
+ if self.limit:
127
+ asks = asks[: self.limit]
128
+ bids = bids[: self.limit]
129
+
130
+ self._find_and_delete({'s': symbol})
131
+ self._update([
132
+ self._make_entry(
133
+ symbol,
134
+ "a",
135
+ entry.get("price", '0'),
136
+ entry.get("vol", '0'),
137
+ )
138
+ for entry in asks
139
+ ])
140
+ self._update([
141
+ self._make_entry(
142
+ symbol,
143
+ "b",
144
+ entry.get("price", '0'),
145
+ entry.get("vol", '0'),
146
+ )
147
+ for entry in bids
148
+ ])
98
149
 
99
150
  def sorted(self, query: Item | None = None, limit: int | None = None) -> dict[str, list[Item]]:
100
151
  return self._sorted(
@@ -278,8 +329,12 @@ class BitmartDataStore(DataStoreCollection):
278
329
  def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
279
330
  if isinstance(msg, dict):
280
331
  group = msg.get("group")
281
- if isinstance(group, str) and group.startswith("Depth"):
282
- self.book._on_message(msg)
332
+
333
+ if isinstance(group, str):
334
+ if group.startswith("futures/depth"):
335
+ self.book._on_message_api(msg)
336
+ if group.startswith("Depth"):
337
+ self.book._on_message(msg)
283
338
 
284
339
  @property
285
340
  def book(self) -> Book:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 1.1
3
+ Version: 1.21
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
@@ -6,7 +6,7 @@ hyperquant/logkit.py,sha256=nUo7nx5eONvK39GOhWwS41zNRL756P2J7-5xGzwXnTY,8462
6
6
  hyperquant/notikit.py,sha256=x5yAZ_tAvLQRXcRbcg-VabCaN45LUhvlTZnUqkIqfAA,3596
7
7
  hyperquant/broker/auth.py,sha256=C8B5-x8Qcaeafm4ZwPCVFR7GRURmHC3CE4_vdg00Qgw,12139
8
8
  hyperquant/broker/bitget.py,sha256=X_S0LKZ7FZAEb6oEMr1vdGP1fondzK74BhmNTpRDSEA,9488
9
- hyperquant/broker/bitmart.py,sha256=gOMh_w22vd0L-rMLLCLvtBDqHpXPKeru6bnd9hx1u-M,17604
9
+ hyperquant/broker/bitmart.py,sha256=2Yd_Jzn0XYzl3rYN1rmNuwy7Wy74T3bBQ-ue7RvIbMU,22206
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=6lOpQXLcuR7MEayvMt_e-okE2A8CCUvpzAT6aCxWSaM,20455
23
+ hyperquant/broker/models/bitmart.py,sha256=hPxSFLmsJif9wm4nTln5G_zCbsoRnM1BF9fnfciZIHo,22061
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.1.dist-info/METADATA,sha256=6HPHF63atspmUAmbE3Gig8F3TwhHFSPe74kPgX2zglQ,4408
36
- hyperquant-1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- hyperquant-1.1.dist-info/RECORD,,
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,,