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.
- {hyperquant-0.33 → hyperquant-0.35}/PKG-INFO +1 -1
- {hyperquant-0.33 → hyperquant-0.35}/pyproject.toml +1 -1
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/auth.py +5 -1
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/models/ourbit.py +135 -87
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/ourbit.py +7 -0
- hyperquant-0.35/src/hyperquant/broker/ws.py +37 -0
- {hyperquant-0.33 → hyperquant-0.35}/test.py +24 -12
- {hyperquant-0.33 → hyperquant-0.35}/uv.lock +1 -1
- hyperquant-0.33/src/hyperquant/broker/ws.py +0 -12
- {hyperquant-0.33 → hyperquant-0.35}/.gitignore +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/.python-version +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/README.md +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/data/logs/notikit.log +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/pub.sh +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/requirements-dev.lock +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/requirements.lock +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/__init__.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/hyperliquid.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/lib/hpstore.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/lib/hyper_types.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/broker/models/hyperliquid.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/core.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/datavison/_util.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/datavison/binance.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/datavison/coinglass.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/datavison/okx.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/db.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/draw.py +0 -0
- {hyperquant-0.33 → hyperquant-0.35}/src/hyperquant/logkit.py +0 -0
- {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.
|
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,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
|
-
"
|
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
|
-
|
480
|
-
|
481
|
-
"""账户余额数据
|
482
|
-
|
483
|
-
Data structure:
|
484
|
-
.. code:: python
|
498
|
+
.. code:: json
|
499
|
+
|
485
500
|
[
|
486
501
|
{
|
487
|
-
"
|
488
|
-
"
|
489
|
-
"
|
490
|
-
"
|
491
|
-
"
|
492
|
-
"
|
493
|
-
"
|
494
|
-
"
|
495
|
-
"
|
496
|
-
"
|
497
|
-
"
|
498
|
-
"
|
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
|
-
|
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
|