hyperquant 0.91__tar.gz → 0.92__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.

Potentially problematic release.


This version of hyperquant might be problematic. Click here for more details.

Files changed (56) hide show
  1. {hyperquant-0.91 → hyperquant-0.92}/PKG-INFO +1 -1
  2. {hyperquant-0.91 → hyperquant-0.92}/pyproject.toml +1 -1
  3. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/coinup.py +9 -8
  4. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/models/coinup.py +2 -1
  5. hyperquant-0.92/tests/test_coinup.py +281 -0
  6. {hyperquant-0.91 → hyperquant-0.92}/uv.lock +1 -1
  7. hyperquant-0.91/tests/test_coinup.py +0 -41
  8. {hyperquant-0.91 → hyperquant-0.92}/.gitignore +0 -0
  9. {hyperquant-0.91 → hyperquant-0.92}/.python-version +0 -0
  10. {hyperquant-0.91 → hyperquant-0.92}/README.md +0 -0
  11. {hyperquant-0.91 → hyperquant-0.92}/apis.json +0 -0
  12. {hyperquant-0.91 → hyperquant-0.92}/data/alpine_smoke.log +0 -0
  13. {hyperquant-0.91 → hyperquant-0.92}/data/logs/notikit.log +0 -0
  14. {hyperquant-0.91 → hyperquant-0.92}/data/logs/test_order_sync.log +0 -0
  15. {hyperquant-0.91 → hyperquant-0.92}/data/records_swap.csv +0 -0
  16. {hyperquant-0.91 → hyperquant-0.92}/data/records_swapc.csv +0 -0
  17. {hyperquant-0.91 → hyperquant-0.92}/doc/coinup.md +0 -0
  18. {hyperquant-0.91 → hyperquant-0.92}/doc/lbank.md +0 -0
  19. {hyperquant-0.91 → hyperquant-0.92}/pub.sh +0 -0
  20. {hyperquant-0.91 → hyperquant-0.92}/requirements-dev.lock +0 -0
  21. {hyperquant-0.91 → hyperquant-0.92}/requirements.lock +0 -0
  22. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/__init__.py +0 -0
  23. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/auth.py +0 -0
  24. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/bitget.py +0 -0
  25. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/coinw.py +0 -0
  26. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/edgex.py +0 -0
  27. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/hyperliquid.py +0 -0
  28. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/lbank.py +0 -0
  29. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  30. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/lib/hpstore.py +0 -0
  31. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  32. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/lib/util.py +0 -0
  33. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/models/bitget.py +0 -0
  34. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/models/coinw.py +0 -0
  35. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/models/edgex.py +0 -0
  36. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  37. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/models/lbank.py +0 -0
  38. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/models/ourbit.py +0 -0
  39. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/ourbit.py +0 -0
  40. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/broker/ws.py +0 -0
  41. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/core.py +0 -0
  42. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/datavison/_util.py +0 -0
  43. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/datavison/binance.py +0 -0
  44. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/datavison/coinglass.py +0 -0
  45. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/datavison/okx.py +0 -0
  46. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/db.py +0 -0
  47. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/draw.py +0 -0
  48. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/logkit.py +0 -0
  49. {hyperquant-0.91 → hyperquant-0.92}/src/hyperquant/notikit.py +0 -0
  50. {hyperquant-0.91 → hyperquant-0.92}/tests/test_bitget.py +0 -0
  51. {hyperquant-0.91 → hyperquant-0.92}/tests/test_coinw.py +0 -0
  52. {hyperquant-0.91 → hyperquant-0.92}/tests/test_draw.py +0 -0
  53. {hyperquant-0.91 → hyperquant-0.92}/tests/test_edgex.py +0 -0
  54. {hyperquant-0.91 → hyperquant-0.92}/tests/test_lbank.py +0 -0
  55. {hyperquant-0.91 → hyperquant-0.92}/tests/test_ourbit.py +0 -0
  56. {hyperquant-0.91 → hyperquant-0.92}/tests/tmp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.91
3
+ Version: 0.92
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,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "0.91"
3
+ version = "0.92"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -209,27 +209,27 @@ class Coinup:
209
209
 
210
210
  async def sub_orderbook(
211
211
  self,
212
- channels: Sequence[str] | str,
212
+ symbols: Sequence[str] | str,
213
213
  *,
214
214
  depth_step: str = "step0",
215
215
  depth_limit: int | None = None,
216
216
  ) -> pybotters.ws.WebSocketApp:
