hyperquant 0.76__tar.gz → 0.78__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 (50) hide show
  1. {hyperquant-0.76 → hyperquant-0.78}/PKG-INFO +1 -1
  2. {hyperquant-0.76 → hyperquant-0.78}/apis.json +1 -1
  3. {hyperquant-0.76 → hyperquant-0.78}/pyproject.toml +1 -1
  4. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/bitget.py +36 -16
  5. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/lbank.py +1 -1
  6. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/models/lbank.py +3 -1
  7. hyperquant-0.78/tests/test_bitget.py +193 -0
  8. {hyperquant-0.76 → hyperquant-0.78}/tests/test_lbank.py +17 -15
  9. hyperquant-0.78/uv.lock +2031 -0
  10. hyperquant-0.76/tests/test_bitget.py +0 -88
  11. hyperquant-0.76/uv.lock +0 -1637
  12. {hyperquant-0.76 → hyperquant-0.78}/.gitignore +0 -0
  13. {hyperquant-0.76 → hyperquant-0.78}/.python-version +0 -0
  14. {hyperquant-0.76 → hyperquant-0.78}/README.md +0 -0
  15. {hyperquant-0.76 → hyperquant-0.78}/data/alpine_smoke.log +0 -0
  16. {hyperquant-0.76 → hyperquant-0.78}/data/logs/notikit.log +0 -0
  17. {hyperquant-0.76 → hyperquant-0.78}/data/logs/test_order_sync.log +0 -0
  18. {hyperquant-0.76 → hyperquant-0.78}/data/records_swap.csv +0 -0
  19. {hyperquant-0.76 → hyperquant-0.78}/data/records_swapc.csv +0 -0
  20. {hyperquant-0.76 → hyperquant-0.78}/doc/lbank.md +0 -0
  21. {hyperquant-0.76 → hyperquant-0.78}/pub.sh +0 -0
  22. {hyperquant-0.76 → hyperquant-0.78}/requirements-dev.lock +0 -0
  23. {hyperquant-0.76 → hyperquant-0.78}/requirements.lock +0 -0
  24. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/__init__.py +0 -0
  25. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/auth.py +0 -0
  26. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/edgex.py +0 -0
  27. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/hyperliquid.py +0 -0
  28. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  29. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/lib/hpstore.py +0 -0
  30. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  31. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/lib/util.py +0 -0
  32. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/models/bitget.py +0 -0
  33. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/models/edgex.py +0 -0
  34. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  35. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/models/ourbit.py +0 -0
  36. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/ourbit.py +0 -0
  37. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/broker/ws.py +0 -0
  38. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/core.py +0 -0
  39. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/datavison/_util.py +0 -0
  40. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/datavison/binance.py +0 -0
  41. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/datavison/coinglass.py +0 -0
  42. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/datavison/okx.py +0 -0
  43. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/db.py +0 -0
  44. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/draw.py +0 -0
  45. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/logkit.py +0 -0
  46. {hyperquant-0.76 → hyperquant-0.78}/src/hyperquant/notikit.py +0 -0
  47. {hyperquant-0.76 → hyperquant-0.78}/tests/test_draw.py +0 -0
  48. {hyperquant-0.76 → hyperquant-0.78}/tests/test_edgex.py +0 -0
  49. {hyperquant-0.76 → hyperquant-0.78}/tests/test_ourbit.py +0 -0
  50. {hyperquant-0.76 → hyperquant-0.78}/tests/tmp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.76
3
+ Version: 0.78
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
@@ -8,7 +8,7 @@
8
8
  "3MB7nAnnNPTxAnwdbjDPew-02b746d6a832346a46a97faf054b2909c1a0b58a35e04c3504923a99a5503c1c"
9
9
  ],
10
10
  "lbank": [
11
- "925a645e03c84c8e96b96008e98b75f7"
11
+ "36e99e63f77d4ab18137c23d30beda4e"
12
12
  ],
13
13
  "bitget":[
14
14
  "bg_03e0445d9282f248d22842cfe6f30192",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "0.76"
3
+ version = "0.78"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import logging
5
+ import uuid
4
6
  from typing import Any, Literal
5
7
 
6
8
  import pybotters
@@ -31,7 +33,9 @@ class Bitget:
31
33
  self.ws_url = ws_url or "wss://ws.bitget.com/v2/ws/public"
32
34
  self.ws_url_private = ws_url or "wss://ws.bitget.com/v2/ws/private"
33
35
 
34
- self._ws_app = None
36
+ self.ws_app = None
37
+ self.has_sub_personal = False
38
+
35
39
 
36
40
  async def __aenter__(self) -> "Bitget":
37
41
  await self.update("detail")
@@ -53,11 +57,13 @@ class Bitget:
53
57
  {"instType": "USDT-FUTURES", "channel": "account", "coin": "default"},
54
58
  ],
