hyperquant 1.48__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.

Files changed (42) hide show
  1. hyperquant/__init__.py +8 -0
  2. hyperquant/broker/auth.py +972 -0
  3. hyperquant/broker/bitget.py +311 -0
  4. hyperquant/broker/bitmart.py +720 -0
  5. hyperquant/broker/coinw.py +487 -0
  6. hyperquant/broker/deepcoin.py +651 -0
  7. hyperquant/broker/edgex.py +500 -0
  8. hyperquant/broker/hyperliquid.py +570 -0
  9. hyperquant/broker/lbank.py +661 -0
  10. hyperquant/broker/lib/edgex_sign.py +455 -0
  11. hyperquant/broker/lib/hpstore.py +252 -0
  12. hyperquant/broker/lib/hyper_types.py +48 -0
  13. hyperquant/broker/lib/polymarket/ctfAbi.py +721 -0
  14. hyperquant/broker/lib/polymarket/safeAbi.py +1138 -0
  15. hyperquant/broker/lib/util.py +22 -0
  16. hyperquant/broker/lighter.py +679 -0
  17. hyperquant/broker/models/apexpro.py +150 -0
  18. hyperquant/broker/models/bitget.py +359 -0
  19. hyperquant/broker/models/bitmart.py +635 -0
  20. hyperquant/broker/models/coinw.py +724 -0
  21. hyperquant/broker/models/deepcoin.py +809 -0
  22. hyperquant/broker/models/edgex.py +1053 -0
  23. hyperquant/broker/models/hyperliquid.py +284 -0
  24. hyperquant/broker/models/lbank.py +557 -0
  25. hyperquant/broker/models/lighter.py +868 -0
  26. hyperquant/broker/models/ourbit.py +1155 -0
  27. hyperquant/broker/models/polymarket.py +1071 -0
  28. hyperquant/broker/ourbit.py +550 -0
  29. hyperquant/broker/polymarket.py +2399 -0
  30. hyperquant/broker/ws.py +132 -0
  31. hyperquant/core.py +513 -0
  32. hyperquant/datavison/_util.py +18 -0
  33. hyperquant/datavison/binance.py +111 -0
  34. hyperquant/datavison/coinglass.py +237 -0
  35. hyperquant/datavison/okx.py +177 -0
  36. hyperquant/db.py +191 -0
  37. hyperquant/draw.py +1200 -0
  38. hyperquant/logkit.py +205 -0
  39. hyperquant/notikit.py +124 -0
  40. hyperquant-1.48.dist-info/METADATA +32 -0
  41. hyperquant-1.48.dist-info/RECORD +42 -0
  42. hyperquant-1.48.dist-info/WHEEL +4 -0