217
217
  """订阅订单簿深度频道。
218
218
 
219
- 参数 ``channels`` 支持 ``subSymbol`` 或完整频道名称:
220
-
221
219
  - ``"e_wlfiusdt"`` -> 发送 ``market_e_wlfiusdt_depth_step0``
222
220
  - ``"market_e_wlfiusdt_depth_step0"`` -> 原样发送
223
221
  """
224
222
 
225
- if isinstance(channels, str):
226
- channels = [channels]
223
+ if isinstance(symbols, str):
224
+ symbols = [symbols]
227
225
 
228
226
  payloads = []
229
- for channel in channels:
230
- ch = channel.lower()
227
+ for symbol in symbols:
228
+ ch = symbol.lower()
229
+ # 去除特殊符号
230
+ ch = ch.replace("-", "").replace("_", "")
231
231
  if not ch.startswith("market_"):
232
- ch = f"market_{ch}_depth_{depth_step}"
232
+ ch = f"market_e_{ch}_depth_{depth_step}"
233
233
  payloads.append(
234
234
  {
235
235
  "event": "sub",
@@ -239,6 +239,7 @@ class Coinup:
239
239
  },
240
240
  }
241
241
  )
242
+ print(payloads)
242
243
 
243
244
  if not payloads:
244
245
  raise ValueError("channels must not be empty")
@@ -32,13 +32,14 @@ class Book(DataStore):
32
32
  bids = bids[: self.limit]
33
33
  chanel = msg["channel"]
34
34
  symbol = chanel.split("_")[2].upper()
35
+ symbol = symbol.replace("USDT", "-USDT")
35
36
  asks = [
36
37
  {"s": symbol, "S": "a", "p": float(price), "q": float(quantity)} for price, quantity in asks
37
38
  ]
38
39
  bids = [
39
40
  {"s": symbol, "S": "b", "p": float(price), "q": float(quantity)} for price, quantity in bids
40
41
  ]
41
- self._clear()
42
+ self._find_and_delete({"s": symbol})
42
43
  self._insert(asks + bids)
43
44
 
44
45
  class Detail(DataStore):
