hyperquant 0.2__tar.gz → 0.21__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.2
3
+ Version: 0.21
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
@@ -19,6 +19,7 @@ Requires-Dist: cryptography>=44.0.2
19
19
  Requires-Dist: duckdb>=1.2.2
20
20
  Requires-Dist: numpy>=1.21.0
21
21
  Requires-Dist: pandas>=2.2.3
22
+ Requires-Dist: pybotters>=1.8.2
22
23
  Requires-Dist: pyecharts>=2.0.8
23
24
  Description-Content-Type: text/markdown
24
25
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "0.2"
3
+ version = "0.21"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -13,6 +13,7 @@ dependencies = [
13
13
  "cryptography>=44.0.2",
14
14
  "numpy>=1.21.0", # Added numpy as a new dependency
15
15
  "duckdb>=1.2.2",
16
+ "pybotters>=1.8.2",
16
17
  ]
17
18
  readme = "README.md"
18
19
  requires-python = ">=3.9"
@@ -0,0 +1,538 @@
1
+ # hyperliquid_trader_optimized.py
2
+ """Async wrapper around Hyperliquid REST + WebSocket endpoints.
3
+
4
+ Key design goals
5
+ ----------------
6
+ * **Single point of truth** – all endpoint paths & constants live at module scope.
7
+ * **Safety** – every public coroutine is fully‑typed and guarded with rich
8
+ error messages; internal state is protected by an `asyncio.Lock` where
9
+ necessary.
10
+ * **Performance** – expensive metadata is fetched once and cached; price/size
11
+ formatting uses the `decimal` module only when needed.
12
+ * **Ergonomics** – high‑level order helpers (`buy`, `sell`) are provided on top
13
+ of the generic `place_order` routine; context‑manager semantics make sure
14
+ network resources are cleaned up.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import decimal
20
+ import itertools
21
+ import logging
22
+ from dataclasses import dataclass
23
+ import time
24
+ from typing import Any, Dict, Optional
25
+
26
+ import pybotters
27
+ from yarl import URL
28
+
29
+ from .lib.hpstore import MyHyperStore
30
+ from .lib.hyper_types import AccountBalance
31
+
32
+ import uuid
33
+
34
+
35
+ __all__ = [
36
+ "HyperliquidTrader",
37
+ ]
38
+
39
+ _API_BASE_MAIN = "https://api.hyperliquid.xyz"
40
+ _API_BASE_TEST = "https://api.hyperliquid-testnet.xyz"
41
+ _WSS_URL_MAIN = "wss://api.hyperliquid.xyz/ws"
42
+ _WSS_URL_TEST = "wss://api.hyperliquid-testnet.xyz/ws"
43
+ _INFO = "/info"
44
+ _EXCHANGE = "/exchange"
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+
49
+ # ╭─────────────────────────────────────────────────────────────────────────╮
50
+ # │ Helpers │
51
+ # ╰─────────────────────────────────────────────────────────────────────────╯
52
+ @dataclass(frozen=True, slots=True)
53
+ class AssetMeta:
54
+ """Metadata for a tradable asset."""
55
+
56
+ asset_id: int
57
+ name: str
58
+ sz_decimals: int
59
+
60
+ @dataclass(frozen=True, slots=True)
61
+ class SpotAssetMeta:
62
+ """Metadata for a tradable asset."""
63
+
64
+ asset_id: int # eg. 10000
65
+ name: str # eg. "#173"
66
+ sz_decimals: int # eg. 2
67
+ index: int # eg. 0
68
+ token_name: str # eg. "FEUSD"
69
+ mark_price: float = None
70
+
71
+ @dataclass
72
+ class OrderData():
73
+ o_id: str = ''
74
+ c_id: str = ''
75
+ name: str = ''
76
+ status: str = 'resting'
77
+ price: float = None
78
+ sz: float = None
79
+
80
+
81
+ _DECIMAL_CTX_5 = decimal.Context(prec=5)
82
+
83
+
84
+ def _normalize_number(number: str) -> str:
85
+ """Normalize a number.
86
+
87
+ e.g. "3300.0" -> "3300"
88
+
89
+ Hyperliquid API expects normalized numbers. Otherwise, `L1 error` will occur.
90
+ """
91
+ return format(decimal.Decimal(number).normalize(), "f")
92
+
93
+ def _fmt_price(price: float, sz_decimals: int, *, max_decimals: int = 6) -> str:
94
+ """Format *price* according to Hyperliquid rules.
95
+
96
+ * For `price >= 1` keep five significant digits.
97
+ * For small prices keep *max_decimals - sz_decimals* after the dot.
98
+ """
99
+ if price >= 1:
100
+ return format(_DECIMAL_CTX_5.create_decimal(price).normalize(), "f")
101
+
102
+ decimal_places = max_decimals - sz_decimals
103
+ return _normalize_number(format(decimal.Decimal(price).quantize(decimal.Decimal(f"1e-{decimal_places}")), "f"))
104
+
105
+
106
+ def _fmt_size(size: float, sz_decimals: int) -> str:
107
+ return _normalize_number(format(decimal.Decimal(size).quantize(decimal.Decimal(f"1e-{sz_decimals}")), "f"))
108
+
109
+
110
+ # ╭─────────────────────────────────────────────────────────────────────────╮
111
+ # │ Public main class │
112
+ # ╰─────────────────────────────────────────────────────────────────────────╯
113
+ class HyperliquidTrader:
114
+ """High‑level async client for Hyperliquid."""
115
+
116
+ def __init__(
117
+ self,
118
+ apis: str | dict | None = None,
119
+ *,
120
+ client: Optional[pybotters.Client] = None,
121
+ user_address: Optional[str] = None,
122
+ msg_callback: Optional[callable[[dict, pybotters.ws.WebSocketAppProtocol], None]] = None,
123
+ testnet: bool = False,
124
+ ) -> None:
125
+ self._external_client = client is not None
126
+ self._client: pybotters.Client | None = client
127
+ self._apis = apis
128
+ self._user = user_address
129
+ self._msg_cb_user = msg_callback
130
+
131
+ self._testnet = testnet
132
+ self._api_base = _API_BASE_TEST if testnet else _API_BASE_MAIN
133
+ self._wss_url = _WSS_URL_TEST if testnet else _WSS_URL_MAIN
134
+
135
+ self._assets: dict[str, AssetMeta] = {}
136
+ self._spot_assets: dict[str, SpotAssetMeta] = {}
137
+
138
+ self._assets_with_name: dict[str, AssetMeta] = {}
139
+ self._spot_assets_with_name: dict[str, SpotAssetMeta] = {}
140
+
141
+ self._next_id = itertools.count().__next__ # fast thread‑safe counter
142
+ self._waiters: dict[int, asyncio.Future[dict[str, Any]]] = {}
143
+ self._waiter_lock = asyncio.Lock()
144
+
145
+ self._ws_app: Optional[pybotters.ws.WebSocketConnection] = None
146
+ self.store: MyHyperStore = MyHyperStore()
147
+
148
+
149
+ # ──────────────────────────────────────────────────────────────────────
150
+ # Lifecyle helpers
151
+ # ──────────────────────────────────────────────────────────────────────
152
+ async def __aenter__(self) -> "HyperliquidTrader":
153
+ if self._client is None:
154
+ self._client = await pybotters.Client(apis=self._apis, base_url=self._api_base).__aenter__()
155
+
156
+ await self._fetch_meta()
157
+ await self._fech_spot_meta()
158
+
159
+ self._ws_app:pybotters.WebSocketApp = await self._client.ws_connect(
160
+ self._wss_url,
161
+ send_json=[],
162
+ hdlr_json=self._dispatch_msg,
163
+ )
164
+
165
+ if self._user:
166
+ await self.store.initialize(
167
+ ("orders", self._client.post(_INFO, data={"type": "openOrders", "user": self._user})),
168
+ )
169
+ self._client.ws_connect(
170
+ self._wss_url,
171
+ send_json=[
172
+ {
173
+ "method": "subscribe",
174
+ "subscription": {
175
+ "type": "orderUpdates",
176
+ "user": self._user,
177
+ },
178
+ },
179
+ {
180
+ "method": "subscribe",
181
+ "subscription": {
182
+ "type": "userFills",
183
+ "user": self._user,
184
+ },
185
+ },
186
+ {
187
+ "method": "subscribe",
188
+ "subscription": {
189
+ "type": "webData2",
190
+ "user": self._user,
191
+ },
192
+ }],
193
+ hdlr_json=self.store.onmessage,
194
+ )
195
+
196
+ return self
197
+
198
+ async def __aexit__(self, exc_type, exc, tb): # noqa: D401
199
+ if not self._external_client and self._client is not None:
200
+ await self._client.__aexit__(exc_type, exc, tb)
201
+
202
+ # ──────────────────────────────────────────────────────────────────────
203
+ # Internal – metadata & formatting helpers
204
+ # ──────────────────────────────────────────────────────────────────────
205
+ async def _fetch_meta(self) -> None:
206
+ assert self._client is not None # mypy
207
+ resp = await self._client.fetch("POST", _INFO, data={"type": "meta"})
208
+ if not resp.data:
209
+ raise RuntimeError(f"Failed to fetch meta: {resp.error}")
210
+
211
+ self._assets = {
212
+ d["name"]: AssetMeta(asset_id=i, name=d["name"], sz_decimals=d["szDecimals"])
213
+ for i, d in enumerate(resp.data["universe"])
214
+ }
215
+
216
+ logger.debug("Loaded %d assets", len(self._assets))
217
+
218
+ async def _fech_spot_meta(self) -> None:
219
+ assert self._client is not None # mypy
220
+ resp = await self._client.fetch("POST", _INFO, data={"type": "spotMeta"})
221
+ if not resp.data:
222
+ raise RuntimeError(f"Failed to fetch meta: {resp.error}")
223
+
224
+ metadata = resp.data
225
+
226
+ tokens = metadata['tokens']
227
+
228
+ for u in metadata['universe']:
229
+ coin_name = u['name']
230
+ index = u['index']
231
+ tk_id = u['tokens'][0]
232
+ token_name = tokens[tk_id]['name']
233
+ szDecimals = tokens[tk_id]['szDecimals']
234
+
235
+ meta = SpotAssetMeta(
236
+ asset_id= 10000 + index,
237
+ name=coin_name,
238
+ sz_decimals=szDecimals,
239
+ index=index,
240
+ token_name=token_name,
241
+ mark_price=0.0,
242
+ )
243
+ self._spot_assets[token_name] = meta
244
+ self._spot_assets_with_name[coin_name] = meta
245
+
246
+
247
+
248
+ def _asset(self, symbol: str, is_spot: bool = False) -> AssetMeta:
249
+ try:
250
+ if is_spot:
251
+ return self._spot_assets[symbol]
252
+ else:
253
+ return self._assets[symbol]
254
+ except KeyError as exc:
255
+ raise ValueError(f"Unknown asset '{symbol}'. Have you called __aenter__()?") from exc
256
+
257
+ def fmt_price(self, price: float, symbol: str, is_spot: bool = False) -> float:
258
+ asset = self._asset(symbol, is_spot=is_spot)
259
+ return float(_fmt_price(price, asset.sz_decimals))
260
+
261
+ def fmt_size(self, size: float, symbol: str, is_spot: bool = False) -> float:
262
+ asset = self._asset(symbol, is_spot=is_spot)
263
+ return float(_fmt_size(size, asset.sz_decimals))
264
+
265
+
266
+ def sub_l2_book(self, symbol: str, is_spot: bool = False) -> None:
267
+
268
+ asset = self._asset(symbol, is_spot=is_spot)
269
+
270
+ self._client.ws_connect(
271
+ self._wss_url,
272
+ send_json={
273
+ "method": "subscribe",
274
+ "subscription": {
275
+ "type": "l2Book",
276
+ "coin": asset.name,
277
+ },
278
+ },
279
+ hdlr_json=self.store.onmessage
280
+ )
281
+
282
+
283
+ # ──────────────────────────────────────────────────────────────────────
284
+ # Internal – WebSocket message routing
285
+ # ──────────────────────────────────────────────────────────────────────
286
+ def _dispatch_msg(self, msg: dict[str, Any], wsapp): # noqa: ANN001
287
+ mid = msg.get("data", {}).get("id")
288
+ if mid is not None:
289
+ fut = self._waiters.pop(mid, None)
290
+ if fut and not fut.done():
291
+ fut.set_result(msg)
292
+ return
293
+ # fallback: hand over to user callback / buffer
294
+ if self._msg_cb_user is not None:
295
+ self._msg_cb_user(msg, wsapp)
296
+ else:
297
+ logger.debug("Unhandled WS message: %s", msg)
298
+
299
+ async def _wait_for_id(self, rid: int) -> dict[str, Any]:
300
+ async with self._waiter_lock:
301
+ fut = self._waiters[rid] = asyncio.get_event_loop().create_future()
302
+ return await fut
303
+
304
+ # ──────────────────────────────────────────────────────────────────────
305
+ # REST helpers
306
+ # ──────────────────────────────────────────────────────────────────────
307
+ async def _post(self, path: str, data: dict[str, Any]):
308
+ assert self._client is not None
309
+ info = await self._client.fetch("POST", path, data=data)
310
+ info.response.raise_for_status()
311
+ return info.data
312
+
313
+ # ──────────────────────────────────────────────────────────────────────
314
+ # Public API – account
315
+ # ──────────────────────────────────────────────────────────────────────
316
+ async def balances(self, user: Optional[str] = None) -> AccountBalance | None: # todo support spot
317
+ try:
318
+ user = user or self._user
319
+ if user is None:
320
+ raise ValueError("User address required – pass it now or in constructor")
321
+ data = await self._post(_INFO, {"type": "clearinghouseState", "user": user})
322
+ match data:
323
+ case pybotters.NotJSONContent():
324
+ print('可能参数有误')
325
+ return None
326
+ return data
327
+ except Exception as e:
328
+ print(f"Error fetching balances: {e}")
329
+ return None
330
+
331
+ async def open_orders(self, user: Optional[str] = None):
332
+ user = user or self._user
333
+ if user is None:
334
+ raise ValueError("User address required – pass it now or in constructor")
335
+ return await self._post(_INFO, {"type": "openOrders", "user": user})
336
+
337
+ async def cancel_order(
338
+ self,
339
+ asset: str,
340
+ order_id: str,
341
+ *,
342
+ use_ws: bool = False,
343
+ is_spot: bool = False,
344
+ ) -> dict[str, Any]:
345
+
346
+ meta = self._asset(asset, is_spot=is_spot)
347
+
348
+ payload = {
349
+ "action": {
350
+ "type": "cancel",
351
+ "cancels": [{"a": meta.asset_id, "o": order_id}],
352
+ }
353
+ }
354
+
355
+
356
+
357
+ if not use_ws:
358
+ return await self._post(_EXCHANGE, payload)
359
+
360
+ signed = await self._ws_sign(payload)
361
+ assert self._ws_app is not None
362
+ await self._ws_app.current_ws.send_json(signed)
363
+ return order_id
364
+
365
+ async def cancel_all_orders(
366
+ self,
367
+ asset: Optional[str] = None,
368
+ *,
369
+ use_ws: bool = False,
370
+ is_spot: bool = False,
371
+ ) -> dict[str, Any]:
372
+ # [{'coin': '@153', 'side': 'A', 'limitPx': '0.9904', 'sz': '202.33', 'oid': 93287495240, 'timestamp': 1747118448026, 'origSz': '202.33', 'cloid': '0x441f6c3b8dde4ccfb34a24ec23419f2d'}, {'coin': '@153', 'side': 'B', 'limitPx': '0.9864', 'sz': '202.76', 'oid': 93278072490, 'timestamp': 1747115006552, 'origSz': '202.76', 'cloid': '0x90fac8c56d224a20b4fd0ab6cf4eae5e'}]
373
+ orders = await self.open_orders()
374
+
375
+ if asset:
376
+ meta = self._asset(asset, is_spot=is_spot)
377
+ orders = [o for o in orders if o['coin'] == meta.name]
378
+
379
+ if is_spot:
380
+ for o in orders:
381
+ o['asset_id'] = self._spot_assets_with_name[o['coin']].asset_id
382
+ else:
383
+ for o in orders:
384
+ o['asset_id'] = self._asset(o['coin']).asset_id
385
+
386
+ # 构建payload
387
+ payload = {
388
+ "action": {
389
+ "type": "cancel",
390
+ "cancels": [
391
+ {"a": o['asset_id'], "o": o['oid']}
392
+ for o in orders
393
+ ],
394
+ }
395
+ }
396
+ print(payload)
397
+
398
+ if not use_ws:
399
+ return await self._post(_EXCHANGE, payload)
400
+ signed = await self._ws_sign(payload)
401
+ assert self._ws_app is not None
402
+ await self._ws_app.current_ws.send_json(signed)
403
+ return orders
404
+
405
+
406
+ async def get_mid(self, asset: str, is_spot: bool = False) -> float:
407
+ """Get the mid price of an asset."""
408
+ meta = self._asset(asset, is_spot=is_spot)
409
+ book = await self._post(_INFO, {"type": "l2Book", "coin": meta.name})
410
+ bid = float(book["levels"][0][0]["px"])
411
+ ask = float(book["levels"][1][0]["px"])
412
+ return float(_fmt_price((bid + ask) / 2, meta.sz_decimals))
413
+
414
+ async def get_books(self, asset: str, is_spot: bool = False) -> list[float]:
415
+ """Get the ask prices of an asset."""
416
+ meta = self._asset(asset, is_spot=is_spot)
417
+ book = await self._post(_INFO, {"type": "l2Book", "coin": meta.name})
418
+ levels = book["levels"]
419
+ bids = levels[0]
420
+ asks = levels[1]
421
+ return {
422
+ "bids": bids,
423
+ "asks": asks,
424
+ }
425
+
426
+ # ──────────────────────────────────────────────────────────────────────
427
+ # Public API – trading
428
+ # ──────────────────────────────────────────────────────────────────────
429
+ async def place_order(
430
+ self,
431
+ asset: str,
432
+ *,
433
+ side: str = "buy", # "buy" | "sell"
434
+ order_type: str = "market", # "market" | "limit"
435
+ size: float,
436
+ slippage: float = 0.02,
437
+ price: Optional[float] = None, # required for limit orders
438
+ use_ws: bool = False,
439
+ is_spot: bool = False,
440
+ ) -> OrderData:
441
+ """`place_order` 下订单
442
+
443
+ 返回值:
444
+ 'OrderData'
445
+ """
446
+ meta = self._asset(asset, is_spot=is_spot)
447
+ is_buy = side.lower() == "buy"
448
+
449
+ if order_type == "limit":
450
+ if price is None:
451
+ raise ValueError("price must be supplied for limit orders")
452
+ price_str = _fmt_price(price, meta.sz_decimals)
453
+ tif = "Gtc"
454
+ else:
455
+ # emulate market by crossing the spread via mid ± slippage
456
+ if price is None:
457
+ mid = await self.get_mid(asset, is_spot=is_spot)
458
+ else:
459
+ mid = price # allow user‑supplied mid for testing
460
+ crossed = mid * (1 + slippage) if is_buy else mid * (1 - slippage)
461
+ price_str = _fmt_price(crossed, meta.sz_decimals)
462
+ tif = "Ioc"
463
+
464
+ size_str = _fmt_size(size, meta.sz_decimals)
465
+ order_payload = {
466
+ "action": {
467
+ "type": "order",
468
+ "orders": [
469
+ {
470
+ "a": meta.asset_id,
471
+ "b": is_buy,
472
+ "p": price_str,
473
+ "s": size_str,
474
+ "r": False,
475
+ "t": {"limit": {"tif": tif}},
476
+ }
477
+ ],
478
+ "grouping": "na",
479
+ }
480
+ }
481
+ # 创建时间戳cid
482
+ # Generate a 128-bit client order ID as a hex string (0x-prefixed, 32 hex chars)
483
+ cloid = uuid.uuid4().hex
484
+ cloid = "0x" + cloid
485
+
486
+ order_payload["action"]["orders"][0]["c"] = cloid
487
+
488
+ # print(f"Placing order: {order_payload}")
489
+ print(order_payload)
490
+
491
+ if not use_ws:
492
+ ret = await self._post(_EXCHANGE, order_payload)
493
+ if 'error' in str(ret):
494
+ raise RuntimeError(f"Failed to place order: {ret}")
495
+ elif 'filled' in str(ret):
496
+ return OrderData(
497
+ o_id=ret['response']['data']['statuses'][0]['filled']['oid'],
498
+ c_id=cloid,
499
+ name=asset,
500
+ status='filled',
501
+ price=float(ret['response']['data']['statuses'][0]['filled']['avgPx']),
502
+ sz=float(ret['response']['data']['statuses'][0]['filled']['totalSz']),
503
+ )
504
+ elif 'resting' in str(ret):
505
+ return OrderData(
506
+ o_id=ret['response']['data']['statuses'][0]['resting']['oid'],
507
+ c_id=cloid,
508
+ price=float(price_str),
509
+ name=asset,
510
+ status='resting',
511
+ )
512
+
513
+ # else – signed WS flow
514
+ signed = await self._ws_sign(order_payload)
515
+ assert self._ws_app is not None
516
+ await self._ws_app.current_ws.send_json(signed)
517
+ return OrderData(o_id=cloid, name=asset, price=float(price_str))
518
+
519
+ # Convenience wrappers -------------------------------------------------
520
+ async def buy(self, asset: str, **kw):
521
+ return await self.place_order(asset, side="buy", **kw)
522
+
523
+ async def sell(self, asset: str, **kw):
524
+ return await self.place_order(asset, side="sell", **kw)
525
+
526
+ # ──────────────────────────────────────────────────────────────────────
527
+ # Internal – signing helper
528
+ # ──────────────────────────────────────────────────────────────────────
529
+ async def _ws_sign(self, payload): # noqa: ANN001 – hyperliquid internal format
530
+ # mimic pybotters signing util for WS‑POST messages
531
+ url = URL(f"{self._api_base}/abc")
532
+ pybotters.Auth.hyperliquid((None, url), {"data": payload, "session": self._client._session}) # type: ignore[attr-defined]
533
+ rid = self._next_id()
534
+ return {
535
+ "method": "post",
536
+ "id": rid,
537
+ "request": {"type": "action", "payload": payload},
538
+ }
@@ -0,0 +1,52 @@
1
+ # import asyncio
2
+ # from datetime import datetime, timedelta
3
+ # from src.hyperquant.core import Exchange
4
+ # from src.hyperquant.datavison.coinglass import CoinglassApi
5
+
6
+
7
+ # async def main():
8
+ # api = CoinglassApi()
9
+ # await api.connect()
10
+ # df = await api.fetch_price_klines('Binance_BTCUSDT', datetime.now() - timedelta(days=1))
11
+ # print(df)
12
+ # await api.stop()
13
+
14
+ # if __name__ == '__main__':
15
+ # asyncio.run(main())
16
+
17
+
18
+ import asyncio
19
+ from datetime import datetime, timedelta
20
+ from hyperquant.datavison.binance import BinanceSwap
21
+ from hyperquant.broker.hyperliquid import HyperliquidTrader
22
+
23
+ async def test1():
24
+ symbol = "BTCUSDT"
25
+ interval = "1m"
26
+ # 取最近10分钟的K线
27
+ end_time = int(time.time() * 1000)
28
+ start_time = end_time - 10 * 60 * 1000
29
+ swap = BinanceSwap()
30
+
31
+
32
+ # 新增时间参数例子
33
+ start_date = datetime.now() - timedelta(minutes=5)
34
+ end_date = datetime.now()
35
+ klines = await swap.get_index_klines(symbol, interval, start_date, end_date)
36
+
37
+ print(klines)
38
+
39
+ await swap.session.close()
40
+
41
+ async def test2():
42
+ async with HyperliquidTrader() as trader:
43
+ # 获取当前账户信息
44
+ print(
45
+ await trader.get_mid('FEUSD', True)
46
+ )
47
+
48
+
49
+ if __name__ == "__main__":
50
+ import time
51
+
52
+ asyncio.run(test2())
@@ -530,7 +530,7 @@ wheels = [
530
530
 
531
531
  [[package]]
532
532
  name = "hyperquant"
533
- version = "0.1.9"
533
+ version = "0.21"
534
534
  source = { editable = "." }
535
535
  dependencies = [
536
536
  { name = "aiohttp" },
@@ -540,6 +540,7 @@ dependencies = [
540
540
  { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
541
541
  { name = "numpy", version = "2.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
542
542
  { name = "pandas" },
543
+ { name = "pybotters" },
543
544
  { name = "pyecharts" },
544
545
  ]
545
546
 
@@ -556,6 +557,7 @@ requires-dist = [
556
557
  { name = "duckdb", specifier = ">=1.2.2" },
557
558
  { name = "numpy", specifier = ">=1.21.0" },
558
559
  { name = "pandas", specifier = ">=2.2.3" },
560
+ { name = "pybotters", specifier = ">=1.8.2" },
559
561
  { name = "pyecharts", specifier = ">=2.0.8" },
560
562
  ]
561
563
 
@@ -1217,6 +1219,19 @@ wheels = [
1217
1219
  { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376 },
1218
1220
  ]
1219
1221
 
1222
+ [[package]]
1223
+ name = "pybotters"
1224
+ version = "1.8.2"
1225
+ source = { registry = "https://pypi.org/simple" }
1226
+ dependencies = [
1227
+ { name = "aiohttp" },
1228
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
1229
+ ]
1230
+ sdist = { url = "https://files.pythonhosted.org/packages/3d/dd/dedd0e9882aba4ac14ec4697e97c33a70c7f0bc1433581c376f50b093e82/pybotters-1.8.2.tar.gz", hash = "sha256:b15e7c52582f139cf3fb6858296324fe0cbc5349084b86b9dbc7f76c3ea3c109", size = 551553 }
1231
+ wheels = [
1232
+ { url = "https://files.pythonhosted.org/packages/89/21/f0cc2bedac2b2ef5914321515970f9e7813ab85250c0e86fe13a850da92a/pybotters-1.8.2-py3-none-any.whl", hash = "sha256:e3955f6dfd100c7fded4f8d60bbe736e0dd7837517868bc7aab3b08a740416d0", size = 516721 },
1233
+ ]
1234
+
1220
1235
  [[package]]
1221
1236
  name = "pycparser"
1222
1237
  version = "2.22"
hyperquant-0.2/test.py DELETED
@@ -1,44 +0,0 @@
1
- # import asyncio
2
- # from datetime import datetime, timedelta
3
- # from src.hyperquant.core import Exchange
4
- # from src.hyperquant.datavison.coinglass import CoinglassApi
5
-
6
-
7
- # async def main():
8
- # api = CoinglassApi()
9
- # await api.connect()
10
- # df = await api.fetch_price_klines('Binance_BTCUSDT', datetime.now() - timedelta(days=1))
11
- # print(df)
12
- # await api.stop()
13
-
14
- # if __name__ == '__main__':
15
- # asyncio.run(main())
16
-
17
-
18
- import asyncio
19
- from datetime import datetime, timedelta
20
- from hyperquant.datavison.binance import BinanceSwap
21
-
22
-
23
- if __name__ == "__main__":
24
- import time
25
-
26
- async def main():
27
- symbol = "BTCUSDT"
28
- interval = "1m"
29
- # 取最近10分钟的K线
30
- end_time = int(time.time() * 1000)
31
- start_time = end_time - 10 * 60 * 1000
32
- swap = BinanceSwap()
33
-
34
-
35
- # 新增时间参数例子
36
- start_date = datetime.now() - timedelta(minutes=5)
37
- end_date = datetime.now()
38
- klines = await swap.get_index_klines(symbol, interval, start_date, end_date)
39
-
40
- print(klines)
41
-
42
- await swap.session.close()
43
-
44
- asyncio.run(main())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes