hyperquant 0.4__py3-none-any.whl → 0.6__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/edgex.py +183 -0
- hyperquant/broker/models/edgex.py +513 -0
- hyperquant/broker/models/ourbit.py +270 -169
- hyperquant/broker/ourbit.py +173 -123
- hyperquant/core.py +13 -6
- hyperquant/logkit.py +16 -1
- {hyperquant-0.4.dist-info → hyperquant-0.6.dist-info}/METADATA +1 -1
- {hyperquant-0.4.dist-info → hyperquant-0.6.dist-info}/RECORD +9 -7
- {hyperquant-0.4.dist-info → hyperquant-0.6.dist-info}/WHEEL +0 -0
@@ -22,19 +22,20 @@ class Book(DataStore):
|
|
22
22
|
Channel: push.depth.step
|
23
23
|
|
24
24
|
用于存储和管理订单簿深度数据,包含买卖盘的价格和数量信息
|
25
|
-
Keys: ["
|
26
|
-
-
|
27
|
-
-
|
28
|
-
-
|
25
|
+
Keys: ["s", "S", "p"]
|
26
|
+
- s: 交易对符号
|
27
|
+
- S: 买卖方向 (A: ask卖出, B: bid买入)
|
28
|
+
- p: 价格
|
29
29
|
|
30
30
|
|
31
31
|
"""
|
32
32
|
|
33
|
-
_KEYS = ["
|
33
|
+
_KEYS = ["s", "S", "p"]
|
34
34
|
|
35
35
|
def _init(self) -> None:
|
36
36
|
# super().__init__()
|
37
37
|
self._time: int | None = None
|
38
|
+
self.limit = 1
|
38
39
|
|
39
40
|
def _on_message(self, msg: dict[str, Any]) -> None:
|
40
41
|
|
@@ -43,29 +44,29 @@ class Book(DataStore):
|
|
43
44
|
asks = data.get("asks", [])
|
44
45
|
bids = data.get("bids", [])
|
45
46
|
# 提速 默认 5当前
|
46
|
-
asks = asks[:
|
47
|
-
bids = bids[:
|
47
|
+
asks = asks[:self.limit]
|
48
|
+
bids = bids[:self.limit]
|
48
49
|
|
49
50
|
timestamp = data.get("ct") # 使用服务器时间
|
50
51
|
|
51
52
|
data_to_insert: list[Item] = []
|
52
53
|
|
53
54
|
# 先删除旧的订单簿数据
|
54
|
-
self._find_and_delete({"
|
55
|
+
self._find_and_delete({"s": symbol})
|
55
56
|
|
56
57
|
# 处理买卖盘数据
|
57
|
-
for side_id, levels in (("
|
58
|
+
for side_id, levels in (("b", bids), ("a", asks)):
|
58
59
|
for i, level in enumerate(levels):
|
59
60
|
# level格式: [price, size, count]
|
60
61
|
if len(level) >= 3:
|
61
62
|
price, size, count = level[0:3]
|
62
63
|
data_to_insert.append(
|
63
64
|
{
|
64
|
-
"
|
65
|
-
"
|
66
|
-
"
|
67
|
-
"
|
68
|
-
"
|
65
|
+
"s": symbol,
|
66
|
+
"S": side_id,
|
67
|
+
"p": str(price),
|
68
|
+
"q": str(size),
|
69
|
+
"ct": count,
|
69
70
|
"i": i
|
70
71
|
}
|
71
72
|
)
|
@@ -79,31 +80,17 @@ class Book(DataStore):
|
|
79
80
|
"""返回最后更新时间"""
|
80
81
|
return self._time
|
81
82
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
Returns:
|
87
|
-
返回按价格排序的买卖盘数据,卖盘升序,买盘降序
|
88
|
-
|
89
|
-
.. code-block:: python
|
83
|
+
def sorted(
|
84
|
+
self, query: Item | None = None, limit: int | None = None
|
85
|
+
) -> dict[str, list[Item]]:
|
90
86
|
|
91
|
-
{
|
92
|
-
"asks": [
|
93
|
-
{"symbol": "BTC_USDT", "side": "A", "px": "110152.5", "sz": "53539", "count": 1},
|
94
|
-
{"symbol": "BTC_USDT", "side": "A", "px": "110152.6", "sz": "95513", "count": 2}
|
95
|
-
],
|
96
|
-
"bids": [
|
97
|
-
{"symbol": "BTC_USDT", "side": "B", "px": "110152.4", "sz": "76311", "count": 1},
|
98
|
-
{"symbol": "BTC_USDT", "side": "B", "px": "110152.3", "sz": "104688", "count": 2}
|
99
|
-
]
|
100
|
-
}
|
101
|
-
"""
|
102
87
|
return self._sorted(
|
103
|
-
item_key="
|
104
|
-
item_asc_key="
|
105
|
-
item_desc_key="
|
106
|
-
sort_key="
|
88
|
+
item_key="S",
|
89
|
+
item_asc_key="a", # asks 升序
|
90
|
+
item_desc_key="b", # bids 降序
|
91
|
+
sort_key="p",
|
92
|
+
query=query,
|
93
|
+
limit=limit,
|
107
94
|
)
|
108
95
|
|
109
96
|
|
@@ -146,15 +133,19 @@ class Orders(DataStore):
|
|
146
133
|
def _fmt(self, order:dict):
|
147
134
|
return {
|
148
135
|
"order_id": order.get("orderId"),
|
136
|
+
"position_id": order.get("positionId"),
|
149
137
|
"symbol": order.get("symbol"),
|
150
|
-
"
|
138
|
+
"price": order.get("price"),
|
151
139
|
"vol": order.get("vol"),
|
152
140
|
"lev": order.get("leverage"),
|
153
141
|
"side": "buy" if order.get("side") == 1 else "sell",
|
154
|
-
"
|
155
|
-
"
|
142
|
+
"deal_quantity": order.get("dealVol"),
|
143
|
+
"avg_price": order.get("dealAvgPrice"),
|
156
144
|
"create_ts": order.get("createTime"),
|
157
145
|
"update_ts": order.get("updateTime"),
|
146
|
+
"fee": order.get("makerFee"),
|
147
|
+
"profit": order.get("profit"),
|
148
|
+
"used_margin": order.get("usedMargin"),
|
158
149
|
"state": "open"
|
159
150
|
}
|
160
151
|
|
@@ -192,6 +183,13 @@ class Orders(DataStore):
|
|
192
183
|
self._find_and_delete({
|
193
184
|
"order_id": order.get("order_id")
|
194
185
|
})
|
186
|
+
else:
|
187
|
+
order = self._fmt(data)
|
188
|
+
order["state"] = f"unknown_{state}"
|
189
|
+
self._update([order])
|
190
|
+
self._find_and_delete({
|
191
|
+
"order_id": order.get("order_id")
|
192
|
+
})
|
195
193
|
|
196
194
|
class Detail(DataStore):
|
197
195
|
_KEYS = ["symbol"]
|
@@ -214,7 +212,8 @@ class Detail(DataStore):
|
|
214
212
|
"io": detail.get("io"),
|
215
213
|
"contract_sz": detail.get("cs"),
|
216
214
|
"minv": detail.get("minV"),
|
217
|
-
"maxv": detail.get("maxV")
|
215
|
+
"maxv": detail.get("maxV"),
|
216
|
+
"online_time": detail.get("tcd")
|
218
217
|
}
|
219
218
|
)
|
220
219
|
self._update(data_to_insert)
|
@@ -225,27 +224,27 @@ class Position(DataStore):
|
|
225
224
|
|
226
225
|
def _fmt(self, position:dict):
|
227
226
|
return {
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
227
|
+
"position_id": position.get("positionId"),
|
228
|
+
"symbol": position.get("symbol"),
|
229
|
+
"side": "short" if position.get("positionType") == 2 else "long",
|
230
|
+
"open_type": position.get("openType"),
|
231
|
+
"state": position.get("state"),
|
232
|
+
"hold_vol": position.get("holdVol"),
|
233
|
+
"frozen_vol": position.get("frozenVol"),
|
234
|
+
"close_vol": position.get("closeVol"),
|
235
|
+
"hold_avg_price": position.get("holdAvgPriceFullyScale"),
|
236
|
+
"open_avg_price": position.get("openAvgPriceFullyScale"),
|
237
|
+
"close_avg_price": str(position.get("closeAvgPrice")),
|
238
|
+
"liquidate_price": str(position.get("liquidatePrice")),
|
239
|
+
"oim": position.get("oim"),
|
240
|
+
"im": position.get("im"),
|
241
|
+
"hold_fee": position.get("holdFee"),
|
242
|
+
"realised": position.get("realised"),
|
243
|
+
"leverage": position.get("leverage"),
|
244
|
+
"margin_ratio": position.get("marginRatio"),
|
245
|
+
"create_ts": position.get("createTime"),
|
246
|
+
"update_ts": position.get("updateTime"),
|
247
|
+
}
|
249
248
|
|
250
249
|
def _onresponse(self, data: dict[str, Any]):
|
251
250
|
positions = data.get("data", [])
|
@@ -405,8 +404,8 @@ class OurbitSwapDataStore(DataStoreCollection):
|
|
405
404
|
"io": ["binance", "mexc"], # 交易所列表
|
406
405
|
"contract_sz": 1,
|
407
406
|
"minv": 1,
|
408
|
-
"maxv": 10000
|
409
|
-
|
407
|
+
"maxv": 10000,
|
408
|
+
"online_time": 1625247600000 # 上线时间
|
410
409
|
}
|
411
410
|
]
|
412
411
|
"""
|
@@ -415,32 +414,37 @@ class OurbitSwapDataStore(DataStoreCollection):
|
|
415
414
|
@property
|
416
415
|
def book(self) -> Book:
|
417
416
|
"""订单簿深度数据流
|
418
|
-
|
417
|
+
|
418
|
+
提供实时订单簿深度数据,包含买卖双方价格和数量信息
|
419
|
+
|
419
420
|
Data type: Mutable
|
420
|
-
|
421
|
-
Keys: ("
|
421
|
+
|
422
|
+
Keys: ("s", "S", "p")
|
423
|
+
- s: 交易对符号,如 "BTC_USDT"
|
424
|
+
- S: 买卖方向,"a" 表示卖单(ask),"b" 表示买单(bid)
|
425
|
+
- p: 价格
|
422
426
|
|
423
427
|
Data structure:
|
424
428
|
|
425
429
|
.. code:: python
|
426
430
|
|
427
431
|
[
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
432
|
+
{
|
433
|
+
"s": "BTC_USDT", # 交易对符号
|
434
|
+
"S": "a", # 卖单方向(ask)
|
435
|
+
"p": "110152.5", # 价格
|
436
|
+
"q": "53539", # 数量
|
437
|
+
"ct": 1, # 该价格的订单数量
|
438
|
+
"i": 0 # 价格档位索引(从0开始)
|
439
|
+
},
|
440
|
+
{
|
441
|
+
"s": "BTC_USDT", # 交易对符号
|
442
|
+
"S": "b", # 买单方向(bid)
|
443
|
+
"p": "110152.4", # 价格
|
444
|
+
"q": "76311", # 数量
|
445
|
+
"ct": 1, # 该价格的订单数量
|
446
|
+
"i": 0 # 价格档位索引(从0开始)
|
447
|
+
}
|
444
448
|
]
|
445
449
|
"""
|
446
450
|
return self._get("book", Book)
|
@@ -592,7 +596,11 @@ class SpotBalance(DataStore):
|
|
592
596
|
def _on_message(self, msg: dict[str, Any]) -> None:
|
593
597
|
data = msg.get("d", {})
|
594
598
|
item = self._fmt_ws(data)
|
595
|
-
|
599
|
+
av = float(item.get("available", 0))
|
600
|
+
if av == 0:
|
601
|
+
self._find_and_delete({'currency': item.get("currency")})
|
602
|
+
else:
|
603
|
+
self._update([item])
|
596
604
|
|
597
605
|
|
598
606
|
# SpotOrders: 现货订单数据存储
|
@@ -601,17 +609,22 @@ class SpotOrders(DataStore):
|
|
601
609
|
|
602
610
|
|
603
611
|
def _fmt(self, order: dict) -> dict:
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
612
|
+
# 状态映射:1=open, 2=filled(整单成交), 3=partially_filled, 4=canceled
|
613
|
+
state_num = order.get("state") or order.get("status")
|
614
|
+
if state_num == 1:
|
615
|
+
state_txt = "open"
|
616
|
+
elif state_num == 2:
|
617
|
+
state_txt = "filled" # ✔ 2 才是整单成交
|
618
|
+
elif state_num == 3:
|
619
|
+
state_txt = "partially_filled"
|
620
|
+
elif state_num == 4:
|
621
|
+
state_txt = "canceled"
|
622
|
+
else:
|
623
|
+
state_txt = "unknown"
|
611
624
|
|
612
625
|
return {
|
613
|
-
"order_id": order.get("id"),
|
614
|
-
"symbol": order.get("symbol"),
|
626
|
+
"order_id": order.get("id") or order.get("orderId"),
|
627
|
+
"symbol": order.get("symbol") or order.get("s"),
|
615
628
|
"currency": order.get("currency"),
|
616
629
|
"market": order.get("market"),
|
617
630
|
"trade_type": order.get("tradeType"),
|
@@ -622,15 +635,14 @@ class SpotOrders(DataStore):
|
|
622
635
|
"deal_quantity": order.get("dealQuantity"),
|
623
636
|
"deal_amount": order.get("dealAmount"),
|
624
637
|
"avg_price": order.get("avgPrice"),
|
625
|
-
"state":
|
626
|
-
"source": order.get("source"),
|
638
|
+
"state": state_txt,
|
639
|
+
"source": order.get("source") or order.get("internal"),
|
627
640
|
"fee": order.get("fee"),
|
628
641
|
"create_ts": order.get("createTime"),
|
629
642
|
"unique_id": order.get("uniqueId"),
|
630
643
|
}
|
631
644
|
|
632
645
|
|
633
|
-
|
634
646
|
def _onresponse(self, data: dict[str, Any]):
|
635
647
|
orders = (data.get("data") or {}).get("resultList", [])
|
636
648
|
items = [self._fmt(order) for order in orders]
|
@@ -639,9 +651,9 @@ class SpotOrders(DataStore):
|
|
639
651
|
self._insert(items)
|
640
652
|
|
641
653
|
def _on_message(self, msg: dict[str, Any]) -> None:
|
642
|
-
d:dict = msg.get("d", {})
|
654
|
+
d: dict = msg.get("d", {})
|
643
655
|
|
644
|
-
|
656
|
+
# 基础字段
|
645
657
|
item = {
|
646
658
|
"order_id": d.get("id"),
|
647
659
|
"symbol": msg.get("s") or d.get("symbol"),
|
@@ -652,7 +664,6 @@ class SpotOrders(DataStore):
|
|
652
664
|
"amount": d.get("amount"),
|
653
665
|
"remain_quantity": d.get("remainQ"),
|
654
666
|
"remain_amount": d.get("remainA"),
|
655
|
-
"state": d.get("status"),
|
656
667
|
"client_order_id": d.get("clientOrderId"),
|
657
668
|
"is_taker": d.get("isTaker"),
|
658
669
|
"create_ts": d.get("createTime"),
|
@@ -661,28 +672,78 @@ class SpotOrders(DataStore):
|
|
661
672
|
|
662
673
|
state = d.get("status")
|
663
674
|
|
664
|
-
if state == 2 or state == 1:
|
665
|
-
item["state"] = "open"
|
666
|
-
self._insert([item])
|
667
|
-
|
668
|
-
elif state == 3:
|
669
|
-
item["state"] = "filled"
|
670
|
-
|
671
|
-
# 如果这三个字段存在追加
|
672
|
-
if d.get("singleDealId") and d.get("singleDealPrice") and d.get("singleDealQuantity"):
|
673
|
-
item.update({
|
674
|
-
"unique_id": d.get("singleDealId"),
|
675
|
-
"avg_price": d.get("singleDealPrice"),
|
676
|
-
"deal_quantity": d.get("singleDealQuantity"),
|
677
|
-
})
|
678
675
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
676
|
+
|
677
|
+
# 成交片段(部分/完全)
|
678
|
+
if d.get("singleDealPrice"):
|
679
|
+
# 单片段信息(可能多次推送;需做增量累计 + 去重)
|
680
|
+
single_id = d.get("singleDealId")
|
681
|
+
single_px = d.get("singleDealPrice")
|
682
|
+
single_qty = d.get("singleDealQuantity")
|
683
|
+
try:
|
684
|
+
px_i = float(single_px) if single_px is not None else 0.0
|
685
|
+
qty_i = float(single_qty) if single_qty is not None else 0.0
|
686
|
+
except Exception:
|
687
|
+
px_i, qty_i = 0.0, 0.0
|
688
|
+
|
689
|
+
old = self.get({"order_id": d.get("id")})
|
690
|
+
old_qty = float(old.get("deal_quantity") or 0.0) if old else 0.0
|
691
|
+
old_avg = float(old.get("avg_price") or 0.0) if old else 0.0
|
692
|
+
old_last_single = old.get("last_single_id") if old else None
|
693
|
+
|
694
|
+
# 去重:若与上一片段 ID 相同,认为是重复推送,直接按状态更新不累计
|
695
|
+
if old and single_id and old_last_single == single_id:
|
696
|
+
new_qty = old_qty
|
697
|
+
new_avg = old_avg
|
698
|
+
else:
|
699
|
+
# VWAP 累计
|
700
|
+
new_qty = old_qty + qty_i
|
701
|
+
if new_qty > 0:
|
702
|
+
new_avg = (old_avg * old_qty + px_i * qty_i) / new_qty
|
703
|
+
else:
|
704
|
+
new_avg = px_i
|
705
|
+
|
706
|
+
# 写回
|
707
|
+
item.update({
|
708
|
+
"avg_price": str(new_avg) if new_qty > 0 else old.get("avg_price") if old else None,
|
709
|
+
"deal_quantity": str(new_qty) if new_qty > 0 else old.get("deal_quantity") if old else None,
|
710
|
+
"single_id": single_id,
|
711
|
+
"last_single_id": single_id,
|
712
|
+
})
|
713
|
+
|
714
|
+
# 状态文本:2=filled(整单), 3=partially_filled
|
715
|
+
# item["state"] = "filled" if state == 2 else "partially_filled"
|
716
|
+
if state == 2:
|
717
|
+
item["state"] = "filled"
|
718
|
+
elif state == 3:
|
719
|
+
item["state"] = "partially_filled"
|
720
|
+
else:
|
721
|
+
item["state"] = "unknown_"+str(state)
|
722
|
+
|
684
723
|
self._update([item])
|
685
|
-
|
724
|
+
|
725
|
+
# 整单成交 或者 部分取消 → 删除
|
726
|
+
if state == 2 or 'unknown' in item["state"]:
|
727
|
+
self._find_and_delete({"order_id": d.get("id")})
|
728
|
+
return
|
729
|
+
else:
|
730
|
+
# 新建 / 已挂出
|
731
|
+
if state == 1:
|
732
|
+
item["state"] = "open"
|
733
|
+
self._insert([item])
|
734
|
+
return
|
735
|
+
|
736
|
+
elif state == 4:
|
737
|
+
item["state"] = "canceled"
|
738
|
+
self._update([item])
|
739
|
+
self._find_and_delete({"order_id": d.get("id")})
|
740
|
+
return
|
741
|
+
else:
|
742
|
+
|
743
|
+
# 未知状态:更新后删除,避免脏数据残留
|
744
|
+
item["state"] = "unknown_"+str(state)
|
745
|
+
self._update([item])
|
746
|
+
self._find_and_delete({"order_id": d.get("id")})
|
686
747
|
|
687
748
|
|
688
749
|
|
@@ -693,69 +754,110 @@ class SpotBook(DataStore):
|
|
693
754
|
# super().__init__()
|
694
755
|
self._time: int | None = None
|
695
756
|
self.limit = 1
|
757
|
+
self.loss = {} # 改为字典,按symbol跟踪
|
758
|
+
self.versions = {}
|
759
|
+
self.cache = []
|
696
760
|
|
697
761
|
def _onresponse(self, data: dict[str, Any]):
|
762
|
+
data = data.get("data")
|
763
|
+
symbol = data.get("symbol")
|
764
|
+
book_data = data.get("data")
|
765
|
+
asks = book_data.get("asks", [])
|
766
|
+
bids = book_data.get("bids", [])
|
767
|
+
version = int(data.get("version", None))
|
698
768
|
|
699
|
-
top = data.get("data") or data.get("d") or data
|
700
|
-
symbol = (
|
701
|
-
top.get("s")
|
702
|
-
or top.get("symbol")
|
703
|
-
or (top.get("data") or {}).get("symbol")
|
704
|
-
)
|
705
769
|
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
items: list
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
p = level.get("p")
|
719
|
-
q = level.get("q")
|
720
|
-
return (p, q)
|
721
|
-
if isinstance(level, (list, tuple)) and len(level) >= 2:
|
722
|
-
return (level[0], level[1])
|
723
|
-
return None
|
770
|
+
# 保存当前快照版本
|
771
|
+
self.versions[symbol] = version
|
772
|
+
|
773
|
+
# # 应用缓存的增量(只保留连续的部分)
|
774
|
+
# items: list = self.find({"s": symbol})
|
775
|
+
# items.sort(key=lambda x: x.get("fv", 0)) # 按 fromVersion 排序
|
776
|
+
# self._find_and_delete({"s": symbol})
|
777
|
+
|
778
|
+
# 应为我们先连接的ws, 所以可能有缓存需要去处理
|
779
|
+
items = [item for item in self.cache if item.get("s") == symbol]
|
780
|
+
items.sort(key=lambda x: x.get("fv", 0)) # 按 fromVersion 排序
|
781
|
+
self.cache = [item for item in self.cache if item.get("s") != symbol]
|
724
782
|
|
725
783
|
for side, S in ((asks, "a"), (bids, "b")):
|
726
|
-
for
|
727
|
-
|
728
|
-
if not pq:
|
729
|
-
continue
|
730
|
-
p, q = pq
|
731
|
-
if p is None or q is None:
|
732
|
-
continue
|
733
|
-
try:
|
734
|
-
if float(q) == 0.0:
|
735
|
-
continue
|
736
|
-
except (TypeError, ValueError):
|
737
|
-
continue
|
738
|
-
items.append({"s": symbol, "S": S, "p": p, "q": q})
|
784
|
+
for item in side:
|
785
|
+
self._insert([{"s": symbol, "S": S, "p": item["p"], "q": item["q"]}])
|
739
786
|
|
740
787
|
if items:
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
788
|
+
min_version = min(item.get("fv", 0) for item in items)
|
789
|
+
max_version = max(item.get("tv", 0) for item in items)
|
790
|
+
# self.version = max_version
|
791
|
+
self.versions[symbol] = max_version
|
792
|
+
|
793
|
+
# if max_version == 0:
|
794
|
+
# print('vvv---')
|
795
|
+
# print(items)
|
796
|
+
|
797
|
+
if not (min_version <= self.versions[symbol] <= max_version):
|
798
|
+
self.loss[symbol] = True
|
799
|
+
logger.warning(f"SpotBook: Snapshot version {self.version} out of range ({min_version}, {max_version}) for symbol={symbol} (丢补丁)")
|
800
|
+
return
|
801
|
+
|
802
|
+
# 处理过往msg内容
|
803
|
+
self.loss[symbol] = False
|
804
|
+
for item in items:
|
805
|
+
fv, tv = item.get("fv", 0), item.get("tv", 0)
|
806
|
+
if self.versions[symbol] <= tv and self.versions[symbol] >= fv:
|
807
|
+
if float(item["q"]) == 0.0:
|
808
|
+
self._find_and_delete({"s": symbol, "S": item["S"], "p": item["p"]})
|
809
|
+
else:
|
810
|
+
self._insert([{ "s": symbol, "S": item["S"], "p": item["p"], "q": item["q"]}])
|
811
|
+
|
812
|
+
sort_data = self.sorted({'s': symbol}, self.limit)
|
813
|
+
asks = sort_data.get('a', [])
|
814
|
+
bids = sort_data.get('b', [])
|
815
|
+
self._find_and_delete({'s': symbol})
|
816
|
+
self._update(asks + bids)
|
817
|
+
|
818
|
+
else:
|
819
|
+
self.loss[symbol] = False
|
749
820
|
|
750
821
|
|
751
822
|
def _on_message(self, msg: dict[str, Any]) -> None:
|
752
|
-
|
823
|
+
|
824
|
+
# ts = time.time() * 1000 # 预留时间戳(如需记录可用)
|
753
825
|
data = msg.get("d", {}) or {}
|
754
826
|
symbol = msg.get("s")
|
755
|
-
|
827
|
+
fv = int(data.get("fromVersion"))
|
828
|
+
tv = int(data.get("toVersion"))
|
829
|
+
if fv == 0 or tv == 0:
|
830
|
+
# print(f'发现fv或tv为0, msg:\n {msg}')
|
831
|
+
return
|
832
|
+
|
756
833
|
asks: list = data.get("asks", []) or []
|
757
834
|
bids: list = data.get("bids", []) or []
|
758
835
|
|
836
|
+
now_version = self.versions.get(symbol, None)
|
837
|
+
|
838
|
+
# 以下几张情况都会被认为正常
|
839
|
+
check_con = (
|
840
|
+
now_version is None or
|
841
|
+
fv <= now_version <= tv or
|
842
|
+
now_version + 1 == fv
|
843
|
+
)
|
844
|
+
|
845
|
+
if not check_con:
|
846
|
+
# logger.warning(f"(丢补丁) version:{now_version} fv:{fv} tv:{tv} ")
|
847
|
+
self.loss[symbol] = True # 暂时不这样做
|
848
|
+
|
849
|
+
|
850
|
+
|
851
|
+
if self.loss.get(symbol, True):
|
852
|
+
for item in asks:
|
853
|
+
self.cache.append({"s": symbol, "S": "a", "p": item["p"], "q": item["q"], "fv": fv, "tv": tv})
|
854
|
+
for item in bids:
|
855
|
+
self.cache.append({"s": symbol, "S": "b", "p": item["p"], "q": item["q"], "fv": fv, "tv": tv})
|
856
|
+
return
|
857
|
+
|
858
|
+
self.versions[symbol] = tv
|
859
|
+
|
860
|
+
|
759
861
|
to_delete, to_update = [], []
|
760
862
|
for side, S in ((asks, "a"), (bids, "b")):
|
761
863
|
for item in side:
|
@@ -929,8 +1031,7 @@ class OurbitSpotDataStore(DataStoreCollection):
|
|
929
1031
|
"""
|
930
1032
|
现货账户余额数据流
|
931
1033
|
|
932
|
-
|
933
|
-
|
1034
|
+
_KEYS = ["currency"]
|
934
1035
|
.. code:: python
|
935
1036
|
|
936
1037
|
[
|
@@ -1015,7 +1116,7 @@ class OurbitSpotDataStore(DataStoreCollection):
|
|
1015
1116
|
return self._get("order", SpotOrders)
|
1016
1117
|
|
1017
1118
|
def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
|
1018
|
-
|
1119
|
+
# print(msg, '\n')
|
1019
1120
|
channel = msg.get("c")
|
1020
1121
|
if 'msg' in msg:
|
1021
1122
|
if 'invalid' in msg['msg']:
|