@@ -0,0 +1,281 @@
1
+ import pybotters
2
+ from hyperquant.broker.coinup import Coinup, CoinUpDataStore
3
+ import asyncio
4
+ from logging import Logger
5
+ import time
6
+ from dataclasses import dataclass
7
+ from typing import Any, Literal
8
+ from hyperquant.logkit import get_logger
9
+ from hyperquant.broker.coinup import Coinup
10
+
11
+ apis = {"coinup": ["948a3841361bd38c30fc37355c3fb01a35135042c1c44698bed6f9cd6262cce0"]}
12
+
13
+
14
+ async def test_update():
15
+ async with pybotters.Client(apis=apis) as client:
16
+ async with Coinup(client=client) as broker:
17
+ # await broker.update("detail")
18
+ # print(broker.store.detail.get({"symbol": "WLFI-USDT"}))
19
+ # await broker.update("position")
20
+ # print(broker.store.position.find())
21
+ await broker.update("history_orders")
22
+ print(broker.store.history_orders.find())
23
+
24
+ # await broker.update('orders')
25
+ # print(broker.store.orders.find())
26
+
27
+ async def test_cancel_order():
28
+ async with pybotters.Client(apis=apis) as client:
29
+ async with Coinup(client=client) as broker:
30
+ await broker.cancel_order('169', '2951914186269633768')
31
+
32
+ async def test_place_order():
33
+ async with pybotters.Client(apis=apis) as client:
34
+ async with Coinup(client=client) as broker:
35
+ # 市价单
36
+ order = await broker.place_order(
37
+ 'WLFI-USDT',
38
+ side='sell',
39
+ volume='1',
40
+ order_type='market',
41
+ offset='CLOSE',
42
+ )
43
+ print(order)
44
+
45
+ async def test_sub_book():
46
+ async with pybotters.Client(apis=apis) as client:
47
+ async with Coinup(client=client) as broker:
48
+ symbols = [d['symbol'] for d in broker.store.detail.find()]
49
+ symbols = symbols[:30]
50
+
51
+ await broker.sub_orderbook(symbols, depth_limit=1)
52
+ print("Subscribed orderbook", symbols)
53
+ while True:
54
+ await asyncio.sleep(1)
55
+ print(broker.store.book.find())
56
+
57
+
58
+ def _to_float(value: Any) -> float:
59
+ try:
60
+ if value is None:
61
+ return 0.0
62
+ return float(value)
63
+ except (TypeError, ValueError):
64
+ return 0.0
65
+
66
+
67
+ def _extract_position_volume(snapshot: dict[str, Any] | None) -> float:
68
+ if not snapshot:
69
+ return 0.0
70
+ return _to_float(
71
+ snapshot.get("canCloseVolume")
72
+ or snapshot.get("positionVolume")
73
+ or snapshot.get("volume")
74
+ )
75
+
76
+
77
+ def _extract_order_id(payload: Any) -> str | None:
78
+ if payload is None:
79
+ return None
80
+ if 'ids' in payload:
81
+ ids = payload.get('ids')
82
+ if isinstance(ids, list) and ids:
83
+ return str(ids[0])
84
+ else:
85
+ return None
86
+
87
+ def _extract_order_state(snapshot: dict[str, Any] | None) -> str | None:
88
+ if not snapshot:
89
+ return None
90
+ for key in ("status", "state", "orderStatus", "statusName"):
91
+ value = snapshot.get(key)
92
+ if value in (None, ""):
93
+ continue
94
+ return str(value)
95
+ return None
96
+
97
+
98
+ def _is_order_final(snapshot: dict[str, Any] | None) -> bool:
99
+ state = _extract_order_state(snapshot)
100
+ if state is None:
101
+ return False
102
+
103
+ normalized = state.strip().lower()
104
+ if normalized in {
105
+ "0",
106
+ "1",
107
+ "new",
108
+ "pending",
109
+ "processing",
110
+ "submitting",
111
+ "submitted",
112
+ "wait",
113
+ "waiting",
114
+ "partial",
115
+ "partialfill",
116
+ "partial-filled",
117
+ "partially_filled",
118
+ "partially-filled",
119
+ }:
120
+ return False
121
+
122
+ try:
123
+ numeric = float(normalized)
124
+ except ValueError:
125
+ pass
126
+ else:
127
+ # CoinUp 使用 0/1 表示活跃状态,其余视为终态
128
+ if int(numeric) in (0, 1):
129
+ return False
130
+ return True
131
+
132
+ if any(token in normalized for token in ("cancel", "done", "finish", "filled", "complete", "success", "fail")):
133
+ return True
134
+
135
+ # 其他未知状态保持保守,由调用方通过时间/仓位变化判断
136
+ return False
137
+
138
+
139
+ @dataclass
140
+ class OrderSyncResult:
141
+ position: dict[str, Any] | None
142
+ order: dict[str, Any] | None
143
+ before_volume: float
144
+ after_volume: float
145
+
146
+
147
+ async def order_sync_polling(
148
+ broker: Coinup,
149
+ *,
150
+ symbol: str,
151
+ side: Literal["BUY", "SELL"],
152
+ volume: float | int | str,
153
+ offset: Literal["OPEN", "CLOSE"],
154
+ order_type: Literal["limit", "market", 1, 2] = "limit",
155
+ price: float | int | str | None = None,
156
+ leverage_level: int | str = 1,
157
+ position_type: int | str = 1,
158
+ order_unit: int | str = 2,
159
+ expect_change: float = 0.0,
160
+ window_sec: float = 6.0,
161
+ poll_interval: float = 0.5,
162
+ cancel_retry: int = 3,
163
+ logger: Logger | None = None,
164
+ ) -> OrderSyncResult:
165
+ await broker.update("position")
166
+ baseline = broker.store.position.get({"symbol": symbol})
167
+ before_volume = _extract_position_volume(baseline)
168
+
169
+ contract_id = broker.get_contract_id(symbol)
170
+ history_payload = {"contractId": contract_id} if contract_id else None
171
+
172
+ order_id: str | None = None
173
+ need_cancel = True
174
+ current_volume = before_volume
175
+ position_snapshot: dict[str, Any] | None = None
176
+ order_snapshot: dict[str, Any] | None = None
177
+ try:
178
+ started_at = time.time() * 1000
179
+ response = await broker.place_order(
180
+ symbol=symbol,
181
+ side=side,
182
+ volume=volume,
183
+ order_type=order_type,
184
+ price=price,
185
+ position_type=position_type,
186
+ leverage_level=leverage_level,
187
+ offset=offset,
188
+ order_unit=order_unit,
189
+ )
190
+ latency = int(time.time() * 1000 - started_at)
191
+ if logger:
192
+ logger.info(f"下单延迟 {latency} ms")
193
+ order_id = _extract_order_id(response)
194
+ if order_id is None:
195
+ raise RuntimeError(f"place_order 缺少 order_id: {response}")
196
+ except Exception:
197
+ raise
198
+
199
+ deadline = time.monotonic() + window_sec
200
+
201
+ try:
202
+ while time.monotonic() < deadline:
203
+ try:
204
+ await broker.update(
205
+ "history_orders",
206
+ history_orders_payload=history_payload,
207
+ )
208
+ except Exception as exc: # pragma: no cover - 防御
209
+ if logger:
210
+ logger.debug(f"history_orders 更新失败: {exc}")
211
+ if order_id is not None:
212
+ candidate = broker.store.history_orders.get({"orderId": order_id})
213
+ if candidate:
214
+ order_snapshot = candidate
215
+ if _is_order_final(candidate):
216
+ need_cancel = False
217
+ break
218
+
219
+ await asyncio.sleep(poll_interval)
220
+ else:
221
+ if logger:
222
+ logger.warning(
223
+ f"订单处理超时: symbol={symbol} side={side} offset={offset} order_id={order_id}"
224
+ )
225
+ finally:
226
+ if order_id and need_cancel:
227
+ for attempt in range(cancel_retry):
228
+ try:
229
+ await broker.cancel_order(symbol, order_id)
230
+ break
231
+ except Exception as exc: # pragma: no cover - 防御
232
+ if logger:
233
+ logger.info(
234
+ f"撤单失败({attempt + 1}/{cancel_retry}): order_id={order_id} err={exc}"
235
+ )
236
+ await asyncio.sleep(0.5)
237
+
238
+ try:
239
+ await broker.update(
240
+ "history_orders",
241
+ history_orders_payload=history_payload,
242
+ )
243
+ except Exception as exc: # pragma: no cover - 防御
244
+ if logger:
245
+ logger.debug(f"history_orders 更新失败: {exc}")
246
+ if order_id is not None:
247
+ latest_entry = broker.store.history_orders.get({"orderId": order_id})
248
+ if latest_entry:
249
+ order_snapshot = latest_entry
250
+
251
+ try:
252
+ await broker.update("position")
253
+ except Exception as exc: # pragma: no cover - 防御
254
+ if logger:
255
+ logger.debug(f"position 更新失败: {exc}")
256
+ position_snapshot = broker.store.position.get({"symbol": symbol})
257
+ current_volume = _extract_position_volume(position_snapshot)
258
+
259
+ return OrderSyncResult(position_snapshot, order_snapshot, before_volume, current_volume)
260
+
261
+ async def test_order_sync_polling():
262
+ logger = get_logger("test_order_sync_polling")
263
+ async with pybotters.Client(apis=apis) as client:
264
+ async with Coinup(client=client) as broker:
265
+ result = await order_sync_polling(
266
+ broker,
267
+ symbol="WLFI-USDT",
268
+ side="BUY",
269
+ volume=3,
270
+ offset="OPEN",
271
+ order_type="market",
272
+ logger=logger,
273
+ )
274
+ print(result)
275
+
276
+
277
+
278
+ if __name__ == "__main__":
279
+ import asyncio
280
+
281
+ asyncio.run(test_sub_book())
@@ -534,7 +534,7 @@ wheels = [
534
534
 
535
535
  [[package]]
536
536
  name = "hyperquant"
537
- version = "0.9"
537
+ version = "0.91"
538
538
  source = { editable = "." }
539
539
  dependencies = [
540
540
  { name = "aiohttp" },
@@ -1,41 +0,0 @@
1
- import pybotters
2
- from hyperquant.broker.coinup import Coinup, CoinUpDataStore
3
-
4
- apis = {"coinup": ["948a3841361bd38c30fc37355c3fb01a35135042c1c44698bed6f9cd6262cce0"]}
5
-
6
-
7
- async def test_update():
8
- async with pybotters.Client(apis=apis) as client:
9
- async with Coinup(client=client) as broker:
10
- # await broker.update("detail")
11
- # print(broker.store.detail.get({"symbol": "WLFI-USDT"}))
12
- # await broker.update("position")
13
- # print(broker.store.position.find())
14
- await broker.update("history_orders")
15
- print(broker.store.history_orders.find())
16
-
17
- # await broker.update('orders')
18
- # print(broker.store.orders.find())
19
-
20
- async def test_cancel_order():
21
- async with pybotters.Client(apis=apis) as client:
22
- async with Coinup(client=client) as broker:
23
- await broker.cancel_order('169', '2951914186269633768')
24
-
25
- async def test_place_order():
26
- async with pybotters.Client(apis=apis) as client:
27
- async with Coinup(client=client) as broker:
28
- # 市价单
29
- order = await broker.place_order(
30
- 'WLFI-USDT',
31
- side='sell',
32
- volume='1',
33
- order_type='market',
34
- offset='CLOSE',
35
- )
36
- print(order)
37
-
38
- if __name__ == "__main__":
39
- import asyncio
40
-
41
- asyncio.run(test_update())
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