@@ -0,0 +1,809 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import TYPE_CHECKING, Any, Awaitable
5
+
6
+ import aiohttp
7
+ from pybotters.store import DataStore, DataStoreCollection
8
+
9
+ if TYPE_CHECKING:
10
+ from pybotters.typedefs import Item
11
+ from pybotters.ws import ClientWebSocketResponse
12
+
13
+
14
+ _DEFAULT_QUOTE = "USDT"
15
+ _DEFAULT_INST_TYPE = "SWAP"
16
+
17
+
18
+ def _extract_base(value: Any) -> str | None:
19
+ """Extract the base currency (e.g., BTC) from various identifier forms."""
20
+ if not value:
21
+ return None
22
+ text = str(value).strip().upper()
23
+ if not text:
24
+ return None
25
+ for sep in ("/", "_"):
26
+ text = text.replace(sep, "-")
27
+ if text.endswith("-SWAP"):
28
+ text = text[:-5]
29
+ parts = text.split("-")
30
+ candidate = parts[0] if parts else text
31
+ if candidate.endswith(_DEFAULT_QUOTE):
32
+ candidate = candidate[: -len(_DEFAULT_QUOTE)]
33
+ candidate = candidate.replace(" ", "")
34
+ return candidate or None
35
+
36
+
37
+ def _ensure_identifiers(entry: dict[str, Any]) -> dict[str, Any]:
38
+ """
39
+ Ensure symbol is formatted as BTCUSDT and instId as BTC-USDT-SWAP
40
+ assuming DeepCoin swap instruments quote in USDT.
41
+ """
42
+ base = (
43
+ entry.get("baseCcy")
44
+ or entry.get("base")
45
+ or _extract_base(entry.get("instId"))
46
+ or _extract_base(entry.get("I"))
47
+ or _extract_base(entry.get("symbol"))
48
+ or _extract_base(entry.get("s"))
49
+ )
50
+ if not base:
51
+ return entry
52
+
53
+ base = str(base).upper()
54
+ symbol = f"{base}{_DEFAULT_QUOTE}"
55
+ inst_id = f"{base}-{_DEFAULT_QUOTE}-{_DEFAULT_INST_TYPE}"
56
+
57
+ entry["s"] = symbol
58
+ entry["symbol"] = symbol
59
+ entry["instId"] = inst_id
60
+ entry["I"] = inst_id
61
+ return entry
62
+
63
+
64
+ def _get_ticker(msg:dict) -> dict | None:
65
+ r = msg.get("r", [])
66
+ if len(r) == 0:
67
+ return None
68
+ ticker = r[0].get('d')
69
+ if ticker:
70
+ _ensure_identifiers(ticker)
71
+ return ticker
72
+
73
+ class Detail(DataStore):
74
+ _KEYS = ["s"]
75
+
76
+ def _on_response(self, msg: dict[str, Any]) -> None:
77
+ data = msg.get('data', [])
78
+ # 展开data 新增tick_size 同 tickSize 字段 step_size 同 lotSize 字段
79
+ for item in data:
80
+ _ensure_identifiers(item)
81
+ if 'tickSize' in item:
82
+ item['tick_size'] = item['tickSize']
83
+ elif 'tickSz' in item:
84
+ item['tick_size'] = item['tickSz']
85
+ if 'lotSize' in item:
86
+ item['step_size'] = item['lotSize']
87
+ elif 'lotSz' in item:
88
+ item['step_size'] = item['lotSz']
89
+
90
+ self._update(data)
91
+
92
+ class Ticker(DataStore):
93
+ _KEYS = ["s"]
94
+
95
+ @staticmethod
96
+ def _normalize(entry: dict[str, Any]) -> dict[str, Any] | None:
97
+ normalized_entry = _ensure_identifiers(dict(entry))
98
+ symbol = normalized_entry.get("s")
99
+ if not symbol:
100
+ return None
101
+
102
+ normalized = dict(normalized_entry)
103
+
104
+ bid = entry.get("bidPx") or entry.get("BP1")
105
+ ask = entry.get("askPx") or entry.get("AP1")
106
+ try:
107
+ if bid is not None:
108
+ normalized["BP1"] = float(bid)
109
+ except (TypeError, ValueError):
110
+ pass
111
+ try:
112
+ if ask is not None:
113
+ normalized["AP1"] = float(ask)
114
+ except (TypeError, ValueError):
115
+ pass
116
+
117
+ return normalized
118
+
119
+ def _on_response(self, msg: dict[str, Any]) -> None:
120
+ data = msg.get("data") or []
121
+ items: list[dict[str, Any]] = []
122
+ for entry in data:
123
+ if not isinstance(entry, dict):
124
+ continue
125
+ normalized = self._normalize(entry)
126
+ if normalized:
127
+ items.append(normalized)
128
+
129
+ self._clear()
130
+ if items:
131
+ self._insert(items)
132
+
133
+ def _on_message(self, msg: dict[str, Any]) -> None:
134
+ ticker = _get_ticker(msg)
135
+ if ticker:
136
+ self._update([ticker])
137
+
138
+
139
+ class Book(DataStore):
140
+ """用ticker数据构建的深度数据(book_best)"""
141
+
142
+ _KEYS = ["s", "S"]
143
+
144
+ def _init(self) -> None:
145
+ self.limit: int | None = None
146
+ self.id_to_symbol: dict[str, str] = {}
147
+ self._state: dict[str, dict[str, dict[float, float]]] = {}
148
+
149
+
150
+ def _on_message(self, msg: dict[str, Any]) -> None:
151
+ ticker = _get_ticker(msg)
152
+ if not ticker:
153
+ return
154
+
155
+ symbol = ticker.get("s")
156
+ self._update([
157
+ {
158
+ 's': symbol,
159
+ 'S': 'b',
160
+ 'p': float(ticker.get('BP1', 0)),
161
+ 'q': 0
162
+ },{
163
+ 's': symbol,
164
+ 'S': 'a',
165
+ 'p': float(ticker.get('AP1', 0)),
166
+ 'q': 0
167
+ }
168
+ ])
169
+
170
+
171
+ class Balance(DataStore):
172
+ """资金余额数据。"""
173
+
174
+ _KEYS = ["ccy"]
175
+
176
+ @staticmethod
177
+ def _normalize_rest(entry: dict[str, Any]) -> dict[str, Any] | None:
178
+ ccy = entry.get("ccy")
179
+ if not ccy:
180
+ return None
181
+ normalized = {
182
+ "ccy": str(ccy),
183
+ "bal": entry.get("bal"),
184
+ "availBal": entry.get("availBal"),
185
+ "frozenBal": entry.get("frozenBal"),
186
+ }
187
+ if entry.get("withdrawable") is not None:
188
+ normalized["withdrawable"] = entry.get("withdrawable")
189
+ return normalized
190
+
191
+ @staticmethod
192
+ def _normalize_ws(data: dict[str, Any]) -> dict[str, Any] | None:
193
+ if not isinstance(data, dict):
194
+ return None
195
+ mapping = {
196
+ "A": "accountId",
197
+ "B": "bal",
198
+ "C": "ccy",
199
+ "M": "memberId",
200
+ "W": "withdrawable",
201
+ "a": "availBal",
202
+ "u": "useMargin",
203
+ "c": "closeProfit",
204
+ "FF": "frozenFee",
205
+ "FM": "frozenMargin",
206
+ }
207
+ normalized: dict[str, Any] = {}
208
+ for key, value in data.items():
209
+ target = mapping.get(key)
210
+ if target:
211
+ normalized[target] = value
212
+ else:
213
+ normalized[key] = value
214
+ if "ccy" not in normalized and "C" in data:
215
+ normalized["ccy"] = data["C"]
216
+ if "bal" not in normalized and "B" in data:
217
+ normalized["bal"] = data["B"]
218
+ if not normalized.get("ccy"):
219
+ return None
220
+ return normalized
221
+
222
+ def _on_response(self, msg: dict[str, Any]) -> None:
223
+ data = msg.get("data") or []
224
+ items: list[dict[str, Any]] = []
225
+ for entry in data:
226
+ if not isinstance(entry, dict):
227
+ continue
228
+ normalized = self._normalize_rest(entry)
229
+ if normalized:
230
+ items.append(normalized)
231
+
232
+ self._clear()
233
+ if items:
234
+ self._insert(items)
235
+
236
+ def _on_message(self, msg: dict[str, Any]) -> None:
237
+ results = msg.get("result") or []
238
+ for item in results:
239
+ data = item.get("data") if isinstance(item, dict) else None
240
+ normalized = self._normalize_ws(data or {})
241
+ if not normalized:
242
+ continue
243
+ key = {"ccy": str(normalized["ccy"])}
244
+ if self.get(key):
245
+ self._update([normalized])
246
+ else:
247
+ self._insert([normalized])
248
+
249
+
250
+ class Position(DataStore):
251
+ """仓位数据。"""
252
+
253
+ _KEYS = ["instId", "posSide"]
254
+
255
+ @staticmethod
256
+ def _normalize_rest(entry: dict[str, Any]) -> dict[str, Any] | None:
257
+ normalized = _ensure_identifiers(dict(entry))
258
+ inst_id = normalized.get("instId")
259
+ pos_side = normalized.get("posSide")
260
+ if not inst_id or pos_side is None:
261
+ return None
262
+ normalized["instId"] = str(inst_id)
263
+ normalized["posSide"] = str(pos_side)
264
+ return normalized
265
+
266
+ @staticmethod
267
+ def _normalize_ws(data: dict[str, Any]) -> dict[str, Any] | None:
268
+ if not isinstance(data, dict):
269
+ return None
270
+ mapping = {
271
+ "A": "accountId",
272
+ "I": "instId",
273
+ "M": "memberId",
274
+ "OP": "avgPx",
275
+ "Po": "pos",
276
+ "U": "updateTime",
277
+ "p": "posSide",
278
+ "u": "useMargin",
279
+ "c": "closeProfit",
280
+ "l": "lever",
281
+ "i": "isCrossMargin",
282
+ }
283
+ normalized: dict[str, Any] = {}
284
+ for key, value in data.items():
285
+ target = mapping.get(key)
286
+ if target:
287
+ normalized[target] = value
288
+ else:
289
+ normalized[key] = value
290
+ normalized = _ensure_identifiers(normalized)
291
+ if "posSide" not in normalized and "p" in data:
292
+ normalized["posSide"] = str(data["p"])
293
+
294
+ if not normalized.get("instId") or normalized.get("posSide") is None:
295
+ return None
296
+ return normalized
297
+
298
+ def _on_response(self, msg: dict[str, Any]) -> None:
299
+ data = msg.get("data") or []
300
+ items: list[dict[str, Any]] = []
301
+ for entry in data:
302
+ if not isinstance(entry, dict):
303
+ continue
304
+ normalized = self._normalize_rest(entry)
305
+ if normalized:
306
+ items.append(normalized)
307
+
308
+ self._clear()
309
+ if items:
310
+ self._insert(items)
311
+
312
+ def _on_message(self, msg: dict[str, Any]) -> None:
313
+ results = msg.get("result") or []
314
+ for item in results:
315
+ data = item.get("data") if isinstance(item, dict) else None
316
+ normalized = self._normalize_ws(data or {})
317
+ if not normalized:
318
+ continue
319
+ key = {"instId": normalized["instId"], "posSide": normalized["posSide"]}
320
+ if normalized.get("pos") in {None, 0, "0"}:
321
+ self._delete([key])
322
+ continue
323
+ if self.get(key):
324
+ self._update([normalized])
325
+ else:
326
+ self._insert([normalized])
327
+
328
+
329
+ class Orders(DataStore):
330
+ """当前委托。"""
331
+
332
+ _KEYS = ["ordId"]
333
+
334
+
335
+ @staticmethod
336
+ def _normalize_rest(entry: dict[str, Any]) -> dict[str, Any] | None:
337
+ ord_id = entry.get("ordId") or entry.get("ordID")
338
+ if not ord_id:
339
+ return None
340
+ normalized = dict(entry)
341
+ normalized["ordId"] = str(ord_id)
342
+ normalized = _ensure_identifiers(normalized)
343
+ if "instId" in normalized and normalized["instId"] is not None:
344
+ normalized["instId"] = str(normalized["instId"])
345
+ return normalized
346
+
347
+ @staticmethod
348
+ def _normalize_ws(data: dict[str, Any]) -> dict[str, Any] | None:
349
+ if not isinstance(data, dict):
350
+ return None
351
+ mapping = {
352
+ "L": "clOrdId",
353
+ "I": "instId",
354
+ "OPT": "orderPriceType",
355
+ "D": "direction",
356
+ "o": "offsetFlag",
357
+ "P": "px",
358
+ "V": "sz",
359
+ "OT": "ordType",
360
+ "i": "isCrossMargin",
361
+ "OS": "ordId",
362
+ "l": "lever",
363
+ "Or": "state",
364
+ "v": "accFillSz",
365
+ "IT": "insertTime",
366
+ "U": "updateTime",
367
+ "T": "turnover",
368
+ "p": "posiDirection",
369
+ "t": "fillPx",
370
+ }
371
+ normalized: dict[str, Any] = {}
372
+ for key, value in data.items():
373
+ target = mapping.get(key)
374
+ if target:
375
+ normalized[target] = value
376
+ else:
377
+ normalized[key] = value
378
+ normalized = _ensure_identifiers(normalized)
379
+ if "ordId" not in normalized and "OS" in data:
380
+ normalized["ordId"] = str(data["OS"])
381
+ if "state" in normalized and isinstance(normalized["state"], (int, float)):
382
+ normalized["state"] = str(normalized["state"])
383
+ normalized = _ensure_identifiers(normalized)
384
+ if not normalized.get("ordId"):
385
+ return None
386
+ # state_tomap # 4 live 6 cancel 1 filled # todo
387
+ state_map = {
388
+ "0": "filled",
389
+ "1": "filled",
390
+ "2": "partially_filled",
391
+ "3": "partially_filled_canceled",
392
+ "4": "live",
393
+ "5": "nofill",
394
+ "6": "canceled",
395
+ "7": "canceled",
396
+ "filled": "filled",
397
+ "partially_filled": "partially_filled",
398
+ "partially_filled_canceled": "partially_filled_canceled",
399
+ "live": "live",
400
+ "nofill": "nofill",
401
+ "canceled": "canceled"
402
+ }
403
+ state = normalized.get("state")
404
+ if state in state_map:
405
+ normalized["state"] = state_map[state]
406
+
407
+ return normalized
408
+
409
+ def _on_response(self, msg: dict[str, Any]) -> None:
410
+ data = msg.get("data") or []
411
+ items: list[dict[str, Any]] = []
412
+ for entry in data:
413
+ if not isinstance(entry, dict):
414
+ continue
415
+ normalized = self._normalize_rest(entry)
416
+ if normalized:
417
+ items.append(normalized)
418
+
419
+ self._clear()
420
+ if items:
421
+ self._insert(items)
422
+
423
+ def _on_message(self, msg: dict[str, Any]) -> None:
424
+ # 4 live 6 cancel 1 filled
425
+ results = msg.get("result") or []
426
+ deletes: list[dict[str, Any]] = []
427
+ updates: list[dict[str, Any]] = []
428
+ inserts: list[dict[str, Any]] = []
429
+ for item in results:
430
+ data = item.get("data") if isinstance(item, dict) else None
431
+ normalized = self._normalize_ws(data or {})
432
+ if not normalized:
433
+ continue
434
+ key = {"ordId": normalized["ordId"]}
435
+ if self.get(key):
436
+ updates.append(normalized)
437
+ else:
438
+ inserts.append(normalized)
439
+ state = str(normalized.get("state") or "").lower()
440
+ if state in {"filled", "canceled", "nofill", "partially_filled_canceled"}:
441
+ deletes.append(normalized)
442
+ if inserts:
443
+ self._insert(inserts)
444
+ if updates:
445
+ self._update(updates)
446
+ if deletes:
447
+ self._delete(deletes)
448
+
449
+
450
+ class Trades(DataStore):
451
+ """成交明细。"""
452
+
453
+ _KEYS = ["tradeId"]
454
+
455
+ @staticmethod
456
+ def _normalize_rest(entry: dict[str, Any]) -> dict[str, Any] | None:
457
+ trade_id = entry.get("tradeId") or entry.get("tradeID")
458
+ if not trade_id:
459
+ return None
460
+ normalized = _ensure_identifiers(dict(entry))
461
+ normalized["tradeId"] = str(trade_id)
462
+ return normalized
463
+
464
+ @staticmethod
465
+ def _normalize_ws(data: dict[str, Any]) -> dict[str, Any] | None:
466
+ if not isinstance(data, dict):
467
+ return None
468
+ mapping = {
469
+ "TI": "tradeId",
470
+ "D": "direction",
471
+ "OS": "ordId",
472
+ "M": "memberId",
473
+ "A": "accountId",
474
+ "I": "instId",
475
+ "o": "offsetFlag",
476
+ "P": "px",
477
+ "V": "sz",
478
+ "TT": "tradeTime",
479
+ "IT": "insertTime",
480
+ "T": "turnover",
481
+ "F": "fee",
482
+ "f": "feeCcy",
483
+ "CC": "clearCurrency",
484
+ "m": "matchRole",
485
+ "l": "lever",
486
+ "CP": "closeProfit",
487
+ }
488
+ normalized: dict[str, Any] = {}
489
+ for key, value in data.items():
490
+ target = mapping.get(key)
491
+ if target:
492
+ normalized[target] = value
493
+ else:
494
+ normalized[key] = value
495
+ normalized = _ensure_identifiers(normalized)
496
+ if "tradeId" not in normalized and "TI" in data:
497
+ normalized["tradeId"] = str(data["TI"])
498
+ if not normalized.get("tradeId"):
499
+ return None
500
+ return normalized
501
+
502
+ def _on_response(self, msg: dict[str, Any]) -> None:
503
+ data = msg.get("data") or []
504
+ items: list[dict[str, Any]] = []
505
+ for entry in data:
506
+ if not isinstance(entry, dict):
507
+ continue
508
+ normalized = self._normalize_rest(entry)
509
+ if normalized:
510
+ items.append(normalized)
511
+
512
+ if not items:
513
+ return
514
+ keys = [{"tradeId": item["tradeId"]} for item in items]
515
+ self._delete(keys)
516
+ self._insert(items)
517
+
518
+ def _on_message(self, msg: dict[str, Any]) -> None:
519
+ results = msg.get("result") or []
520
+ items: list[dict[str, Any]] = []
521
+ for item in results:
522
+ data = item.get("data") if isinstance(item, dict) else None
523
+ normalized = self._normalize_ws(data or {})
524
+ if normalized:
525
+ items.append(normalized)
526
+ if not items:
527
+ return
528
+ keys = [{"tradeId": item["tradeId"]} for item in items]
529
+ self._delete(keys)
530
+ self._insert(items)
531
+
532
+
533
+ class DeepCoinDataStore(DataStoreCollection):
534
+ """DeepCoin 合约数据存储集合"""
535
+
536
+ def _init(self) -> None:
537
+ self._create("detail", datastore_class=Detail)
538
+ self._create("ticker", datastore_class=Ticker)
539
+ self._create("book", datastore_class=Book)
540
+ self._create("orders", datastore_class=Orders)
541
+ self._create("position", datastore_class=Position)
542
+ self._create("balance", datastore_class=Balance)
543
+ self._create("trades", datastore_class=Trades)
544
+
545
+ def _on_message(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
546
+ chan = msg.get("a")
547
+ if chan == 'PO':
548
+ self.ticker._on_message(msg)
549
+ self.book._on_message(msg)
550
+ return
551
+
552
+ action = msg.get("action")
553
+ if action == "PushOrder":
554
+ self.orders._on_message(msg)
555
+ elif action == "PushAccount":
556
+ self.balance._on_message(msg)
557
+ elif action == "PushPosition":
558
+ self.position._on_message(msg)
559
+ elif action == "PushTrade":
560
+ self.trades._on_message(msg)
561
+
562
+ def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
563
+ self._on_message(msg, ws)
564
+
565
+ async def initialize(self, *aws: Awaitable[aiohttp.ClientResponse]) -> None:
566
+ for fut in asyncio.as_completed(aws):
567
+ res = await fut
568
+ data = await res.json()
569
+ path = res.url.path
570
+ if path.endswith("/market/instruments"):
571
+ self.detail._clear()
572
+ self.detail._on_response(data)
573
+ elif path.endswith("/market/tickers"):
574
+ self.ticker._on_response(data)
575
+ elif path.endswith("/trade/v2/orders-pending"):
576
+ self.orders._on_response(data)
577
+ elif path.endswith("/account/positions"):
578
+ self.position._on_response(data)
579
+ elif path.endswith("/account/balances"):
580
+ self.balance._on_response(data)
581
+ elif path.endswith("/trade/fills"):
582
+ self.trades._on_response(data)
583
+ elif path.endswith("/trade/orders-history"):
584
+ self.orders._on_response(data)
585
+
586
+ @property
587
+ def ticker(self) -> Ticker:
588
+ """
589
+ _key: s
590
+ .. code :: json
591
+
592
+ [{
593
+ "s": "BTCUSDT",
594
+ "I": "BTCUSDT",
595
+ "U": 1757642301089,
596
+ "PF": 1756690200,
597
+ "E": 0.0005251816,
598
+ "O": 114206.7,
599
+ "H": 116346,
600
+ "L": 114132.8,
601
+ "V": 7688046,
602
+ "T": 885654450.392686,
603
+ "N": 115482.9,
604
+ "M": 115473.7,
605
+ "D": 115455.77,
606
+ "V2": 19978848,
607
+ "T2": 2288286517.724497,
608
+ "F": 57727.9,
609
+ "C": 173183.6,
610
+ "BP1": 115482.8,
611
+ "AP1": 115482.9
612
+ }]
613
+ """
614
+ return self._get("ticker")
615
+
616
+ @property
617
+ def book(self) -> Book:
618
+ """
619
+ _key: s, S
620
+ .. code :: json
621
+
622
+ [
623
+ {
624
+ "s": "BTCUSDT",
625
+ "S": "b",
626
+ "p": 115482.8,
627
+ "q": 0
628
+ },
629
+ {
630
+ "s": "BTCUSDT",
631
+ "S": "a",
632
+ "p": 115482.9,
633
+ "q": 0
634
+ }
635
+ ]
636
+
637
+ """
638
+ return self._get("book")
639
+
640
+ @property
641
+ def detail(self) -> Detail:
642
+ """
643
+ _key: s
644
+ .. code :: json
645
+
646
+ [
647
+ {
648
+ "s": "BTCUSDT",
649
+ "instType": "SWAP",
650
+ "instId": "BTC-USDT-SWAP",
651
+ "uly": "",
652
+ "baseCcy": "BTC",
653
+ "quoteCcy": "USDT",
654
+ "ctVal": "0.001",
655
+ "ctValCcy": "",
656
+ "listTime": "0",
657
+ "lever": "125",
658
+ "tickSz": "0.1",
659
+ "lotSz": "1",
660
+ "minSz": "1",
661
+ "ctType": "",
662
+ "alias": "",
663
+ "state": "live",
664
+ "maxLmtSz": "200000",
665
+ "maxMktSz": "200000",
666
+ "tick_size": "0.1",
667
+ "step_size": "1"
668
+ }
669
+ ]
670
+ """
671
+ return self._get("detail")
672
+
673
+ @property
674
+ def orders(self) -> Orders:
675
+ """
676
+ 当前委托订单数据。
677
+
678
+ 该数据结构用于记录当前账户下所有活跃的委托订单(如限价单、市价单等),
679
+ 包含订单的唯一标识、交易对、状态、价格、数量等关键信息。生命周期上,
680
+ 订单在下单、成交、撤单等过程中会不断更新,配合 `trades` 可追踪完整的订单执行历程。
681
+
682
+ _key: ordId
683
+ .. code :: json
684
+
685
+ [
686
+ {
687
+ "ordId": "1234567890",
688
+ "instId": "BTC-USDT-SWAP",
689
+ "clOrdId": "myorder001",
690
+ "px": "115000",
691
+ "sz": "0.01",
692
+ "ordType": "limit",
693
+ "side": "buy",
694
+ "state": "live",
695
+ "accFillSz": "0",
696
+ "insertTime": 1711111111111,
697
+ "updateTime": 1711111112222
698
+ }
699
+ ]
700
+ 主要字段:
701
+ - ordId: 订单唯一id
702
+ - instId: 交易对
703
+ - px: 委托价格
704
+ - sz: 委托数量
705
+ - ordType: 订单类型(如限价、市价)
706
+ - state: 当前订单状态(如live/partially_filled/filled/canceled等)
707
+ - accFillSz: 已成交数量
708
+ - insertTime: 下单时间
709
+ - updateTime: 更新时间
710
+ """
711
+ return self._get("orders")
712
+
713
+ @property
714
+ def position(self) -> Position:
715
+ """
716
+ 当前持仓数据。
717
+
718
+ 记录账户在各交易对上的持仓情况,包括方向、多空、持仓量、均价、杠杆等。
719
+ 持仓数据在开仓、平仓、爆仓等事件发生时实时更新,是风险监控与盈亏计算的基础。
720
+
721
+ _key: instId, posSide
722
+ .. code :: json
723
+
724
+ [
725
+ {
726
+ "instType": "SWAP",
727
+ "mgnMode": "cross",
728
+ "instId": "DOT-USDT-SWAP",
729
+ "posId": "1001113501647163",
730
+ "posSide": "long",
731
+ "pos": "5",
732
+ "avgPx": "2.624",
733
+ "lever": "20",
734
+ "liqPx": "0.001",
735
+ "useMargin": "0.0656",
736
+ "unrealizedProfit": "0.001000000000000112",
737
+ "mrgPosition": "merge",
738
+ "ccy": "USDT",
739
+ "uTime": "1762350896000",
740
+ "cTime": "1762350896000"
741
+ }
742
+ ]
743
+ """
744
+ return self._get("position")
745
+
746
+ @property
747
+ def balance(self) -> Balance:
748
+ """
749
+ 账户余额数据。
750
+
751
+ 反映账户在各币种上的余额、可用余额、冻结余额等资金情况,是资金划转与风险控制的重要依据。
752
+ 余额数据在充值、提现、成交、资金划转等环节实时变动。
753
+
754
+ _key: ccy
755
+ .. code :: json
756
+
757
+ [
758
+ {
759
+ "ccy": "USDT",
760
+ "bal": "1000.0",
761
+ "availBal": "800.0",
762
+ "frozenBal": "200.0",
763
+ "withdrawable": "750.0"
764
+ }
765
+ ]
766
+ 主要字段:
767
+ - ccy: 币种
768
+ - bal: 总余额
769
+ - availBal: 可用余额
770
+ - frozenBal: 冻结余额
771
+ - withdrawable: 可提余额
772
+ """
773
+ return self._get("balance")
774
+
775
+ @property
776
+ def trades(self) -> Trades:
777
+ """
778
+ 成交明细数据。
779
+
780
+ 记录账户所有历史成交(包括主动成交与被动成交),用于统计订单执行情况、盈亏分析和对账。
781
+ 每条成交数据包含唯一成交id、订单id、成交方向、成交数量、成交价格、手续费等。
782
+
783
+ _key: tradeId
784
+ .. code :: json
785
+
786
+ [
787
+ {
788
+ "tradeId": "9876543210",
789
+ "ordId": "1234567890",
790
+ "instId": "BTCUSDT",
791
+ "direction": "buy",
792
+ "sz": "0.01",
793
+ "px": "115100",
794
+ "fee": "-0.02",
795
+ "feeCcy": "USDT",
796
+ "tradeTime": 1711111114444
797
+ }
798
+ ]
799
+ 主要字段:
800
+ - tradeId: 成交唯一id
801
+ - ordId: 所属订单id
802
+ - instId: 交易对
803
+ - direction: 买卖方向
804
+ - sz: 成交数量
805
+ - px: 成交价格
806
+ - fee: 手续费
807
+ - tradeTime: 成交时间
808
+ """
809
+ return self._get("trades")