hyperquant 0.65__py3-none-any.whl → 0.67__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.
- hyperquant/broker/auth.py +144 -9
- hyperquant/broker/edgex.py +331 -14
- hyperquant/broker/lbank.py +235 -17
- hyperquant/broker/lib/edgex_sign.py +455 -0
- hyperquant/broker/lib/util.py +9 -0
- hyperquant/broker/models/edgex.py +540 -5
- hyperquant/broker/models/lbank.py +342 -0
- hyperquant/broker/ws.py +4 -3
- {hyperquant-0.65.dist-info → hyperquant-0.67.dist-info}/METADATA +1 -1
- {hyperquant-0.65.dist-info → hyperquant-0.67.dist-info}/RECORD +11 -9
- {hyperquant-0.65.dist-info → hyperquant-0.67.dist-info}/WHEEL +0 -0
@@ -4,6 +4,7 @@ import asyncio
|
|
4
4
|
from typing import Any, Awaitable, TYPE_CHECKING
|
5
5
|
|
6
6
|
from aiohttp import ClientResponse
|
7
|
+
import aiohttp
|
7
8
|
from pybotters.store import DataStore, DataStoreCollection
|
8
9
|
|
9
10
|
if TYPE_CHECKING:
|
@@ -121,7 +122,6 @@ class Book(DataStore):
|
|
121
122
|
if updates:
|
122
123
|
self._update(updates)
|
123
124
|
self._trim(contract_id, contract_name)
|
124
|
-
|
125
125
|
|
126
126
|
def _build_items(
|
127
127
|
self,
|
@@ -235,6 +235,22 @@ class Ticker(DataStore):
|
|
235
235
|
else:
|
236
236
|
self._update([item])
|
237
237
|
|
238
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
239
|
+
entries = data.get("data") or []
|
240
|
+
|
241
|
+
if not isinstance(entries, list):
|
242
|
+
entries = [entries]
|
243
|
+
|
244
|
+
items = []
|
245
|
+
for entry in entries:
|
246
|
+
item = self._format(entry)
|
247
|
+
if item:
|
248
|
+
items.append(item)
|
249
|
+
|
250
|
+
self._clear()
|
251
|
+
if items:
|
252
|
+
self._insert(items)
|
253
|
+
|
238
254
|
def _format(self, entry: dict[str, Any]) -> dict[str, Any] | None:
|
239
255
|
contract_id = entry.get("contractId")
|
240
256
|
if contract_id is None:
|
@@ -279,6 +295,311 @@ class Ticker(DataStore):
|
|
279
295
|
return item
|
280
296
|
|
281
297
|
|
298
|
+
class Order(DataStore):
|
299
|
+
"""Order data store combining REST results with trade-event deltas.
|
300
|
+
|
301
|
+
We only keep fields that are practical for trading book-keeping: identifiers,
|
302
|
+
basic order parameters, cumulative fills, high-level status and timestamps.
|
303
|
+
Network payloads carry hundreds of fields (``l2`` signatures, TPSL templates,
|
304
|
+
liquidation metadata, etc.), but the extra data adds noise and bloats memory
|
305
|
+
consumption. This store narrows every entry to a compact schema while still
|
306
|
+
supporting diff events from the private websocket feed.
|
307
|
+
"""
|
308
|
+
|
309
|
+
_KEYS = ["orderId"]
|
310
|
+
|
311
|
+
_TERMINAL_STATUSES = {
|
312
|
+
"FILLED",
|
313
|
+
"CANCELED",
|
314
|
+
"CANCELLED",
|
315
|
+
"REJECTED",
|
316
|
+
"EXPIRED",
|
317
|
+
}
|
318
|
+
|
319
|
+
_ACTIVE_STATUSES = {
|
320
|
+
"OPEN",
|
321
|
+
"PARTIALLY_FILLED",
|
322
|
+
"PENDING",
|
323
|
+
"CREATED",
|
324
|
+
"ACKNOWLEDGED",
|
325
|
+
}
|
326
|
+
|
327
|
+
_KEEP_FIELDS = (
|
328
|
+
"userId",
|
329
|
+
"accountId",
|
330
|
+
"coinId",
|
331
|
+
"contractId",
|
332
|
+
"clientOrderId",
|
333
|
+
"type",
|
334
|
+
"timeInForce",
|
335
|
+
"reduceOnly",
|
336
|
+
"price",
|
337
|
+
"size",
|
338
|
+
"cumFillSize",
|
339
|
+
"cumFillValue",
|
340
|
+
"cumMatchSize",
|
341
|
+
"cumMatchValue",
|
342
|
+
"cumMatchFee",
|
343
|
+
"triggerPrice",
|
344
|
+
"triggerPriceType",
|
345
|
+
"cancelReason",
|
346
|
+
"createdTime",
|
347
|
+
"updatedTime",
|
348
|
+
"matchSequenceId",
|
349
|
+
)
|
350
|
+
|
351
|
+
_BOOL_FIELDS = {"reduceOnly"}
|
352
|
+
|
353
|
+
def _on_message(self, msg: dict[str, Any]) -> None:
|
354
|
+
content = msg.get("content") or {}
|
355
|
+
data = content.get("data") or {}
|
356
|
+
orders = data.get("order") or []
|
357
|
+
|
358
|
+
if not isinstance(orders, list):
|
359
|
+
orders = [orders]
|
360
|
+
|
361
|
+
items = [self._format(order) for order in orders]
|
362
|
+
items = [item for item in items if item]
|
363
|
+
if not items:
|
364
|
+
return
|
365
|
+
|
366
|
+
event = (content.get("event") or "").lower()
|
367
|
+
if event == "snapshot":
|
368
|
+
self._clear()
|
369
|
+
self._insert(items)
|
370
|
+
return
|
371
|
+
|
372
|
+
for item in items:
|
373
|
+
status = str(item.get("status") or "").upper()
|
374
|
+
criteria = {"orderId": item["orderId"]}
|
375
|
+
existing = self.find(criteria)
|
376
|
+
|
377
|
+
if status in self._TERMINAL_STATUSES:
|
378
|
+
if existing:
|
379
|
+
self._update([item])
|
380
|
+
else:
|
381
|
+
self._insert([item])
|
382
|
+
self._find_and_delete(criteria)
|
383
|
+
continue
|
384
|
+
|
385
|
+
if status and status not in self._ACTIVE_STATUSES:
|
386
|
+
if existing:
|
387
|
+
self._update([item])
|
388
|
+
else:
|
389
|
+
self._insert([item])
|
390
|
+
self._find_and_delete(criteria)
|
391
|
+
continue
|
392
|
+
|
393
|
+
if existing:
|
394
|
+
self._update([item])
|
395
|
+
else:
|
396
|
+
self._insert([item])
|
397
|
+
|
398
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
399
|
+
payload = data.get("data")
|
400
|
+
|
401
|
+
if isinstance(payload, dict):
|
402
|
+
orders = payload.get("dataList") or payload.get("orderList") or []
|
403
|
+
else:
|
404
|
+
orders = payload or []
|
405
|
+
|
406
|
+
if not isinstance(orders, list):
|
407
|
+
orders = [orders]
|
408
|
+
|
409
|
+
items = [self._format(order) for order in orders]
|
410
|
+
items = [item for item in items if item]
|
411
|
+
|
412
|
+
self._clear()
|
413
|
+
if items:
|
414
|
+
self._insert(items)
|
415
|
+
|
416
|
+
@staticmethod
|
417
|
+
def _normalize_order_id(value: Any) -> str | None:
|
418
|
+
if value is None:
|
419
|
+
return None
|
420
|
+
return str(value)
|
421
|
+
|
422
|
+
@staticmethod
|
423
|
+
def _normalize_side(value: Any) -> str | None:
|
424
|
+
if value is None:
|
425
|
+
return None
|
426
|
+
if isinstance(value, str):
|
427
|
+
return value.lower()
|
428
|
+
return str(value)
|
429
|
+
|
430
|
+
@staticmethod
|
431
|
+
def _normalize_status(value: Any) -> str | None:
|
432
|
+
if value is None:
|
433
|
+
return None
|
434
|
+
return str(value).upper()
|
435
|
+
|
436
|
+
@staticmethod
|
437
|
+
def _stringify(value: Any) -> Any:
|
438
|
+
if value is None:
|
439
|
+
return None
|
440
|
+
if isinstance(value, (bool, dict, list)):
|
441
|
+
return value
|
442
|
+
return str(value)
|
443
|
+
|
444
|
+
def _format(self, order: dict[str, Any] | None) -> dict[str, Any] | None:
|
445
|
+
if not order:
|
446
|
+
return None
|
447
|
+
|
448
|
+
order_id = (
|
449
|
+
order.get("orderId")
|
450
|
+
or order.get("id")
|
451
|
+
or order.get("order_id")
|
452
|
+
or order.get("orderID")
|
453
|
+
)
|
454
|
+
|
455
|
+
normalized_id = self._normalize_order_id(order_id)
|
456
|
+
if normalized_id is None:
|
457
|
+
return None
|
458
|
+
|
459
|
+
item: dict[str, Any] = {"orderId": normalized_id, "id": normalized_id}
|
460
|
+
|
461
|
+
side = self._normalize_side(order.get("side"))
|
462
|
+
if side is not None:
|
463
|
+
item["side"] = side
|
464
|
+
|
465
|
+
status = self._normalize_status(order.get("status"))
|
466
|
+
if status is not None:
|
467
|
+
item["status"] = status
|
468
|
+
|
469
|
+
contract_name = order.get("contractName")
|
470
|
+
if contract_name:
|
471
|
+
symbol = self._stringify(contract_name)
|
472
|
+
item["contractName"] = symbol
|
473
|
+
item.setdefault("symbol", symbol)
|
474
|
+
|
475
|
+
for field in self._KEEP_FIELDS:
|
476
|
+
if field in ("side", "status"):
|
477
|
+
continue
|
478
|
+
value = order.get(field)
|
479
|
+
if value is None:
|
480
|
+
continue
|
481
|
+
if field in self._BOOL_FIELDS:
|
482
|
+
item[field] = bool(value)
|
483
|
+
else:
|
484
|
+
item[field] = self._stringify(value)
|
485
|
+
|
486
|
+
return item
|
487
|
+
|
488
|
+
|
489
|
+
class Balance(DataStore):
|
490
|
+
"""Account balance snapshot retaining only the trading-critical fields."""
|
491
|
+
|
492
|
+
_KEYS = ["accountId", "coinId"]
|
493
|
+
|
494
|
+
|
495
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
496
|
+
data = data.get('data', {})
|
497
|
+
collateral_assets = data.get('collateralAssetModelList') or []
|
498
|
+
if collateral_assets:
|
499
|
+
self._update(collateral_assets)
|
500
|
+
|
501
|
+
def _on_message(self, msg: dict[str, Any]) -> None:
|
502
|
+
pass
|
503
|
+
|
504
|
+
|
505
|
+
class Position(DataStore):
|
506
|
+
"""
|
507
|
+
Stores per-account open positions in a simplified camelCase schema.
|
508
|
+
Only the current open position fields are retained: positionId, contractId, accountId,
|
509
|
+
userId, coinId, side, size, value, fee, fundingFee.
|
510
|
+
"""
|
511
|
+
|
512
|
+
_KEYS = ["positionId"]
|
513
|
+
|
514
|
+
@staticmethod
|
515
|
+
def _stringify(value: Any) -> Any:
|
516
|
+
if value is None:
|
517
|
+
return None
|
518
|
+
if isinstance(value, (bool, dict, list)):
|
519
|
+
return value
|
520
|
+
return str(value)
|
521
|
+
|
522
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
523
|
+
"""
|
524
|
+
Handle REST response for getAccountAsset (open positions snapshot).
|
525
|
+
Expects data from getAccountAsset (REST), which returns a snapshot of **current open positions**,
|
526
|
+
as a list in data["positionList"].
|
527
|
+
Each entry is normalized to camelCase schema, only including essential fields for the current open position.
|
528
|
+
"""
|
529
|
+
data = data.get("data", {}) or {}
|
530
|
+
positions = data.get("positionList") or []
|
531
|
+
if not isinstance(positions, list):
|
532
|
+
positions = [positions]
|
533
|
+
items = [self._normalize_position(pos) for pos in positions]
|
534
|
+
self._clear()
|
535
|
+
if items:
|
536
|
+
self._update(items)
|
537
|
+
|
538
|
+
def _on_message(self, msg: dict[str, Any]) -> None:
|
539
|
+
data = msg.get("content", {}).get("data", {})
|
540
|
+
if not data:
|
541
|
+
return
|
542
|
+
positions = data.get("position")
|
543
|
+
if not positions:
|
544
|
+
return
|
545
|
+
items = [self._normalize_position(pos) for pos in positions]
|
546
|
+
self._clear()
|
547
|
+
if items:
|
548
|
+
self._update(items)
|
549
|
+
|
550
|
+
def _normalize_position(self, pos: dict[str, Any]) -> dict[str, Any]:
|
551
|
+
# Only keep essential fields for the current open position
|
552
|
+
def get(key, *alts):
|
553
|
+
for k in (key,) + alts:
|
554
|
+
if k in pos and pos[k] is not None:
|
555
|
+
return pos[k]
|
556
|
+
return None
|
557
|
+
|
558
|
+
open_size = get("openSize")
|
559
|
+
open_value = get("openValue")
|
560
|
+
open_fee = get("openFee")
|
561
|
+
funding_fee = get("fundingFee")
|
562
|
+
|
563
|
+
# side: "long" if openSize > 0, "short" if openSize < 0, None if 0
|
564
|
+
side = None
|
565
|
+
try:
|
566
|
+
if open_size is not None:
|
567
|
+
fsize = float(open_size)
|
568
|
+
if fsize > 0:
|
569
|
+
side = "long"
|
570
|
+
elif fsize < 0:
|
571
|
+
side = "short"
|
572
|
+
except Exception:
|
573
|
+
side = None
|
574
|
+
|
575
|
+
size = None
|
576
|
+
if open_size is not None:
|
577
|
+
try:
|
578
|
+
size = str(abs(float(open_size)))
|
579
|
+
except Exception:
|
580
|
+
size = str(open_size)
|
581
|
+
value = None
|
582
|
+
if open_value is not None:
|
583
|
+
try:
|
584
|
+
value = str(abs(float(open_value)))
|
585
|
+
except Exception:
|
586
|
+
value = str(open_value)
|
587
|
+
|
588
|
+
item = {
|
589
|
+
"positionId": self._stringify(get("positionId", "position_id")),
|
590
|
+
"contractId": self._stringify(get("contractId")),
|
591
|
+
"accountId": self._stringify(get("accountId")),
|
592
|
+
"userId": self._stringify(get("userId")),
|
593
|
+
"coinId": self._stringify(get("coinId")),
|
594
|
+
"side": side,
|
595
|
+
"size": size,
|
596
|
+
"value": value,
|
597
|
+
"fee": self._stringify(open_fee),
|
598
|
+
"fundingFee": self._stringify(funding_fee),
|
599
|
+
}
|
600
|
+
return item
|
601
|
+
|
602
|
+
|
282
603
|
class CoinMeta(DataStore):
|
283
604
|
"""Coin metadata (precision, StarkEx info, etc.)."""
|
284
605
|
|
@@ -310,7 +631,7 @@ class CoinMeta(DataStore):
|
|
310
631
|
class ContractMeta(DataStore):
|
311
632
|
"""Per-contract trading parameters from the metadata endpoint."""
|
312
633
|
|
313
|
-
_KEYS = ["
|
634
|
+
_KEYS = ["contractName"]
|
314
635
|
|
315
636
|
_FIELDS = (
|
316
637
|
"contractName",
|
@@ -342,7 +663,9 @@ class ContractMeta(DataStore):
|
|
342
663
|
payload = {"contractId": str(contract_id)}
|
343
664
|
for key in self._FIELDS:
|
344
665
|
payload[key] = contract.get(key)
|
345
|
-
payload["riskTierList"] = self._simplify_risk_tiers(
|
666
|
+
payload["riskTierList"] = self._simplify_risk_tiers(
|
667
|
+
contract.get("riskTierList")
|
668
|
+
)
|
346
669
|
|
347
670
|
items.append(payload)
|
348
671
|
|
@@ -366,14 +689,57 @@ class ContractMeta(DataStore):
|
|
366
689
|
)
|
367
690
|
return items
|
368
691
|
|
692
|
+
|
693
|
+
class AppMeta(DataStore):
|
694
|
+
"""Global metadata (appName, env, fee account, etc.)."""
|
695
|
+
|
696
|
+
_KEYS = ["appName"]
|
697
|
+
|
698
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
699
|
+
appdata = (data.get("data") or {}).get("global") or {}
|
700
|
+
if not appdata:
|
701
|
+
self._clear()
|
702
|
+
return
|
703
|
+
# Convert all values to str where appropriate, but preserve fields as-is (for bool/int etc).
|
704
|
+
item = {}
|
705
|
+
for k, v in appdata.items():
|
706
|
+
if k == "starkExCollateralCoin" and isinstance(v, dict):
|
707
|
+
# Flatten the dict into top-level fields with prefix
|
708
|
+
for subk, subv in v.items():
|
709
|
+
# Compose the flattened key
|
710
|
+
prefix = "starkExCollateral"
|
711
|
+
# Capitalize first letter of subkey
|
712
|
+
if subk and subk[0].islower():
|
713
|
+
flatkey = prefix + subk[0].upper() + subk[1:]
|
714
|
+
else:
|
715
|
+
flatkey = prefix + subk
|
716
|
+
item[flatkey] = subv if subv is None or isinstance(subv, (bool, int, float)) else str(subv)
|
717
|
+
continue
|
718
|
+
# Convert to str except for None; preserve bool/int/float as-is
|
719
|
+
if v is None:
|
720
|
+
item[k] = v
|
721
|
+
elif isinstance(v, (bool, int, float)):
|
722
|
+
item[k] = v
|
723
|
+
else:
|
724
|
+
item[k] = str(v)
|
725
|
+
self._clear()
|
726
|
+
if item:
|
727
|
+
self._insert([item])
|
728
|
+
|
729
|
+
|
369
730
|
class EdgexDataStore(DataStoreCollection):
|
370
731
|
"""Edgex DataStore collection exposing the order book feed."""
|
371
732
|
|
372
733
|
def _init(self) -> None:
|
373
734
|
self._create("book", datastore_class=Book)
|
374
735
|
self._create("ticker", datastore_class=Ticker)
|
736
|
+
self._create("orders", datastore_class=Order)
|
737
|
+
self._create("balance", datastore_class=Balance)
|
738
|
+
# Position store holds per-account open positions in simplified camelCase form
|
739
|
+
self._create("position", datastore_class=Position)
|
375
740
|
self._create("meta_coin", datastore_class=CoinMeta)
|
376
741
|
self._create("detail", datastore_class=ContractMeta)
|
742
|
+
self._create("app", datastore_class=AppMeta)
|
377
743
|
|
378
744
|
@property
|
379
745
|
def book(self) -> Book:
|
@@ -394,8 +760,121 @@ class EdgexDataStore(DataStoreCollection):
|
|
394
760
|
"""
|
395
761
|
return self._get("book")
|
396
762
|
|
763
|
+
@property
|
764
|
+
def orders(self) -> Order:
|
765
|
+
"""
|
766
|
+
账户订单数据流(REST 快照 + 私有 WS 增量)。
|
767
|
+
|
768
|
+
存储为**精简 schema**,仅保留实操必需字段。终态订单(FILLED / CANCELED / CANCELLED / REJECTED / EXPIRED)
|
769
|
+
会在写入一次后从本地缓存删除,只保留进行中的订单(OPEN / PARTIALLY_FILLED / PENDING / CREATED / ACKNOWLEDGED)。
|
397
770
|
|
398
771
|
|
772
|
+
存储示例(本地条目)
|
773
|
+
-------------------
|
774
|
+
REST 快照:
|
775
|
+
|
776
|
+
.. code:: json
|
777
|
+
|
778
|
+
[
|
779
|
+
{
|
780
|
+
"orderId": "564815695875932430",
|
781
|
+
"id": "564815695875932430",
|
782
|
+
"contractId": "10000001",
|
783
|
+
"contractName": "BTCUSD",
|
784
|
+
"symbol": "BTCUSD",
|
785
|
+
"side": "buy",
|
786
|
+
"status": "OPEN",
|
787
|
+
"type": "LIMIT",
|
788
|
+
"timeInForce": "GOOD_TIL_CANCEL",
|
789
|
+
"reduceOnly": false,
|
790
|
+
"price": "97444.5",
|
791
|
+
"size": "0.010",
|
792
|
+
"cumFillSize": "0.000",
|
793
|
+
"cumFillValue": "0",
|
794
|
+
"clientOrderId": "553364074986685",
|
795
|
+
"createdTime": "1734662555665",
|
796
|
+
"updatedTime": "1734662555665"
|
797
|
+
}
|
798
|
+
]
|
799
|
+
|
800
|
+
|
801
|
+
"""
|
802
|
+
return self._get("orders")
|
803
|
+
|
804
|
+
@property
|
805
|
+
def balance(self) -> Balance:
|
806
|
+
"""
|
807
|
+
获取账户资产余额(REST 快照 + 私有 WS 增量)。
|
808
|
+
|
809
|
+
.. code:: json
|
810
|
+
|
811
|
+
[
|
812
|
+
{
|
813
|
+
userId: "663528067892773124",
|
814
|
+
accountId: "663528067938910372",
|
815
|
+
coinId: "1000",
|
816
|
+
totalEquity: "22.721859",
|
817
|
+
totalPositionValueAbs: "0",
|
818
|
+
initialMarginRequirement: "0",
|
819
|
+
starkExRiskValue: "0",
|
820
|
+
pendingWithdrawAmount: "0",
|
821
|
+
pendingTransferOutAmount: "0",
|
822
|
+
orderFrozenAmount: "3.001126965030794963240623474121093750",
|
823
|
+
availableAmount: "19.720732",
|
824
|
+
},
|
825
|
+
]
|
826
|
+
|
827
|
+
"""
|
828
|
+
return self._get("balance")
|
829
|
+
|
830
|
+
@property
|
831
|
+
def position(self) -> "Position":
|
832
|
+
"""
|
833
|
+
获取账户当前未平仓持仓(open positions,来自 getAccountAsset)。
|
834
|
+
|
835
|
+
本属性提供**当前未平仓持仓**的快照(由 REST ``getAccountAsset`` 提供),每条数据为当前账户的一个持仓(多/空/逐仓/全仓等)。
|
836
|
+
字段为 snake_case,包含持仓数量、均价、强平价、杠杆、保证金率等信息,适合用于持仓管理与风险监控。
|
837
|
+
|
838
|
+
数据示例:
|
839
|
+
|
840
|
+
.. code:: python
|
841
|
+
|
842
|
+
[
|
843
|
+
{
|
844
|
+
orderId: "665307878751470244",
|
845
|
+
id: "665307878751470244",
|
846
|
+
side: "buy",
|
847
|
+
status: "OPEN",
|
848
|
+
userId: "663528067892773124",
|
849
|
+
accountId: "663528067938910372",
|
850
|
+
coinId: "1000",
|
851
|
+
contractId: "10000003",
|
852
|
+
clientOrderId: "32570392453812747",
|
853
|
+
type: "LIMIT",
|
854
|
+
timeInForce: "GOOD_TIL_CANCEL",
|
855
|
+
reduceOnly: False,
|
856
|
+
price: "210.00",
|
857
|
+
size: "0.3",
|
858
|
+
cumFillSize: "0",
|
859
|
+
cumFillValue: "0",
|
860
|
+
cumMatchSize: "0",
|
861
|
+
cumMatchValue: "0",
|
862
|
+
cumMatchFee: "0",
|
863
|
+
triggerPrice: "0",
|
864
|
+
triggerPriceType: "UNKNOWN_PRICE_TYPE",
|
865
|
+
cancelReason: "UNKNOWN_ORDER_CANCEL_REASON",
|
866
|
+
createdTime: "1758621759117",
|
867
|
+
updatedTime: "1758621759122",
|
868
|
+
matchSequenceId: "784278904",
|
869
|
+
},
|
870
|
+
];
|
871
|
+
|
872
|
+
|
873
|
+
本属性仅包含**当前持有的未平仓持仓**(由 REST ``getAccountAsset`` 提供)。
|
874
|
+
若需获取**历史已平仓持仓周期**,请调用 ``getPositionTermPage``。
|
875
|
+
"""
|
876
|
+
return self._get("position")
|
877
|
+
|
399
878
|
@property
|
400
879
|
def coins(self) -> CoinMeta:
|
401
880
|
"""
|
@@ -425,7 +904,7 @@ class EdgexDataStore(DataStoreCollection):
|
|
425
904
|
[
|
426
905
|
{
|
427
906
|
"contractId": "10000001",
|
428
|
-
"contractName": "
|
907
|
+
"contractName": "BTCUSD",
|
429
908
|
"baseCoinId": "1001",
|
430
909
|
"quoteCoinId": "1000",
|
431
910
|
"tickSize": "0.1",
|
@@ -494,10 +973,20 @@ class EdgexDataStore(DataStoreCollection):
|
|
494
973
|
for fut in asyncio.as_completed(aws):
|
495
974
|
res = await fut
|
496
975
|
data = await res.json()
|
976
|
+
if data['code'] != 'SUCCESS':
|
977
|
+
raise ValueError(f"Unexpected response code: {data}")
|
497
978
|
if res.url.path == "/api/v1/public/meta/getMetaData":
|
498
979
|
self._apply_metadata(data)
|
980
|
+
elif res.url.path == "/api/v1/private/account/getAccountAsset":
|
981
|
+
self.balance._onresponse(data)
|
982
|
+
self.position._onresponse(data)
|
983
|
+
elif res.url.path == "/api/v1/private/order/getActiveOrderPage":
|
984
|
+
self.orders._onresponse(data)
|
985
|
+
elif res.url.path == "/api/v1/public/quote/getTicker":
|
986
|
+
self.ticker._onresponse(data)
|
499
987
|
|
500
988
|
def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
|
989
|
+
# print(msg)
|
501
990
|
channel = (msg.get("channel") or "").lower()
|
502
991
|
msg_type = (msg.get("type") or "").lower()
|
503
992
|
|
@@ -506,13 +995,59 @@ class EdgexDataStore(DataStoreCollection):
|
|
506
995
|
asyncio.create_task(ws.send_json(payload))
|
507
996
|
return
|
508
997
|
|
998
|
+
if msg_type in {"trade-event", "trade_event", "order-event", "order_event"}:
|
999
|
+
self.orders._on_message(msg)
|
1000
|
+
self.position._on_message(msg)
|
1001
|
+
|
509
1002
|
if "depth" in channel and msg_type in {"quote-event", "payload"}:
|
510
1003
|
self.book._on_message(msg)
|
511
1004
|
|
512
1005
|
if channel.startswith("ticker") and msg_type in {"payload", "quote-event"}:
|
513
1006
|
self.ticker._on_message(msg)
|
514
1007
|
|
515
|
-
|
516
1008
|
def _apply_metadata(self, data: dict[str, Any]) -> None:
|
1009
|
+
self.app._onresponse(data)
|
517
1010
|
self.coins._onresponse(data)
|
518
1011
|
self.detail._onresponse(data)
|
1012
|
+
|
1013
|
+
|
1014
|
+
@property
|
1015
|
+
def app(self) -> AppMeta:
|
1016
|
+
"""
|
1017
|
+
获取全局元数据,如 appName、环境、fee 账户等。
|
1018
|
+
|
1019
|
+
.. code:: python
|
1020
|
+
|
1021
|
+
|
1022
|
+
[
|
1023
|
+
{
|
1024
|
+
"appName": "edgeX",
|
1025
|
+
"appEnv": "mainnet",
|
1026
|
+
"appOnlySignOn": "https://pro.edgex.exchange",
|
1027
|
+
"feeAccountId": "256105",
|
1028
|
+
"feeAccountL2Key": "0x70092acf49d535fbb64d99883abda95dcf9a4fc60f494437a3d76f27db0a0f5",
|
1029
|
+
"poolAccountId": "508126509156794507",
|
1030
|
+
"poolAccountL2Key": "0x7f2e1e8a572c847086ee93c9b5bbce8b96320aaa69147df1cfca91d5e90bc60",
|
1031
|
+
"fastWithdrawAccountId": "508126509156794507",
|
1032
|
+
"fastWithdrawAccountL2Key": "0x7f2e1e8a572c847086ee93c9b5bbce8b96320aaa69147df1cfca91d5e90bc60",
|
1033
|
+
"fastWithdrawMaxAmount": "100000",
|
1034
|
+
"fastWithdrawRegistryAddress": "0xBE9a129909EbCb954bC065536D2bfAfBd170d27A",
|
1035
|
+
"starkExChainId": "0x1",
|
1036
|
+
"starkExContractAddress": "0xfAaE2946e846133af314d1Df13684c89fA7d83DD",
|
1037
|
+
"starkExCollateralCoinId": "1000",
|
1038
|
+
"starkExCollateralCoinName": "USD",
|
1039
|
+
"starkExCollateralStepSize": "0.000001",
|
1040
|
+
"starkExCollateralShowStepSize": "0.0001",
|
1041
|
+
"starkExCollateralIconUrl": "https://static.edgex.exchange/icons/coin/USDT.svg",
|
1042
|
+
"starkExCollateralStarkExAssetId": "0x2ce625e94458d39dd0bf3b45a843544dd4a14b8169045a3a3d15aa564b936c5",
|
1043
|
+
"starkExCollateralStarkExResolution": "0xf4240",
|
1044
|
+
"starkExMaxFundingRate": 12000,
|
1045
|
+
"starkExOrdersTreeHeight": 64,
|
1046
|
+
"starkExPositionsTreeHeight": 64,
|
1047
|
+
"starkExFundingValidityPeriod": 86400,
|
1048
|
+
"starkExPriceValidityPeriod": 86400,
|
1049
|
+
"maintenanceReason": "",
|
1050
|
+
}
|
1051
|
+
]
|
1052
|
+
"""
|
1053
|
+
return self._get("app")
|