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,651 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import time
6
+ from contextlib import suppress
7
+ from decimal import Decimal, ROUND_CEILING, ROUND_DOWN, ROUND_HALF_UP
8
+ from typing import Any, Sequence, Literal
9
+
10
+ import pybotters
11
+
12
+ from .models.deepcoin import DeepCoinDataStore
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class DeepCoin:
18
+ """DeepCoin 合约客户端(REST + WebSocket)。"""
19
+
20
+ def __init__(
21
+ self,
22
+ client: pybotters.Client,
23
+ *,
24
+ rest_api: str | None = None,
25
+ ws_public: str | None = None,
26
+ ws_private: str | None = None,
27
+ inst_type: str = "SWAP",
28
+ ) -> None:
29
+ self.client = client
30
+ self.store = DeepCoinDataStore()
31
+
32
+ self.rest_api = rest_api or "https://api.deepcoin.com"
33
+ self.ws_public = (
34
+ ws_public
35
+ or "wss://stream.deepcoin.com/streamlet/trade/public/swap?platform=api"
36
+ )
37
+ self.ws_private = ws_private or "wss://stream.deepcoin.com/v1/private"
38
+ self.inst_type = inst_type
39
+
40
+ self._ws_public: pybotters.ws.WebSocketApp | None = None
41
+ self._ws_public_ready = asyncio.Event()
42
+ self._ws_private: pybotters.ws.WebSocketApp | None = None
43
+ self._ws_private_ready = asyncio.Event()
44
+ self._listen_key: str | None = None
45
+ self._listen_key_expire_at: float = 0.0
46
+ self._listen_key_task: asyncio.Task | None = None
47
+ self._listen_key_lock = asyncio.Lock()
48
+
49
+ async def __aenter__(self) -> "DeepCoin":
50
+ await self.update("detail")
51
+ return self
52
+
53
+ async def __aexit__(self, exc_type, exc, tb) -> None: # pragma: no cover - symmetry
54
+ await self.aclose()
55
+
56
+ async def aclose(self) -> None:
57
+ if self._ws_public is not None:
58
+ await self._ws_public.current_ws.close()
59
+ self._ws_public = None
60
+ self._ws_public_ready.clear()
61
+ if self._ws_private is not None:
62
+ await self._ws_private.current_ws.close()
63
+ self._ws_private = None
64
+ self._ws_private_ready.clear()
65
+ if self._listen_key_task is not None:
66
+ self._listen_key_task.cancel()
67
+ with suppress(Exception):
68
+ await self._listen_key_task
69
+ self._listen_key_task = None
70
+ self._listen_key = None
71
+ self._listen_key_expire_at = 0.0
72
+
73
+ async def update(
74
+ self,
75
+ update_type: Literal[
76
+ "all", "detail", "ticker", "orders", "positions", "balances", "trades", "orders-history"
77
+ ] = "all",
78
+ *,
79
+ inst_type: str | None = None,
80
+ inst_id: str | None = None,
81
+ symbol: str | None = None,
82
+ page: int = 1,
83
+ limit: int | None = None,
84
+ ) -> None:
85
+ """刷新本地缓存(detail / ticker / 私有数据)。"""
86
+
87
+ inst = inst_type or self.inst_type
88
+ requests: list[Any] = []
89
+
90
+ include_detail = update_type in {"detail", "all"}
91
+ include_ticker = update_type in {"ticker", "all"}
92
+ include_orders = update_type in {"orders"} or (update_type == "all" and inst_id)
93
+ include_history_orders = update_type in {"orders-history"}
94
+ include_positions = update_type in {"position", "positions", "all"}
95
+ include_balances = update_type in {"balance", "balances", "account", "all"}
96
+ include_trades = update_type in {"trade", "trades"} or (
97
+ update_type == "all" and inst_id
98
+ )
99
+
100
+ if include_detail:
101
+ params = {"instType": inst}
102
+ requests.append(
103
+ self.client.get(
104
+ f"{self.rest_api}/deepcoin/market/instruments", params=params
105
+ )
106
+ )
107
+
108
+ if include_ticker:
109
+ params = {"instType": inst}
110
+ requests.append(
111
+ self.client.get(
112
+ f"{self.rest_api}/deepcoin/market/tickers", params=params
113
+ )
114
+ )
115
+
116
+ if include_history_orders:
117
+ params = {"instType": inst}
118
+ if limit:
119
+ params["limit"] = limit
120
+
121
+ requests.append(
122
+ self.client.get(
123
+ f"{self.rest_api}/deepcoin/trade/orders-history",
124
+ params=params,
125
+ )
126
+ )
127
+
128
+ if include_orders:
129
+ if not inst_id:
130
+ raise ValueError("inst_id is required when updating orders")
131
+ params = {"instId": inst_id, "index": page}
132
+ if limit is not None:
133
+ params["limit"] = limit
134
+ requests.append(
135
+ self.client.get(
136
+ f"{self.rest_api}/deepcoin/trade/v2/orders-pending",
137
+ params=params,
138
+ )
139
+ )
140
+
141
+ if include_positions:
142
+ params = {"instType": inst}
143
+
144
+ requests.append(
145
+ self.client.get(
146
+ f"{self.rest_api}/deepcoin/account/positions",
147
+ params=params,
148
+ )
149
+ )
150
+
151
+ if include_balances:
152
+ params = {"instType": inst}
153
+ requests.append(
154
+ self.client.get(
155
+ f"{self.rest_api}/deepcoin/account/balances",
156
+ params=params,
157
+ )
158
+ )
159
+
160
+ if include_trades:
161
+ if not inst_id:
162
+ raise ValueError("inst_id is required when updating trades")
163
+ params = {"instId": inst_id}
164
+ if limit is not None:
165
+ params["limit"] = limit
166
+ requests.append(
167
+ self.client.get(
168
+ f"{self.rest_api}/deepcoin/trade/fills",
169
+ params=params,
170
+ )
171
+ )
172
+
173
+ if not requests:
174
+ raise ValueError(f"Unsupported update_type: {update_type}")
175
+
176
+ await self.store.initialize(*requests)
177
+
178
+ async def sub_ticker(
179
+ self,
180
+ symbols: Sequence[str] | str,
181
+ *,
182
+ resume_no: int = -1,
183
+ local_no_start: int = 1,
184
+ action: str = "1",
185
+ ) -> pybotters.ws.WebSocketApp:
186
+ """订阅 PO 频道(顶深度行情)。"""
187
+
188
+ if isinstance(symbols, str):
189
+ symbol_list = [symbols]
190
+ else:
191
+ symbol_list = list(symbols)
192
+
193
+ if not symbol_list:
194
+ raise ValueError("symbols must not be empty")
195
+
196
+ payload: list[dict[str, Any]] = []
197
+ for idx, symbol in enumerate(symbol_list):
198
+ payload.append(
199
+ {
200
+ "SendTopicAction": {
201
+ "Action": action,
202
+ "FilterValue": f"DeepCoin_{symbol}",
203
+ "LocalNo": local_no_start + idx,
204
+ "ResumeNo": resume_no,
205
+ "TopicID": "7",
206
+ }
207
+ }
208
+ )
209
+
210
+ ws_app = self.client.ws_connect(
211
+ self.ws_public,
212
+ send_json=payload if len(payload) > 1 else payload[0],
213
+ hdlr_json=self.store.onmessage,
214
+ )
215
+
216
+ await ws_app._event.wait()
217
+ self._ws_public = ws_app
218
+ self._ws_public_ready.set()
219
+ return ws_app
220
+
221
+ async def sub_orderbook(
222
+ self,
223
+ symbols: Sequence[str] | str,
224
+ **kwargs: Any,
225
+ ) -> pybotters.ws.WebSocketApp:
226
+ """订阅顶档深度(PO 频道)的别名。"""
227
+
228
+ return await self.sub_ticker(symbols, **kwargs)
229
+
230
+ async def _acquire_listen_key(self) -> str:
231
+ res = await self.client.get(f"{self.rest_api}/deepcoin/listenkey/acquire")
232
+ payload = await res.json()
233
+ data = payload.get("data") or {}
234
+ listen_key = data.get("listenkey")
235
+ if not listen_key:
236
+ raise RuntimeError(f"Failed to acquire DeepCoin listenKey: {payload}")
237
+ expire_time = data.get("expire_time")
238
+ self._listen_key = listen_key
239
+ self._listen_key_expire_at = (
240
+ float(expire_time) if expire_time else time.time() + 3600
241
+ )
242
+ return listen_key
243
+
244
+ async def _extend_listen_key(self) -> None:
245
+ if not self._listen_key:
246
+ raise RuntimeError("listenKey not initialized")
247
+ params = {"listenkey": self._listen_key}
248
+ res = await self.client.get(
249
+ f"{self.rest_api}/deepcoin/listenkey/extend", params=params
250
+ )
251
+ payload = await res.json()
252
+ data = payload.get("data") or {}
253
+ expire_time = data.get("expire_time")
254
+ if expire_time:
255
+ self._listen_key_expire_at = float(expire_time)
256
+
257
+ async def _ensure_listen_key(self) -> str:
258
+ async with self._listen_key_lock:
259
+ now = time.time()
260
+ if self._listen_key and now < self._listen_key_expire_at - 300:
261
+ return self._listen_key
262
+ if self._listen_key and now < self._listen_key_expire_at - 60:
263
+ await self._extend_listen_key()
264
+ return self._listen_key
265
+ return await self._acquire_listen_key()
266
+
267
+ async def _keep_listen_key_alive(self) -> None:
268
+ try:
269
+ while True:
270
+ await asyncio.sleep(1800)
271
+ if self._listen_key is None:
272
+ continue
273
+ try:
274
+ await self._extend_listen_key()
275
+ except Exception:
276
+ logger.exception("DeepCoin listenKey keepalive failed")
277
+ except asyncio.CancelledError: # pragma: no cover - task control flow
278
+ pass
279
+
280
+ async def sub_private(self) -> pybotters.ws.WebSocketApp:
281
+ """订阅私有频道,推送订单/资金/持仓/成交。"""
282
+
283
+ if self._ws_private is not None and not self._ws_private.closed:
284
+ return self._ws_private
285
+
286
+ listen_key = await self._ensure_listen_key()
287
+ url = f"{self.ws_private}?listenKey={listen_key}"
288
+ ws_app = self.client.ws_connect(
289
+ url,
290
+ hdlr_json=self.store.onmessage,
291
+ )
292
+ await ws_app._event.wait()
293
+ self._ws_private = ws_app
294
+ self._ws_private_ready.set()
295
+ if self._listen_key_task is None or self._listen_key_task.done():
296
+ self._listen_key_task = asyncio.create_task(self._keep_listen_key_alive())
297
+ return ws_app
298
+
299
+ # ------------------------------------------------------------------
300
+ # Helpers
301
+
302
+ async def _ensure_detail_cache(self) -> None:
303
+ if not self.store.detail.find():
304
+ await self.update("detail")
305
+
306
+ async def _resolve_instrument(
307
+ self,
308
+ inst_id: str | None = None,
309
+ symbol: str | None = None,
310
+ ) -> tuple[str, dict[str, Any]]:
311
+ await self._ensure_detail_cache()
312
+
313
+ entries = list(self.store.detail.find())
314
+
315
+ def _enrich(detail: dict[str, Any]) -> tuple[str, dict[str, Any]] | None:
316
+ if not detail:
317
+ return None
318
+ inst = detail.get("instId")
319
+ if not inst:
320
+ base = detail.get("baseCcy") or detail.get("base")
321
+ quote = detail.get("quoteCcy") or detail.get("quote")
322
+ inst_type = detail.get("instType")
323
+ if base and quote:
324
+ inst = f"{base}-{quote}"
325
+ if inst_type:
326
+ inst = f"{inst}-{str(inst_type).upper()}"
327
+ if inst:
328
+ detail = dict(detail)
329
+ detail["instId"] = inst
330
+ return inst, detail
331
+ return None
332
+
333
+ if inst_id:
334
+ search_keys = {
335
+ inst_id,
336
+ inst_id.upper(),
337
+ inst_id.replace("-", ""),
338
+ inst_id.replace("-", "").upper(),
339
+ }
340
+ for detail in entries:
341
+ inst = str(detail.get("instId", ""))
342
+ if inst and inst in search_keys:
343
+ enriched = _enrich(detail)
344
+ if enriched:
345
+ return enriched
346
+ s_val = str(detail.get("s", ""))
347
+ if s_val and s_val in search_keys:
348
+ enriched = _enrich(detail)
349
+ if enriched:
350
+ return enriched
351
+
352
+ if symbol:
353
+ normalized_symbol = symbol.replace("-", "").upper()
354
+ for detail in entries:
355
+ s_val = str(detail.get("s", "")).upper()
356
+ inst = str(detail.get("instId", ""))
357
+ inst_compact = inst.replace("-", "").upper()
358
+ if normalized_symbol in {s_val, inst_compact}:
359
+ enriched = _enrich(detail)
360
+ if enriched:
361
+ return enriched
362
+
363
+ if normalized_symbol.endswith("USDT") and self.inst_type.upper() == "SWAP":
364
+ base = normalized_symbol[:-4]
365
+ guess = f"{base}-USDT-SWAP"
366
+ return await self._resolve_instrument(inst_id=guess)
367
+
368
+ # fallback: refresh detail and try again
369
+ await self.update("detail")
370
+ if inst_id or symbol:
371
+ return await self._resolve_instrument(inst_id=inst_id, symbol=symbol)
372
+
373
+ raise ValueError(
374
+ "Unable to resolve instrument; please provide inst_id or symbol"
375
+ )
376
+
377
+ @staticmethod
378
+ def _to_decimal(value: float | str | Decimal) -> Decimal:
379
+ if isinstance(value, Decimal):
380
+ return value
381
+ return Decimal(str(value))
382
+
383
+ @staticmethod
384
+ def _quantize(
385
+ value: Decimal, step: str | float | Decimal | None, rounding
386
+ ) -> Decimal:
387
+ if step is None:
388
+ return value
389
+ step_dec = Decimal(str(step))
390
+ if step_dec == 0:
391
+ return value
392
+ return (value / step_dec).to_integral_value(rounding=rounding) * step_dec
393
+
394
+ @staticmethod
395
+ def _ceil_to_step(value: Decimal, step: str | float | Decimal | None) -> Decimal:
396
+ if step is None:
397
+ return value
398
+ step_dec = Decimal(str(step))
399
+ if step_dec == 0:
400
+ return value
401
+ return (value / step_dec).to_integral_value(rounding=ROUND_CEILING) * step_dec
402
+
403
+ @staticmethod
404
+ def _decimal_to_str(value: Decimal) -> str:
405
+ s = format(value, "f")
406
+ if "." in s:
407
+ s = s.rstrip("0").rstrip(".")
408
+ return s or "0"
409
+
410
+ def inst_id_to_symbol(self, inst_id: str) -> str:
411
+ """将 DeepCoin 的 inst_id 转换为标准 symbol 格式。"""
412
+ parts = inst_id.split("-")
413
+ if len(parts) >= 2:
414
+ base = parts[0]
415
+ quote = parts[1]
416
+ return f"{base}{quote}"
417
+ return inst_id
418
+
419
+ def symbol_to_inst_id(self, symbol: str) -> str:
420
+ """将标准 symbol 格式转换为 DeepCoin 的 inst_id 格式。"""
421
+ if symbol.endswith("USDT"):
422
+ base = symbol[:-4]
423
+ quote = "USDT"
424
+ elif symbol.endswith("USD"):
425
+ base = symbol[:-3]
426
+ quote = "USD"
427
+ else:
428
+ raise ValueError(f"Unsupported symbol format: {symbol}")
429
+ return f"{base}-{quote}-SWAP"
430
+
431
+ # ------------------------------------------------------------------
432
+ # Trading APIs
433
+
434
+ async def place_order(
435
+ self,
436
+ *,
437
+ inst_id: str | None = None,
438
+ symbol: str | None = None,
439
+ side: Literal["buy", "sell"],
440
+ ord_type: Literal["limit", "market", "post_only", "ioc"],
441
+ qty_contract: float | str | None = None,
442
+ qty_base: float | str | None = None,
443
+ price: float | str | None = None,
444
+ td_mode: Literal["isolated", "cross"] = "cross",
445
+ pos_side: Literal["long", "short", "net"] | None = None,
446
+ mrg_position: Literal["merge", "split"] | None = None,
447
+ reduce_only: bool | None = None,
448
+ ccy: str | None = None,
449
+ cl_ord_id: str | None = None,
450
+ tag: str | None = None,
451
+ close_pos_id: str | None = None,
452
+ tgt_ccy: str | None = None,
453
+ tp_trigger_px: float | str | None = None,
454
+ sl_trigger_px: float | str | None = None,
455
+ ) -> dict[str, Any]:
456
+ """``POST /deepcoin/trade/order`` with precision auto-adjustment.
457
+
458
+ {'ordId': '1001113832243662', 'clOrdId': '', 'tag': '', 'sCode': '0', 'sMsg': ''}
459
+ """
460
+
461
+ resolved_inst_id, detail = await self._resolve_instrument(
462
+ inst_id=inst_id, symbol=symbol
463
+ )
464
+
465
+ lot_step = detail.get("lotSz") or detail.get("step_size")
466
+ tick_step = detail.get("tickSz") or detail.get("tick_size")
467
+ min_size = detail.get("minSz")
468
+
469
+ if qty_contract is None and qty_base is None:
470
+ raise ValueError("Either qty_contract or qty_base must be provided")
471
+
472
+ contract_value_raw = (
473
+ detail.get("ctVal")
474
+ or detail.get("contractValue")
475
+ or detail.get("faceValue")
476
+ or "1"
477
+ )
478
+ try:
479
+ contract_value_dec = Decimal(str(contract_value_raw))
480
+ except Exception:
481
+ contract_value_dec = Decimal("1")
482
+ if contract_value_dec <= 0:
483
+ contract_value_dec = Decimal("1")
484
+
485
+ qty_contract_dec: Decimal | None = None
486
+
487
+ if qty_contract is not None:
488
+ qty_contract_dec = self._to_decimal(qty_contract)
489
+
490
+ if qty_base is not None:
491
+ qty_base_dec = self._to_decimal(qty_base)
492
+ converted = qty_base_dec / contract_value_dec
493
+ if qty_contract_dec is None:
494
+ qty_contract_dec = converted
495
+ elif abs(qty_contract_dec - converted) > Decimal("1e-8"):
496
+ qty_contract_dec = converted
497
+
498
+ if qty_contract_dec is None:
499
+ raise ValueError("Unable to determine qty_contract from inputs")
500
+
501
+ qty_contract_dec = self._quantize(qty_contract_dec, lot_step, ROUND_DOWN)
502
+ if min_size is not None:
503
+ min_dec = self._to_decimal(min_size)
504
+ if qty_contract_dec < min_dec:
505
+ qty_contract_dec = self._ceil_to_step(min_dec, lot_step)
506
+ if qty_contract_dec <= 0:
507
+ raise ValueError("qty_contract is too small after precision adjustment")
508
+
509
+ payload: dict[str, Any] = {
510
+ "instId": resolved_inst_id,
511
+ "tdMode": td_mode,
512
+ "side": side,
513
+ "ordType": ord_type,
514
+ "sz": self._decimal_to_str(qty_contract_dec),
515
+ }
516
+ if detail.get("instType"):
517
+ payload["instType"] = detail["instType"]
518
+
519
+ inst_id_upper = resolved_inst_id.upper()
520
+ requires_pos = inst_id_upper.endswith("-SWAP")
521
+ if requires_pos:
522
+ if pos_side is None:
523
+ effective_pos = "long" if side == "buy" else "short"
524
+ else:
525
+ effective_pos = pos_side
526
+ payload["posSide"] = effective_pos
527
+ if mrg_position or td_mode == "cross":
528
+ payload["mrgPosition"] = mrg_position or "merge"
529
+ elif pos_side is not None:
530
+ payload["posSide"] = pos_side
531
+ if mrg_position and not requires_pos:
532
+ payload["mrgPosition"] = mrg_position
533
+
534
+ if ord_type in {"limit", "post_only"}:
535
+ if price is None:
536
+ raise ValueError("price is required for limit/post_only orders")
537
+ price_dec = self._quantize(
538
+ self._to_decimal(price), tick_step, ROUND_HALF_UP
539
+ )
540
+ if price_dec <= 0:
541
+ raise ValueError("price must be positive after precision adjustment")
542
+ payload["px"] = self._decimal_to_str(price_dec)
543
+ elif price is not None:
544
+ price_dec = self._quantize(
545
+ self._to_decimal(price), tick_step, ROUND_HALF_UP
546
+ )
547
+ if price_dec > 0:
548
+ payload["px"] = self._decimal_to_str(price_dec)
549
+
550
+ if reduce_only is not None:
551
+ payload["reduceOnly"] = bool(reduce_only)
552
+ if ccy:
553
+ payload["ccy"] = ccy
554
+ if cl_ord_id:
555
+ payload["clOrdId"] = cl_ord_id
556
+ if tag:
557
+ payload["tag"] = tag
558
+ if close_pos_id:
559
+ payload["closePosId"] = close_pos_id
560
+ if tgt_ccy:
561
+ payload["tgtCcy"] = tgt_ccy
562
+
563
+ if tp_trigger_px is not None:
564
+ tp_dec = self._quantize(
565
+ self._to_decimal(tp_trigger_px), tick_step, ROUND_HALF_UP
566
+ )
567
+ payload["tpTriggerPx"] = self._decimal_to_str(tp_dec)
568
+ if sl_trigger_px is not None:
569
+ sl_dec = self._quantize(
570
+ self._to_decimal(sl_trigger_px), tick_step, ROUND_HALF_UP
571
+ )
572
+ payload["slTriggerPx"] = self._decimal_to_str(sl_dec)
573
+
574
+ res = await self.client.post(
575
+ f"{self.rest_api}/deepcoin/trade/order",
576
+ data=payload,
577
+ )
578
+ data:dict = await res.json()
579
+ code = data.get("code", '0')
580
+ if code != '0':
581
+ raise RuntimeError(f"Failed to place order: {data.get('msg','')}")
582
+
583
+ data = data.get("data", {})
584
+ sccode = str(data.get("sCode", ""))
585
+ smsg = data.get("sMsg", "")
586
+ if sccode != "0":
587
+ raise RuntimeError(f"Failed to place order: {sccode} {smsg}")
588
+ return data
589
+
590
+ async def cancel_order(
591
+ self,
592
+ *,
593
+ inst_id: str | None = None,
594
+ symbol: str | None = None,
595
+ ord_id: str,
596
+ ) -> dict[str, Any]:
597
+ resolved_inst_id, _ = await self._resolve_instrument(
598
+ inst_id=inst_id, symbol=symbol
599
+ )
600
+ payload = {"instId": resolved_inst_id, "ordId": ord_id}
601
+ res = await self.client.post(
602
+ f"{self.rest_api}/deepcoin/trade/cancel-order",
603
+ data=payload,
604
+ )
605
+ resp = await res.json()
606
+ data = resp.get("data", {})
607
+ sc_code = str(data.get("sCode", ""))
608
+ if sc_code != "0":
609
+ raise RuntimeError(f"Failed to cancel order: {resp}")
610
+ return data
611
+
612
+ async def get_price_list(
613
+ self,
614
+ ) -> dict[str, Any]:
615
+
616
+ """
617
+ 返回值示例:
618
+ .. code :: json
619
+ {
620
+ "code": 0,
621
+ "msg": "OK",
622
+ "data": [
623
+ {
624
+ "ProductGroup": "SwapU",
625
+ "InstrumentID": "LAYERUSDT",
626
+ "OpenPrice": 0.2015,
627
+ "LastPrice": 0.2039,
628
+ "MarkedPrice": 0.204,
629
+ "LowerLimitPrice": 0.1022,
630
+ "UpperLimitPrice": 0.3065,
631
+ "HighestPrice": 0.2085,
632
+ "LowestPrice": 0.1929,
633
+ "Volume": 9747616,
634
+ "Turnover": 1961292.57819997,
635
+ "AskPrice1": 0.204,
636
+ "BidPrice1": 0.2039,
637
+ "Volume24": 13529402,
638
+ "Turnover24": 2721574.44530007
639
+ }
640
+ ]
641
+ }
642
+ """
643
+
644
+ # https://www.deepcoin.com/v2/public/query/swap/price-list?system=SwapU
645
+
646
+ res = await self.client.get(
647
+ f"{self.rest_api}/v2/public/query/swap/price-list",
648
+ params={"system": "SwapU"},
649
+ )
650
+
651
+ return await res.json()