hyperquant 0.77__tar.gz → 0.78__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.77 → hyperquant-0.78}/PKG-INFO +1 -1
- {hyperquant-0.77 → hyperquant-0.78}/pyproject.toml +1 -1
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/bitget.py +36 -16
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/lbank.py +1 -1
- hyperquant-0.78/tests/test_bitget.py +193 -0
- hyperquant-0.78/uv.lock +2031 -0
- hyperquant-0.77/tests/test_bitget.py +0 -88
- hyperquant-0.77/uv.lock +0 -1637
- {hyperquant-0.77 → hyperquant-0.78}/.gitignore +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/.python-version +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/README.md +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/apis.json +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/data/alpine_smoke.log +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/data/logs/notikit.log +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/data/logs/test_order_sync.log +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/data/records_swap.csv +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/data/records_swapc.csv +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/doc/lbank.md +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/pub.sh +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/requirements-dev.lock +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/requirements.lock +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/__init__.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/auth.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/edgex.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/hyperliquid.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/lib/hpstore.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/lib/hyper_types.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/lib/util.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/models/bitget.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/models/edgex.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/models/hyperliquid.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/models/lbank.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/models/ourbit.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/ourbit.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/broker/ws.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/core.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/datavison/_util.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/datavison/binance.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/datavison/coinglass.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/datavison/okx.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/db.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/draw.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/logkit.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/src/hyperquant/notikit.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/tests/test_draw.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/tests/test_edgex.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/tests/test_lbank.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/tests/test_ourbit.py +0 -0
- {hyperquant-0.77 → hyperquant-0.78}/tests/tmp.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperquant
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.78
|
|
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,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import logging
|
|
5
|
+
import uuid
|
|
4
6
|
from typing import Any, Literal
|
|
5
7
|
|
|
6
8
|
import pybotters
|
|
@@ -31,7 +33,9 @@ class Bitget:
|
|
|
31
33
|
self.ws_url = ws_url or "wss://ws.bitget.com/v2/ws/public"
|
|
32
34
|
self.ws_url_private = ws_url or "wss://ws.bitget.com/v2/ws/private"
|
|
33
35
|
|
|
34
|
-
self.
|
|
36
|
+
self.ws_app = None
|
|
37
|
+
self.has_sub_personal = False
|
|
38
|
+
|
|
35
39
|
|
|
36
40
|
async def __aenter__(self) -> "Bitget":
|
|
37
41
|
await self.update("detail")
|
|
@@ -53,11 +57,13 @@ class Bitget:
|
|
|
53
57
|
{"instType": "USDT-FUTURES", "channel": "account", "coin": "default"},
|
|
54
58
|
],
|
|
55
59
|
}
|
|
56
|
-
self.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
self.ws_app = await self._ensure_private_ws()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
await self.ws_app.current_ws.send_json(sub_msg)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
self.has_sub_personal = True
|
|
61
67
|
|
|
62
68
|
async def update(
|
|
63
69
|
self,
|
|
@@ -108,13 +114,17 @@ class Bitget:
|
|
|
108
114
|
margin_coin: str = "USDT",
|
|
109
115
|
reduce_only: bool | None = None,
|
|
110
116
|
offset_flag: Literal["open", "close", "0", "1"] | None = None,
|
|
111
|
-
client_order_id: str | None = None
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
client_order_id: str | None = None
|
|
118
|
+
) -> dict[str, Any] | None:
|
|
119
|
+
"""
|
|
120
|
+
请求成功返回示例:
|
|
121
|
+
|
|
122
|
+
.. code:: json
|
|
115
123
|
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
{
|
|
125
|
+
"clientOid": "121211212122",
|
|
126
|
+
"orderId": "121211212122"
|
|
127
|
+
}
|
|
118
128
|
"""
|
|
119
129
|
|
|
120
130
|
side = self._normalize_direction(direction)
|
|
@@ -159,9 +169,6 @@ class Bitget:
|
|
|
159
169
|
if client_order_id:
|
|
160
170
|
payload["clientOid"] = client_order_id
|
|
161
171
|
|
|
162
|
-
if extra_params:
|
|
163
|
-
payload.update(extra_params)
|
|
164
|
-
|
|
165
172
|
res = await self.client.post(
|
|
166
173
|
f"{self.rest_api}/api/v2/mix/order/place-order",
|
|
167
174
|
data=payload,
|
|
@@ -195,7 +202,7 @@ class Bitget:
|
|
|
195
202
|
|
|
196
203
|
res = await self.client.post(
|
|
197
204
|
f"{self.rest_api}/api/v2/mix/order/cancel-order",
|
|
198
|
-
|
|
205
|
+
data=payload,
|
|
199
206
|
)
|
|
200
207
|
data = await res.json()
|
|
201
208
|
return self._ensure_ok("cancel_order", data)
|
|
@@ -223,6 +230,19 @@ class Bitget:
|
|
|
223
230
|
)
|
|
224
231
|
return detail
|
|
225
232
|
|
|
233
|
+
async def _ensure_private_ws(self):
|
|
234
|
+
wsqueue = pybotters.WebSocketQueue()
|
|
235
|
+
ws_app = self.client.ws_connect(
|
|
236
|
+
self.ws_url_private,
|
|
237
|
+
hdlr_json=self.store.onmessage,
|
|
238
|
+
)
|
|
239
|
+
# async for msg in wsqueue:
|
|
240
|
+
# print(msg)
|
|
241
|
+
|
|
242
|
+
await ws_app._event.wait()
|
|
243
|
+
await ws_app.current_ws._wait_authtask()
|
|
244
|
+
return ws_app
|
|
245
|
+
|
|
226
246
|
@staticmethod
|
|
227
247
|
def _format_with_step(value: float, step: Any) -> str:
|
|
228
248
|
if step in (None, 0, "0"):
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import pybotters
|
|
6
|
+
from hyperquant.broker.models.bitget import BitgetDataStore
|
|
7
|
+
pybotters.auth
|
|
8
|
+
|
|
9
|
+
async def test_update():
|
|
10
|
+
async with pybotters.Client() as client:
|
|
11
|
+
store = BitgetDataStore()
|
|
12
|
+
# await store.initialize(
|
|
13
|
+
# client.get("https://api.bitget.com/api/v2/mix/market/contracts?productType=usdt-futures")
|
|
14
|
+
# )
|
|
15
|
+
# print(store.detail.find())
|
|
16
|
+
await store.initialize(
|
|
17
|
+
client.get(
|
|
18
|
+
"https://api.bitget.com/api/v2/mix/market/tickers?productType=usdt-futures"
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
print(store.ticker.find({"symbol": "BTCUSDT"}))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def subscribe_book():
|
|
25
|
+
|
|
26
|
+
async with pybotters.Client() as client:
|
|
27
|
+
store = BitgetDataStore()
|
|
28
|
+
client.ws_connect(
|
|
29
|
+
"wss://ws.bitget.com/v2/ws/public",
|
|
30
|
+
send_json={
|
|
31
|
+
"op": "subscribe",
|
|
32
|
+
"args": [
|
|
33
|
+
{"instType": "SPOT", "channel": "books1", "instId": "BTCUSDT"}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
hdlr_json=store.onmessage
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
while True:
|
|
40
|
+
await asyncio.sleep(1)
|
|
41
|
+
print(store.book.find())
|
|
42
|
+
|
|
43
|
+
from hyperquant.broker.bitget import Bitget
|
|
44
|
+
async def test_broker_update():
|
|
45
|
+
|
|
46
|
+
async with pybotters.Client() as client:
|
|
47
|
+
bg = Bitget(client)
|
|
48
|
+
store = BitgetDataStore()
|
|
49
|
+
# await bg.update('all')
|
|
50
|
+
# print(bg.store.detail.find())
|
|
51
|
+
await bg.update('ticker')
|
|
52
|
+
print(bg.store.ticker.find())
|
|
53
|
+
|
|
54
|
+
async def test_broker_sub_orderbook():
|
|
55
|
+
async with pybotters.Client() as client:
|
|
56
|
+
bg = Bitget(client)
|
|
57
|
+
await bg.sub_orderbook(['BTCUSDT', 'ETHUSDT'])
|
|
58
|
+
while True:
|
|
59
|
+
await asyncio.sleep(1)
|
|
60
|
+
print(bg.store.book.find())
|
|
61
|
+
|
|
62
|
+
async def test_order():
|
|
63
|
+
async with pybotters.Client(apis='./apis.json') as client:
|
|
64
|
+
bg = Bitget(client)
|
|
65
|
+
await bg.__aenter__()
|
|
66
|
+
ts = time.time() * 1000
|
|
67
|
+
res = await bg.place_order(
|
|
68
|
+
'SOLUSDT',
|
|
69
|
+
direction='long',
|
|
70
|
+
order_type='limit_gtc',
|
|
71
|
+
volume=0.1,
|
|
72
|
+
price=185
|
|
73
|
+
)
|
|
74
|
+
# print(res)
|
|
75
|
+
print(f'订单延迟: {time.time() * 1000 - ts} ms')
|
|
76
|
+
|
|
77
|
+
async def test_sub_personal():
|
|
78
|
+
async with pybotters.Client(apis='./apis.json') as client:
|
|
79
|
+
bg = Bitget(client)
|
|
80
|
+
await bg.__aenter__()
|
|
81
|
+
await bg.sub_personal()
|
|
82
|
+
|
|
83
|
+
# # 监听订单变化
|
|
84
|
+
# with bg.store.orders.watch() as stream:
|
|
85
|
+
# async for change in stream:
|
|
86
|
+
# print("Orders changed:", change)
|
|
87
|
+
|
|
88
|
+
# 监听持仓变化
|
|
89
|
+
with bg.store.positions.watch() as stream:
|
|
90
|
+
async for change in stream:
|
|
91
|
+
print("Positions changed:", change)
|
|
92
|
+
|
|
93
|
+
async def order_sync_polling(
|
|
94
|
+
broker: Bitget,
|
|
95
|
+
*,
|
|
96
|
+
symbol: str,
|
|
97
|
+
direction: Literal["buy", "sell", "long", "short"] = "buy",
|
|
98
|
+
order_type: Literal[
|
|
99
|
+
"market",
|
|
100
|
+
"limit_gtc",
|
|
101
|
+
"limit_ioc",
|
|
102
|
+
"limit_fok",
|
|
103
|
+
"limit_post_only",
|
|
104
|
+
"limit",
|
|
105
|
+
] = "limit_gtc",
|
|
106
|
+
price: float | None = None,
|
|
107
|
+
volume: float | None = None,
|
|
108
|
+
margin_mode: Literal["isolated", "crossed"] = "crossed",
|
|
109
|
+
product_type: str = "USDT-FUTURES",
|
|
110
|
+
margin_coin: str = "USDT",
|
|
111
|
+
window_sec: float = 5.0,
|
|
112
|
+
grace_sec: float = 5.0,
|
|
113
|
+
) -> dict:
|
|
114
|
+
"""
|
|
115
|
+
基于 Bitget 私有 WS 的订单轮询:window 期内等待终态,超时后撤单并返回结果。
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
norm_type = order_type.lower()
|
|
119
|
+
if norm_type not in {
|
|
120
|
+
"market",
|
|
121
|
+
"limit",
|
|
122
|
+
"limit_gtc",
|
|
123
|
+
"limit_ioc",
|
|
124
|
+
"limit_fok",
|
|
125
|
+
"limit_post_only",
|
|
126
|
+
}:
|
|
127
|
+
raise ValueError(f"unsupported order_type: {order_type}")
|
|
128
|
+
|
|
129
|
+
order = None
|
|
130
|
+
try:
|
|
131
|
+
async with asyncio.timeout(window_sec):
|
|
132
|
+
# 监控订单
|
|
133
|
+
with broker.store.orders.watch() as stream:
|
|
134
|
+
started = int(time.time() * 1000)
|
|
135
|
+
resp = await broker.place_order(
|
|
136
|
+
symbol,
|
|
137
|
+
direction=direction,
|
|
138
|
+
order_type=norm_type,
|
|
139
|
+
price=price,
|
|
140
|
+
volume=volume,
|
|
141
|
+
margin_mode=margin_mode,
|
|
142
|
+
product_type=product_type,
|
|
143
|
+
margin_coin=margin_coin,
|
|
144
|
+
)
|
|
145
|
+
latency = int(time.time() * 1000) - started
|
|
146
|
+
print(f"下单延迟 {latency} ms")
|
|
147
|
+
|
|
148
|
+
order_id = resp.get("orderId")
|
|
149
|
+
|
|
150
|
+
if not order_id:
|
|
151
|
+
raise RuntimeError(f"place_order 返回缺少 order_id: {resp}")
|
|
152
|
+
while True:
|
|
153
|
+
change = await stream.__anext__()
|
|
154
|
+
|
|
155
|
+
if change.data.get("orderId") == order_id:
|
|
156
|
+
order = change.data
|
|
157
|
+
print(change.operation)
|
|
158
|
+
if change.operation == "delete":
|
|
159
|
+
return change.data
|
|
160
|
+
except TimeoutError:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
for i in range(3):
|
|
164
|
+
try:
|
|
165
|
+
await broker.cancel_order(order_id, symbol=symbol, margin_mode='crossed')
|
|
166
|
+
break
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print(f"撤单异常: {e}")
|
|
169
|
+
await asyncio.sleep(1.0)
|
|
170
|
+
|
|
171
|
+
return order
|
|
172
|
+
|
|
173
|
+
async def test_order_sync_polling():
|
|
174
|
+
async with pybotters.Client(apis="./apis.json") as client:
|
|
175
|
+
bg = Bitget(client)
|
|
176
|
+
await bg.__aenter__()
|
|
177
|
+
await bg.sub_personal()
|
|
178
|
+
|
|
179
|
+
result = await order_sync_polling(
|
|
180
|
+
bg,
|
|
181
|
+
symbol="SOLUSDT",
|
|
182
|
+
direction="long",
|
|
183
|
+
order_type="limit_gtc",
|
|
184
|
+
price=185,
|
|
185
|
+
volume=0.1,
|
|
186
|
+
window_sec=5.0,
|
|
187
|
+
grace_sec=5.0,
|
|
188
|
+
)
|
|
189
|
+
print(result)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
asyncio.run(test_order_sync_polling())
|