hyperquant 0.55__tar.gz → 0.57__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.55 → hyperquant-0.57}/PKG-INFO +1 -1
- {hyperquant-0.55 → hyperquant-0.57}/pyproject.toml +1 -1
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/broker/models/ourbit.py +4 -2
- {hyperquant-0.55 → hyperquant-0.57}/tests/test_ourbit.py +29 -2
- {hyperquant-0.55 → hyperquant-0.57}/uv.lock +1 -1
- hyperquant-0.55/tests/test.py +0 -183
- hyperquant-0.55/tests/test_order_sync.py +0 -82
- hyperquant-0.55/tests/test_store.py +0 -16
- {hyperquant-0.55 → hyperquant-0.57}/.gitignore +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/.python-version +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/README.md +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/apis.json +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/backtest_tmp.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/data/alpine_smoke.log +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/data/logs/notikit.log +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/data/logs/test_order_sync.log +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/deals.json +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/pnl_chart.html +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/pub.sh +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/requirements-dev.lock +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/requirements.lock +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/__init__.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/broker/auth.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/broker/hyperliquid.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/broker/lib/hpstore.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/broker/lib/hyper_types.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/broker/models/hyperliquid.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/broker/ourbit.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/broker/ws.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/core.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/datavison/_util.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/datavison/binance.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/datavison/coinglass.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/datavison/okx.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/db.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/draw.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/logkit.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/src/hyperquant/notikit.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/tests/test_draw.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/tests/test_lbank.py +0 -0
- {hyperquant-0.55 → hyperquant-0.57}/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.57
|
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
|
@@ -134,6 +134,7 @@ class Orders(DataStore):
|
|
134
134
|
def _fmt(self, order:dict):
|
135
135
|
return {
|
136
136
|
"order_id": order.get("orderId"),
|
137
|
+
"position_id": order.get("positionId"),
|
137
138
|
"symbol": order.get("symbol"),
|
138
139
|
"price": order.get("price"),
|
139
140
|
"vol": order.get("vol"),
|
@@ -213,6 +214,7 @@ class Detail(DataStore):
|
|
213
214
|
"contract_sz": detail.get("cs"),
|
214
215
|
"minv": detail.get("minV"),
|
215
216
|
"maxv": detail.get("maxV")
|
217
|
+
"online_time": detail.get("tcd")
|
216
218
|
}
|
217
219
|
)
|
218
220
|
self._update(data_to_insert)
|
@@ -403,8 +405,8 @@ class OurbitSwapDataStore(DataStoreCollection):
|
|
403
405
|
"io": ["binance", "mexc"], # 交易所列表
|
404
406
|
"contract_sz": 1,
|
405
407
|
"minv": 1,
|
406
|
-
"maxv": 10000
|
407
|
-
|
408
|
+
"maxv": 10000,
|
409
|
+
"online_time": 1625247600000 # 上线时间
|
408
410
|
}
|
409
411
|
]
|
410
412
|
"""
|
@@ -173,11 +173,38 @@ async def test_order_sync_swap():
|
|
173
173
|
# await ob.sub_orderbook(["SOL_USDT"]) # 订单簿频道
|
174
174
|
# # 示例:市价
|
175
175
|
# now= time.time()
|
176
|
-
result = await order_sync( ob, symbol="SOL_USDT", side="buy", order_type="
|
176
|
+
result = await order_sync( ob, symbol="SOL_USDT", side="buy", order_type="market", usdt_amount=8, price=200, window_sec=3)
|
177
177
|
print(result)
|
178
|
+
|
179
|
+
|
180
|
+
async def test_order_close_swap():
|
181
|
+
async with pybotters.Client(
|
182
|
+
apis={
|
183
|
+
"ourbit": [
|
184
|
+
"WEBb401428e69af1815808e470be0a4f4e8a70a5c5cc0b0df0a33220f689167c629"
|
185
|
+
]
|
186
|
+
}
|
187
|
+
) as client:
|
188
|
+
ob = OurbitSwap(client)
|
189
|
+
await ob.__aenter__()
|
190
|
+
# await ob.sub_personal() # 私有频道
|
191
|
+
# result = await order_sync( ob, symbol="SOL_USDT", side="buy", order_type="market", usdt_amount=8, price=200, window_sec=3)
|
192
|
+
# print(result)
|
193
|
+
# oid = await ob.place_order(
|
194
|
+
# "SOL_USDT",
|
195
|
+
# "buy",
|
196
|
+
# order_type="market",
|
197
|
+
# size=1
|
198
|
+
# )
|
199
|
+
|
200
|
+
# print(oid)
|
201
|
+
# await ob.update('position')
|
202
|
+
# print(ob.store.position.find())
|
203
|
+
|
204
|
+
await ob.place_order('SOL_USDT', 'close_sell', 1, position_id=6062178)
|
178
205
|
|
179
206
|
|
180
207
|
|
181
208
|
if __name__ == "__main__":
|
182
209
|
import asyncio
|
183
|
-
asyncio.run(
|
210
|
+
asyncio.run(test_order_close_swap())
|
hyperquant-0.55/tests/test.py
DELETED
@@ -1,183 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
import time
|
3
|
-
from typing import Optional, TypedDict, Literal
|
4
|
-
|
5
|
-
import pybotters
|
6
|
-
from hyperquant.broker.ourbit import OurbitSpot
|
7
|
-
from hyperquant.logkit import get_logger
|
8
|
-
|
9
|
-
logger = get_logger("alpine_test", "./data/alpine_smoke.log", True)
|
10
|
-
|
11
|
-
|
12
|
-
class Hold(TypedDict, total=False):
|
13
|
-
symbol: str
|
14
|
-
status: Literal["pending", "filled", "closing"]
|
15
|
-
oid: str
|
16
|
-
ts: float
|
17
|
-
p: float
|
18
|
-
q: float
|
19
|
-
filled_ts: float
|
20
|
-
|
21
|
-
|
22
|
-
class AlpineSmokeTest:
|
23
|
-
"""
|
24
|
-
仅针对 ALPINE_USDT:市价开仓 -> 持有 5 秒 -> 市价平仓。
|
25
|
-
- 目的:测试 WS 事件链路与下单路径稳定性(不依赖价差/策略信号)。
|
26
|
-
- 仅订阅 Ourbit 订单簿(可选) 和私有频道,验证参数与心跳。
|
27
|
-
"""
|
28
|
-
|
29
|
-
SYMBOL = "SOL_USDT"
|
30
|
-
|
31
|
-
def __init__(self, hold_seconds: float = 5.0, open_timeout_ms: int = 2000, close_timeout_ms: int = 3000):
|
32
|
-
self.hold_seconds = hold_seconds
|
33
|
-
self.open_timeout_ms = open_timeout_ms
|
34
|
-
self.close_timeout_ms = close_timeout_ms
|
35
|
-
self.client: Optional[pybotters.Client] = None
|
36
|
-
self.ourbit: Optional[OurbitSpot] = None
|
37
|
-
self.ob_store = None
|
38
|
-
|
39
|
-
def _msg_debug(self,msg: dict, ws):
|
40
|
-
|
41
|
-
if 'spot@private.orders' in msg.get("c", ""):
|
42
|
-
logger.info(f"orders消息: {msg}")
|
43
|
-
|
44
|
-
async def _msg_debug2(self):
|
45
|
-
with self.ob_store.orders.watch() as stream:
|
46
|
-
async for change in stream:
|
47
|
-
print(change)
|
48
|
-
|
49
|
-
async def __aenter__(self):
|
50
|
-
self.client = pybotters.Client(apis="./apis.json")
|
51
|
-
await self.client.__aenter__()
|
52
|
-
|
53
|
-
self.ourbit = OurbitSpot(self.client)
|
54
|
-
await self.ourbit.__aenter__()
|
55
|
-
self.ob_store = self.ourbit.store
|
56
|
-
|
57
|
-
# 仅订阅 ALPINE 订单簿与私有频道(用于测试 WS 参数与心跳)
|
58
|
-
await self.ourbit.sub_orderbook([self.SYMBOL])
|
59
|
-
await self.ourbit.sub_personal()
|
60
|
-
logger.info("已就绪:仅订阅 Ourbit ALPINE 订单簿/私有频道")
|
61
|
-
asyncio.create_task(self._msg_debug2())
|
62
|
-
return self
|
63
|
-
|
64
|
-
async def __aexit__(self, et, ev, tb):
|
65
|
-
if self.client:
|
66
|
-
await self.client.__aexit__(et, ev, tb)
|
67
|
-
|
68
|
-
# ============ helpers ============
|
69
|
-
async def _open_market_and_wait(self, symbol: str, usdt_amount: float, timeout_ms: int) -> Optional[Hold]:
|
70
|
-
"""以市价开仓并在本地流中等待终态。成功返回 Hold;失败/超时返回 None。"""
|
71
|
-
start = time.time() * 1000
|
72
|
-
with self.ob_store.orders.watch() as stream:
|
73
|
-
try:
|
74
|
-
oid = await self.ourbit.place_order(symbol, "buy", order_type="market", usdt_amount=usdt_amount)
|
75
|
-
logger.info(f"准备开仓: {symbol} oid={oid}")
|
76
|
-
except Exception as e:
|
77
|
-
logger.error(f"开仓下单失败 {symbol}: {e}")
|
78
|
-
return None
|
79
|
-
|
80
|
-
while True:
|
81
|
-
remain = timeout_ms - (time.time() * 1000 - start)
|
82
|
-
if remain <= 0:
|
83
|
-
logger.warning(f"开仓超时: {symbol} oid={oid}")
|
84
|
-
return None
|
85
|
-
try:
|
86
|
-
change = await asyncio.wait_for(stream.__anext__(), timeout=remain / 1000)
|
87
|
-
except asyncio.TimeoutError:
|
88
|
-
logger.warning(f"开仓超时: {symbol} oid={oid}")
|
89
|
-
return None
|
90
|
-
|
91
|
-
if change.operation != "delete":
|
92
|
-
continue
|
93
|
-
data = change.data
|
94
|
-
if data.get("order_id") != oid:
|
95
|
-
continue
|
96
|
-
|
97
|
-
state = data.get("state")
|
98
|
-
if state == "filled":
|
99
|
-
p = float(data["avg_price"]) ; q = float(data["deal_quantity"]) ; ts = time.time() * 1000
|
100
|
-
logger.ok(f"建仓成交: {symbol} p={p} q={q}")
|
101
|
-
return {"symbol": symbol, "status": "filled", "oid": oid, "p": p, "q": q, "filled_ts": ts}
|
102
|
-
if state in ("canceled", "expired", "rejected", "failed"):
|
103
|
-
logger.warning(f"开仓失败: {symbol} state={state}")
|
104
|
-
return None
|
105
|
-
|
106
|
-
async def _close_market_and_wait(self, symbol: str, qty: float, timeout_ms: int) -> bool:
|
107
|
-
"""以市价平仓并等待终态。成功返回 True。"""
|
108
|
-
start = time.time() * 1000
|
109
|
-
with self.ob_store.orders.watch() as stream:
|
110
|
-
try:
|
111
|
-
oid = await self.ourbit.place_order(symbol, "sell", order_type="market", quantity=qty)
|
112
|
-
logger.info(f"触发平仓: {symbol} qty={qty} oid={oid}")
|
113
|
-
except Exception as e:
|
114
|
-
logger.error(f"平仓下单失败 {symbol}: {e}")
|
115
|
-
return False
|
116
|
-
|
117
|
-
|
118
|
-
while True:
|
119
|
-
remain = timeout_ms - (time.time() * 1000 - start)
|
120
|
-
if remain <= 0:
|
121
|
-
logger.error(f"平仓超时: {symbol} oid={oid}")
|
122
|
-
return False
|
123
|
-
try:
|
124
|
-
change = await asyncio.wait_for(stream.__anext__(), timeout=remain / 1000)
|
125
|
-
except asyncio.TimeoutError:
|
126
|
-
logger.error(f"平仓超时: {symbol} oid={oid}")
|
127
|
-
return False
|
128
|
-
|
129
|
-
if change.operation != "delete":
|
130
|
-
continue
|
131
|
-
data = change.data
|
132
|
-
if data.get("order_id") != oid:
|
133
|
-
continue
|
134
|
-
|
135
|
-
state = data.get("state")
|
136
|
-
if state == "filled":
|
137
|
-
logger.ok(f"平仓完成: {symbol} close_p={data.get('avg_price')}")
|
138
|
-
return True
|
139
|
-
if state in ("canceled", "expired", "rejected", "failed"):
|
140
|
-
logger.error(f"平仓失败: {symbol} state={state}")
|
141
|
-
return False
|
142
|
-
|
143
|
-
# ============ one-shot test flow ============
|
144
|
-
async def run_once(self, usdt_amount: float = 10.0):
|
145
|
-
"""一次性流程:开仓 -> 持有 N 秒 -> 平仓。"""
|
146
|
-
# 1) 开仓
|
147
|
-
hold = await self._open_market_and_wait(self.SYMBOL, usdt_amount, timeout_ms=self.open_timeout_ms)
|
148
|
-
if not hold:
|
149
|
-
logger.warning("开仓未成功,终止本次测试")
|
150
|
-
return
|
151
|
-
|
152
|
-
# 2) 持有 N 秒
|
153
|
-
logger.info(f"开始持有 {self.hold_seconds}s: {self.SYMBOL} q={hold['q']} p={hold['p']}")
|
154
|
-
await asyncio.sleep(self.hold_seconds)
|
155
|
-
|
156
|
-
# 3) 平仓
|
157
|
-
ok = await self._close_market_and_wait(self.SYMBOL, float(hold["q"]), timeout_ms=self.close_timeout_ms)
|
158
|
-
if ok:
|
159
|
-
logger.info("本次测试完成 ✅")
|
160
|
-
else:
|
161
|
-
logger.error("本次测试失败 ❌")
|
162
|
-
|
163
|
-
async def run_loop(self, usdt_amount: float = 10.0, interval_seconds: float = 60.0):
|
164
|
-
"""循环执行 run_once,间隔 N 秒。"""
|
165
|
-
while True:
|
166
|
-
await self.run_once(usdt_amount)
|
167
|
-
logger.info(f"等待 {interval_seconds}s 后进行下一轮测试")
|
168
|
-
await asyncio.sleep(interval_seconds)
|
169
|
-
|
170
|
-
|
171
|
-
async def main():
|
172
|
-
async with AlpineSmokeTest(hold_seconds=5.0) as test:
|
173
|
-
# await test.run_once(usdt_amount=10.0)
|
174
|
-
await test.run_loop(usdt_amount=10.0, interval_seconds=5)
|
175
|
-
|
176
|
-
|
177
|
-
if __name__ == "__main__":
|
178
|
-
try:
|
179
|
-
asyncio.run(main())
|
180
|
-
except KeyboardInterrupt:
|
181
|
-
print("\n👋 程序已退出")
|
182
|
-
except Exception as e:
|
183
|
-
print(f"❌ 程序异常: {e}")
|
@@ -1,82 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
import time
|
3
|
-
from hyperquant.broker.ourbit import OurbitSpot
|
4
|
-
import pybotters
|
5
|
-
from hyperquant.logkit import get_logger
|
6
|
-
|
7
|
-
logger = get_logger('test_order_sync', './data/logs/test_order_sync.log', show_time=True)
|
8
|
-
|
9
|
-
|
10
|
-
# 等待指定 oid 的最终 delete,超时抛 TimeoutError
|
11
|
-
async def wait_delete(stream: pybotters.StoreStream, oid: str, seconds: float):
|
12
|
-
async with asyncio.timeout(seconds):
|
13
|
-
while True:
|
14
|
-
change = await stream.__anext__()
|
15
|
-
if change.operation == "delete" and change.data.get("order_id") == oid:
|
16
|
-
return change.data # 内含 state / avg_price / deal_quantity 等累计字段
|
17
|
-
|
18
|
-
|
19
|
-
async def order_sync(
|
20
|
-
ob: OurbitSpot,
|
21
|
-
symbol: str = "SOL_USDT",
|
22
|
-
side: str = "buy",
|
23
|
-
order_type: str = "market", # "market" / "limit"
|
24
|
-
usdt_amount: float | None = None, # 市价可填
|
25
|
-
quantity: float | None = None, # 市价可填
|
26
|
-
price: float | None = None, # 限价必填
|
27
|
-
window_sec: float = 2.0, # 主等待窗口(限价可设为 5.0)
|
28
|
-
grace_sec: float = 2, # 撤单后宽限
|
29
|
-
):
|
30
|
-
with ob.store.orders.watch() as stream:
|
31
|
-
# 下单(只保留最简两种入参形态)
|
32
|
-
try:
|
33
|
-
oid = await ob.place_order(
|
34
|
-
symbol,
|
35
|
-
side,
|
36
|
-
order_type=order_type,
|
37
|
-
usdt_amount=usdt_amount,
|
38
|
-
quantity=quantity,
|
39
|
-
price=price,
|
40
|
-
)
|
41
|
-
except Exception as e:
|
42
|
-
return {"symbol": symbol, "state": "error", "error": str(e)}
|
43
|
-
|
44
|
-
# 步骤1:主窗口内等待这单的最终 delete
|
45
|
-
try:
|
46
|
-
return await wait_delete(stream, oid, window_sec)
|
47
|
-
except TimeoutError:
|
48
|
-
# 步骤2:到点撤单(市价通常用不到;限价才有意义)
|
49
|
-
for i in range(3):
|
50
|
-
try:
|
51
|
-
await ob.cancel_order(oid)
|
52
|
-
break
|
53
|
-
except Exception:
|
54
|
-
pass
|
55
|
-
await asyncio.sleep(0.1)
|
56
|
-
# 固定宽限内再等“迟到”的最终 delete
|
57
|
-
try:
|
58
|
-
return await wait_delete(stream, oid, grace_sec)
|
59
|
-
except TimeoutError:
|
60
|
-
return {"order_id": oid, "symbol": symbol, "state": "timeout"}
|
61
|
-
|
62
|
-
|
63
|
-
async def test_order_sync():
|
64
|
-
async with pybotters.Client(
|
65
|
-
apis={
|
66
|
-
"ourbit": [
|
67
|
-
"WEB3bf088f8b2f2fae07592fe1a6240e2d798100a9cb2a91f8fda1056b6865ab3ee"
|
68
|
-
]
|
69
|
-
}
|
70
|
-
) as client:
|
71
|
-
ob = OurbitSpot(client)
|
72
|
-
await ob.__aenter__()
|
73
|
-
await ob.sub_personal() # 私有频道
|
74
|
-
ob.store.book.limit = 3
|
75
|
-
await ob.sub_orderbook(["SOL_USDT"]) # 订单簿频道
|
76
|
-
# # 示例:市价
|
77
|
-
# now= time.time()
|
78
|
-
result = await order_sync( ob, symbol="SOL_USDT", side="buy", order_type="market", usdt_amount=8, price=200, window_sec=2)
|
79
|
-
print(result)
|
80
|
-
|
81
|
-
|
82
|
-
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
import pybotters
|
3
|
-
|
4
|
-
|
5
|
-
async def main():
|
6
|
-
ds = pybotters.store.DataStore(keys=["id"])
|
7
|
-
loop = asyncio.get_running_loop()
|
8
|
-
|
9
|
-
wait_task = loop.create_task(ds.wait())
|
10
|
-
loop.call_soon(
|
11
|
-
ds._insert, [{"id": 1, "val": 1}, {"id": 2, "val": 2}, {"id": 3, "val": 3}]
|
12
|
-
)
|
13
|
-
await asyncio.wait_for(wait_task, timeout=5.0)
|
14
|
-
|
15
|
-
ds._insert, [{"id": 1, "val": 1}, {"id": 2, "val": 2}, {"id": 3, "val": 3}]
|
16
|
-
|
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
|
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
|