hyperquant 0.88__tar.gz → 0.89__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.88 → hyperquant-0.89}/PKG-INFO +1 -1
- {hyperquant-0.88 → hyperquant-0.89}/pyproject.toml +1 -1
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/coinw.py +47 -11
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/coinw.py +49 -34
- {hyperquant-0.88 → hyperquant-0.89}/tests/test_coinw.py +49 -17
- {hyperquant-0.88 → hyperquant-0.89}/uv.lock +1 -1
- {hyperquant-0.88 → hyperquant-0.89}/.gitignore +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/.python-version +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/README.md +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/apis.json +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/data/alpine_smoke.log +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/data/logs/notikit.log +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/data/logs/test_order_sync.log +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/data/records_swap.csv +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/data/records_swapc.csv +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/doc/lbank.md +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/pub.sh +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/requirements-dev.lock +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/requirements.lock +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/__init__.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/auth.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/bitget.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/edgex.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/hyperliquid.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lbank.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lib/hpstore.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lib/hyper_types.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/lib/util.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/bitget.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/edgex.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/hyperliquid.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/lbank.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/models/ourbit.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/ourbit.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/broker/ws.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/core.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/datavison/_util.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/datavison/binance.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/datavison/coinglass.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/datavison/okx.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/db.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/draw.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/logkit.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/src/hyperquant/notikit.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/tests/test_bitget.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/tests/test_draw.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/tests/test_edgex.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/tests/test_lbank.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/tests/test_ourbit.py +0 -0
- {hyperquant-0.88 → hyperquant-0.89}/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.89
|
|
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
|
|
@@ -60,11 +60,10 @@ class Coinw:
|
|
|
60
60
|
"all",
|
|
61
61
|
] = "all",
|
|
62
62
|
*,
|
|
63
|
-
instrument: str | None = None,
|
|
64
63
|
position_type: Literal["execute", "plan", "planTrigger"] = "execute",
|
|
65
64
|
page: int | None = None,
|
|
66
65
|
page_size: int | None = None,
|
|
67
|
-
|
|
66
|
+
instrument: str | None = None,
|
|
68
67
|
) -> None:
|
|
69
68
|
"""刷新本地缓存,使用 CoinW REST API。
|
|
70
69
|
|
|
@@ -108,16 +107,8 @@ class Coinw:
|
|
|
108
107
|
)
|
|
109
108
|
|
|
110
109
|
if include_position:
|
|
111
|
-
if not instrument:
|
|
112
|
-
raise ValueError("instrument is required when updating positions")
|
|
113
|
-
params = {"instrument": instrument}
|
|
114
|
-
if open_ids:
|
|
115
|
-
params["openIds"] = open_ids
|
|
116
110
|
requests.append(
|
|
117
|
-
self.client.get(
|
|
118
|
-
f"{self.rest_api}/v1/perpum/positions",
|
|
119
|
-
params=params,
|
|
120
|
-
)
|
|
111
|
+
self.client.get(f"{self.rest_api}/v1/perpum/positions/all")
|
|
121
112
|
)
|
|
122
113
|
|
|
123
114
|
if include_balance:
|
|
@@ -186,6 +177,51 @@ class Coinw:
|
|
|
186
177
|
data = await res.json()
|
|
187
178
|
return self._ensure_ok("place_order", data)
|
|
188
179
|
|
|
180
|
+
async def close_position(
|
|
181
|
+
self,
|
|
182
|
+
open_id: str | int,
|
|
183
|
+
*,
|
|
184
|
+
position_type: Literal["plan", "planTrigger", "execute"] = "plan",
|
|
185
|
+
close_num: str | float | int | None = None,
|
|
186
|
+
close_rate: str | float | int | None = None,
|
|
187
|
+
order_price: str | float | None = None,
|
|
188
|
+
instrument: str | None = None,
|
|
189
|
+
) -> dict[str, Any]:
|
|
190
|
+
"""关闭单个仓位(``DELETE /v1/perpum/positions``)。
|
|
191
|
+
|
|
192
|
+
Params
|
|
193
|
+
------
|
|
194
|
+
open_id: ``openId`` / 持仓唯一 ID。
|
|
195
|
+
position_type: 订单类型 ``plan`` / ``planTrigger`` / ``execute``。
|
|
196
|
+
close_num: 按合约数量平仓(与 ``close_rate`` 至少指定其一)。
|
|
197
|
+
close_rate: 按比例平仓(0-1)。
|
|
198
|
+
order_price: 限价平仓时指定价格。
|
|
199
|
+
instrument: 交易品种(部分情况下需要传入,例如限价单)。
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
if close_num is None and close_rate is None:
|
|
203
|
+
raise ValueError("close_num or close_rate must be provided")
|
|
204
|
+
|
|
205
|
+
payload: dict[str, Any] = {
|
|
206
|
+
"id": str(open_id),
|
|
207
|
+
"positionType": position_type,
|
|
208
|
+
}
|
|
209
|
+
if close_num is not None:
|
|
210
|
+
payload["closeNum"] = str(close_num)
|
|
211
|
+
if close_rate is not None:
|
|
212
|
+
payload["closeRate"] = str(close_rate)
|
|
213
|
+
if order_price is not None:
|
|
214
|
+
payload["orderPrice"] = str(order_price)
|
|
215
|
+
if instrument is not None:
|
|
216
|
+
payload["instrument"] = instrument
|
|
217
|
+
|
|
218
|
+
res = await self.client.delete(
|
|
219
|
+
f"{self.rest_api}/v1/perpum/positions",
|
|
220
|
+
data=payload,
|
|
221
|
+
)
|
|
222
|
+
data = await res.json()
|
|
223
|
+
return self._ensure_ok("close_position", data)
|
|
224
|
+
|
|
189
225
|
async def place_order_web(
|
|
190
226
|
self,
|
|
191
227
|
instrument: str,
|
|
@@ -282,20 +282,6 @@ class Position(DataStore):
|
|
|
282
282
|
|
|
283
283
|
_KEYS = ["openId"]
|
|
284
284
|
|
|
285
|
-
@staticmethod
|
|
286
|
-
def _normalize(entry: dict[str, Any]) -> dict[str, Any] | None:
|
|
287
|
-
open_id = entry.get("openId")
|
|
288
|
-
if open_id is None:
|
|
289
|
-
return None
|
|
290
|
-
normalized = dict(entry)
|
|
291
|
-
normalized["openId"] = str(open_id)
|
|
292
|
-
normalized["status"] = str(entry.get("status") or entry.get("orderStatus") or "").lower()
|
|
293
|
-
normalized["is_closed"] = normalized["status"] in {"close", "closed", "finish"}
|
|
294
|
-
normalized["currentPiece"] = str(entry.get("currentPiece")) if entry.get("currentPiece") is not None else None
|
|
295
|
-
normalized["closedPiece"] = str(entry.get("closedPiece")) if entry.get("closedPiece") is not None else None
|
|
296
|
-
normalized["quantity"] = str(entry.get("quantity")) if entry.get("quantity") is not None else None
|
|
297
|
-
normalized["updatedDate"] = entry.get("updatedDate")
|
|
298
|
-
return normalized
|
|
299
285
|
|
|
300
286
|
def _onresponse(self, data: Any) -> None:
|
|
301
287
|
payload = []
|
|
@@ -308,9 +294,8 @@ class Position(DataStore):
|
|
|
308
294
|
for entry in payload or []:
|
|
309
295
|
if not isinstance(entry, dict):
|
|
310
296
|
continue
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
items.append(normalized)
|
|
297
|
+
entry['openId'] = str(entry.get("id"))
|
|
298
|
+
items.append(entry)
|
|
314
299
|
|
|
315
300
|
self._clear()
|
|
316
301
|
if items:
|
|
@@ -318,6 +303,7 @@ class Position(DataStore):
|
|
|
318
303
|
|
|
319
304
|
def _on_message(self, msg: dict[str, Any]) -> None:
|
|
320
305
|
data = msg.get("data")
|
|
306
|
+
|
|
321
307
|
if isinstance(data, dict) and data.get("result") is not None:
|
|
322
308
|
return
|
|
323
309
|
|
|
@@ -337,17 +323,14 @@ class Position(DataStore):
|
|
|
337
323
|
for entry in entries:
|
|
338
324
|
if not isinstance(entry, dict):
|
|
339
325
|
continue
|
|
340
|
-
normalized =
|
|
341
|
-
if not normalized:
|
|
342
|
-
continue
|
|
326
|
+
normalized = entry
|
|
343
327
|
|
|
344
|
-
criteria = {"openId": normalized["openId"]}
|
|
345
328
|
|
|
346
|
-
if normalized.get("
|
|
347
|
-
to_delete.append(
|
|
329
|
+
if normalized.get("status") == 'close':
|
|
330
|
+
to_delete.append(normalized)
|
|
348
331
|
continue
|
|
349
332
|
|
|
350
|
-
if self.find(
|
|
333
|
+
if self.find(normalized):
|
|
351
334
|
to_update.append(normalized)
|
|
352
335
|
else:
|
|
353
336
|
to_insert.append(normalized)
|
|
@@ -460,6 +443,7 @@ class CoinwFuturesDataStore(DataStoreCollection):
|
|
|
460
443
|
|
|
461
444
|
def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
|
|
462
445
|
msg_type = msg.get("type")
|
|
446
|
+
# print(msg)
|
|
463
447
|
if msg_type == "depth":
|
|
464
448
|
self.book._on_message(msg)
|
|
465
449
|
elif msg_type == "order":
|
|
@@ -481,7 +465,7 @@ class CoinwFuturesDataStore(DataStoreCollection):
|
|
|
481
465
|
self.ticker._onresponse(data)
|
|
482
466
|
elif res.url.path == "/v1/perpum/orders/open":
|
|
483
467
|
self.orders._onresponse(data)
|
|
484
|
-
elif res.url.path == "/v1/perpum/positions":
|
|
468
|
+
elif res.url.path == "/v1/perpum/positions/all":
|
|
485
469
|
self.position._onresponse(data)
|
|
486
470
|
elif res.url.path == "/v1/perpum/account/getUserAssets":
|
|
487
471
|
self.balance._onresponse(data)
|
|
@@ -666,17 +650,48 @@ class CoinwFuturesDataStore(DataStoreCollection):
|
|
|
666
650
|
- REST: ``GET /v1/perpum/positions``
|
|
667
651
|
- WebSocket: ``type == "position"``
|
|
668
652
|
|
|
669
|
-
|
|
653
|
+
.. code:: json
|
|
670
654
|
|
|
671
655
|
{
|
|
672
|
-
"
|
|
673
|
-
"
|
|
674
|
-
"
|
|
675
|
-
"
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
"
|
|
679
|
-
|
|
656
|
+
"currentPiece": "0",
|
|
657
|
+
"isProfession": 0,
|
|
658
|
+
"leverage": "10",
|
|
659
|
+
"originalType": "execute",
|
|
660
|
+
"orderId": "33309059291614824",
|
|
661
|
+
"contractType": 1,
|
|
662
|
+
"openId": "2435521222638707873",
|
|
663
|
+
"fee": "0.00020724",
|
|
664
|
+
"openPrice": "0.3456",
|
|
665
|
+
"orderStatus": "finish",
|
|
666
|
+
"instrument": "JUP",
|
|
667
|
+
"quantityUnit": 1,
|
|
668
|
+
"source": "api",
|
|
669
|
+
"updatedDate": 1761192795412,
|
|
670
|
+
"positionModel": 1,
|
|
671
|
+
"feeRate": "0.0006",
|
|
672
|
+
"netProfit": "-0.00040724",
|
|
673
|
+
"baseSize": "1",
|
|
674
|
+
"quote": "usdt",
|
|
675
|
+
"liquidateBy": "manual",
|
|
676
|
+
"totalPiece": "1",
|
|
677
|
+
"orderPrice": "0",
|
|
678
|
+
"id": "23469279597150213",
|
|
679
|
+
"fundingSettle": "0",
|
|
680
|
+
"direction": "long",
|
|
681
|
+
"margin": "0.03435264",
|
|
682
|
+
"takerMaker": 1,
|
|
683
|
+
"indexPrice": "0.3455",
|
|
684
|
+
"quantity": "0.03456",
|
|
685
|
+
"userId": "1757458",
|
|
686
|
+
"closedPiece": "1",
|
|
687
|
+
"createdDate": 1761192793000,
|
|
688
|
+
"hedgeId": "23469279597150214",
|
|
689
|
+
"closePrice": "0.3454",
|
|
690
|
+
"positionMargin": "0.03435264",
|
|
691
|
+
"base": "jup",
|
|
692
|
+
"realPrice": "0.3454",
|
|
693
|
+
"status": "close"
|
|
694
|
+
}
|
|
680
695
|
"""
|
|
681
696
|
|
|
682
697
|
return self._get("position", Position)
|
|
@@ -23,8 +23,11 @@ async def test_update_private() -> None:
|
|
|
23
23
|
"""Refresh private endpoints (requires ./apis.json with coinw credentials)."""
|
|
24
24
|
async with pybotters.Client(apis="./apis.json") as client:
|
|
25
25
|
async with Coinw(client) as cw:
|
|
26
|
-
await cw.update("balance")
|
|
27
|
-
print(cw.store.balance.find())
|
|
26
|
+
# await cw.update("balance")
|
|
27
|
+
# print(cw.store.balance.find())
|
|
28
|
+
await cw.update("position")
|
|
29
|
+
print(cw.store.position.find())
|
|
30
|
+
|
|
28
31
|
# await cw.update("position", instrument="BTC")
|
|
29
32
|
# print(cw.store.position.find())
|
|
30
33
|
# await cw.update("orders", instrument="BTC")
|
|
@@ -98,6 +101,36 @@ async def test_place_cancel() -> None:
|
|
|
98
101
|
position_model=1,
|
|
99
102
|
)
|
|
100
103
|
|
|
104
|
+
async def test_close_position():
|
|
105
|
+
async with pybotters.Client(apis="./apis.json") as client:
|
|
106
|
+
async with Coinw(client) as cw:
|
|
107
|
+
# await cw.update('position')
|
|
108
|
+
await cw.sub_personal()
|
|
109
|
+
with cw.store.position.watch() as watcher:
|
|
110
|
+
# 2435521222638707402
|
|
111
|
+
resp = await cw.place_order(
|
|
112
|
+
instrument="JUP",
|
|
113
|
+
direction="long",
|
|
114
|
+
quantity_unit=1,
|
|
115
|
+
leverage=10,
|
|
116
|
+
quantity=1,
|
|
117
|
+
position_model='cross',
|
|
118
|
+
position_type='execute'
|
|
119
|
+
)
|
|
120
|
+
print("开仓响应:", resp)
|
|
121
|
+
|
|
122
|
+
await asyncio.sleep(2)
|
|
123
|
+
|
|
124
|
+
for position in cw.store.position.find():
|
|
125
|
+
open_id = position.get('openId')
|
|
126
|
+
print(f'关闭持仓: {open_id}')
|
|
127
|
+
resp = await cw.close_position(open_id, position_type='execute', close_num=1)
|
|
128
|
+
print(resp)
|
|
129
|
+
|
|
130
|
+
async for change in watcher:
|
|
131
|
+
print(change)
|
|
132
|
+
print('\n\n----\n\n')
|
|
133
|
+
|
|
101
134
|
async def test_place_web() -> None:
|
|
102
135
|
"""Use the web interface to place an order (requires device/token)."""
|
|
103
136
|
async with pybotters.Client(apis="./apis.json") as client:
|
|
@@ -145,7 +178,7 @@ async def order_sync_polling(
|
|
|
145
178
|
|
|
146
179
|
try:
|
|
147
180
|
async with asyncio.timeout(window_sec):
|
|
148
|
-
with broker.store.
|
|
181
|
+
with broker.store.position.watch() as stream:
|
|
149
182
|
started = int(time.time() * 1000)
|
|
150
183
|
resp = await broker.place_order(
|
|
151
184
|
instrument=instrument,
|
|
@@ -176,16 +209,13 @@ async def order_sync_polling(
|
|
|
176
209
|
order_id = str(raw_id)
|
|
177
210
|
|
|
178
211
|
async for change in stream:
|
|
179
|
-
|
|
212
|
+
print(change.data)
|
|
180
213
|
data = change.data or {}
|
|
181
|
-
if str(data.get("
|
|
214
|
+
if str(data.get("orderId")) != order_id:
|
|
182
215
|
continue
|
|
183
216
|
snapshot = data
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if change.operation == "delete":
|
|
187
|
-
return change.source
|
|
188
|
-
if status in {"finish", "cancel", "canceled"}:
|
|
217
|
+
order_finish = data.get('orderStatus') == 'finish'
|
|
218
|
+
if order_finish:
|
|
189
219
|
return snapshot
|
|
190
220
|
|
|
191
221
|
except TimeoutError:
|
|
@@ -207,10 +237,12 @@ async def test_subp() -> None:
|
|
|
207
237
|
async with pybotters.Client(apis="./apis.json") as client:
|
|
208
238
|
async with Coinw(client) as cw:
|
|
209
239
|
await cw.sub_personal()
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
240
|
+
await asyncio.sleep(1.0)
|
|
241
|
+
print(cw.store.position.find())
|
|
242
|
+
# with cw.store.position.watch() as watcher:
|
|
243
|
+
# async for change in watcher:
|
|
244
|
+
# print(change)
|
|
245
|
+
# print('\n\n----\n\n')
|
|
214
246
|
|
|
215
247
|
|
|
216
248
|
async def test_order_sync_polling() -> None:
|
|
@@ -221,9 +253,9 @@ async def test_order_sync_polling() -> None:
|
|
|
221
253
|
print("Placing order with order_sync_polling...")
|
|
222
254
|
result = await order_sync_polling(
|
|
223
255
|
broker=cw,
|
|
224
|
-
instrument="
|
|
256
|
+
instrument="JUP",
|
|
225
257
|
direction="long",
|
|
226
|
-
leverage=
|
|
258
|
+
leverage=10,
|
|
227
259
|
quantity=1,
|
|
228
260
|
quantity_unit=1,
|
|
229
261
|
position_model="cross",
|
|
@@ -235,4 +267,4 @@ async def test_order_sync_polling() -> None:
|
|
|
235
267
|
print("order_sync_polling result:", result)
|
|
236
268
|
|
|
237
269
|
if __name__ == "__main__":
|
|
238
|
-
asyncio.run(
|
|
270
|
+
asyncio.run(test_close_position())
|
|
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
|
|
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
|