hyperquant 0.74__tar.gz → 0.76__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 (48) hide show
  1. {hyperquant-0.74 → hyperquant-0.76}/PKG-INFO +1 -1
  2. {hyperquant-0.74 → hyperquant-0.76}/pyproject.toml +1 -1
  3. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/lbank.py +26 -4
  4. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/models/lbank.py +8 -4
  5. {hyperquant-0.74 → hyperquant-0.76}/tests/test_lbank.py +79 -109
  6. {hyperquant-0.74 → hyperquant-0.76}/uv.lock +1 -1
  7. {hyperquant-0.74 → hyperquant-0.76}/.gitignore +0 -0
  8. {hyperquant-0.74 → hyperquant-0.76}/.python-version +0 -0
  9. {hyperquant-0.74 → hyperquant-0.76}/README.md +0 -0
  10. {hyperquant-0.74 → hyperquant-0.76}/apis.json +0 -0
  11. {hyperquant-0.74 → hyperquant-0.76}/data/alpine_smoke.log +0 -0
  12. {hyperquant-0.74 → hyperquant-0.76}/data/logs/notikit.log +0 -0
  13. {hyperquant-0.74 → hyperquant-0.76}/data/logs/test_order_sync.log +0 -0
  14. {hyperquant-0.74 → hyperquant-0.76}/data/records_swap.csv +0 -0
  15. {hyperquant-0.74 → hyperquant-0.76}/data/records_swapc.csv +0 -0
  16. {hyperquant-0.74 → hyperquant-0.76}/doc/lbank.md +0 -0
  17. {hyperquant-0.74 → hyperquant-0.76}/pub.sh +0 -0
  18. {hyperquant-0.74 → hyperquant-0.76}/requirements-dev.lock +0 -0
  19. {hyperquant-0.74 → hyperquant-0.76}/requirements.lock +0 -0
  20. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/__init__.py +0 -0
  21. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/auth.py +0 -0
  22. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/bitget.py +0 -0
  23. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/edgex.py +0 -0
  24. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/hyperliquid.py +0 -0
  25. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  26. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/lib/hpstore.py +0 -0
  27. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  28. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/lib/util.py +0 -0
  29. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/models/bitget.py +0 -0
  30. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/models/edgex.py +0 -0
  31. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  32. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/models/ourbit.py +0 -0
  33. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/ourbit.py +0 -0
  34. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/broker/ws.py +0 -0
  35. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/core.py +0 -0
  36. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/datavison/_util.py +0 -0
  37. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/datavison/binance.py +0 -0
  38. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/datavison/coinglass.py +0 -0
  39. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/datavison/okx.py +0 -0
  40. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/db.py +0 -0
  41. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/draw.py +0 -0
  42. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/logkit.py +0 -0
  43. {hyperquant-0.74 → hyperquant-0.76}/src/hyperquant/notikit.py +0 -0
  44. {hyperquant-0.74 → hyperquant-0.76}/tests/test_bitget.py +0 -0
  45. {hyperquant-0.74 → hyperquant-0.76}/tests/test_draw.py +0 -0
  46. {hyperquant-0.74 → hyperquant-0.76}/tests/test_edgex.py +0 -0
  47. {hyperquant-0.74 → hyperquant-0.76}/tests/test_ourbit.py +0 -0
  48. {hyperquant-0.74 → hyperquant-0.76}/tests/tmp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.74
3
+ Version: 0.76
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.74"
3
+ version = "0.76"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -223,7 +223,31 @@ class Lbank:
223
223
  page_index: int = 1,
224
224
  page_size: int = 20,
225
225
  ) -> dict[str, Any]:
226
- """Aggregate trade executions to summarize overall order statistics."""
226
+ """
227
+ 返回值示例:
228
+
229
+ .. code:: json
230
+
231
+ {
232
+ "order_id": "1000632478428573",
233
+ "instrument_id": "SOLUSDT",
234
+ "position_id": "1000632478428573",
235
+ "direction": "0",
236
+ "offset_flag": "0",
237
+ "trade_time": 1760123456,
238
+ "avg_price": 182.5,
239
+ "volume": 0.03,
240
+ "turnover": 5.475,
241
+ "fee": 0.003285,
242
+ "trade_count": 1
243
+ }
244
+
245
+ 如果没有订单成交返回
246
+ {
247
+ "order_id": "1000632478428573",
248
+ "trade_count": 0
249
+ }
250
+ """
227
251
 
228
252
  if not order_id:
229
253
  raise ValueError("order_id is required to query order statistics")
@@ -440,9 +464,7 @@ class Lbank:
440
464
  # logger.warning("Price is ignored for market orders")
441
465
  pass
442
466
 
