hyperquant 0.64__py3-none-any.whl → 0.66__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 +111 -11
- hyperquant/broker/edgex.py +318 -13
- hyperquant/broker/lbank.py +6 -1
- hyperquant/broker/lib/edgex_sign.py +455 -0
- hyperquant/broker/lib/util.py +9 -0
- hyperquant/broker/models/edgex.py +522 -5
- hyperquant/broker/ws.py +4 -3
- {hyperquant-0.64.dist-info → hyperquant-0.66.dist-info}/METADATA +1 -1
- {hyperquant-0.64.dist-info → hyperquant-0.66.dist-info}/RECORD +10 -8
- {hyperquant-0.64.dist-info → hyperquant-0.66.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,
|
@@ -279,6 +279,311 @@ class Ticker(DataStore):
|
|
279
279
|
return item
|
280
280
|
|
281
281
|
|
282
|
+
class Order(DataStore):
|
283
|
+
"""Order data store combining REST results with trade-event deltas.
|
284
|
+
|
285
|
+
We only keep fields that are practical for trading book-keeping: identifiers,
|
286
|
+
basic order parameters, cumulative fills, high-level status and timestamps.
|
287
|
+
Network payloads carry hundreds of fields (``l2`` signatures, TPSL templates,
|
288
|
+
liquidation metadata, etc.), but the extra data adds noise and bloats memory
|
289
|
+
consumption. This store narrows every entry to a compact schema while still
|
290
|
+
supporting diff events from the private websocket feed.
|
291
|
+
"""
|
292
|
+
|
293
|
+
_KEYS = ["orderId"]
|
294
|
+
|
295
|
+
_TERMINAL_STATUSES = {
|
296
|
+
"FILLED",
|
297
|
+
"CANCELED",
|
298
|
+
"CANCELLED",
|
299
|
+
"REJECTED",
|
300
|
+
"EXPIRED",
|
301
|
+
}
|
302
|
+
|
303
|
+
_ACTIVE_STATUSES = {
|
304
|
+
"OPEN",
|
305
|
+
"PARTIALLY_FILLED",
|
306
|
+
"PENDING",
|
307
|
+
"CREATED",
|
308
|
+
"ACKNOWLEDGED",
|
309
|
+
}
|
310
|
+
|
311
|
+
_KEEP_FIELDS = (
|
312
|
+
"userId",
|
313
|
+
"accountId",
|
314
|
+
"coinId",
|
315
|
+
"contractId",
|
316
|
+
"clientOrderId",
|
317
|
+
"type",
|
318
|
+
"timeInForce",
|
319
|
+
"reduceOnly",
|
320
|
+
"price",
|
321
|
+
"size",
|
322
|
+
"cumFillSize",
|
323
|
+
"cumFillValue",
|
324
|
+
"cumMatchSize",
|
325
|
+
"cumMatchValue",
|
326
|
+
"cumMatchFee",
|
327
|
+
"triggerPrice",
|
328
|
+
"triggerPriceType",
|
329
|
+
"cancelReason",
|
330
|
+
"createdTime",
|
331
|
+
"updatedTime",
|
332
|
+
"matchSequenceId",
|
333
|
+
)
|
334
|
+
|
335
|
+
_BOOL_FIELDS = {"reduceOnly"}
|
336
|
+
|
337
|
+
def _on_message(self, msg: dict[str, Any]) -> None:
|
338
|
+
content = msg.get("content") or {}
|
339
|
+
data = content.get("data") or {}
|
340
|
+
orders = data.get("order") or []
|
341
|
+
|
342
|
+
if not isinstance(orders, list):
|
343
|
+
orders = [orders]
|
344
|
+
|
345
|
+
items = [self._format(order) for order in orders]
|
346
|
+
items = [item for item in items if item]
|
347
|
+
if not items:
|
348
|
+
return
|
349
|
+
|
350
|
+
event = (content.get("event") or "").lower()
|
351
|
+
if event == "snapshot":
|
352
|
+
self._clear()
|
353
|
+
self._insert(items)
|
354
|
+
return
|
355
|
+
|
356
|
+
for item in items:
|
357
|
+
status = str(item.get("status") or "").upper()
|
358
|
+
criteria = {"orderId": item["orderId"]}
|
359
|
+
existing = self.find(criteria)
|
360
|
+
|
361
|
+
if status in self._TERMINAL_STATUSES:
|
362
|
+
if existing:
|
363
|
+
self._update([item])
|
364
|
+
else:
|
365
|
+
self._insert([item])
|
366
|
+
self._find_and_delete(criteria)
|
367
|
+
continue
|
368
|
+
|
369
|
+
if status and status not in self._ACTIVE_STATUSES:
|
370
|
+
if existing:
|
371
|
+
self._update([item])
|
372
|
+
else:
|
373
|
+
self._insert([item])
|
374
|
+
self._find_and_delete(criteria)
|
375
|
+
continue
|
376
|
+
|
377
|
+
if existing:
|
378
|
+
self._update([item])
|
379
|
+
else:
|
380
|
+
self._insert([item])
|
381
|
+
|
382
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
383
|
+
payload = data.get("data")
|
384
|
+
|
385
|
+
if isinstance(payload, dict):
|
386
|
+
orders = payload.get("dataList") or payload.get("orderList") or []
|
387
|
+
else:
|
388
|
+
orders = payload or []
|
389
|
+
|
390
|
+
if not isinstance(orders, list):
|
391
|
+
orders = [orders]
|
392
|
+
|
393
|
+
items = [self._format(order) for order in orders]
|
394
|
+
items = [item for item in items if item]
|
395
|
+
|
396
|
+
self._clear()
|
397
|
+
if items:
|
398
|
+
self._insert(items)
|
399
|
+
|
400
|
+
@staticmethod
|
401
|
+
def _normalize_order_id(value: Any) -> str | None:
|
402
|
+
if value is None:
|
403
|
+
return None
|
404
|
+
return str(value)
|
405
|
+
|
406
|
+
@staticmethod
|
407
|
+
def _normalize_side(value: Any) -> str | None:
|
408
|
+
if value is None:
|
409
|
+
return None
|
410
|
+
if isinstance(value, str):
|
411
|
+
return value.lower()
|
412
|
+
return str(value)
|
413
|
+
|
414
|
+
@staticmethod
|
415
|
+
def _normalize_status(value: Any) -> str | None:
|
416
|
+
if value is None:
|
417
|
+
return None
|
418
|
+
return str(value).upper()
|
419
|
+
|
420
|
+
@staticmethod
|
421
|
+
def _stringify(value: Any) -> Any:
|
422
|
+
if value is None:
|
423
|
+
return None
|
424
|
+
if isinstance(value, (bool, dict, list)):
|
425
|
+
return value
|
426
|
+
return str(value)
|
427
|
+
|
428
|
+
def _format(self, order: dict[str, Any] | None) -> dict[str, Any] | None:
|
429
|
+
if not order:
|
430
|
+
return None
|
431
|
+
|
432
|
+
order_id = (
|
433
|
+
order.get("orderId")
|
434
|
+
or order.get("id")
|
435
|
+
or order.get("order_id")
|
436
|
+
or order.get("orderID")
|
437
|
+
)
|
438
|
+
|
439
|
+
normalized_id = self._normalize_order_id(order_id)
|
440
|
+
if normalized_id is None:
|
441
|
+
return None
|
442
|
+
|
443
|
+
item: dict[str, Any] = {"orderId": normalized_id, "id": normalized_id}
|
444
|
+
|
445
|
+
side = self._normalize_side(order.get("side"))
|
446
|
+
if side is not None:
|
447
|
+
item["side"] = side
|
448
|
+
|
449
|
+
status = self._normalize_status(order.get("status"))
|
450
|
+
if status is not None:
|
451
|
+
item["status"] = status
|
452
|
+
|
453
|
+
contract_name = order.get("contractName")
|
454
|
+
if contract_name:
|
455
|
+
symbol = self._stringify(contract_name)
|
456
|
+
item["contractName"] = symbol
|
457
|
+
item.setdefault("symbol", symbol)
|
458
|
+
|
459
|
+
for field in self._KEEP_FIELDS:
|
460
|
+
if field in ("side", "status"):
|
461
|
+
continue
|
462
|
+
value = order.get(field)
|
463
|
+
if value is None:
|
464
|
+
continue
|
465
|
+
if field in self._BOOL_FIELDS:
|
466
|
+
item[field] = bool(value)
|
467
|
+
else:
|
468
|
+
item[field] = self._stringify(value)
|
469
|
+
|
470
|
+
return item
|
471
|
+
|
472
|
+
|
473
|
+
class Balance(DataStore):
|
474
|
+
"""Account balance snapshot retaining only the trading-critical fields."""
|
475
|
+
|
476
|
+
_KEYS = ["accountId", "coinId"]
|
477
|
+
|
478
|
+
|
479
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
480
|
+
data = data.get('data', {})
|
481
|
+
collateral_assets = data.get('collateralAssetModelList') or []
|
482
|
+
if collateral_assets:
|
483
|
+
self._update(collateral_assets)
|
484
|
+
|
485
|
+
def _on_message(self, msg: dict[str, Any]) -> None:
|
486
|
+
pass
|
487
|
+
|
488
|
+
|
489
|
+
class Position(DataStore):
|
490
|
+
"""
|
491
|
+
Stores per-account open positions in a simplified camelCase schema.
|
492
|
+
Only the current open position fields are retained: positionId, contractId, accountId,
|
493
|
+
userId, coinId, side, size, value, fee, fundingFee.
|
494
|
+
"""
|
495
|
+
|
496
|
+
_KEYS = ["positionId"]
|
497
|
+
|
498
|
+
@staticmethod
|
499
|
+
def _stringify(value: Any) -> Any:
|
500
|
+
if value is None:
|
501
|
+
return None
|
502
|
+
if isinstance(value, (bool, dict, list)):
|
503
|
+
return value
|
504
|
+
return str(value)
|
505
|
+
|
506
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
507
|
+
"""
|
508
|
+
Handle REST response for getAccountAsset (open positions snapshot).
|
509
|
+
Expects data from getAccountAsset (REST), which returns a snapshot of **current open positions**,
|
510
|
+
as a list in data["positionList"].
|
511
|
+
Each entry is normalized to camelCase schema, only including essential fields for the current open position.
|
512
|
+
"""
|
513
|
+
data = data.get("data", {}) or {}
|
514
|
+
positions = data.get("positionList") or []
|
515
|
+
if not isinstance(positions, list):
|
516
|
+
positions = [positions]
|
517
|
+
items = [self._normalize_position(pos) for pos in positions]
|
518
|
+
self._clear()
|
519
|
+
if items:
|
520
|
+
self._update(items)
|
521
|
+
|
522
|
+
def _on_message(self, msg: dict[str, Any]) -> None:
|
523
|
+
data = msg.get("content", {}).get("data", {})
|
524
|
+
if not data:
|
525
|
+
return
|
526
|
+
positions = data.get("position")
|
527
|
+
if not positions:
|
528
|
+
return
|
529
|
+
items = [self._normalize_position(pos) for pos in positions]
|
530
|
+
self._clear()
|
531
|
+
if items:
|
532
|
+
self._update(items)
|
533
|
+
|
534
|
+
def _normalize_position(self, pos: dict[str, Any]) -> dict[str, Any]:
|
535
|
+
# Only keep essential fields for the current open position
|
536
|
+
def get(key, *alts):
|
537
|
+
for k in (key,) + alts:
|
538
|
+
if k in pos and pos[k] is not None:
|
539
|
+
return pos[k]
|
540
|
+
return None
|
541
|
+
|
542
|
+
open_size = get("openSize")
|
543
|
+
open_value = get("openValue")
|
544
|
+
open_fee = get("openFee")
|
545
|
+
funding_fee = get("fundingFee")
|
546
|
+
|
547
|
+
# side: "long" if openSize > 0, "short" if openSize < 0, None if 0
|
548
|
+
side = None
|
549
|
+
try:
|
550
|
+
if open_size is not None:
|
551
|
+
fsize = float(open_size)
|
552
|
+
if fsize > 0:
|
553
|
+
side = "long"
|
554
|
+
elif fsize < 0:
|
555
|
+
side = "short"
|
556
|
+
except Exception:
|
557
|
+
side = None
|
558
|
+
|
559
|
+
size = None
|
560
|
+
if open_size is not None:
|
561
|
+
try:
|
562
|
+
size = str(abs(float(open_size)))
|
563
|
+
except Exception:
|
564
|
+
size = str(open_size)
|
565
|
+
value = None
|
566
|
+
if open_value is not None:
|
567
|
+
try:
|
568
|
+
value = str(abs(float(open_value)))
|
569
|
+
except Exception:
|
570
|
+
value = str(open_value)
|
571
|
+
|
572
|
+
item = {
|
573
|
+
"positionId": self._stringify(get("positionId", "position_id")),
|
574
|
+
"contractId": self._stringify(get("contractId")),
|
575
|
+
"accountId": self._stringify(get("accountId")),
|
576
|
+
"userId": self._stringify(get("userId")),
|
577
|
+
"coinId": self._stringify(get("coinId")),
|
578
|
+
"side": side,
|
579
|
+
"size": size,
|
580
|
+
"value": value,
|
581
|
+
"fee": self._stringify(open_fee),
|
582
|
+
"fundingFee": self._stringify(funding_fee),
|
583
|
+
}
|
584
|
+
return item
|
585
|
+
|
586
|
+
|
282
587
|
class CoinMeta(DataStore):
|
283
588
|
"""Coin metadata (precision, StarkEx info, etc.)."""
|
284
589
|
|
@@ -310,7 +615,7 @@ class CoinMeta(DataStore):
|
|
310
615
|
class ContractMeta(DataStore):
|
311
616
|
"""Per-contract trading parameters from the metadata endpoint."""
|
312
617
|
|
313
|
-
_KEYS = ["
|
618
|
+
_KEYS = ["contractName"]
|
314
619
|
|
315
620
|
_FIELDS = (
|
316
621
|
"contractName",
|
@@ -342,7 +647,9 @@ class ContractMeta(DataStore):
|
|
342
647
|
payload = {"contractId": str(contract_id)}
|
343
648
|
for key in self._FIELDS:
|
344
649
|
payload[key] = contract.get(key)
|
345
|
-
payload["riskTierList"] = self._simplify_risk_tiers(
|
650
|
+
payload["riskTierList"] = self._simplify_risk_tiers(
|
651
|
+
contract.get("riskTierList")
|
652
|
+
)
|
346
653
|
|
347
654
|
items.append(payload)
|
348
655
|
|
@@ -366,14 +673,57 @@ class ContractMeta(DataStore):
|
|
366
673
|
)
|
367
674
|
return items
|
368
675
|
|
676
|
+
|
677
|
+
class AppMeta(DataStore):
|
678
|
+
"""Global metadata (appName, env, fee account, etc.)."""
|
679
|
+
|
680
|
+
_KEYS = ["appName"]
|
681
|
+
|
682
|
+
def _onresponse(self, data: dict[str, Any]) -> None:
|
683
|
+
appdata = (data.get("data") or {}).get("global") or {}
|
684
|
+
if not appdata:
|
685
|
+
self._clear()
|
686
|
+
return
|
687
|
+
# Convert all values to str where appropriate, but preserve fields as-is (for bool/int etc).
|
688
|
+
item = {}
|
689
|
+
for k, v in appdata.items():
|
690
|
+
if k == "starkExCollateralCoin" and isinstance(v, dict):
|
691
|
+
# Flatten the dict into top-level fields with prefix
|
692
|
+
for subk, subv in v.items():
|
693
|
+
# Compose the flattened key
|
694
|
+
prefix = "starkExCollateral"
|
695
|
+
# Capitalize first letter of subkey
|
696
|
+
if subk and subk[0].islower():
|
697
|
+
flatkey = prefix + subk[0].upper() + subk[1:]
|
698
|
+
else:
|
699
|
+
flatkey = prefix + subk
|
700
|
+
item[flatkey] = subv if subv is None or isinstance(subv, (bool, int, float)) else str(subv)
|
701
|
+
continue
|
702
|
+
# Convert to str except for None; preserve bool/int/float as-is
|
703
|
+
if v is None:
|
704
|
+
item[k] = v
|
705
|
+
elif isinstance(v, (bool, int, float)):
|
706
|
+
item[k] = v
|
707
|
+
else:
|
708
|
+
item[k] = str(v)
|
709
|
+
self._clear()
|
710
|
+
if item:
|
711
|
+
self._insert([item])
|
712
|
+
|
713
|
+
|
369
714
|
class EdgexDataStore(DataStoreCollection):
|
370
715
|
"""Edgex DataStore collection exposing the order book feed."""
|
371
716
|
|
372
717
|
def _init(self) -> None:
|
373
718
|
self._create("book", datastore_class=Book)
|
374
719
|
self._create("ticker", datastore_class=Ticker)
|
720
|
+
self._create("orders", datastore_class=Order)
|
721
|
+
self._create("balance", datastore_class=Balance)
|
722
|
+
# Position store holds per-account open positions in simplified camelCase form
|
723
|
+
self._create("position", datastore_class=Position)
|
375
724
|
self._create("meta_coin", datastore_class=CoinMeta)
|
376
725
|
self._create("detail", datastore_class=ContractMeta)
|
726
|
+
self._create("app", datastore_class=AppMeta)
|
377
727
|
|
378
728
|
@property
|
379
729
|
def book(self) -> Book:
|
@@ -394,8 +744,121 @@ class EdgexDataStore(DataStoreCollection):
|
|
394
744
|
"""
|
395
745
|
return self._get("book")
|
396
746
|
|
747
|
+
@property
|
748
|
+
def orders(self) -> Order:
|
749
|
+
"""
|
750
|
+
账户订单数据流(REST 快照 + 私有 WS 增量)。
|
751
|
+
|
752
|
+
存储为**精简 schema**,仅保留实操必需字段。终态订单(FILLED / CANCELED / CANCELLED / REJECTED / EXPIRED)
|
753
|
+
会在写入一次后从本地缓存删除,只保留进行中的订单(OPEN / PARTIALLY_FILLED / PENDING / CREATED / ACKNOWLEDGED)。
|
754
|
+
|
755
|
+
|
756
|
+
存储示例(本地条目)
|
757
|
+
-------------------
|
758
|
+
REST 快照:
|
759
|
+
|
760
|
+
.. code:: json
|
761
|
+
|
762
|
+
[
|
763
|
+
{
|
764
|
+
"orderId": "564815695875932430",
|
765
|
+
"id": "564815695875932430",
|
766
|
+
"contractId": "10000001",
|
767
|
+
"contractName": "BTCUSD",
|
768
|
+
"symbol": "BTCUSD",
|
769
|
+
"side": "buy",
|
770
|
+
"status": "OPEN",
|
771
|
+
"type": "LIMIT",
|
772
|
+
"timeInForce": "GOOD_TIL_CANCEL",
|
773
|
+
"reduceOnly": false,
|
774
|
+
"price": "97444.5",
|
775
|
+
"size": "0.010",
|
776
|
+
"cumFillSize": "0.000",
|
777
|
+
"cumFillValue": "0",
|
778
|
+
"clientOrderId": "553364074986685",
|
779
|
+
"createdTime": "1734662555665",
|
780
|
+
"updatedTime": "1734662555665"
|
781
|
+
}
|
782
|
+
]
|
783
|
+
|
784
|
+
|
785
|
+
"""
|
786
|
+
return self._get("orders")
|
787
|
+
|
788
|
+
@property
|
789
|
+
def balance(self) -> Balance:
|
790
|
+
"""
|
791
|
+
获取账户资产余额(REST 快照 + 私有 WS 增量)。
|
397
792
|
|
793
|
+
.. code:: json
|
794
|
+
|
795
|
+
[
|
796
|
+
{
|
797
|
+
userId: "663528067892773124",
|
798
|
+
accountId: "663528067938910372",
|
799
|
+
coinId: "1000",
|
800
|
+
totalEquity: "22.721859",
|
801
|
+
totalPositionValueAbs: "0",
|
802
|
+
initialMarginRequirement: "0",
|
803
|
+
starkExRiskValue: "0",
|
804
|
+
pendingWithdrawAmount: "0",
|
805
|
+
pendingTransferOutAmount: "0",
|
806
|
+
orderFrozenAmount: "3.001126965030794963240623474121093750",
|
807
|
+
availableAmount: "19.720732",
|
808
|
+
},
|
809
|
+
]
|
398
810
|
|
811
|
+
"""
|
812
|
+
return self._get("balance")
|
813
|
+
|
814
|
+
@property
|
815
|
+
def position(self) -> "Position":
|
816
|
+
"""
|
817
|
+
获取账户当前未平仓持仓(open positions,来自 getAccountAsset)。
|
818
|
+
|
819
|
+
本属性提供**当前未平仓持仓**的快照(由 REST ``getAccountAsset`` 提供),每条数据为当前账户的一个持仓(多/空/逐仓/全仓等)。
|
820
|
+
字段为 snake_case,包含持仓数量、均价、强平价、杠杆、保证金率等信息,适合用于持仓管理与风险监控。
|
821
|
+
|
822
|
+
数据示例:
|
823
|
+
|
824
|
+
.. code:: python
|
825
|
+
|
826
|
+
[
|
827
|
+
{
|
828
|
+
orderId: "665307878751470244",
|
829
|
+
id: "665307878751470244",
|
830
|
+
side: "buy",
|
831
|
+
status: "OPEN",
|
832
|
+
userId: "663528067892773124",
|
833
|
+
accountId: "663528067938910372",
|
834
|
+
coinId: "1000",
|
835
|
+
contractId: "10000003",
|
836
|
+
clientOrderId: "32570392453812747",
|
837
|
+
type: "LIMIT",
|
838
|
+
timeInForce: "GOOD_TIL_CANCEL",
|
839
|
+
reduceOnly: False,
|
840
|
+
price: "210.00",
|
841
|
+
size: "0.3",
|
842
|
+
cumFillSize: "0",
|
843
|
+
cumFillValue: "0",
|
844
|
+
cumMatchSize: "0",
|
845
|
+
cumMatchValue: "0",
|
846
|
+
cumMatchFee: "0",
|
847
|
+
triggerPrice: "0",
|
848
|
+
triggerPriceType: "UNKNOWN_PRICE_TYPE",
|
849
|
+
cancelReason: "UNKNOWN_ORDER_CANCEL_REASON",
|
850
|
+
createdTime: "1758621759117",
|
851
|
+
updatedTime: "1758621759122",
|
852
|
+
matchSequenceId: "784278904",
|
853
|
+
},
|
854
|
+
];
|
855
|
+
|
856
|
+
|
857
|
+
本属性仅包含**当前持有的未平仓持仓**(由 REST ``getAccountAsset`` 提供)。
|
858
|
+
若需获取**历史已平仓持仓周期**,请调用 ``getPositionTermPage``。
|
859
|
+
"""
|
860
|
+
return self._get("position")
|
861
|
+
|
399
862
|
@property
|
400
863
|
def coins(self) -> CoinMeta:
|
401
864
|
"""
|
@@ -425,7 +888,7 @@ class EdgexDataStore(DataStoreCollection):
|
|
425
888
|
[
|
426
889
|
{
|
427
890
|
"contractId": "10000001",
|
428
|
-
"contractName": "
|
891
|
+
"contractName": "BTCUSD",
|
429
892
|
"baseCoinId": "1001",
|
430
893
|
"quoteCoinId": "1000",
|
431
894
|
"tickSize": "0.1",
|
@@ -494,10 +957,18 @@ class EdgexDataStore(DataStoreCollection):
|
|
494
957
|
for fut in asyncio.as_completed(aws):
|
495
958
|
res = await fut
|
496
959
|
data = await res.json()
|
960
|
+
if data['code'] != 'SUCCESS':
|
961
|
+
raise ValueError(f"Unexpected response code: {data}")
|
497
962
|
if res.url.path == "/api/v1/public/meta/getMetaData":
|
498
963
|
self._apply_metadata(data)
|
964
|
+
elif res.url.path == "/api/v1/private/account/getAccountAsset":
|
965
|
+
self.balance._onresponse(data)
|
966
|
+
self.position._onresponse(data)
|
967
|
+
elif res.url.path == "/api/v1/private/order/getActiveOrderPage":
|
968
|
+
self.orders._onresponse(data)
|
499
969
|
|
500
970
|
def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
|
971
|
+
# print(msg)
|
501
972
|
channel = (msg.get("channel") or "").lower()
|
502
973
|
msg_type = (msg.get("type") or "").lower()
|
503
974
|
|
@@ -506,13 +977,59 @@ class EdgexDataStore(DataStoreCollection):
|
|
506
977
|
asyncio.create_task(ws.send_json(payload))
|
507
978
|
return
|
508
979
|
|
980
|
+
if msg_type in {"trade-event", "trade_event", "order-event", "order_event"}:
|
981
|
+
self.orders._on_message(msg)
|
982
|
+
self.position._on_message(msg)
|
983
|
+
|
509
984
|
if "depth" in channel and msg_type in {"quote-event", "payload"}:
|
510
985
|
self.book._on_message(msg)
|
511
986
|
|
512
987
|
if channel.startswith("ticker") and msg_type in {"payload", "quote-event"}:
|
513
988
|
self.ticker._on_message(msg)
|
514
989
|
|
515
|
-
|
516
990
|
def _apply_metadata(self, data: dict[str, Any]) -> None:
|
991
|
+
self.app._onresponse(data)
|
517
992
|
self.coins._onresponse(data)
|
518
993
|
self.detail._onresponse(data)
|
994
|
+
|
995
|
+
|
996
|
+
@property
|
997
|
+
def app(self) -> AppMeta:
|
998
|
+
"""
|
999
|
+
获取全局元数据,如 appName、环境、fee 账户等。
|
1000
|
+
|
1001
|
+
.. code:: python
|
1002
|
+
|
1003
|
+
|
1004
|
+
[
|
1005
|
+
{
|
1006
|
+
"appName": "edgeX",
|
1007
|
+
"appEnv": "mainnet",
|
1008
|
+
"appOnlySignOn": "https://pro.edgex.exchange",
|
1009
|
+
"feeAccountId": "256105",
|
1010
|
+
"feeAccountL2Key": "0x70092acf49d535fbb64d99883abda95dcf9a4fc60f494437a3d76f27db0a0f5",
|
1011
|
+
"poolAccountId": "508126509156794507",
|
1012
|
+
"poolAccountL2Key": "0x7f2e1e8a572c847086ee93c9b5bbce8b96320aaa69147df1cfca91d5e90bc60",
|
1013
|
+
"fastWithdrawAccountId": "508126509156794507",
|
1014
|
+
"fastWithdrawAccountL2Key": "0x7f2e1e8a572c847086ee93c9b5bbce8b96320aaa69147df1cfca91d5e90bc60",
|
1015
|
+
"fastWithdrawMaxAmount": "100000",
|
1016
|
+
"fastWithdrawRegistryAddress": "0xBE9a129909EbCb954bC065536D2bfAfBd170d27A",
|
1017
|
+
"starkExChainId": "0x1",
|
1018
|
+
"starkExContractAddress": "0xfAaE2946e846133af314d1Df13684c89fA7d83DD",
|
1019
|
+
"starkExCollateralCoinId": "1000",
|
1020
|
+
"starkExCollateralCoinName": "USD",
|
1021
|
+
"starkExCollateralStepSize": "0.000001",
|
1022
|
+
"starkExCollateralShowStepSize": "0.0001",
|
1023
|
+
"starkExCollateralIconUrl": "https://static.edgex.exchange/icons/coin/USDT.svg",
|
1024
|
+
"starkExCollateralStarkExAssetId": "0x2ce625e94458d39dd0bf3b45a843544dd4a14b8169045a3a3d15aa564b936c5",
|
1025
|
+
"starkExCollateralStarkExResolution": "0xf4240",
|
1026
|
+
"starkExMaxFundingRate": 12000,
|
1027
|
+
"starkExOrdersTreeHeight": 64,
|
1028
|
+
"starkExPositionsTreeHeight": 64,
|
1029
|
+
"starkExFundingValidityPeriod": 86400,
|
1030
|
+
"starkExPriceValidityPeriod": 86400,
|
1031
|
+
"maintenanceReason": "",
|
1032
|
+
}
|
1033
|
+
]
|
1034
|
+
"""
|
1035
|
+
return self._get("app")
|
hyperquant/broker/ws.py
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
import asyncio
|
2
|
+
import base64
|
2
3
|
import time
|
4
|
+
from typing import Any
|
3
5
|
|
4
6
|
import pybotters
|
5
7
|
from pybotters.ws import ClientWebSocketResponse, logger
|
6
8
|
from pybotters.auth import Hosts
|
9
|
+
import urllib
|
7
10
|
import yarl
|
8
11
|
|
9
12
|
|
@@ -59,7 +62,5 @@ class WssAuth:
|
|
59
62
|
break
|
60
63
|
else:
|
61
64
|
logger.warning(f"WebSocket login failed: {data}")
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
+
|
65
66
|
pybotters.ws.AuthHosts.items['futures.ourbit.com'] = pybotters.auth.Item("ourbit", WssAuth.ourbit)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hyperquant
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.66
|
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
|
@@ -4,15 +4,17 @@ hyperquant/db.py,sha256=i2TjkCbmH4Uxo7UTDvOYBfy973gLcGexdzuT_YcSeIE,6678
|
|
4
4
|
hyperquant/draw.py,sha256=up_lQ3pHeVLoNOyh9vPjgNwjD0M-6_IetSGviQUgjhY,54624
|
5
5
|
hyperquant/logkit.py,sha256=nUo7nx5eONvK39GOhWwS41zNRL756P2J7-5xGzwXnTY,8462
|
6
6
|
hyperquant/notikit.py,sha256=x5yAZ_tAvLQRXcRbcg-VabCaN45LUhvlTZnUqkIqfAA,3596
|
7
|
-
hyperquant/broker/auth.py,sha256=
|
8
|
-
hyperquant/broker/edgex.py,sha256=
|
7
|
+
hyperquant/broker/auth.py,sha256=PgWw6eFGQtVbbA_JcJkK71L4JckinO8h2B0FrEV5G-U,6516
|
8
|
+
hyperquant/broker/edgex.py,sha256=7oUY3HJUR87eSnkRFMEh1ttZCZkeAB_CkQF4Rodoevs,17645
|
9
9
|
hyperquant/broker/hyperliquid.py,sha256=7MxbI9OyIBcImDelPJu-8Nd53WXjxPB5TwE6gsjHbto,23252
|
10
|
-
hyperquant/broker/lbank.py,sha256=
|
10
|
+
hyperquant/broker/lbank.py,sha256=kuxRfZylGZK3LRMzQJcB3w_nOXWM9-si65EwDsj0DnY,3325
|
11
11
|
hyperquant/broker/ourbit.py,sha256=NUcDSIttf-HGWzoW1uBTrGLPHlkuemMjYCm91MigTno,18228
|
12
|
-
hyperquant/broker/ws.py,sha256=
|
12
|
+
hyperquant/broker/ws.py,sha256=9Zu5JSLj-ylYEVmFmRwvZDDnVYKwb37cLHfZzA0AZGc,2200
|
13
|
+
hyperquant/broker/lib/edgex_sign.py,sha256=lLUCmY8HHRLfLKyGrlTJYaBlSHPsIMWg3EZnQJKcmyk,95785
|
13
14
|
hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb84D8,8296
|
14
15
|
hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
|
15
|
-
hyperquant/broker/
|
16
|
+
hyperquant/broker/lib/util.py,sha256=u02kGb-7LMCi32UNLeKoPaZBZ2LBEjx72KRkaKX0yQg,275
|
17
|
+
hyperquant/broker/models/edgex.py,sha256=ba9ogBprp3uGPgYprjBJoVcVWUBbmZ_l2hYCIGQCRpA,33449
|
16
18
|
hyperquant/broker/models/hyperliquid.py,sha256=c4r5739ibZfnk69RxPjQl902AVuUOwT8RNvKsMtwXBY,9459
|
17
19
|
hyperquant/broker/models/lbank.py,sha256=ArYBHHF9p66rNXs9fn41W2asa5mK-mqgplajCHr51YA,7106
|
18
20
|
hyperquant/broker/models/ourbit.py,sha256=xMcbuCEXd3XOpPBq0RYF2zpTFNnxPtuNJZCexMZVZ1k,41965
|
@@ -20,6 +22,6 @@ hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw
|
|
20
22
|
hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
|
21
23
|
hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
|
22
24
|
hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
|
23
|
-
hyperquant-0.
|
24
|
-
hyperquant-0.
|
25
|
-
hyperquant-0.
|
25
|
+
hyperquant-0.66.dist-info/METADATA,sha256=GVJWSwmMpQU_6dpHiOreOSExsvq4e3Pg34_QUHh_Y9U,4317
|
26
|
+
hyperquant-0.66.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
27
|
+
hyperquant-0.66.dist-info/RECORD,,
|
File without changes
|