hyperquant 0.87__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.87 → hyperquant-0.89}/PKG-INFO +1 -1
  2. {hyperquant-0.87 → hyperquant-0.89}/pyproject.toml +1 -1
  3. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/coinw.py +48 -12
  4. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/models/coinw.py +58 -32
  5. {hyperquant-0.87 → hyperquant-0.89}/tests/test_coinw.py +76 -33
  6. {hyperquant-0.87 → hyperquant-0.89}/uv.lock +1 -1
  7. {hyperquant-0.87 → hyperquant-0.89}/.gitignore +0 -0
  8. {hyperquant-0.87 → hyperquant-0.89}/.python-version +0 -0
  9. {hyperquant-0.87 → hyperquant-0.89}/README.md +0 -0
  10. {hyperquant-0.87 → hyperquant-0.89}/apis.json +0 -0
  11. {hyperquant-0.87 → hyperquant-0.89}/data/alpine_smoke.log +0 -0
  12. {hyperquant-0.87 → hyperquant-0.89}/data/logs/notikit.log +0 -0
  13. {hyperquant-0.87 → hyperquant-0.89}/data/logs/test_order_sync.log +0 -0
  14. {hyperquant-0.87 → hyperquant-0.89}/data/records_swap.csv +0 -0
  15. {hyperquant-0.87 → hyperquant-0.89}/data/records_swapc.csv +0 -0
  16. {hyperquant-0.87 → hyperquant-0.89}/doc/lbank.md +0 -0
  17. {hyperquant-0.87 → hyperquant-0.89}/pub.sh +0 -0
  18. {hyperquant-0.87 → hyperquant-0.89}/requirements-dev.lock +0 -0
  19. {hyperquant-0.87 → hyperquant-0.89}/requirements.lock +0 -0
  20. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/__init__.py +0 -0
  21. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/auth.py +0 -0
  22. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/bitget.py +0 -0
  23. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/edgex.py +0 -0
  24. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/hyperliquid.py +0 -0
  25. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/lbank.py +0 -0
  26. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  27. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/lib/hpstore.py +0 -0
  28. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  29. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/lib/util.py +0 -0
  30. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/models/bitget.py +0 -0
  31. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/models/edgex.py +0 -0
  32. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  33. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/models/lbank.py +0 -0
  34. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/models/ourbit.py +0 -0
  35. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/ourbit.py +0 -0
  36. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/broker/ws.py +0 -0
  37. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/core.py +0 -0
  38. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/datavison/_util.py +0 -0
  39. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/datavison/binance.py +0 -0
  40. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/datavison/coinglass.py +0 -0
  41. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/datavison/okx.py +0 -0
  42. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/db.py +0 -0
  43. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/draw.py +0 -0
  44. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/logkit.py +0 -0
  45. {hyperquant-0.87 → hyperquant-0.89}/src/hyperquant/notikit.py +0 -0
  46. {hyperquant-0.87 → hyperquant-0.89}/tests/test_bitget.py +0 -0
  47. {hyperquant-0.87 → hyperquant-0.89}/tests/test_draw.py +0 -0
  48. {hyperquant-0.87 → hyperquant-0.89}/tests/test_edgex.py +0 -0
  49. {hyperquant-0.87 → hyperquant-0.89}/tests/test_lbank.py +0 -0
  50. {hyperquant-0.87 → hyperquant-0.89}/tests/test_ourbit.py +0 -0
  51. {hyperquant-0.87 → 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.87
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.87"
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,
@@ -292,7 +328,7 @@ class Coinw:
292
328
  ws_app = await self._ensure_private_ws()
293
329
  payloads = [
294
330
  {"event": "sub", "params": {"biz": "futures", "type": "order"}},
295
- {"event": "sub", "params": {"biz": "futures", "type": "position"}},
331
+ # {"event": "sub", "params": {"biz": "futures", "type": "position"}},
296
332
  {"event": "sub", "params": {"biz": "futures", "type": "position_change"}},
297
333
  {"event": "sub", "params": {"biz": "futures", "type": "assets"}},
298
334
  ]
@@ -280,16 +280,8 @@ class Orders(DataStore):
280
280
  class Position(DataStore):
281
281
  """CoinW 当前持仓数据存储。"""
282
282
 
283
- _KEYS = ["id"]
283
+ _KEYS = ["openId"]
284
284
 
285
- @staticmethod
286
- def _normalize(entry: dict[str, Any]) -> dict[str, Any] | None:
287
- position_id = entry.get("id")
288
- if position_id is None:
289
- return None
290
- normalized = dict(entry)
291
- normalized["id"] = str(position_id)
292
- return normalized
293
285
 
294
286
  def _onresponse(self, data: Any) -> None:
295
287
  payload = []
@@ -302,9 +294,8 @@ class Position(DataStore):
302
294
  for entry in payload or []:
303
295
  if not isinstance(entry, dict):
304
296
  continue
305
- normalized = self._normalize(entry)
306
- if normalized:
307
- items.append(normalized)
297
+ entry['openId'] = str(entry.get("id"))
298
+ items.append(entry)
308
299
 
309
300
  self._clear()
310
301
  if items:
@@ -312,6 +303,7 @@ class Position(DataStore):
312
303
 
313
304
  def _on_message(self, msg: dict[str, Any]) -> None:
314
305
  data = msg.get("data")
306
+
315
307
  if isinstance(data, dict) and data.get("result") is not None:
316
308
  return
317
309
 
@@ -325,26 +317,28 @@ class Position(DataStore):
325
317
  return
326
318
 
327
319
  to_insert: list[dict[str, Any]] = []
320
+ to_update: list[dict[str, Any]] = []
328
321
  to_delete: list[dict[str, Any]] = []
329
322
 
330
323
  for entry in entries:
331
324
  if not isinstance(entry, dict):
332
325
  continue
333
- normalized = self._normalize(entry)
334
- if not normalized:
335
- continue
326
+ normalized = entry
336
327
 
337
- status = normalized.get("status")
338
- normalized_status = str(status).lower() if status is not None else ""
339
- remove = normalized_status in {"close", "closed", "1", "2"}
340
328
 
341
- query = {"id": normalized["id"]}
342
- to_delete.append(query)
343
- if not remove:
329
+ if normalized.get("status") == 'close':
330
+ to_delete.append(normalized)
331
+ continue
332
+
333
+ if self.find(normalized):
334
+ to_update.append(normalized)
335
+ else:
344
336
  to_insert.append(normalized)
345
337
 
346
338
  if to_delete:
347
339
  self._delete(to_delete)
340
+ if to_update:
341
+ self._update(to_update)
348
342
  if to_insert:
349
343
  self._insert(to_insert)
350
344
 
@@ -449,11 +443,12 @@ class CoinwFuturesDataStore(DataStoreCollection):
449
443
 
450
444
  def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
451
445
  msg_type = msg.get("type")
446
+ # print(msg)
452
447
  if msg_type == "depth":
453
448
  self.book._on_message(msg)
454
449
  elif msg_type == "order":
455
450
  self.orders._on_message(msg)
456
- elif msg_type == "position":
451
+ elif msg_type == "position" or msg_type == "position_change":
457
452
  self.position._on_message(msg)
458
453
  elif msg_type == "assets":
459
454
  self.balance._on_message(msg)
@@ -470,7 +465,7 @@ class CoinwFuturesDataStore(DataStoreCollection):
470
465
  self.ticker._onresponse(data)
471
466
  elif res.url.path == "/v1/perpum/orders/open":
472
467
  self.orders._onresponse(data)
473
- elif res.url.path == "/v1/perpum/positions":
468
+ elif res.url.path == "/v1/perpum/positions/all":
474
469
  self.position._onresponse(data)
475
470
  elif res.url.path == "/v1/perpum/account/getUserAssets":
476
471
  self.balance._onresponse(data)
@@ -655,17 +650,48 @@ class CoinwFuturesDataStore(DataStoreCollection):
655
650
  - REST: ``GET /v1/perpum/positions``
656
651
  - WebSocket: ``type == "position"``
657
652
 
658
- 数据结构(节选)::
653
+ .. code:: json
659
654
 
660
655
  {
661
- "id": 2435521222631982507,
662
- "instrument": "BTC",
663
- "direction": "short",
664
- "openPrice": 88230.5,
665
- "currentPiece": 1,
666
- "profitUnreal": 0.0086,
667
- "status": "open"
668
- }
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
+ }
669
695
  """
670
696
 
671
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")
@@ -67,25 +70,66 @@ async def test_place_cancel() -> None:
67
70
  async with pybotters.Client(apis="./apis.json") as client:
68
71
  async with Coinw(client) as cw:
69
72
  start = time.time()
70
- order = await cw.place_order(
71
- instrument="SOL",
72
- direction="long",
73
- quantity_unit=0,
74
- leverage=25,
75
- quantity=0.8,
73
+ # order = await cw.place_order(
74
+ # instrument="SOL",
75
+ # direction="long",
76
+ # quantity_unit=1,
77
+ # leverage=25,
78
+ # quantity=2,
79
+ # position_type="plan",
80
+ # price=175,
81
+ # position_model="cross",
82
+ # )
83
+ # latency = time.time() - start
84
+ # print(f'下单延迟: {latency*1000:.2f} ms')
85
+ # order_id = order.get("value") or order.get("data")
86
+ # print("place_order response:", order)
87
+ # if order_id:
88
+ # await asyncio.sleep(1)
89
+ # cancel_resp = await cw.cancel_order(order_id)
90
+ # print("cancel_order response:", cancel_resp)
91
+
92
+ # {'instrument': 'JUP', 'direction': 'short', 'leverage': 50, 'quantityUnit': 1, 'quantity': '57', 'positionModel': 1, 'positionType': 'plan', 'openPrice': 0.3527}
93
+ await cw.place_order(
94
+ instrument="JUP",
95
+ direction="short",
96
+ quantity_unit=1,
97
+ leverage=50,
98
+ quantity="57",
76
99
  position_type="plan",
77
- price=175,
78
- position_model="cross",
100
+ price=0.3527,
101
+ position_model=1,
79
102
  )
80
- latency = time.time() - start
81
- print(f'下单延迟: {latency*1000:.2f} ms')
82
- order_id = order.get("value") or order.get("data")
83
- print("place_order response:", order)
84
- if order_id:
85
- await asyncio.sleep(1)
86
- cancel_resp = await cw.cancel_order(order_id)
87
- print("cancel_order response:", cancel_resp)
88
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')
89
133
 
90
134
  async def test_place_web() -> None:
91
135
  """Use the web interface to place an order (requires device/token)."""
@@ -134,7 +178,7 @@ async def order_sync_polling(
134
178
 
135
179
  try:
136
180
  async with asyncio.timeout(window_sec):
137
- with broker.store.orders.watch() as stream:
181
+ with broker.store.position.watch() as stream:
138
182
  started = int(time.time() * 1000)
139
183
  resp = await broker.place_order(
140
184
  instrument=instrument,
@@ -165,16 +209,13 @@ async def order_sync_polling(
165
209
  order_id = str(raw_id)
166
210
 
167
211
  async for change in stream:
168
-
212
+ print(change.data)
169
213
  data = change.data or {}
170
- if str(data.get("id")) != order_id:
214
+ if str(data.get("orderId")) != order_id:
171
215
  continue
172
216
  snapshot = data
173
- status = str(data.get("orderStatus") or data.get("status") or "").lower()
174
- print(change.operation )
175
- if change.operation == "delete":
176
- return change.source
177
- if status in {"finish", "cancel", "canceled"}:
217
+ order_finish = data.get('orderStatus') == 'finish'
218
+ if order_finish:
178
219
  return snapshot
179
220
 
180
221
  except TimeoutError:
@@ -196,9 +237,12 @@ async def test_subp() -> None:
196
237
  async with pybotters.Client(apis="./apis.json") as client:
197
238
  async with Coinw(client) as cw:
198
239
  await cw.sub_personal()
199
- with cw.store.orders.watch() as watcher:
200
- async for change in watcher:
201
- print(change)
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')
202
246
 
203
247
 
204
248
  async def test_order_sync_polling() -> None:
@@ -209,9 +253,9 @@ async def test_order_sync_polling() -> None:
209
253
  print("Placing order with order_sync_polling...")
210
254
  result = await order_sync_polling(
211
255
  broker=cw,
212
- instrument="SOL",
256
+ instrument="JUP",
213
257
  direction="long",
214
- leverage=25,
258
+ leverage=10,
215
259
  quantity=1,
216
260
  quantity_unit=1,
217
261
  position_model="cross",
@@ -222,6 +266,5 @@ async def test_order_sync_polling() -> None:
222
266
  )
223
267
  print("order_sync_polling result:", result)
224
268
 
225
-
226
269
  if __name__ == "__main__":
227
- asyncio.run(test_sub_orderbook())
270
+ asyncio.run(test_close_position())
@@ -662,7 +662,7 @@ wheels = [
662
662
 
663
663
  [[package]]
664
664
  name = "hyperquant"
665
- version = "0.86"
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