443
- # if client_order_id:
444
- # payload["LocalID"] = client_order_id
445
- print(payload)
467
+
446
468
  res = await self.client.post(
447
469
  f"{self.front_api}/cfd/cff/v1/SendOrderInsert",
448
470
  json=payload,
@@ -274,10 +274,14 @@ class Position(DataStore):
274
274
  bus_id = entry.get("BusinessNo")
275
275
  if not position_id:
276
276
  return None
277
-
278
- direction_code = str(entry.get("PosiDirection")) if entry.get("PosiDirection") is not None else None
279
- side = self._POS_DIRECTION_MAP.get(direction_code, direction_code)
280
-
277
+
278
+ q = float(entry.get("Position", 0))
279
+ side = "net"
280
+ if q > 0:
281
+ side = "long"
282
+ elif q < 0:
283
+ side = "short"
284
+
281
285
  return {
282
286
  "position_id": position_id,
283
287
  "bus_id": bus_id,
@@ -148,44 +148,17 @@ async def order_sync_polling(
148
148
  volume: float | None = None,
149
149
  window_sec: float = 5.0,
150
150
  grace_sec: float = 5.0,
151
- poll_interval: float = 0.5,
152
- ) -> Union[dict, None]:
151
+ poll_interval: float = 0.5
152
+ ) :
153
+
153
154
  """
154
- 由于 LBank 暂无订单推送,这里通过 REST ``orders`` 与 ``position`` 查询实现订单同步,可在不同状态下返回仓位快照。
155
-
156
- - window_sec: 主轮询窗口,订单若持续存在则触发撤单流程;
157
- - grace_sec: 撤单后的额外等待窗口;
158
- - 返回值示例:
159
- .. code:: json
160
-
161
- {
162
- "position_id": "1000633222380983",
163
- "bus_id": "1001770970175249",
164
- "symbol": "SOLUSDT",
165
- "side": "long",
166
- "quantity": "0.06",
167
- "available": "0.0",
168
- "avg_price": "183.62",
169
- "entry_price": "183.62",
170
- "leverage": "100.0",
171
- "liquidation_price": "0",
172
- "margin_used": "0.110175",
173
- "unrealized_pnl": "0.0",
174
- "realized_pnl": "0.0",
175
- "update_time": "1760195121",
176
- "insert_time": "1758806193"
177
- }
155
+ 返回
156
+ {'order_id': '1000633291976722', 'instrument_id': 'SOLUSDT', 'position_id': '1000633291830781', 'direction': '1', 'offset_flag': '0', 'trade_time': 1760203736, 'avg_price': 183.95000000000002, 'volume': 0.03, 'turnover': 5.5185, 'fee': 0.0033111, 'trade_count': 1}
157
+ 或者
158
+ {'order_id': '1000633291976722', 'trade_count': 0}
178
159
  """
179
160
 
180
161
  norm_type = order_type.lower()
181
- if norm_type not in {"market", "limit_gtc", "limit_ioc"}:
182
- raise ValueError(f"unsupported order_type: {order_type}")
183
-
184
- if norm_type != "market" and price is None:
185
- raise ValueError("price is required for limit orders")
186
- if volume is None:
187
- raise ValueError("volume is required for LBank orders")
188
-
189
162
  started = int(time.time() * 1000)
190
163
  resp = await broker.place_order(
191
164
  symbol,
@@ -196,90 +169,72 @@ async def order_sync_polling(
196
169
  )
197
170
 
198
171
  latency = int(time.time() * 1000) - started
199
- print(f"下单延迟 {latency} ms")
200
172
 
201
- order_id = (
202
- resp.get("orderSysID")
203
- or resp.get("OrderSysID")
204
- or resp.get("order_id")
205
- or resp.get("orderId")
206
- )
173
+ order_id = resp.get("orderSysID")
174
+ # print(f"下单延迟 {latency} ms, 订单号: {order_id}")
175
+
207
176
  if not order_id:
208
177
  raise RuntimeError(f"place_order 返回缺少 order_id: {resp}")
209
178
 
210
- position_id = (
211
- resp.get("PositionID")
212
- or resp.get("positionID")
213
- or resp.get("positionId")
214
- )
215
179
 
216
- async def _refresh_position(*, allow_symbol_fallback: bool) -> dict | None:
217
- try:
218
- await broker.update("position")
219
- except ContentTypeError:
220
- await asyncio.sleep(poll_interval)
221
- return None
222
- if position_id:
223
- pos = broker.store.position.get({"position_id": position_id})
224
- if pos and pos.get("avg_price") is not None:
225
- return pos
226
- if allow_symbol_fallback:
227
- candidates = broker.store.position.find({"symbol": symbol}) or []
228
- if candidates:
229
- pos = candidates[0]
230
- if pos and pos.get("avg_price") is not None:
231
- return pos
232
- return None
233
-
234
- async def _poll_orders(timeout_sec: float, *, allow_symbol_fallback: bool) -> dict | None:
235
- nonlocal position_id
236
- order_seen = False
180
+ position_id = resp.get("positionID")
181
+ if not position_id:
182
+ raise RuntimeError(f"place_order 返回缺少 position_id: {resp}")
183
+
184
+ trade_resp = None
185
+
186
+ if 'volumeRemain' in resp and float(resp['volumeRemain']) == 0 and float(resp.get('turnover', 0)) != 0:
187
+ return {
188
+ "order_id": order_id,
189
+ "trade_count": 1,
190
+ "volume": volume,
191
+ "avg_price": float(resp.get('openPrice', 0)),
192
+ "turnover": float(resp.get('turnover', 0)),
193
+ "fee": float(resp.get('fee', 0)),
194
+ "position_id": position_id,
195
+ "direction": direction,
196
+ "offset_flag": resp.get('offsetFlag', '0'),
197
+ "trade_time": resp.get('updateTime'),
198
+ }
199
+
200
+ if 'ioc' in norm_type:
201
+ return {
202
+ "order_id": order_id,
203
+ "trade_count": 0
204
+ }
205
+
206
+ async def _poll_orders(timeout_sec: float) -> dict | None:
207
+ nonlocal trade_resp
237
208
  async with asyncio.timeout(timeout_sec):
238
209
  while True:
239
- try:
240
- await broker.update("orders")
241
- except ContentTypeError:
242
- await asyncio.sleep(poll_interval)
243
- continue
244
- snapshot = broker.store.orders.get({"order_id": order_id})
245
- if snapshot is None:
246
- if not order_seen and not allow_symbol_fallback:
247
- await asyncio.sleep(poll_interval)
248
- continue
249
- pos_snapshot = await _refresh_position(allow_symbol_fallback=allow_symbol_fallback)
250
- if pos_snapshot is not None:
251
- return pos_snapshot
252
- await asyncio.sleep(poll_interval)
253
- continue
254
- order_seen = True
255
- position_id = position_id or snapshot.get("position_id")
210
+ trade_resp = await broker.query_order(order_id)
211
+ traded_volume = float(trade_resp.get("volume", 0))
212
+ if traded_volume == volume:
213
+ return
256
214
  await asyncio.sleep(poll_interval)
257
215
 
258
216
  try:
259
- polled_position = await _poll_orders(window_sec, allow_symbol_fallback=False)
260
- if polled_position:
261
- return polled_position
217
+ await _poll_orders(window_sec)
218
+
262
219
  except TimeoutError:
263
220
  pass
264
221
 
265
- for _attempt in range(3):
266
- try:
267
- await broker.cancel_order(order_id)
268
- break
269
- except Exception as e:
270
- if '不存在' in str(e):
222
+ for _attempt in range(3):
223
+ try:
224
+ await broker.cancel_order(order_id)
271
225
  break
272
- else:
273
- print(f'撤单失败, 重试 {_attempt+1}/3: {e}')
274
- try:
275
- polled_position = await _poll_orders(grace_sec, allow_symbol_fallback=True)
276
- if polled_position:
277
- return polled_position
278
- except TimeoutError:
279
- pass
226
+ except Exception as e:
227
+ if '不存在' in str(e):
228
+ break
229
+ else:
230
+ print(f'撤单失败, 重试 {_attempt+1}/3: {e}')
231
+
232
+ try:
233
+ await _poll_orders(window_sec)
234
+ except TimeoutError:
235
+ pass
280
236
 
281
- # 超过宽限期仍没有仓位变更,尝试最后一次使用 symbol 兜底
282
- return await _refresh_position(allow_symbol_fallback=True)
237
+ return trade_resp
283
238
 
284
239
 
285
240
  async def test_order_sync_polling():
@@ -288,27 +243,42 @@ async def test_order_sync_polling():
288
243
  await lb.sub_orderbook(["SOLUSDT"], limit=1)
289
244
  await lb.store.book.wait()
290
245
  bid0 = float(lb.store.book.find({"s": "SOLUSDT", 'S': 'b'})[0]['p'])
291
- bid0 = bid0 - 0.03
246
+ # bid0 = bid0 - 0.01
247
+ bid0 = bid0 + 1
248
+ # bid0 = bid0 - 1
292
249
  print(bid0)
293
250
 
294
- result = await order_sync_polling(
251
+ snapshot = await order_sync_polling(
295
252
  lb,
296
253
  symbol="SOLUSDT",
297
254
  direction="buy",
298
- order_type="limit_GTC",
255
+ order_type="market",
299
256
  price=bid0,
300
257
  volume=0.03,
301
258
  window_sec=3.0,
302
259
  grace_sec=1,
303
260
  poll_interval=1
304
261
  )
305
- print(result)
262
+
263
+ position_id = snapshot.get("position_id")
264
+ if not position_id:
265
+ print('没有 position_id')
266
+
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)
274
+
275
+ print(position)
306
276
 
307
277
  async def test_query_order():
308
278
  async with pybotters.Client(apis='./apis.json') as client:
309
279
  async with Lbank(client) as lb:
310
- res = await lb.query_order("1000633129818889")
280
+ res = await lb.query_order("1000633284579483")
311
281
  print(res)
312
282
 
313
283
  if __name__ == "__main__":
314
- asyncio.run(test_order_sync_polling())
284
+ asyncio.run(test_update())
@@ -530,7 +530,7 @@ wheels = [
530
530
 
531
531
  [[package]]
532
532
  name = "hyperquant"
533
- version = "0.73"
533
+ version = "0.76"
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
File without changes
File without changes
File without changes
File without changes