hyperquant 0.93__py3-none-any.whl → 0.95__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.
@@ -0,0 +1,609 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import TYPE_CHECKING, Any, Literal, Sequence
5
+
6
+ from pybotters.store import DataStore, DataStoreCollection
7
+
8
+ if TYPE_CHECKING:
9
+ from pybotters.typedefs import Item
10
+ from pybotters.ws import ClientWebSocketResponse
11
+
12
+
13
+ def _maybe_to_dict(payload: Any) -> Any:
14
+ if payload is None:
15
+ return None
16
+ if hasattr(payload, "to_dict"):
17
+ return payload.to_dict()
18
+ if hasattr(payload, "model_dump"):
19
+ return payload.model_dump()
20
+ return payload
21
+
22
+ class Ticker(DataStore):
23
+ """Bitmart 合约行情数据。"""
24
+
25
+ _KEYS = ["contract_id"]
26
+
27
+ def _onresponse(self, data: Any) -> None:
28
+ payload = _maybe_to_dict(data) or {}
29
+ tickers = payload.get("data", {}).get("tickers") or []
30
+ items: list[dict[str, Any]] = []
31
+ for entry in tickers:
32
+ if not isinstance(entry, dict):
33
+ continue
34
+ contract_id = entry.get("contract_id")
35
+ if contract_id is None:
36
+ continue
37
+ items.append(entry)
38
+ self._clear()
39
+ if items:
40
+ self._insert(items)
41
+
42
+ class Book(DataStore):
43
+ """Bitmart 合约深度数据。"""
44
+
45
+ _KEYS = ["s", "S", "p"]
46
+
47
+ def _init(self) -> None:
48
+ self.limit: int | None = None
49
+ self.id_to_symbol: dict[str, str] = {}
50
+ self._state: dict[str, dict[str, dict[float, float]]] = {}
51
+ self._last_update: float = 0.0
52
+
53
+ @classmethod
54
+ def _make_entry(cls, symbol: str, side: Literal["a", "b"], price: str, size: str) -> dict[str, Any]:
55
+ return {
56
+ "s": symbol,
57
+ "S": side,
58
+ "p": price,
59
+ "q": size,
60
+ }
61
+
62
+ def _on_message(self, msg: dict[str, Any]) -> None:
63
+ data = msg.get("data")
64
+ group = msg.get("group")
65
+ if not isinstance(data, dict) or not isinstance(group, str):
66
+ return
67
+
68
+ try:
69
+ _, contract_id = group.split(":", 1)
70
+ except ValueError:
71
+ return
72
+
73
+ symbol = self.id_to_symbol.get(contract_id) or contract_id
74
+
75
+ state = self._state.setdefault(symbol, {"a": {}, "b": {}})
76
+
77
+ way = data.get("way")
78
+ depths = data.get("depths") or []
79
+ if way not in {1, 2} or not isinstance(depths, Sequence):
80
+ return
81
+
82
+ side_key = "b" if way == 1 else "a"
83
+ if self.limit:
84
+ depths = depths[: self.limit]
85
+
86
+ self._find_and_delete({'s': symbol, 'S': side_key})
87
+ self._update([
88
+ self._make_entry(
89
+ symbol,
90
+ side_key,
91
+ entry.get("price", '0'),
92
+ entry.get("vol", '0'),
93
+ )
94
+ for entry in depths
95
+ ])
96
+
97
+ self._last_update = time.time()
98
+
99
+ def sorted(self, query: Item | None = None, limit: int | None = None) -> dict[str, list[Item]]:
100
+ return self._sorted(
101
+ item_key="S",
102
+ item_asc_key="a",
103
+ item_desc_key="b",
104
+ sort_key="p",
105
+ query=query,
106
+ limit=limit,
107
+ )
108
+
109
+ @property
110
+ def last_update(self) -> float:
111
+ return self._last_update
112
+
113
+
114
+ class Detail(DataStore):
115
+ """Bitmart 合约详情。"""
116
+
117
+ _KEYS = ["name"]
118
+
119
+ def _onresponse(self, data: Any) -> None:
120
+ payload = _maybe_to_dict(data) or {}
121
+ contracts = payload.get("data", {}).get("contracts") or []
122
+
123
+ records: list[dict[str, Any]] = []
124
+ for item in contracts:
125
+ if not isinstance(item, dict):
126
+ continue
127
+ record: dict[str, Any] = {}
128
+ contract = item.get("contract") or {}
129
+ if isinstance(contract, dict):
130
+ record.update(contract)
131
+ risk_limit = item.get("risk_limit") or {}
132
+ if isinstance(risk_limit, dict):
133
+ record.update({f"risk_{k}": v for k, v in risk_limit.items()})
134
+ fee_config = item.get("fee_config") or {}
135
+ if isinstance(fee_config, dict):
136
+ record.update({f"fee_{k}": v for k, v in fee_config.items()})
137
+ plan_order_config = item.get("plan_order_config") or {}
138
+ if isinstance(plan_order_config, dict):
139
+ record.update({f"plan_{k}": v for k, v in plan_order_config.items()})
140
+ tag_detail = item.get("contract_tag_detail") or {}
141
+ if isinstance(tag_detail, dict):
142
+ record.update({f"tag_{k}": v for k, v in tag_detail.items()})
143
+
144
+ contract_id = record.get("contract_id") or contract.get("contract_id")
145
+ if contract_id is None:
146
+ continue
147
+ record["contract_id"] = contract_id
148
+ record["tick_size"] = record.get("price_unit")
149
+ records.append(record)
150
+
151
+ self._clear()
152
+ if records:
153
+ self._insert(records)
154
+
155
+
156
+ class Ticker(DataStore):
157
+ """24h 数据信息。"""
158
+
159
+ _KEYS = ["contract_id"]
160
+
161
+ def _onresponse(self, data: Any) -> None:
162
+ payload = _maybe_to_dict(data) or {}
163
+ tickers = payload.get("data", {}).get("tickers") or []
164
+ items: list[dict[str, Any]] = []
165
+ for entry in tickers:
166
+ if not isinstance(entry, dict):
167
+ continue
168
+ contract_id = entry.get("contract_id")
169
+ if contract_id is None:
170
+ continue
171
+ record = dict(entry)
172
+ record["contract_id"] = contract_id
173
+ items.append(record)
174
+
175
+ self._clear()
176
+ if items:
177
+ self._insert(items)
178
+
179
+
180
+ class Orders(DataStore):
181
+ """订单列表。"""
182
+
183
+ _KEYS = ["order_id"]
184
+
185
+ @staticmethod
186
+ def _normalize(entry: dict[str, Any]) -> dict[str, Any] | None:
187
+ order_id = entry.get("order_id")
188
+ if order_id is None:
189
+ return None
190
+ normalized = dict(entry)
191
+ normalized["order_id"] = order_id
192
+ return normalized
193
+
194
+ def _onresponse(self, data: Any) -> None:
195
+ payload = _maybe_to_dict(data) or {}
196
+ orders = payload.get("data", {}).get("orders") or []
197
+ items: list[dict[str, Any]] = []
198
+ for entry in orders:
199
+ if not isinstance(entry, dict):
200
+ continue
201
+ normalized = self._normalize(entry)
202
+ if normalized:
203
+ items.append(normalized)
204
+ self._clear()
205
+ if items:
206
+ self._insert(items)
207
+
208
+
209
+ class Positions(DataStore):
210
+ """持仓信息。"""
211
+
212
+ _KEYS = ["position_id"]
213
+
214
+ @staticmethod
215
+ def _normalize(entry: dict[str, Any]) -> dict[str, Any] | None:
216
+ position_id = entry.get("position_id")
217
+ if position_id is None:
218
+ return None
219
+ normalized = dict(entry)
220
+ normalized["position_id"] = position_id
221
+ return normalized
222
+
223
+ def _onresponse(self, data: Any) -> None:
224
+ payload = _maybe_to_dict(data) or {}
225
+ positions = payload.get("data", {}).get("positions") or []
226
+ items: list[dict[str, Any]] = []
227
+ for entry in positions:
228
+ if not isinstance(entry, dict):
229
+ continue
230
+ normalized = self._normalize(entry)
231
+ if normalized:
232
+ items.append(normalized)
233
+ self._clear()
234
+ if items:
235
+ self._insert(items)
236
+
237
+
238
+ class Balances(DataStore):
239
+ """账户资产信息。"""
240
+
241
+ _KEYS = ["coin_code"]
242
+
243
+ @staticmethod
244
+ def _normalize(entry: dict[str, Any]) -> dict[str, Any] | None:
245
+ coin = entry.get("coin_code")
246
+ if coin is None:
247
+ return None
248
+ normalized = dict(entry)
249
+ normalized["coin_code"] = coin
250
+ return normalized
251
+
252
+ def _onresponse(self, data: Any) -> None:
253
+ payload = _maybe_to_dict(data) or {}
254
+ assets = payload.get("data", {}).get("assets") or []
255
+ items: list[dict[str, Any]] = []
256
+ for entry in assets:
257
+ if not isinstance(entry, dict):
258
+ continue
259
+ normalized = self._normalize(entry)
260
+ if normalized:
261
+ items.append(normalized)
262
+ self._clear()
263
+ if items:
264
+ self._insert(items)
265
+
266
+
267
+ class BitmartDataStore(DataStoreCollection):
268
+ """Bitmart 合约数据集合。"""
269
+
270
+ def _init(self) -> None:
271
+ self._create("book", datastore_class=Book)
272
+ self._create("detail", datastore_class=Detail)
273
+ self._create("orders", datastore_class=Orders)
274
+ self._create("positions", datastore_class=Positions)
275
+ self._create("balances", datastore_class=Balances)
276
+ self._create("ticker", datastore_class=Ticker)
277
+
278
+ def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
279
+ if isinstance(msg, dict):
280
+ group = msg.get("group")
281
+ if isinstance(group, str) and group.startswith("Depth"):
282
+ self.book._on_message(msg)
283
+
284
+ @property
285
+ def book(self) -> Book:
286
+ """
287
+ .. code:: json
288
+
289
+ {
290
+ "s": "BTCUSDT",
291
+ "S": "a", # 卖单
292
+ "p": "95640.3",
293
+ "q": "0.807"
294
+ }
295
+ """
296
+ return self._get("book")
297
+
298
+ @property
299
+ def detail(self) -> Detail:
300
+ """`ifcontract/contracts_all` 返回的合约配置信息。
301
+
302
+ .. code:: json
303
+
304
+ {
305
+ "contract_id": 1,
306
+ "index_id": 1,
307
+ "name": "BTCUSDT",
308
+ "display_name": "BTCUSDT 永续合约",
309
+ "display_name_en": "BTCUSDT_SWAP",
310
+ "contract_type": 1,
311
+ "base_coin": "BTC",
312
+ "quote_coin": "USDT",
313
+ "price_coin": "BTC",
314
+ "exchange": "*",
315
+ "contract_size": "0.001",
316
+ "begin_at": "2022-02-27T16:00:00Z",
317
+ "end_at": "2020-01-01T00:00:00Z",
318
+ "delive_at": "2018-10-01T02:00:00Z",
319
+ "delivery_cycle": 28800,
320
+ "min_leverage": "1",
321
+ "max_leverage": "200",
322
+ "price_unit": "0.1",
323
+ "tick_size": "0.1",
324
+ "vol_unit": "1",
325
+ "value_unit": "0.1",
326
+ "min_vol": "1",
327
+ "max_vol": "500000",
328
+ "liquidation_warn_ratio": "0.85",
329
+ "fast_liquidation_ratio": "0.8",
330
+ "settle_type": 1,
331
+ "open_type": 3,
332
+ "compensate_type": 1,
333
+ "status": 3,
334
+ "block": 1,
335
+ "rank": 1,
336
+ "created_at": "2018-07-12T09:16:57Z",
337
+ "depth_bord": "0.0375",
338
+ "base_coin_zh": "",
339
+ "base_coin_en": "",
340
+ "max_rate": "0.0375",
341
+ "min_rate": "-0.0375",
342
+ "market_status": 0,
343
+ "hedge_name": "binance",
344
+ "icon_url": "/static-file/public/coin/BTC-20200604060942.png",
345
+ "robot_risk_threshold": "0",
346
+ "fund_rate_threshold": "0",
347
+ "fund_rate_switch": 0,
348
+ "fund_rate_config": "0",
349
+ "market_price_rate": "0.003",
350
+ "robot_fund_rate_offset": "0",
351
+ "credit_max_leverage": 20,
352
+ "limit_ratio": "0.05",
353
+ "max_order_num": 200,
354
+ "min_trade_val": "5",
355
+ "bind_order_flag": false,
356
+ "market_max_vol": "80000",
357
+ "quote_type": 1,
358
+ "risk_contract_id": 1,
359
+ "risk_base_limit": "1000000",
360
+ "risk_step": "1000000",
361
+ "risk_maintenance_margin": "0.0025",
362
+ "risk_initial_margin": "0.005",
363
+ "risk_status": 1,
364
+ "fee_contract_id": 1,
365
+ "fee_maker_fee": "0.0002",
366
+ "fee_taker_fee": "0.0006",
367
+ "fee_settlement_fee": "0",
368
+ "fee_created_at": "2018-07-12T20:47:22Z",
369
+ "plan_contract_id": 0,
370
+ "plan_min_scope": "0.001",
371
+ "plan_max_scope": "2",
372
+ "plan_max_count": 100,
373
+ "plan_min_life_cycle": 24,
374
+ "plan_max_life_cycle": 438000,
375
+ "tag_tag_id": 1,
376
+ "tag_tag_name": "hot"
377
+ }
378
+ """
379
+ return self._get("detail")
380
+
381
+ @property
382
+ def ticker(self) -> Ticker:
383
+ """Bitmart 24h 行情 (`ifcontract/tickers`)。
384
+
385
+ .. code:: json
386
+
387
+ {
388
+ "contract_id": 1,
389
+ "last_price": "95500.1",
390
+ "open_24h": "91000",
391
+ "high_24h": "96000",
392
+ "low_24h": "90000",
393
+ "base_vol_24h": "12345",
394
+ "quote_vol_24h": "118000000"
395
+ }
396
+ """
397
+ return self._get("ticker")
398
+
399
+ @property
400
+ def orders(self) -> Orders:
401
+ """用户订单 (`userAllOrders`)。
402
+
403
+ .. code:: json
404
+
405
+ [
406
+ {
407
+ "order_id": 3000236525013551,
408
+ "contract_id": 72,
409
+ "position_id": 0,
410
+ "account_id": 2008001004625862,
411
+ "price": "0.25",
412
+ "vol": "1",
413
+ "done_vol": "0",
414
+ "done_avg_price": "0",
415
+ "way": 1,
416
+ "category": 1,
417
+ "make_fee": "0",
418
+ "take_fee": "0",
419
+ "origin": "web",
420
+ "created_at": "2025-10-29T08:23:20.745717Z",
421
+ "updated_at": "2025-10-29T08:23:20.753482Z",
422
+ "finished_at": "",
423
+ "status": 2,
424
+ "errno": 0,
425
+ "mode": 1,
426
+ "leverage": "10",
427
+ "open_type": 2,
428
+ "order_type": 0,
429
+ "extends": {
430
+ "remark": "default",
431
+ "broker_id": "",
432
+ "order_type": 0,
433
+ "bonus_only": false,
434
+ "request_trace_id": "",
435
+ "trigger_ratio_type": 0,
436
+ "is_guaranteed_sl_or_tp": false,
437
+ "is_market_zero_slippage": false,
438
+ "zero_slippage_ratio": ""
439
+ },
440
+ "client_order_id": "",
441
+ "executive_price": "",
442
+ "life_cycle": 0,
443
+ "price_type": 0,
444
+ "price_way": 0,
445
+ "plan_category": 0,
446
+ "activation_price": "",
447
+ "callback_rate": "",
448
+ "executive_order_id": 0,
449
+ "bind_leverage": "",
450
+ "pre_plan_order_id": 0,
451
+ "stop_profit_executive_price": "",
452
+ "stop_profit_price_type": 0,
453
+ "stop_loss_executive_price": "",
454
+ "stop_loss_price_type": 0,
455
+ "liquidation_fee": "",
456
+ "account_type": 0,
457
+ "pnl": "",
458
+ "data_type": "",
459
+ "position_mode": 1,
460
+ "pnl_rate": "",
461
+ "preset_is_guaranteed_sl": false,
462
+ "preset_is_guaranteed_tp": false
463
+ }
464
+ ]
465
+ """
466
+ return self._get("orders")
467
+
468
+ @property
469
+ def positions(self) -> Positions:
470
+ """用户持仓 (`userPositions`)。
471
+
472
+ .. code:: json
473
+
474
+ [
475
+ {
476
+ "position_id": 3000236533088511,
477
+ "account_id": 2008001004625862,
478
+ "contract_id": 72,
479
+ "hold_vol": "1",
480
+ "freeze_vol": "0",
481
+ "close_vol": "0",
482
+ "hold_avg_price": "0.2964901",
483
+ "open_avg_price": "0.2964901",
484
+ "close_avg_price": "0",
485
+ "oim": "0.02982690406",
486
+ "im": "0.02982690406",
487
+ "mm": "0.000741261625",
488
+ "realised_profit": "-0.00017789406",
489
+ "earnings": "-0.00017789406",
490
+ "hold_fee": "0",
491
+ "open_type": 2,
492
+ "position_type": 1,
493
+ "status": 1,
494
+ "errno": 0,
495
+ "created_at": "2025-10-29T11:16:37.63704Z",
496
+ "updated_at": "2025-10-29T11:16:37.63704Z",
497
+ "notional_value": "0.2964901",
498
+ "fair_value": "0.29650465",
499
+ "current_value": "0.2965151",
500
+ "liquidation_value": "-10.702412850255",
501
+ "bankruptcy_value": "0",
502
+ "close_able_vol": "1",
503
+ "bankruptcy_fee": "0.00017789406",
504
+ "current_un_earnings": "0.000025",
505
+ "fair_un_earnings": "0.00001455",
506
+ "liquidate_price": "0",
507
+ "current_roe": "0.0008431570971989815",
508
+ "fair_roe": "0.0004907174305698073",
509
+ "current_notional_roe": "0.0008431984744178642",
510
+ "fair_notional_roe": "0.000490741512111197",
511
+ "leverage": "0.0269540457075690273",
512
+ "bind_leverage": "10",
513
+ "account_type": 0,
514
+ "position_mode": 0,
515
+ "fee": "-0.00017789406"
516
+ }
517
+ ]
518
+ """
519
+ return self._get("positions")
520
+
521
+ @property
522
+ def balances(self) -> Balances:
523
+ """账户资产 (`copy/trade/user/info`)。
524
+
525
+ .. code:: json
526
+
527
+ [
528
+ {
529
+ "account_id": 14794011,
530
+ "coin_code": "USDT",
531
+ "available_vol": "0",
532
+ "cash_vol": "0",
533
+ "freeze_vol": "0",
534
+ "realised_vol": "0",
535
+ "un_realised_vol": "0",
536
+ "earnings_vol": "0",
537
+ "total_im": "",
538
+ "margin_balance": "",
539
+ "available_balance": "",
540
+ "trans_out_balance": "",
541
+ "status": 0,
542
+ "total_balance": "",
543
+ "account_rights": "0",
544
+ "bonus_voucher_vol": "",
545
+ "freeze_bonus_voucher_vol": ""
546
+ }
547
+ ]
548
+ """
549
+ return self._get("balances")
550
+
551
+ @property
552
+ def ticker(self) -> Ticker:
553
+ """Bitmart 合约行情数据(`/v1/ifcontract/tickers` 返回)。
554
+
555
+ 表示 Bitmart 合约行情接口 `/v1/ifcontract/tickers` 返回的数据,包含合约的最新价格、成交量、指数价格、公允价格、资金费率等信息。
556
+
557
+ .. code:: json
558
+
559
+ [
560
+ {
561
+ "last_price": "0.002296",
562
+ "open": "0.002347",
563
+ "close": "0.002296",
564
+ "low": "0.00224",
565
+ "high": "0.002394",
566
+ "avg_price": "0.0023197328648874",
567
+ "volume": "6",
568
+ "total_volume": "200110472",
569
+ "timestamp": 1761812348,
570
+ "rise_fall_rate": "-0.0217298679164891",
571
+ "rise_fall_value": "-0.000051",
572
+ "contract_id": 33125,
573
+ "contract_name": "IOSTUSDT",
574
+ "position_size": "",
575
+ "volume24": "229630336",
576
+ "amount24": "533620.3631860018137124",
577
+ "high_price_24": "0.002394",
578
+ "low_price_24": "0.00224",
579
+ "base_coin_volume": "200110472",
580
+ "quote_coin_volume": "464202.8385065298408528",
581
+ "ask_price": "0.002302",
582
+ "ask_vol": "6396074",
583
+ "bid_price": "0.002289",
584
+ "bid_vol": "3214783",
585
+ "index_price": "0.00229906",
586
+ "fair_price": "0.002296",
587
+ "depth_price": {
588
+ "bid_price": "0",
589
+ "ask_price": "0",
590
+ "mid_price": "0"
591
+ },
592
+ "fair_basis": "",
593
+ "fair_value": "",
594
+ "rate": {
595
+ "quote_rate": "0",
596
+ "base_rate": "0",
597
+ "interest_rate": "0"
598
+ },
599
+ "premium_index": "",
600
+ "funding_rate": "-0.0000601",
601
+ "next_funding_rate": "",
602
+ "next_funding_at": "2025-10-30T16:00:00Z",
603
+ "pps": "0",
604
+ "quote_coin": "USDT",
605
+ "base_coin": "IOST"
606
+
607
+ ]
608
+ """
609
+ return self._get("ticker")