55
59
  }
56
- self.client.ws_connect(
57
- self.ws_url_private,
58
- send_json=sub_msg,
59
- hdlr_json=self.store.onmessage,
60
- )
60
+ self.ws_app = await self._ensure_private_ws()
61
+
62
+
63
+ await self.ws_app.current_ws.send_json(sub_msg)
64
+
65
+
66
+ self.has_sub_personal = True
61
67
 
62
68
  async def update(
63
69
  self,
@@ -108,13 +114,17 @@ class Bitget:
108
114
  margin_coin: str = "USDT",
109
115
  reduce_only: bool | None = None,
110
116
  offset_flag: Literal["open", "close", "0", "1"] | None = None,
111
- client_order_id: str | None = None,
112
- extra_params: dict[str, Any] | None = None,
113
- ) -> dict[str, Any]:
114
- """Create an order via ``POST /api/v2/mix/order/place-order``.
117
+ client_order_id: str | None = None
118
+ ) -> dict[str, Any] | None:
119
+ """
120
+ 请求成功返回示例:
121
+
122
+ .. code:: json
115
123
 
116
- Parameters mirror the Bitget V2 contract REST API but are normalized to
117
- match the broker interface (``direction``, ``order_type`` etc.).
124
+ {
125
+ "clientOid": "121211212122",
126
+ "orderId": "121211212122"
127
+ }
118
128
  """
119
129
 
120
130
  side = self._normalize_direction(direction)
@@ -159,9 +169,6 @@ class Bitget:
159
169
  if client_order_id:
160
170
  payload["clientOid"] = client_order_id
161
171
 
162
- if extra_params:
163
- payload.update(extra_params)
164
-
165
172
  res = await self.client.post(
166
173
  f"{self.rest_api}/api/v2/mix/order/place-order",
167
174
  data=payload,
@@ -195,7 +202,7 @@ class Bitget:
195
202
 
196
203
  res = await self.client.post(
197
204
  f"{self.rest_api}/api/v2/mix/order/cancel-order",
198
- json=payload,
205
+ data=payload,
199
206
  )
200
207
  data = await res.json()
201
208
  return self._ensure_ok("cancel_order", data)
@@ -223,6 +230,19 @@ class Bitget:
223
230
  )
224
231
  return detail
225
232
 
233
+ async def _ensure_private_ws(self):
234
+ wsqueue = pybotters.WebSocketQueue()
235
+ ws_app = self.client.ws_connect(
236
+ self.ws_url_private,
237
+ hdlr_json=self.store.onmessage,
238
+ )
239
+ # async for msg in wsqueue:
240
+ # print(msg)
241
+
242
+ await ws_app._event.wait()
243
+ await ws_app.current_ws._wait_authtask()
244
+ return ws_app
245
+
226
246
  @staticmethod
227
247
  def _format_with_step(value: float, step: Any) -> str:
228
248
  if step in (None, 0, "0"):
@@ -569,4 +569,4 @@ class Lbank:
569
569
  batch = send_jsons[i:i+5]
570
570
  await asyncio.gather(*(sub(send_json) for send_json in batch))
571
571
  if i + 5 < len(send_jsons):
572
- await asyncio.sleep(0.1)
572
+ await asyncio.sleep(0.3)
@@ -298,6 +298,7 @@ class Position(DataStore):
298
298
  "realized_pnl": entry.get("CloseProfit"),
299
299
  "update_time": entry.get("UpdateTime"),
300
300
  "insert_time": entry.get("InsertTime"),
301
+ "begin_time": entry.get("BeginTime")
301
302
  }
302
303
 
303
304
  def _onresponse(self, data: dict[str, Any] | None) -> None:
@@ -492,7 +493,8 @@ class LbankDataStore(DataStoreCollection):
492
493
  "unrealized_pnl": <未实现盈亏>,
493
494
  "realized_pnl": <已实现盈亏>,
494
495
  "update_time": <更新时间>,
495
- "insert_time": <插入时间>
496
+ "insert_time": <插入时间>,
497
+ "begin_time": <持仓开始时间>
496
498
  },
497
499
  ...
498
500
  ]
@@ -0,0 +1,193 @@
1
+ import asyncio
2
+ import time
3
+ from typing import Literal
4
+
5
+ import pybotters
6
+ from hyperquant.broker.models.bitget import BitgetDataStore
7
+ pybotters.auth
8
+
9
+ async def test_update():
10
+ async with pybotters.Client() as client:
11
+ store = BitgetDataStore()
12
+ # await store.initialize(
13
+ # client.get("https://api.bitget.com/api/v2/mix/market/contracts?productType=usdt-futures")
14
+ # )
15
+ # print(store.detail.find())
16
+ await store.initialize(
17
+ client.get(
18
+ "https://api.bitget.com/api/v2/mix/market/tickers?productType=usdt-futures"
19
+ )
20
+ )
21
+ print(store.ticker.find({"symbol": "BTCUSDT"}))
22
+
23
+
24
+ async def subscribe_book():
25
+
26
+ async with pybotters.Client() as client:
27
+ store = BitgetDataStore()
28
+ client.ws_connect(
29
+ "wss://ws.bitget.com/v2/ws/public",
30
+ send_json={
31
+ "op": "subscribe",
32
+ "args": [
33
+ {"instType": "SPOT", "channel": "books1", "instId": "BTCUSDT"}
34
+ ]
35
+ },
36
+ hdlr_json=store.onmessage
37
+ )
38
+
39
+ while True:
40
+ await asyncio.sleep(1)
41
+ print(store.book.find())
42
+
43
+ from hyperquant.broker.bitget import Bitget
44
+ async def test_broker_update():
45
+
46
+ async with pybotters.Client() as client:
47
+ bg = Bitget(client)
48
+ store = BitgetDataStore()
49
+ # await bg.update('all')
50
+ # print(bg.store.detail.find())
51
+ await bg.update('ticker')
52
+ print(bg.store.ticker.find())
53
+
54
+ async def test_broker_sub_orderbook():
55
+ async with pybotters.Client() as client:
56
+ bg = Bitget(client)
57
+ await bg.sub_orderbook(['BTCUSDT', 'ETHUSDT'])
58
+ while True:
59
+ await asyncio.sleep(1)
60
+ print(bg.store.book.find())
61
+
62
+ async def test_order():
63
+ async with pybotters.Client(apis='./apis.json') as client:
64
+ bg = Bitget(client)
65
+ await bg.__aenter__()
66
+ ts = time.time() * 1000
67
+ res = await bg.place_order(
68
+ 'SOLUSDT',
69
+ direction='long',
70
+ order_type='limit_gtc',
71
+ volume=0.1,
72
+ price=185
73
+ )
74
+ # print(res)
75
+ print(f'订单延迟: {time.time() * 1000 - ts} ms')
76
+
77
+ async def test_sub_personal():
78
+ async with pybotters.Client(apis='./apis.json') as client:
79
+ bg = Bitget(client)
80
+ await bg.__aenter__()
81
+ await bg.sub_personal()
82
+
83
+ # # 监听订单变化
84
+ # with bg.store.orders.watch() as stream:
85
+ # async for change in stream:
86
+ # print("Orders changed:", change)
87
+
88
+ # 监听持仓变化
89
+ with bg.store.positions.watch() as stream:
90
+ async for change in stream:
91
+ print("Positions changed:", change)
92
+
93
+ async def order_sync_polling(
94
+ broker: Bitget,
95
+ *,
96
+ symbol: str,
97
+ direction: Literal["buy", "sell", "long", "short"] = "buy",
98
+ order_type: Literal[
99
+ "market",
100
+ "limit_gtc",
101
+ "limit_ioc",
102
+ "limit_fok",
103
+ "limit_post_only",
104
+ "limit",
105
+ ] = "limit_gtc",
106
+ price: float | None = None,
107
+ volume: float | None = None,
108
+ margin_mode: Literal["isolated", "crossed"] = "crossed",
109
+ product_type: str = "USDT-FUTURES",
110
+ margin_coin: str = "USDT",
111
+ window_sec: float = 5.0,
112
+ grace_sec: float = 5.0,
113
+ ) -> dict:
114
+ """
115
+ 基于 Bitget 私有 WS 的订单轮询:window 期内等待终态,超时后撤单并返回结果。
116
+ """
117
+
118
+ norm_type = order_type.lower()
119
+ if norm_type not in {
120
+ "market",
121
+ "limit",
122
+ "limit_gtc",
123
+ "limit_ioc",
124
+ "limit_fok",
125
+ "limit_post_only",
126
+ }:
127
+ raise ValueError(f"unsupported order_type: {order_type}")
128
+
129
+ order = None
130
+ try:
131
+ async with asyncio.timeout(window_sec):
132
+ # 监控订单
133
+ with broker.store.orders.watch() as stream:
134
+ started = int(time.time() * 1000)
135
+ resp = await broker.place_order(
136
+ symbol,
137
+ direction=direction,
138
+ order_type=norm_type,
139
+ price=price,
140
+ volume=volume,
141
+ margin_mode=margin_mode,
142
+ product_type=product_type,
143
+ margin_coin=margin_coin,
144
+ )
145
+ latency = int(time.time() * 1000) - started
146
+ print(f"下单延迟 {latency} ms")
147
+
148
+ order_id = resp.get("orderId")
149
+
150
+ if not order_id:
151
+ raise RuntimeError(f"place_order 返回缺少 order_id: {resp}")
152
+ while True:
153
+ change = await stream.__anext__()
154
+
155
+ if change.data.get("orderId") == order_id:
156
+ order = change.data
157
+ print(change.operation)
158
+ if change.operation == "delete":
159
+ return change.data
160
+ except TimeoutError:
161
+ pass
162
+
163
+ for i in range(3):
164
+ try:
165
+ await broker.cancel_order(order_id, symbol=symbol, margin_mode='crossed')
166
+ break
167
+ except Exception as e:
168
+ print(f"撤单异常: {e}")
169
+ await asyncio.sleep(1.0)
170
+
171
+ return order
172
+
173
+ async def test_order_sync_polling():
174
+ async with pybotters.Client(apis="./apis.json") as client:
175
+ bg = Bitget(client)
176
+ await bg.__aenter__()
177
+ await bg.sub_personal()
178
+
179
+ result = await order_sync_polling(
180
+ bg,
181
+ symbol="SOLUSDT",
182
+ direction="long",
183
+ order_type="limit_gtc",
184
+ price=185,
185
+ volume=0.1,
186
+ window_sec=5.0,
187
+ grace_sec=5.0,
188
+ )
189
+ print(result)
190
+
191
+
192
+ if __name__ == "__main__":
193
+ asyncio.run(test_order_sync_polling())
@@ -169,6 +169,7 @@ async def order_sync_polling(
169
169
  )
170
170
 
171
171
  latency = int(time.time() * 1000) - started
172
+ print(resp)
172
173
 
173
174
  order_id = resp.get("orderSysID")
174
175
  # print(f"下单延迟 {latency} ms, 订单号: {order_id}")
@@ -188,7 +189,7 @@ async def order_sync_polling(
188
189
  "order_id": order_id,
189
190
  "trade_count": 1,
190
191
  "volume": volume,
191
- "avg_price": float(resp.get('openPrice', 0)),
192
+ "avg_price": float(resp.get('tradePrice', 0)),
192
193
  "turnover": float(resp.get('turnover', 0)),
193
194
  "fee": float(resp.get('fee', 0)),
194
195
  "position_id": position_id,
@@ -251,7 +252,7 @@ async def test_order_sync_polling():
251
252
  snapshot = await order_sync_polling(
252
253
  lb,
253
254
  symbol="SOLUSDT",
254
- direction="buy",
255
+ direction="sell",
255
256
  order_type="market",
256
257
  price=bid0,
257
258
  volume=0.03,
@@ -259,26 +260,27 @@ async def test_order_sync_polling():
259
260
  grace_sec=1,
260
261
  poll_interval=1
261
262
  )
263
+ print(snapshot)
262
264
 
263
- position_id = snapshot.get("position_id")
264
- if not position_id:
265
- print('没有 position_id')
265
+ # position_id = snapshot.get("position_id")
266
+ # if not position_id:
267
+ # print('没有 position_id')
266
268
 
267
- position = None
268
- for _ in range(5):
269
- await lb.update("position")
270
- position = lb.store.position.get({"position_id": position_id})
271
- if position:
272
- break
273
- await asyncio.sleep(0.1)
269
+ # position = None
270
+ # for _ in range(5):
271
+ # await lb.update("position")
272
+ # position = lb.store.position.get({"position_id": position_id})
273
+ # if position:
274
+ # break
275
+ # await asyncio.sleep(0.1)
274
276
 
275
- print(position)
277
+ # print(position)
276
278
 
277
279
  async def test_query_order():
278
280
  async with pybotters.Client(apis='./apis.json') as client:
279
281
  async with Lbank(client) as lb:
280
- res = await lb.query_order("1000633284579483")
282
+ res = await lb.query_order("1000633355403954")
281
283
  print(res)
282
284
 
283
285
  if __name__ == "__main__":
284
- asyncio.run(test_update())
286
+ asyncio.run(test_order_sync_polling())