hyperquant 0.33__tar.gz → 0.35__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.33 → hyperquant-0.35}/PKG-INFO +1 -1
  2. {hyperquant-0.33 → hyperquant-0.35}/pyproject.toml +1 -1
  3. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/auth.py +5 -1
  4. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/models/ourbit.py +135 -87
  5. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/ourbit.py +7 -0
  6. hyperquant-0.35/src/hyperquant/broker/ws.py +37 -0
  7. {hyperquant-0.33 → hyperquant-0.35}/test.py +24 -12
  8. {hyperquant-0.33 → hyperquant-0.35}/uv.lock +1 -1
  9. hyperquant-0.33/src/hyperquant/broker/ws.py +0 -12
  10. {hyperquant-0.33 → hyperquant-0.35}/.gitignore +0 -0
  11. {hyperquant-0.33 → hyperquant-0.35}/.python-version +0 -0
  12. {hyperquant-0.33 → hyperquant-0.35}/README.md +0 -0
  13. {hyperquant-0.33 → hyperquant-0.35}/data/logs/notikit.log +0 -0
  14. {hyperquant-0.33 → hyperquant-0.35}/pub.sh +0 -0
  15. {hyperquant-0.33 → hyperquant-0.35}/requirements-dev.lock +0 -0
  16. {hyperquant-0.33 → hyperquant-0.35}/requirements.lock +0 -0
  17. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/__init__.py +0 -0
  18. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/hyperliquid.py +0 -0
  19. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/lib/hpstore.py +0 -0
  20. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  21. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  22. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/core.py +0 -0
  23. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/datavison/_util.py +0 -0
  24. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/datavison/binance.py +0 -0
  25. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/datavison/coinglass.py +0 -0
  26. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/datavison/okx.py +0 -0
  27. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/db.py +0 -0
  28. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/draw.py +0 -0
  29. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/logkit.py +0 -0
  30. {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/notikit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.33
3
+ Version: 0.35
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.33"
3
+ version = "0.35"
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,7 @@
1
1
  import time
2
2
  import hashlib
3
3
  from typing import Any
4
+ from aiohttp import ClientWebSocketResponse
4
5
  from multidict import CIMultiDict
5
6
  from yarl import URL
6
7
  import pybotters
@@ -45,7 +46,10 @@ class Auth:
45
46
 
46
47
  # 更新 kwargs.body,保证发出去的与签名一致
47
48
  kwargs.update({"data": raw_body_for_sign})
48
-
49
+
49
50
  return args
51
+
50
52
 
51
53
  pybotters.auth.Hosts.items['futures.ourbit.com'] = pybotters.auth.Item("ourbit", Auth.ourbit)
54
+
55
+
@@ -137,6 +137,21 @@ class Ticker(DataStore):
137
137
  class Orders(DataStore):
138
138
  _KEYS = ["order_id"]
139
139
 
140
+ def _fmt(self, order:dict):
141
+ return {
142
+ "order_id": order.get("orderId"),
143
+ "symbol": order.get("symbol"),
144
+ "px": order.get("price"),
145
+ "vol": order.get("vol"),
146
+ "lev": order.get("leverage"),
147
+ "side": "buy" if order.get("side") == 1 else "sell",
148
+ "deal_vol": order.get("dealVol"),
149
+ "deal_avg_px": order.get("dealAvgPrice"),
150
+ "create_ts": order.get("createTime"),
151
+ "update_ts": order.get("updateTime"),
152
+ "state": "open"
153
+ }
154
+
140
155
  # {'success': True, 'code': 0, 'data': [{'orderId': '219108574599630976', 'symbol': 'SOL_USDT', 'positionId': 0, 'price': 190, 'priceStr': '190', 'vol': 1, 'leverage': 20, 'side': 1, 'category': 1, 'orderType': 1, 'dealAvgPrice': 0, 'dealAvgPriceStr': '0', 'dealVol': 0, 'orderMargin': 0.09652, 'takerFee': 0, 'makerFee': 0, 'profit': 0, 'feeCurrency': 'USDT', 'openType': 1, 'state': 2, 'externalOid': '_m_2228b23a75204e1982b301e44d439cbb', 'errorCode': 0, 'usedMargin': 0, 'createTime': 1756277955008, 'updateTime': 1756277955037, 'positionMode': 1, 'version': 1, 'showCancelReason': 0, 'showProfitRateShare': 0, 'voucher': False}]}
141
156
  def _onresponse(self, data: dict[str, Any]):
142
157
  orders = data.get("data", [])
@@ -144,25 +159,33 @@ class Orders(DataStore):
144
159
  data_to_insert: list[Item] = []
145
160
  for order in orders:
146
161
  order: dict[str, Any] = order
147
-
148
- data_to_insert.append(
149
- {
150
- "order_id": order.get("orderId"),
151
- "symbol": order.get("symbol"),
152
- "px": order.get("priceStr"),
153
- "vol": order.get("vol"),
154
- "lev": order.get("leverage"),
155
- "side": "buy" if order.get("side") == 1 else "sell",
156
- "deal_vol": order.get("dealVol"),
157
- "deal_avg_px": order.get("dealAvgPriceStr"),
158
- "create_ts": order.get("createTime"),
159
- "update_ts": order.get("updateTime"),
160
- }
161
- )
162
+ data_to_insert.append(self._fmt(order))
162
163
 
163
164
  self._clear()
164
165
  self._update(data_to_insert)
165
-
166
+
167
+ def _on_message(self, msg: dict[str, Any]) -> None:
168
+ data:dict = msg.get("data", {})
169
+ if msg.get('channel') == 'push.personal.order':
170
+ state = data.get("state")
171
+ if state == 2:
172
+ order = self._fmt(data)
173
+ order["state"] = "open"
174
+ self._insert([order])
175
+ elif state == 3:
176
+ order = self._fmt(data)
177
+ order["state"] = "filled"
178
+ self._update([order])
179
+ self._find_and_delete({
180
+ "order_id": order.get("order_id")
181
+ })
182
+ elif state == 4:
183
+ order = self._fmt(data)
184
+ order["state"] = "canceled"
185
+ self._update([order])
186
+ self._find_and_delete({
187
+ "order_id": order.get("order_id")
188
+ })
166
189
 
167
190
  class Detail(DataStore):
168
191
  _KEYS = ["symbol"]
@@ -193,15 +216,9 @@ class Detail(DataStore):
193
216
  class Position(DataStore):
194
217
  _KEYS = ["position_id"]
195
218
  # {"success":true,"code":0,"data":[{"positionId":5355366,"symbol":"SOL_USDT","positionType":1,"openType":1,"state":1,"holdVol":1,"frozenVol":0,"closeVol":0,"holdAvgPrice":203.44,"holdAvgPriceFullyScale":"203.44","openAvgPrice":203.44,"openAvgPriceFullyScale":"203.44","closeAvgPrice":0,"liquidatePrice":194.07,"oim":0.10253376,"im":0.10253376,"holdFee":0,"realised":-0.0008,"leverage":20,"marginRatio":0.0998,"createTime":1756275984696,"updateTime":1756275984696,"autoAddIm":false,"version":1,"profitRatio":0,"newOpenAvgPrice":203.44,"newCloseAvgPrice":0,"closeProfitLoss":0,"fee":0.00081376}]}
196
- def _onresponse(self, data: dict[str, Any]):
197
- positions = data.get("data", [])
198
- if positions:
199
- data_to_insert: list[Item] = []
200
- for position in positions:
201
- position: dict[str, Any] = position
202
-
203
- data_to_insert.append(
204
- {
219
+
220
+ def _fmt(self, position:dict):
221
+ return {
205
222
  "position_id": position.get("positionId"),
206
223
  "symbol": position.get("symbol"),
207
224
  "side": "short" if position.get("positionType") == 2 else "long",
@@ -222,37 +239,61 @@ class Position(DataStore):
222
239
  "margin_ratio": position.get("marginRatio"),
223
240
  "create_ts": position.get("createTime"),
224
241
  "update_ts": position.get("updateTime"),
225
- }
242
+ }
243
+
244
+ def _onresponse(self, data: dict[str, Any]):
245
+ positions = data.get("data", [])
246
+ if positions:
247
+ data_to_insert: list[Item] = []
248
+ for position in positions:
249
+ position: dict[str, Any] = position
250
+
251
+ data_to_insert.append(
252
+ self._fmt(position)
226
253
  )
227
254
 
228
255
  self._clear()
229
256
  self._insert(data_to_insert)
257
+ else:
258
+ self._clear()
259
+
260
+ def _on_message(self, msg: dict[str, Any]) -> None:
261
+ data:dict = msg.get("data", {})
262
+ state = data.get("state")
263
+ position_id = data.get("positionId")
264
+ if state == 3:
265
+ self._find_and_delete({"position_id": position_id})
266
+ return
267
+
268
+ self._update([self._fmt(data)])
230
269
 
231
270
  class Balance(DataStore):
232
271
  _KEYS = ["currency"]
233
272
 
273
+ def _fmt(self, balance: dict) -> dict:
274
+ return {
275
+ "available_balance": balance.get("availableBalance"),
276
+ "bonus": balance.get("bonus"),
277
+ "currency": balance.get("currency"),
278
+ "frozen_balance": balance.get("frozenBalance"),
279
+ "last_bonus": balance.get("lastBonus"),
280
+ "position_margin": balance.get("positionMargin"),
281
+ "wallet_balance": balance.get("walletBalance"),
282
+ }
283
+
234
284
  def _onresponse(self, data: dict[str, Any]):
235
285
  balances = data.get("data", [])
236
286
  if balances:
237
287
  data_to_insert: list[Item] = []
238
288
  for balance in balances:
239
289
  balance: dict[str, Any] = balance
240
- data_to_insert.append({
241
- "currency": balance.get("currency"),
242
- "position_margin": balance.get("positionMargin"),
243
- "available_balance": balance.get("availableBalance"),
244
- "cash_balance": balance.get("cashBalance"),
245
- "frozen_balance": balance.get("frozenBalance"),
246
- "equity": balance.get("equity"),
247
- "unrealized": balance.get("unrealized"),
248
- "bonus": balance.get("bonus"),
249
- "last_bonus": balance.get("lastBonus"),
250
- "wallet_balance": balance.get("walletBalance"),
251
- "voucher": balance.get("voucher"),
252
- "voucher_using": balance.get("voucherUsing"),
253
- })
290
+ data_to_insert.append(self._fmt(balance))
254
291
  self._clear()
255
292
  self._insert(data_to_insert)
293
+
294
+ def _on_message(self, msg: dict[str, Any]) -> None:
295
+ data: dict = msg.get("data", {})
296
+ self._update([self._fmt(data)])
256
297
 
257
298
  class OurbitSwapDataStore(DataStoreCollection):
258
299
  """
@@ -316,6 +357,12 @@ class OurbitSwapDataStore(DataStoreCollection):
316
357
  self.book._on_message(msg)
317
358
  if channel == "push.tickers":
318
359
  self.ticker._on_message(msg)
360
+ if channel == "push.personal.position":
361
+ self.position._on_message(msg)
362
+ if channel == "push.personal.order":
363
+ self.orders._on_message(msg)
364
+ if channel == "push.personal.asset":
365
+ self.balance._on_message(msg)
319
366
  else:
320
367
  logger.debug(f"未知的channel: {channel}")
321
368
 
@@ -434,7 +481,7 @@ class OurbitSwapDataStore(DataStoreCollection):
434
481
  "side": "buy",
435
482
  "price": "110152.5",
436
483
  "size": "0.1",
437
- "status": "open",
484
+ "state": "open", // ("open", "closed", "canceled")
438
485
  "create_ts": 1625247600000,
439
486
  "update_ts": 1625247600000
440
487
  }
@@ -446,57 +493,58 @@ class OurbitSwapDataStore(DataStoreCollection):
446
493
  def position(self) -> Position:
447
494
  """
448
495
  持仓数据
449
-
450
496
  Data structure:
451
- .. code:: python
452
- [
453
- {
454
- "position_id": "123456",
455
- "symbol": "BTC_USDT",
456
- "side": "long",
457
- "open_type": "limit",
458
- "state": "open",
459
- "hold_vol": "0.1",
460
- "frozen_vol": "0.0",
461
- "close_vol": "0.0",
462
- "hold_avg_price": "110152.5",
463
- "open_avg_price": "110152.5",
464
- "close_avg_price": "0.0",
465
- "liquidate_price": "100000.0",
466
- "oim": "0.0",
467
- "im": "0.0",
468
- "hold_fee": "0.0",
469
- "realised": "0.0",
470
- "leverage": "10",
471
- "margin_ratio": "0.1",
472
- "create_ts": 1625247600000,
473
- "update_ts": 1625247600000
474
- }
475
- ]
476
- """
477
- return self._get("position", Position)
478
497
 
479
- @property
480
- def balance(self) -> Balance:
481
- """账户余额数据
482
-
483
- Data structure:
484
- .. code:: python
498
+ .. code:: json
499
+
485
500
  [
486
501
  {
487
- "currency": "USDT", # 币种
488
- "position_margin": 0.3052, # 持仓保证金
489
- "available_balance": 19.7284, # 可用余额
490
- "cash_balance": 19.7284, # 现金余额
491
- "frozen_balance": 0, # 冻结余额
492
- "equity": 19.9442, # 权益
493
- "unrealized": -0.0895, # 未实现盈亏
494
- "bonus": 0, # 奖励
495
- "last_bonus": 0, # 最后奖励
496
- "wallet_balance": 20.0337, # 钱包余额
497
- "voucher": 0, # 代金券
498
- "voucher_using": 0 # 使用中的代金券
502
+ "position_id": "123456",
503
+ "symbol": "BTC_USDT",
504
+ "side": "long",
505
+ "open_type": "limit",
506
+ "state": "open",
507
+ "hold_vol": "0.1",
508
+ "frozen_vol": "0.0",
509
+ "close_vol": "0.0",
510
+ "hold_avg_price": "110152.5",
511
+ "open_avg_price": "110152.5",
512
+ "close_avg_price": "0.0",
513
+ "liquidate_price": "100000.0",
514
+ "oim": "0.0",
515
+ "im": "0.0",
516
+ "hold_fee": "0.0",
517
+ "realised": "0.0",
518
+ "leverage": "10",
519
+ "margin_ratio": "0.1",
520
+ "create_ts": 1625247600000,
521
+ "update_ts": 1625247600000
499
522
  }
500
523
  ]
501
524
  """
525
+ return self._get("position", Position)
526
+
527
+ @property
528
+ def balance(self) -> Balance:
529
+ @property
530
+ def balance(self) -> Balance:
531
+ """账户余额数据
532
+
533
+ Data structure:
534
+
535
+ .. code:: python
536
+
537
+ [
538
+ {
539
+ "currency": "USDT", # 币种
540
+ "position_margin": 0.3052, # 持仓保证金
541
+ "available_balance": 19.7284, # 可用余额
542
+ "frozen_balance": 0, # 冻结余额
543
+ "bonus": 0, # 奖励
544
+ "last_bonus": 0, # 最后奖励
545
+ "wallet_balance": 20.0337 # 钱包余额
546
+ }
547
+ ]
548
+ """
549
+ return self._get("balance", Balance)
502
550
  return self._get("balance", Balance)
@@ -87,6 +87,13 @@ class OurbitSwap:
87
87
  hdlr_json=self.store.onmessage
88
88
  )
89
89
 
90
+ async def sub_personal(self):
91
+ self.client.ws_connect(
92
+ self.ws_url,
93
+ send_json={ "method": "sub.personal.user.preference"},
94
+ hdlr_json=self.store.onmessage
95
+ )
96
+
90
97
  def ret_content(self, res: pybotters.FetchResult):
91
98
  match res.data:
92
99
  case {"success": True}:
@@ -0,0 +1,37 @@
1
+ import asyncio
2
+ import pybotters
3
+ from pybotters.ws import ClientWebSocketResponse, logger
4
+
5
+ class Heartbeat:
6
+ @staticmethod
7
+ async def ourbit(ws: pybotters.ws.ClientWebSocketResponse):
8
+ while not ws.closed:
9
+ await ws.send_str('{"method":"ping"}')
10
+ await asyncio.sleep(10.0)
11
+
12
+ pybotters.ws.HeartbeatHosts.items['futures.ourbit.com'] = Heartbeat.ourbit
13
+
14
+ class WssAuth:
15
+ @staticmethod
16
+ async def ourbit(ws: ClientWebSocketResponse):
17
+ key: str = ws._response._session.__dict__["_apis"][
18
+ pybotters.ws.AuthHosts.items[ws._response.url.host].name
19
+ ][0]
20
+ await ws.send_json(
21
+ {
22
+ "method": "login",
23
+ "param": {
24
+ "token": key
25
+ }
26
+ }
27
+ )
28
+ async for msg in ws:
29
+ # {"channel":"rs.login","data":"success","ts":1756470267848}
30
+ data = msg.json()
31
+ if data.get("channel") == "rs.login":
32
+ if data.get("data") == "success":
33
+ break
34
+ else:
35
+ logger.warning(f"WebSocket login failed: {data}")
36
+
37
+ pybotters.ws.AuthHosts.items['futures.ourbit.com'] = pybotters.auth.Item("ourbit", WssAuth.ourbit)
@@ -21,6 +21,7 @@
21
21
  import asyncio
22
22
  from hyperquant.broker.ourbit import OurbitSwap
23
23
  import pybotters
24
+ import hyperquant
24
25
 
25
26
  async def test():
26
27
  async with pybotters.Client(
@@ -31,18 +32,29 @@ async def test():
31
32
  }
32
33
  ) as client:
33
34
  ourbit = OurbitSwap(client)
34
- await ourbit.__aenter__()
35
- res = await ourbit.place_order(
36
- symbol="SOL_USDT",
37
- size=1,
38
- side="buy",
39
- order_type="limit_GTC",
40
- price=192
41
- )
42
-
43
- await ourbit.update('orders')
44
-
45
- print(res)
35
+ # await ourbit.__aenter__()
36
+ # res = await ourbit.place_order(
37
+ # symbol="SOL_USDT",
38
+ # size=1,
39
+ # side="buy",
40
+ # order_type="limit_GTC",
41
+ # price=192
42
+ # )
43
+
44
+ # await ourbit.update('orders')
45
+ await ourbit.sub_personal()
46
+
47
+ while True:
48
+ await ourbit.store.balance.wait()
49
+ print(ourbit.store.balance.find())
50
+ # # await ourbit.store.position.wait()
51
+ # # print(ourbit.store.position.find())
52
+ # await ourbit.store.orders.wait()
53
+
54
+ # with ourbit.store.orders.watch() as changes:
55
+ # async for change in changes:
56
+ # print(change.operation, change.data)
57
+
46
58
 
47
59
  # print(res.data)
48
60
 
@@ -530,7 +530,7 @@ wheels = [
530
530
 
531
531
  [[package]]
532
532
  name = "hyperquant"
533
- version = "0.32"
533
+ version = "0.34"
534
534
  source = { editable = "." }
535
535
  dependencies = [
536
536
  { name = "aiohttp" },
@@ -1,12 +0,0 @@
1
- import asyncio
2
- import pybotters
3
-
4
-
5
- class Heartbeat:
6
- @staticmethod
7
- async def ourbit(ws: pybotters.ws.ClientWebSocketResponse):
8
- while not ws.closed:
9
- await ws.send_str('{"method":"ping"}')
10
- await asyncio.sleep(10.0)
11
-
12
- pybotters.ws.HeartbeatHosts.items['futures.ourbit.com'] = Heartbeat.ourbit
File without changes
File without changes
File without changes
File without changes
File without changes