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.
Files changed (30) hide show
  1. {hyperquant-0.44 → hyperquant-0.46}/PKG-INFO +1 -1
  2. {hyperquant-0.44 → hyperquant-0.46}/pyproject.toml +1 -1
  3. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/models/ourbit.py +88 -47
  4. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/ourbit.py +34 -15
  5. {hyperquant-0.44 → hyperquant-0.46}/tmp.py +20 -1
  6. {hyperquant-0.44 → hyperquant-0.46}/uv.lock +1 -1
  7. {hyperquant-0.44 → hyperquant-0.46}/.gitignore +0 -0
  8. {hyperquant-0.44 → hyperquant-0.46}/.python-version +0 -0
  9. {hyperquant-0.44 → hyperquant-0.46}/README.md +0 -0
  10. {hyperquant-0.44 → hyperquant-0.46}/data/logs/notikit.log +0 -0
  11. {hyperquant-0.44 → hyperquant-0.46}/pub.sh +0 -0
  12. {hyperquant-0.44 → hyperquant-0.46}/requirements-dev.lock +0 -0
  13. {hyperquant-0.44 → hyperquant-0.46}/requirements.lock +0 -0
  14. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/__init__.py +0 -0
  15. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/auth.py +0 -0
  16. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/hyperliquid.py +0 -0
  17. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/lib/hpstore.py +0 -0
  18. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  19. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  20. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/broker/ws.py +0 -0
  21. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/core.py +0 -0
  22. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/datavison/_util.py +0 -0
  23. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/datavison/binance.py +0 -0
  24. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/datavison/coinglass.py +0 -0
  25. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/datavison/okx.py +0 -0
  26. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/db.py +0 -0
  27. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/draw.py +0 -0
  28. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/logkit.py +0 -0
  29. {hyperquant-0.44 → hyperquant-0.46}/src/hyperquant/notikit.py +0 -0
  30. {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.44
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "0.44"
3
+ version = "0.46"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -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
- inner = top.get("data") or top
711
- asks = inner.get("asks") or []
712
- bids = inner.get("bids") or []
713
-
714
- items: list[Item] = []
715
- if symbol:
716
- # Snapshot semantics: rebuild entries for this symbol
717
- self._find_and_delete({"s": symbol})
718
-
719
- def extract_pq(level: Any) -> tuple[Any, Any] | None:
720
- # Accept dict {"p": x, "q": y} or list/tuple [p, q, ...]
721
- if isinstance(level, dict):
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 level in side:
731
- pq = extract_pq(level)
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
- self._insert(items)
746
-
747
- sort_data = self.sorted({'s': symbol}, self.limit)
748
- asks = sort_data.get('a', [])
749
- bids = sort_data.get('b', [])
750
- self._find_and_delete({'s': symbol})
751
- self._update(asks + bids)
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
- ts = time.time() * 1000 # 预留时间戳(如需记录可用)
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(test_cancel_order())
160
+ asyncio.run(test_orderbook())
@@ -530,7 +530,7 @@ wheels = [
530
530
 
531
531
  [[package]]
532
532
  name = "hyperquant"
533
- version = "0.43"
533
+ version = "0.45"
534
534
  source = { editable = "." }
535
535
  dependencies = [
536
536
  { name = "aiohttp" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes