hyperquant 0.88__tar.gz → 0.89__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 (51) hide show
  1. {hyperquant-0.88 → hyperquant-0.89}/PKG-INFO +1 -1
  2. {hyperquant-0.88 → hyperquant-0.89}/pyproject.toml +1 -1
  3. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/coinw.py +47 -11
  4. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/coinw.py +49 -34
  5. {hyperquant-0.88 → hyperquant-0.89}/tests/test_coinw.py +49 -17
  6. {hyperquant-0.88 → hyperquant-0.89}/uv.lock +1 -1
  7. {hyperquant-0.88 → hyperquant-0.89}/.gitignore +0 -0
  8. {hyperquant-0.88 → hyperquant-0.89}/.python-version +0 -0
  9. {hyperquant-0.88 → hyperquant-0.89}/README.md +0 -0
  10. {hyperquant-0.88 → hyperquant-0.89}/apis.json +0 -0
  11. {hyperquant-0.88 → hyperquant-0.89}/data/alpine_smoke.log +0 -0
  12. {hyperquant-0.88 → hyperquant-0.89}/data/logs/notikit.log +0 -0
  13. {hyperquant-0.88 → hyperquant-0.89}/data/logs/test_order_sync.log +0 -0
  14. {hyperquant-0.88 → hyperquant-0.89}/data/records_swap.csv +0 -0
  15. {hyperquant-0.88 → hyperquant-0.89}/data/records_swapc.csv +0 -0
  16. {hyperquant-0.88 → hyperquant-0.89}/doc/lbank.md +0 -0
  17. {hyperquant-0.88 → hyperquant-0.89}/pub.sh +0 -0
  18. {hyperquant-0.88 → hyperquant-0.89}/requirements-dev.lock +0 -0
  19. {hyperquant-0.88 → hyperquant-0.89}/requirements.lock +0 -0
  20. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/__init__.py +0 -0
  21. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/auth.py +0 -0
  22. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/bitget.py +0 -0
  23. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/edgex.py +0 -0
  24. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/hyperliquid.py +0 -0
  25. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lbank.py +0 -0
  26. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  27. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lib/hpstore.py +0 -0
  28. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  29. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lib/util.py +0 -0
  30. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/bitget.py +0 -0
  31. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/edgex.py +0 -0
  32. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  33. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/lbank.py +0 -0
  34. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/ourbit.py +0 -0
  35. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/ourbit.py +0 -0
  36. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/ws.py +0 -0
  37. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/core.py +0 -0
  38. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/datavison/_util.py +0 -0
  39. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/datavison/binance.py +0 -0
  40. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/datavison/coinglass.py +0 -0
  41. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/datavison/okx.py +0 -0
  42. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/db.py +0 -0
  43. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/draw.py +0 -0
  44. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/logkit.py +0 -0
  45. {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/notikit.py +0 -0
  46. {hyperquant-0.88 → hyperquant-0.89}/tests/test_bitget.py +0 -0
  47. {hyperquant-0.88 → hyperquant-0.89}/tests/test_draw.py +0 -0
  48. {hyperquant-0.88 → hyperquant-0.89}/tests/test_edgex.py +0 -0
  49. {hyperquant-0.88 → hyperquant-0.89}/tests/test_lbank.py +0 -0
  50. {hyperquant-0.88 → hyperquant-0.89}/tests/test_ourbit.py +0 -0
  51. {hyperquant-0.88 → hyperquant-0.89}/tests/tmp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.88
3
+ Version: 0.89
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.88"
3
+ version = "0.89"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -60,11 +60,10 @@ class Coinw:
60
60
  "all",
61
61
  ] = "all",
62
62
  *,
63
- instrument: str | None = None,
64
63
  position_type: Literal["execute", "plan", "planTrigger"] = "execute",
65
64
  page: int | None = None,
66
65
  page_size: int | None = None,
67
- open_ids: str | None = None,
66
+ instrument: str | None = None,
68
67
  ) -> None:
69
68
  """刷新本地缓存,使用 CoinW REST API。
70
69
 
@@ -108,16 +107,8 @@ class Coinw:
108
107
  )
109
108
 
110
109
  if include_position:
111
- if not instrument:
112
- raise ValueError("instrument is required when updating positions")
113
- params = {"instrument": instrument}
114
- if open_ids:
115
- params["openIds"] = open_ids
116
110
  requests.append(
117
- self.client.get(
118
- f"{self.rest_api}/v1/perpum/positions",
119
- params=params,
120
- )
111
+ self.client.get(f"{self.rest_api}/v1/perpum/positions/all")
121
112
  )
122
113
 
123
114
  if include_balance:
@@ -186,6 +177,51 @@ class Coinw:
186
177
  data = await res.json()
187
178
  return self._ensure_ok("place_order", data)
188
179
 
180
+ async def close_position(
181
+ self,
182
+ open_id: str | int,
183
+ *,
184
+ position_type: Literal["plan", "planTrigger", "execute"] = "plan",
185
+ close_num: str | float | int | None = None,
186
+ close_rate: str | float | int | None = None,
187
+ order_price: str | float | None = None,
188
+ instrument: str | None = None,
189
+ ) -> dict[str, Any]:
190
+ """关闭单个仓位(``DELETE /v1/perpum/positions``)。
191
+
192
+ Params
193
+ ------
194
+ open_id: ``openId`` / 持仓唯一 ID。
195
+ position_type: 订单类型 ``plan`` / ``planTrigger`` / ``execute``。
196
+ close_num: 按合约数量平仓(与 ``close_rate`` 至少指定其一)。
197
+ close_rate: 按比例平仓(0-1)。
198
+ order_price: 限价平仓时指定价格。
199
+ instrument: 交易品种(部分情况下需要传入,例如限价单)。
200
+ """
201
+
202
+ if close_num is None and close_rate is None:
203
+ raise ValueError("close_num or close_rate must be provided")
204
+
205
+ payload: dict[str, Any] = {
206
+ "id": str(open_id),
207
+ "positionType": position_type,
208
+ }
209
+ if close_num is not None:
210
+ payload["closeNum"] = str(close_num)
211
+ if close_rate is not None:
212
+ payload["closeRate"] = str(close_rate)
213
+ if order_price is not None:
214
+ payload["orderPrice"] = str(order_price)
215
+ if instrument is not None:
216
+ payload["instrument"] = instrument
217
+
218
+ res = await self.client.delete(
219
+ f"{self.rest_api}/v1/perpum/positions",
220
+ data=payload,
221
+ )
222
+ data = await res.json()
223
+ return self._ensure_ok("close_position", data)
224
+
189
225
  async def place_order_web(
190
226
  self,
191
227
  instrument: str,
@@ -282,20 +282,6 @@ class Position(DataStore):
282
282
 
283
283
  _KEYS = ["openId"]
284
284
 
285
- @staticmethod
286
- def _normalize(entry: dict[str, Any]) -> dict[str, Any] | None:
287
- open_id = entry.get("openId")
288
- if open_id is None:
289
- return None
290
- normalized = dict(entry)
291
- normalized["openId"] = str(open_id)
292
- normalized["status"] = str(entry.get("status") or entry.get("orderStatus") or "").lower()
293
- normalized["is_closed"] = normalized["status"] in {"close", "closed", "finish"}
294
- normalized["currentPiece"] = str(entry.get("currentPiece")) if entry.get("currentPiece") is not None else None
295
- normalized["closedPiece"] = str(entry.get("closedPiece")) if entry.get("closedPiece") is not None else None
296
- normalized["quantity"] = str(entry.get("quantity")) if entry.get("quantity") is not None else None
297
- normalized["updatedDate"] = entry.get("updatedDate")
298
- return normalized
299
285
 
300
286
  def _onresponse(self, data: Any) -> None:
301
287
  payload = []
@@ -308,9 +294,8 @@ class Position(DataStore):
308
294
  for entry in payload or []:
309
295
  if not isinstance(entry, dict):
310
296
  continue
311
- normalized = self._normalize(entry)
312
- if normalized:
313
- items.append(normalized)
297
+ entry['openId'] = str(entry.get("id"))
298
+ items.append(entry)
314
299
 
315
300
  self._clear()
316
301
  if items:
@@ -318,6 +303,7 @@ class Position(DataStore):
318
303
 
319
304
  def _on_message(self, msg: dict[str, Any]) -> None:
320
305
  data = msg.get("data")
306
+
321
307
  if isinstance(data, dict) and data.get("result") is not None:
322
308
  return
323
309
 
@@ -337,17 +323,14 @@ class Position(DataStore):
337
323
  for entry in entries:
338
324
  if not isinstance(entry, dict):
339
325
  continue
340
- normalized = self._normalize(entry)
341
- if not normalized:
342
- continue
326
+ normalized = entry
343
327
 
344
- criteria = {"openId": normalized["openId"]}
345
328
 
346
- if normalized.get("is_closed"):
347
- to_delete.append(criteria)
329
+ if normalized.get("status") == 'close':
330
+ to_delete.append(normalized)
348
331
  continue
349
332
 
350
- if self.find(criteria):
333
+ if self.find(normalized):
351
334
  to_update.append(normalized)
352
335
  else:
353
336
  to_insert.append(normalized)
@@ -460,6 +443,7 @@ class CoinwFuturesDataStore(DataStoreCollection):
460
443
 
461
444
  def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
462
445
  msg_type = msg.get("type")
446
+ # print(msg)
463
447
  if msg_type == "depth":
464
448
  self.book._on_message(msg)
465
449
  elif msg_type == "order":
@@ -481,7 +465,7 @@ class CoinwFuturesDataStore(DataStoreCollection):
481
465
  self.ticker._onresponse(data)
482
466
  elif res.url.path == "/v1/perpum/orders/open":
483
467
  self.orders._onresponse(data)
484
- elif res.url.path == "/v1/perpum/positions":
468
+ elif res.url.path == "/v1/perpum/positions/all":
485
469
  self.position._onresponse(data)
486
470
  elif res.url.path == "/v1/perpum/account/getUserAssets":
487
471
  self.balance._onresponse(data)
@@ -666,17 +650,48 @@ class CoinwFuturesDataStore(DataStoreCollection):
666
650
  - REST: ``GET /v1/perpum/positions``
667
651
  - WebSocket: ``type == "position"``
668
652
 
669
- 数据结构(节选)::
653
+ .. code:: json
670
654
 
671
655
  {
672
- "id": 2435521222631982507,
673
- "instrument": "BTC",
674
- "direction": "short",
675
- "openPrice": 88230.5,
676
- "currentPiece": 1,
677
- "profitUnreal": 0.0086,
678
- "status": "open"
679
- }
656
+ "currentPiece": "0",
657
+ "isProfession": 0,
658
+ "leverage": "10",
659
+ "originalType": "execute",
660
+ "orderId": "33309059291614824",
661
+ "contractType": 1,
662
+ "openId": "2435521222638707873",
663
+ "fee": "0.00020724",
664
+ "openPrice": "0.3456",
665
+ "orderStatus": "finish",
666
+ "instrument": "JUP",
667
+ "quantityUnit": 1,
668
+ "source": "api",
669
+ "updatedDate": 1761192795412,
670
+ "positionModel": 1,
671
+ "feeRate": "0.0006",
672
+ "netProfit": "-0.00040724",
673
+ "baseSize": "1",
674
+ "quote": "usdt",
675
+ "liquidateBy": "manual",
676
+ "totalPiece": "1",
677
+ "orderPrice": "0",
678
+ "id": "23469279597150213",
679
+ "fundingSettle": "0",
680
+ "direction": "long",
681
+ "margin": "0.03435264",
682
+ "takerMaker": 1,
683
+ "indexPrice": "0.3455",
684
+ "quantity": "0.03456",
685
+ "userId": "1757458",
686
+ "closedPiece": "1",
687
+ "createdDate": 1761192793000,
688
+ "hedgeId": "23469279597150214",
689
+ "closePrice": "0.3454",
690
+ "positionMargin": "0.03435264",
691
+ "base": "jup",
692
+ "realPrice": "0.3454",
693
+ "status": "close"
694
+ }
680
695
  """
681
696
 
682
697
  return self._get("position", Position)
@@ -23,8 +23,11 @@ async def test_update_private() -> None:
23
23
  """Refresh private endpoints (requires ./apis.json with coinw credentials)."""
24
24
  async with pybotters.Client(apis="./apis.json") as client:
25
25
  async with Coinw(client) as cw:
26
- await cw.update("balance")
27
- print(cw.store.balance.find())
26
+ # await cw.update("balance")
27
+ # print(cw.store.balance.find())
28
+ await cw.update("position")
29
+ print(cw.store.position.find())
30
+
28
31
  # await cw.update("position", instrument="BTC")
29
32
  # print(cw.store.position.find())
30
33
  # await cw.update("orders", instrument="BTC")
@@ -98,6 +101,36 @@ async def test_place_cancel() -> None:
98
101
  position_model=1,
99
102
  )
100
103
 
104
+ async def test_close_position():
105
+ async with pybotters.Client(apis="./apis.json") as client:
106
+ async with Coinw(client) as cw:
107
+ # await cw.update('position')
108
+ await cw.sub_personal()
109
+ with cw.store.position.watch() as watcher:
110
+ # 2435521222638707402
111
+ resp = await cw.place_order(
112
+ instrument="JUP",
113
+ direction="long",
114
+ quantity_unit=1,
115
+ leverage=10,
116
+ quantity=1,
117
+ position_model='cross',
118
+ position_type='execute'
119
+ )
120
+ print("开仓响应:", resp)
121
+
122
+ await asyncio.sleep(2)
123
+
124
+ for position in cw.store.position.find():
125
+ open_id = position.get('openId')
126
+ print(f'关闭持仓: {open_id}')
127
+ resp = await cw.close_position(open_id, position_type='execute', close_num=1)
128
+ print(resp)
129
+
130
+ async for change in watcher:
131
+ print(change)
132
+ print('\n\n----\n\n')
133
+
101
134
  async def test_place_web() -> None:
