hyperquant 0.34__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.
- {hyperquant-0.34 → hyperquant-0.35}/PKG-INFO +1 -1
- {hyperquant-0.34 → hyperquant-0.35}/pyproject.toml +1 -1
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/broker/auth.py +5 -1
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/broker/models/ourbit.py +133 -87
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/broker/ourbit.py +7 -0
- hyperquant-0.35/src/hyperquant/broker/ws.py +37 -0
- {hyperquant-0.34 → hyperquant-0.35}/test.py +24 -12
- {hyperquant-0.34 → hyperquant-0.35}/uv.lock +1 -1
- hyperquant-0.34/src/hyperquant/broker/ws.py +0 -12
- {hyperquant-0.34 → hyperquant-0.35}/.gitignore +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/.python-version +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/README.md +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/data/logs/notikit.log +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/pub.sh +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/requirements-dev.lock +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/requirements.lock +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/__init__.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/broker/hyperliquid.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/broker/lib/hpstore.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/broker/lib/hyper_types.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/broker/models/hyperliquid.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/core.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/datavison/_util.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/datavison/binance.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/datavison/coinglass.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/datavison/okx.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/db.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/draw.py +0 -0
- {hyperquant-0.34 → hyperquant-0.35}/src/hyperquant/logkit.py +0 -0
- {hyperquant-0.34 → 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.
|
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,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
|
-
|
197
|
-
|
198
|
-
|
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,7 +239,17 @@ 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()
|
@@ -230,31 +257,43 @@ class Position(DataStore):
|
|
230
257
|
else:
|
231
258
|
self._clear()
|
232
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)])
|
269
|
+
|
233
270
|
class Balance(DataStore):
|
234
271
|
_KEYS = ["currency"]
|
235
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
|
+
|
236
284
|
def _onresponse(self, data: dict[str, Any]):
|
237
285
|
balances = data.get("data", [])
|
238
286
|
if balances:
|
239
287
|
data_to_insert: list[Item] = []
|
240
288
|
for balance in balances:
|
241
289
|
balance: dict[str, Any] = balance
|
242
|
-
data_to_insert.append(
|
243
|
-
"currency": balance.get("currency"),
|
244
|
-
"position_margin": balance.get("positionMargin"),
|
245
|
-
"available_balance": balance.get("availableBalance"),
|
246
|
-
"cash_balance": balance.get("cashBalance"),
|
247
|
-
"frozen_balance": balance.get("frozenBalance"),
|
248
|
-
"equity": balance.get("equity"),
|
249
|
-
"unrealized": balance.get("unrealized"),
|
250
|
-
"bonus": balance.get("bonus"),
|
251
|
-
"last_bonus": balance.get("lastBonus"),
|
252
|
-
"wallet_balance": balance.get("walletBalance"),
|
253
|
-
"voucher": balance.get("voucher"),
|
254
|
-
"voucher_using": balance.get("voucherUsing"),
|
255
|
-
})
|
290
|
+
data_to_insert.append(self._fmt(balance))
|
256
291
|
self._clear()
|
257
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)])
|
258
297
|
|
259
298
|
class OurbitSwapDataStore(DataStoreCollection):
|
260
299
|
"""
|
@@ -318,6 +357,12 @@ class OurbitSwapDataStore(DataStoreCollection):
|
|
318
357
|
self.book._on_message(msg)
|
319
358
|
if channel == "push.tickers":
|
320
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)
|
321
366
|
else:
|
322
367
|
logger.debug(f"未知的channel: {channel}")
|
323
368
|
|
@@ -436,7 +481,7 @@ class OurbitSwapDataStore(DataStoreCollection):
|
|
436
481
|
"side": "buy",
|
437
482
|
"price": "110152.5",
|
438
483
|
"size": "0.1",
|
439
|
-
"
|
484
|
+
"state": "open", // ("open", "closed", "canceled")
|
440
485
|
"create_ts": 1625247600000,
|
441
486
|
"update_ts": 1625247600000
|
442
487
|
}
|
@@ -448,57 +493,58 @@ class OurbitSwapDataStore(DataStoreCollection):
|
|
448
493
|
def position(self) -> Position:
|
449
494
|
"""
|
450
495
|
持仓数据
|
451
|
-
|
452
496
|
Data structure:
|
453
|
-
.. code:: python
|
454
|
-
[
|
455
|
-
{
|
456
|
-
"position_id": "123456",
|
457
|
-
"symbol": "BTC_USDT",
|
458
|
-
"side": "long",
|
459
|
-
"open_type": "limit",
|
460
|
-
"state": "open",
|
461
|
-
"hold_vol": "0.1",
|
462
|
-
"frozen_vol": "0.0",
|
463
|
-
"close_vol": "0.0",
|
464
|
-
"hold_avg_price": "110152.5",
|
465
|
-
"open_avg_price": "110152.5",
|
466
|
-
"close_avg_price": "0.0",
|
467
|
-
"liquidate_price": "100000.0",
|
468
|
-
"oim": "0.0",
|
469
|
-
"im": "0.0",
|
470
|
-
"hold_fee": "0.0",
|
471
|
-
"realised": "0.0",
|
472
|
-
"leverage": "10",
|
473
|
-
"margin_ratio": "0.1",
|
474
|
-
"create_ts": 1625247600000,
|
475
|
-
"update_ts": 1625247600000
|
476
|
-
}
|
477
|
-
]
|
478
|
-
"""
|
479
|
-
return self._get("position", Position)
|
480
497
|
|
481
|
-
|
482
|
-
|
483
|
-
"""账户余额数据
|
484
|
-
|
485
|
-
Data structure:
|
486
|
-
.. code:: python
|
498
|
+
.. code:: json
|
499
|
+
|
487
500
|
[
|
488
501
|
{
|
489
|
-
"
|
490
|
-
"
|
491
|
-
"
|
492
|
-
"
|
493
|
-
"
|
494
|
-
"
|
495
|
-
"
|
496
|
-
"
|
497
|
-
"
|
498
|
-
"
|
499
|
-
"
|
500
|
-
"
|
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
|
501
522
|
}
|
502
523
|
]
|
503
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)
|
504
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
)
|
42
|
-
|
43
|
-
await ourbit.update('orders')
|
44
|
-
|
45
|
-
|
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
|
|
@@ -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
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|