hyperquant 0.44__tar.gz → 0.46__tar.gz
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-0.44 → hyperquant-0.46}/PKG-INFO +1 -1
- {hyperquant-0.44 → hyperquant-0.46}/pyproject.toml +1 -1
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/models/ourbit.py +88 -47
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/ourbit.py +34 -15
- {hyperquant-0.44 → hyperquant-0.46}/tmp.py +20 -1
- {hyperquant-0.44 → hyperquant-0.46}/uv.lock +1 -1
- {hyperquant-0.44 → hyperquant-0.46}/.gitignore +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/.python-version +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/README.md +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/data/logs/notikit.log +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/pub.sh +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/requirements-dev.lock +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/requirements.lock +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/__init__.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/auth.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/hyperliquid.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/lib/hpstore.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/lib/hyper_types.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/models/hyperliquid.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/ws.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/core.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/datavison/_util.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/datavison/binance.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/datavison/coinglass.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/datavison/okx.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/db.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/draw.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/logkit.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/notikit.py +0 -0
- {hyperquant-0.44 → hyperquant-0.46}/test.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hyperquant
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.46
|
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
|
@@ -697,69 +697,110 @@ class SpotBook(DataStore):
|
|
697
697
|
# super().__init__()
|
698
698
|
self._time: int | None = None
|
699
699
|
self.limit = 1
|
700
|
+
self.loss = {} # 改为字典,按symbol跟踪
|
701
|
+
self.versions = {}
|
702
|
+
self.cache = []
|
700
703
|
|
701
704
|
def _onresponse(self, data: dict[str, Any]):
|
705
|
+
data = data.get("data")
|
706
|
+
symbol = data.get("symbol")
|
707
|
+
book_data = data.get("data")
|
708
|
+
asks = book_data.get("asks", [])
|
709
|
+
bids = book_data.get("bids", [])
|
710
|
+
version = int(data.get("version", None))
|
702
711
|
|
703
|
-
top = data.get("data") or data.get("d") or data
|
704
|
-
symbol = (
|
705
|
-
top.get("s")
|
706
|
-
or top.get("symbol")
|
707
|
-
or (top.get("data") or {}).get("symbol")
|
708
|
-
)
|
709
712
|
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
items: list
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
p = level.get("p")
|
723
|
-
q = level.get("q")
|
724
|
-
return (p, q)
|
725
|
-
if isinstance(level, (list, tuple)) and len(level) >= 2:
|
726
|
-
return (level[0], level[1])
|
727
|
-
return None
|
713
|
+
# 保存当前快照版本
|
714
|
+
self.versions[symbol] = version
|
715
|
+
|
716
|
+
# # 应用缓存的增量(只保留连续的部分)
|
717
|
+
# items: list = self.find({"s": symbol})
|
718
|
+
# items.sort(key=lambda x: x.get("fv", 0)) # 按 fromVersion 排序
|
719
|
+
# self._find_and_delete({"s": symbol})
|
720
|
+
|
721
|
+
# 处理缓存
|
722
|
+
items = [item for item in self.cache if item.get("s") == symbol]
|
723
|
+
items.sort(key=lambda x: x.get("fv", 0)) # 按 fromVersion 排序
|
724
|
+
self.cache = [item for item in self.cache if item.get("s") != symbol]
|
728
725
|
|
729
726
|
for side, S in ((asks, "a"), (bids, "b")):
|
730
|
-
for
|
731
|
-
|
732
|
-
if not pq:
|
733
|
-
continue
|
734
|
-
p, q = pq
|
735
|
-
if p is None or q is None:
|
736
|
-
continue
|
737
|
-
try:
|
738
|
-
if float(q) == 0.0:
|
739
|
-
continue
|
740
|
-
except (TypeError, ValueError):
|
741
|
-
continue
|
742
|
-
items.append({"s": symbol, "S": S, "p": p, "q": q})
|
727
|
+
for item in side:
|
728
|
+
self._insert([{"s": symbol, "S": S, "p": item["p"], "q": item["q"]}])
|
743
729
|
|
744
730
|
if items:
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
731
|
+
min_version = min(item.get("fv", 0) for item in items)
|
732
|
+
max_version = max(item.get("tv", 0) for item in items)
|
733
|
+
# self.version = max_version
|
734
|
+
self.versions[symbol] = max_version
|
735
|
+
|
736
|
+
# if max_version == 0:
|
737
|
+
# print('vvv---')
|
738
|
+
# print(items)
|
739
|
+
|
740
|
+
if not (min_version <= self.versions[symbol] <= max_version):
|
741
|
+
self.loss[symbol] = True
|
742
|
+
logger.warning(f"SpotBook: Snapshot version {self.version} out of range ({min_version}, {max_version}) for symbol={symbol} (丢补丁)")
|
743
|
+
return
|
744
|
+
|
745
|
+
# 处理过往msg内容
|
746
|
+
self.loss[symbol] = False
|
747
|
+
for item in items:
|
748
|
+
fv, tv = item.get("fv", 0), item.get("tv", 0)
|
749
|
+
if self.versions[symbol] <= tv and self.versions[symbol] >= fv:
|
750
|
+
if float(item["q"]) == 0.0:
|
751
|
+
self._find_and_delete({"s": symbol, "S": item["S"], "p": item["p"]})
|
752
|
+
else:
|
753
|
+
self._insert([{ "s": symbol, "S": item["S"], "p": item["p"], "q": item["q"]}])
|
754
|
+
|
755
|
+
sort_data = self.sorted({'s': symbol}, self.limit)
|
756
|
+
asks = sort_data.get('a', [])
|
757
|
+
bids = sort_data.get('b', [])
|
758
|
+
self._find_and_delete({'s': symbol})
|
759
|
+
self._update(asks + bids)
|
760
|
+
|
761
|
+
else:
|
762
|
+
self.loss[symbol] = False
|
753
763
|
|
754
764
|
|
755
765
|
def _on_message(self, msg: dict[str, Any]) -> None:
|
756
|
-
|
766
|
+
|
767
|
+
# ts = time.time() * 1000 # 预留时间戳(如需记录可用)
|
757
768
|
data = msg.get("d", {}) or {}
|
758
769
|
symbol = msg.get("s")
|
759
|
-
|
770
|
+
fv = int(data.get("fromVersion"))
|
771
|
+
tv = int(data.get("toVersion"))
|
772
|
+
if fv == 0 or tv == 0:
|
773
|
+
# print(f'发现fv或tv为0, msg:\n {msg}')
|
774
|
+
return
|
775
|
+
|
760
776
|
asks: list = data.get("asks", []) or []
|
761
777
|
bids: list = data.get("bids", []) or []
|
762
778
|
|
779
|
+
now_version = self.versions.get(symbol, None)
|
780
|
+
|
781
|
+
# 以下几张情况都会被认为正常
|
782
|
+
check_con = (
|
783
|
+
now_version is None or
|
784
|
+
fv <= now_version <= tv or
|
785
|
+
now_version + 1 == fv
|
786
|
+
)
|
787
|
+
|
788
|
+
if not check_con:
|
789
|
+
logger.warning(f"(丢补丁) version:{now_version} fv:{fv} tv:{tv} ")
|
790
|
+
self.loss[symbol] = True # 暂时不这样做
|
791
|
+
|
792
|
+
|
793
|
+
|
794
|
+
if self.loss.get(symbol, True):
|
795
|
+
for item in asks:
|
796
|
+
self.cache.append({"s": symbol, "S": "a", "p": item["p"], "q": item["q"], "fv": fv, "tv": tv})
|
797
|
+
for item in bids:
|
798
|
+
self.cache.append({"s": symbol, "S": "b", "p": item["p"], "q": item["q"], "fv": fv, "tv": tv})
|
799
|
+
return
|
800
|
+
|
801
|
+
self.versions[symbol] = tv
|
802
|
+
|
803
|
+
|
763
804
|
to_delete, to_update = [], []
|
764
805
|
for side, S in ((asks, "a"), (bids, "b")):
|
765
806
|
for item in side:
|
@@ -389,31 +389,20 @@ class OurbitSpot:
|
|
389
389
|
Args:
|
390
390
|
symbols: 交易对符号,可以是单个字符串或字符串列表
|
391
391
|
"""
|
392
|
+
import logging
|
393
|
+
logger = logging.getLogger("OurbitSpot")
|
394
|
+
|
392
395
|
if isinstance(symbols, str):
|
393
396
|
symbols = [symbols]
|
394
397
|
|
395
|
-
# 并发获取每个交易对的初始深度数据
|
396
|
-
tasks = [
|
397
|
-
self.client.fetch('GET', f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}")
|
398
|
-
for symbol in symbols
|
399
|
-
]
|
400
|
-
|
401
|
-
# 等待所有请求完成
|
402
|
-
responses = await asyncio.gather(*tasks)
|
403
|
-
|
404
|
-
# 处理响应数据
|
405
|
-
for response in responses:
|
406
|
-
self.store.book._onresponse(response.data)
|
407
|
-
|
408
398
|
# 构建订阅参数
|
409
399
|
subscription_params = []
|
410
400
|
for symbol in symbols:
|
411
401
|
subscription_params.append(f"spot@public.increase.aggre.depth@{symbol}")
|
412
402
|
|
413
|
-
|
414
403
|
# 一次sub20个,超过需要分开订阅
|
415
404
|
for i in range(0, len(subscription_params), 20):
|
416
|
-
self.client.ws_connect(
|
405
|
+
wsapp = self.client.ws_connect(
|
417
406
|
'wss://www.ourbit.com/ws?platform=web',
|
418
407
|
send_json={
|
419
408
|
"method": "SUBSCRIPTION",
|
@@ -422,6 +411,36 @@ class OurbitSpot:
|
|
422
411
|
},
|
423
412
|
hdlr_json=self.store.onmessage
|
424
413
|
)
|
414
|
+
await wsapp._event.wait()
|
415
|
+
|
416
|
+
# await asyncio.sleep(1) # 等待ws连接稳定
|
417
|
+
|
418
|
+
# 并发获取每个交易对的初始深度数据
|
419
|
+
tasks = [
|
420
|
+
self.client.fetch('GET', f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}")
|
421
|
+
for symbol in symbols
|
422
|
+
]
|
423
|
+
|
424
|
+
# 等待所有请求完成
|
425
|
+
responses = await asyncio.gather(*tasks)
|
426
|
+
|
427
|
+
# 处理响应数据
|
428
|
+
for idx, response in enumerate(responses):
|
429
|
+
symbol = symbols[idx]
|
430
|
+
self.store.book._onresponse(response.data)
|
431
|
+
|
432
|
+
async def check_loss():
|
433
|
+
await asyncio.sleep(1)
|
434
|
+
while True:
|
435
|
+
loss = self.store.book.loss
|
436
|
+
for symbol, is_loss in loss.items():
|
437
|
+
if is_loss:
|
438
|
+
resp = await self.client.fetch('GET', f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}")
|
439
|
+
self.store.book._onresponse(resp.data)
|
440
|
+
await asyncio.sleep(1)
|
441
|
+
|
442
|
+
asyncio.create_task(check_loss())
|
443
|
+
|
425
444
|
|
426
445
|
async def place_order(
|
427
446
|
self,
|
@@ -137,5 +137,24 @@ async def test_cancel_order():
|
|
137
137
|
print(oid)
|
138
138
|
# await ob_spot.cancel_orders([oid])
|
139
139
|
|
140
|
+
|
141
|
+
async def test_orderbook():
|
142
|
+
async with pybotters.Client(apis={
|
143
|
+
"ourbit": [
|
144
|
+
"WEB3bf088f8b2f2fae07592fe1a6240e2d798100a9cb2a91f8fda1056b6865ab3ee"
|
145
|
+
]
|
146
|
+
}) as client:
|
147
|
+
ob_spot = OurbitSpot(client)
|
148
|
+
await ob_spot.__aenter__()
|
149
|
+
await ob_spot.update('ticker')
|
150
|
+
# symbols = [d['symbol'] for d in ob_spot.store.ticker.find()][:30]
|
151
|
+
symbols = ['ETC_USDT']
|
152
|
+
|
153
|
+
await ob_spot.sub_orderbook(symbols)
|
154
|
+
while True:
|
155
|
+
await ob_spot.store.book.wait()
|
156
|
+
print(ob_spot.store.book.find())
|
157
|
+
|
158
|
+
|
140
159
|
if __name__ == "__main__":
|
141
|
-
asyncio.run(
|
160
|
+
asyncio.run(test_orderbook())
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|