102
135
  """Use the web interface to place an order (requires device/token)."""
103
136
  async with pybotters.Client(apis="./apis.json") as client:
@@ -145,7 +178,7 @@ async def order_sync_polling(
145
178
 
146
179
  try:
147
180
  async with asyncio.timeout(window_sec):
148
- with broker.store.orders.watch() as stream:
181
+ with broker.store.position.watch() as stream:
149
182
  started = int(time.time() * 1000)
150
183
  resp = await broker.place_order(
151
184
  instrument=instrument,
@@ -176,16 +209,13 @@ async def order_sync_polling(
176
209
  order_id = str(raw_id)
177
210
 
178
211
  async for change in stream:
179
-
212
+ print(change.data)
180
213
  data = change.data or {}
181
- if str(data.get("id")) != order_id:
214
+ if str(data.get("orderId")) != order_id:
182
215
  continue
183
216
  snapshot = data
184
- status = str(data.get("orderStatus") or data.get("status") or "").lower()
185
- print(change.operation )
186
- if change.operation == "delete":
187
- return change.source
188
- if status in {"finish", "cancel", "canceled"}:
217
+ order_finish = data.get('orderStatus') == 'finish'
218
+ if order_finish:
189
219
  return snapshot
190
220
 
191
221
  except TimeoutError:
@@ -207,10 +237,12 @@ async def test_subp() -> None:
207
237
  async with pybotters.Client(apis="./apis.json") as client:
208
238
  async with Coinw(client) as cw:
209
239
  await cw.sub_personal()
210
- with cw.store.position.watch() as watcher:
211
- async for change in watcher:
212
- print(change)
213
- print('\n\n----\n\n')
240
+ await asyncio.sleep(1.0)
241
+ print(cw.store.position.find())
242
+ # with cw.store.position.watch() as watcher:
243
+ # async for change in watcher:
244
+ # print(change)
245
+ # print('\n\n----\n\n')
214
246
 
215
247
 
216
248
  async def test_order_sync_polling() -> None:
@@ -221,9 +253,9 @@ async def test_order_sync_polling() -> None:
221
253
  print("Placing order with order_sync_polling...")
222
254
  result = await order_sync_polling(
223
255
  broker=cw,
224
- instrument="SOL",
256
+ instrument="JUP",
225
257
  direction="long",
226
- leverage=25,
258
+ leverage=10,
227
259
  quantity=1,
228
260
  quantity_unit=1,
229
261
  position_model="cross",
@@ -235,4 +267,4 @@ async def test_order_sync_polling() -> None:
235
267
  print("order_sync_polling result:", result)
236
268
 
237
269
  if __name__ == "__main__":
238
- asyncio.run(test_subp())
270
+ asyncio.run(test_close_position())
@@ -662,7 +662,7 @@ wheels = [
662
662
 
663
663
  [[package]]
664
664
  name = "hyperquant"
665
- version = "0.87"
665
+ version = "0.88"
666
666
  source = { editable = "." }
667
667
  dependencies = [
668
668
  { 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
File without changes