hyperquant 0.69__tar.gz → 0.71__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.
Files changed (53) hide show
  1. {hyperquant-0.69 → hyperquant-0.71}/PKG-INFO +1 -1
  2. {hyperquant-0.69 → hyperquant-0.71}/pyproject.toml +1 -1
  3. hyperquant-0.71/src/hyperquant/broker/bitget.py +107 -0
  4. hyperquant-0.71/src/hyperquant/broker/lib/util.py +22 -0
  5. hyperquant-0.71/src/hyperquant/broker/models/bitget.py +283 -0
  6. hyperquant-0.71/tests/test_bitget.py +59 -0
  7. {hyperquant-0.69 → hyperquant-0.71}/uv.lock +1 -1
  8. hyperquant-0.69/src/hyperquant/broker/lib/util.py +0 -9
  9. {hyperquant-0.69 → hyperquant-0.71}/.gitignore +0 -0
  10. {hyperquant-0.69 → hyperquant-0.71}/.python-version +0 -0
  11. {hyperquant-0.69 → hyperquant-0.71}/README.md +0 -0
  12. {hyperquant-0.69 → hyperquant-0.71}/apis.json +0 -0
  13. {hyperquant-0.69 → hyperquant-0.71}/data/alpine_smoke.log +0 -0
  14. {hyperquant-0.69 → hyperquant-0.71}/data/logs/notikit.log +0 -0
  15. {hyperquant-0.69 → hyperquant-0.71}/data/logs/test_order_sync.log +0 -0
  16. {hyperquant-0.69 → hyperquant-0.71}/data/records_swap.csv +0 -0
  17. {hyperquant-0.69 → hyperquant-0.71}/data/records_swapc.csv +0 -0
  18. {hyperquant-0.69 → hyperquant-0.71}/doc/edgex_act.md +0 -0
  19. {hyperquant-0.69 → hyperquant-0.71}/doc/edgex_debug.md +0 -0
  20. {hyperquant-0.69 → hyperquant-0.71}/doc/edgex_order.md +0 -0
  21. {hyperquant-0.69 → hyperquant-0.71}/doc/edgex_ws.md +0 -0
  22. {hyperquant-0.69 → hyperquant-0.71}/doc/lbank.md +0 -0
  23. {hyperquant-0.69 → hyperquant-0.71}/pub.sh +0 -0
  24. {hyperquant-0.69 → hyperquant-0.71}/requirements-dev.lock +0 -0
  25. {hyperquant-0.69 → hyperquant-0.71}/requirements.lock +0 -0
  26. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/__init__.py +0 -0
  27. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/auth.py +0 -0
  28. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/edgex.py +0 -0
  29. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/hyperliquid.py +0 -0
  30. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/lbank.py +0 -0
  31. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  32. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/lib/hpstore.py +0 -0
  33. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  34. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/models/edgex.py +0 -0
  35. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  36. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/models/lbank.py +0 -0
  37. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/models/ourbit.py +0 -0
  38. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/ourbit.py +0 -0
  39. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/broker/ws.py +0 -0
  40. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/core.py +0 -0
  41. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/datavison/_util.py +0 -0
  42. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/datavison/binance.py +0 -0
  43. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/datavison/coinglass.py +0 -0
  44. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/datavison/okx.py +0 -0
  45. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/db.py +0 -0
  46. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/draw.py +0 -0
  47. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/logkit.py +0 -0
  48. {hyperquant-0.69 → hyperquant-0.71}/src/hyperquant/notikit.py +0 -0
  49. {hyperquant-0.69 → hyperquant-0.71}/tests/test_draw.py +0 -0
  50. {hyperquant-0.69 → hyperquant-0.71}/tests/test_edgex.py +0 -0
  51. {hyperquant-0.69 → hyperquant-0.71}/tests/test_lbank.py +0 -0
  52. {hyperquant-0.69 → hyperquant-0.71}/tests/test_ourbit.py +0 -0
  53. {hyperquant-0.69 → hyperquant-0.71}/tests/tmp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.69
3
+ Version: 0.71
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.69"
3
+ version = "0.71"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import itertools
5
+ import logging
6
+ import time
7
+ from typing import Any, Iterable, Literal
8
+
9
+ import pybotters
10
+
11
+ from .models.bitget import BitgetDataStore
12
+ from .lib.util import fmt_value
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+
18
+ class Bitget:
19
+ """Bitget public market-data client (REST + WS)."""
20
+
21
+ def __init__(
22
+ self,
23
+ client: pybotters.Client,
24
+ *,
25
+ rest_api: str | None = None,
26
+ ws_url: str | None = None,
27
+ ) -> None:
28
+ self.client = client
29
+ self.store = BitgetDataStore()
30
+
31
+ self.rest_api = rest_api or "https://api.bitget.com"
32
+ self.ws_url = ws_url or "wss://ws.bitget.com/v2/ws/public"
33
+ self.ws_url_private = ws_url or "wss://ws.bitget.com/v2/ws/private"
34
+
35
+ self._ws_app = None
36
+
37
+
38
+ async def __aenter__(self) -> "Bitget":
39
+ await self.update("detail")
40
+ return self
41
+
42
+ async def __aexit__(self, exc_type, exc, tb) -> None:
43
+ pass
44
+
45
+ async def update(
46
+ self,
47
+ update_type: Literal["detail", 'ticker', 'all'] = "all",
48
+ ) -> None:
49
+ fet = []
50
+ if update_type in ("detail", "all"):
51
+ fet.append(
52
+ self.client.get(
53
+ f"{self.rest_api}/api/v2/mix/market/contracts?productType=usdt-futures",
54
+ )
55
+ )
56
+ elif update_type in ("ticker", "all"):
57
+ fet.append(
58
+ self.client.get(
59
+ f"{self.rest_api}/api/v2/mix/market/tickers?productType=usdt-futures",
60
+ )
61
+ )
62
+
63
+ await self.store.initialize(*fet)
64
+
65
+ async def place_order(
66
+ self,
67
+ symbol: str,
68
+ *,
69
+ direction: Literal["buy", "sell", "0", "1"],
70
+ volume: float,
71
+ price: float | None = None,
72
+ order_type: Literal["market", "limit_ioc", "limit_gtc"] = "market",
73
+ offset_flag: Literal["open", "close", "0", "1"] = "open",
74
+ exchange_id: str = "Exchange",
75
+ product_group: str = "SwapU",
76
+ order_proportion: str = "0.0000",
77
+ client_order_id: str | None = None,
78
+ ) -> dict[str, Any]:
79
+ pass
80
+
81
+ async def cancel_order(
82
+ self,
83
+ order_sys_id: str,
84
+ *,
85
+ action_flag: str | int = "1",
86
+ ) -> dict[str, Any]:
87
+ pass
88
+
89
+
90
+ async def sub_orderbook(self, symbols: list[str], channel: str = 'books1') -> None:
91
+ """订阅指定交易对的订单簿(遵循 LBank 协议)。
92
+ """
93
+
94
+ submsg = {
95
+ "op": "subscribe",
96
+ "args": []
97
+ }
98
+ for symbol in symbols:
99
+ submsg["args"].append(
100
+ {"instType": "SPOT", "channel": channel, "instId": symbol}
101
+ )
102
+
103
+ self.client.ws_connect(
104
+ self.ws_url,
105
+ send_json=submsg,
106
+ hdlr_json=self.store.onmessage
107
+ )
@@ -0,0 +1,22 @@
1
+ from decimal import ROUND_HALF_UP, Decimal
2
+
3
+
4
+ def fmt_value(price: float, tick: float) -> str:
5
+ tick_dec = Decimal(str(tick))
6
+ price_dec = Decimal(str(price))
7
+ return str(
8
+ (price_dec / tick_dec).quantize(Decimal("1"), rounding=ROUND_HALF_UP) * tick_dec
9
+ )
10
+
11
+
12
+ def place_to_step(place: int) -> float:
13
+ """
14
+ 把 pricePlace / volumePlace 转换成 tick_size / lot_size
15
+
16
+ Args:
17
+ place (int): 小数位数,例如 pricePlace=1, volumePlace=2
18
+
19
+ Returns:
20
+ float: 步长 (step),例如 0.1, 0.01
21
+ """
22
+ return 10 ** (-place)
@@ -0,0 +1,283 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import TYPE_CHECKING, Any, Awaitable
5
+ from aiohttp import ClientResponse
6
+ from pybotters import DataStore
7
+ from pybotters.models.bitget_v2 import BitgetV2DataStore
8
+ from ..lib.util import place_to_step
9
+
10
+ if TYPE_CHECKING:
11
+ from pybotters.typedefs import Item
12
+
13
+ class Detail(DataStore):
14
+ """Futures instrument metadata store obtained from the futures instrument endpoint."""
15
+
16
+ _KEYS = ["symbol"]
17
+
18
+ def _transform(self, entry: dict[str, Any]) -> dict[str, Any] | None:
19
+
20
+ step_size = entry.get('volume_place', 1)
21
+ tick_size = entry.get('price_place', 1)
22
+ step_size = place_to_step(step_size)
23
+ tick_size = place_to_step(tick_size)
24
+ entry['tickSize'] = tick_size
25
+ entry['stepSize'] = step_size
26
+ return entry
27
+
28
+ def _onresponse(self, data: list[dict[str, Any]] | dict[str, Any] | None) -> None:
29
+ if not data:
30
+ self._clear()
31
+ return
32
+ entries = data
33
+ if isinstance(data, dict): # pragma: no cover - defensive guard
34
+ entries = data.get("data") or []
35
+ items: list[dict[str, Any]] = []
36
+ for entry in entries or []:
37
+ transformed = self._transform(entry)
38
+ if transformed:
39
+ items.append(transformed)
40
+ if not items:
41
+ self._clear()
42
+ return
43
+ self._clear()
44
+ self._insert(items)
45
+
46
+
47
+ class Book(DataStore):
48
+ _KEYS = ["t", "s", "S", "p"]
49
+
50
+ def _onmessage(self, msg: Item) -> None:
51
+ action = msg["action"]
52
+ inst_type = msg["arg"]["instType"]
53
+ inst_id = msg["arg"]["instId"]
54
+
55
+ data_to_insert = []
56
+ data_to_update = []
57
+ data_to_delete = []
58
+ for book in msg["data"]:
59
+ for side in ("asks", "bids"):
60
+ for row in book[side]:
61
+ converted_row = {
62
+ "t": inst_type,
63
+ "s": inst_id,
64
+ "S": side[0],
65
+ "p": row[0],
66
+ "q": row[1],
67
+ }
68
+ if action == "snapshot":
69
+ data_to_insert.append(converted_row)
70
+ elif converted_row["q"] != "0":
71
+ data_to_update.append(converted_row)
72
+ else:
73
+ data_to_delete.append(converted_row)
74
+
75
+ # Cleanup on reconnect
76
+ if action == "snapshot":
77
+ self._find_and_delete({"t": inst_type, "s": inst_id})
78
+
79
+ self._insert(data_to_insert)
80
+ self._update(data_to_update)
81
+ self._delete(data_to_delete)
82
+
83
+ def sorted(
84
+ self, query: Item | None = None, limit: int | None = None
85
+ ) -> dict[str, list[Item]]:
86
+ return self._sorted(
87
+ item_key="side",
88
+ item_asc_key="a",
89
+ item_desc_key="b",
90
+ sort_key="p",
91
+ query=query,
92
+ limit=limit,
93
+ )
94
+
95
+
96
+ class BitgetDataStore(BitgetV2DataStore):
97
+
98
+ def _init(self):
99
+ super()._init()
100
+ self._create('detail', datastore_class=Detail)
101
+ self._create("book", datastore_class=Book)
102
+
103
+ async def initialize(self, *aws: Awaitable[ClientResponse]) -> None:
104
+ for fut in asyncio.as_completed(aws):
105
+ res = await fut
106
+ data = await res.json()
107
+ if res.url.path == '/api/v2/mix/market/contracts':
108
+ self.detail._onresponse(data)
109
+ elif res.url.path == '/api/v2/mix/market/tickers':
110
+ self.ticker._clear()
111
+ tickers = data.get('data', [])
112
+ # 为每个ticker添加额外的字段
113
+ for ticker in tickers:
114
+ symbol = ticker.get('symbol')
115
+ ticker['instId'] = symbol
116
+ ticker['instType'] = 'futures'
117
+
118
+ self.ticker._update(tickers)
119
+
120
+ @property
121
+ def detail(self) -> Detail:
122
+ """
123
+ _key: symbol
124
+
125
+ Data Structure:
126
+
127
+ .. code:: json
128
+
129
+ [
130
+ {
131
+ "symbol": "BTCUSDT",
132
+ "baseCoin": "BTC",
133
+ "quoteCoin": "USDT",
134
+ "buyLimitPriceRatio": "0.9",
135
+ "sellLimitPriceRatio": "0.9",
136
+ "feeRateUpRatio": "0.1",
137
+ "makerFeeRate": "0.0004",
138
+ "takerFeeRate": "0.0006",
139
+ "openCostUpRatio": "0.1",
140
+ "supportMarginCoins": [
141
+ "USDT"
142
+ ],
143
+ "minTradeNum": "0.01",
144
+ "priceEndStep": "1",
145
+ "volumePlace": "2",
146
+ "stepSize": "0.01",
147
+ "tickSize": "0.1",
148
+ "pricePlace": "1",
149
+ "sizeMultiplier": "0.01",
150
+ "symbolType": "perpetual",
151
+ "minTradeUSDT": "5",
152
+ "maxSymbolOrderNum": "999999",
153
+ "maxProductOrderNum": "999999",
154
+ "maxPositionNum": "150",
155
+ "symbolStatus": "normal",
156
+ "offTime": "-1",
157
+ "limitOpenTime": "-1",
158
+ "deliveryTime": "",
159
+ "deliveryStartTime": "",
160
+ "launchTime": "",
161
+ "fundInterval": "8",
162
+ "minLever": "1",
163
+ "maxLever": "125",
164
+ "posLimit": "0.05",
165
+ "maintainTime": "1680165535278",
166
+ "maxMarketOrderQty": "220",
167
+ "maxOrderQty": "1200"
168
+ }]
169
+
170
+ """
171
+ return self._get('detail')
172
+
173
+ @property
174
+ def ticker(self) -> DataStore:
175
+ """
176
+ _KEYS = ["instType", "instId"]
177
+
178
+ Data Structure:
179
+
180
+ .. code:: json
181
+
182
+ [
183
+ {
184
+ "symbol": "BTCUSDT",
185
+ "lastPr": "111534.6",
186
+ "askPr": "111534.6",
187
+ "bidPr": "111534.5",
188
+ "bidSz": "23.7924",
189
+ "askSz": "8.1762",
190
+ "high24h": "112300",
191
+ "low24h": "109136.2",
192
+ "ts": "1759115725508",
193
+ "change24h": "0.01906",
194
+ "baseVolume": "35520.11438048",
195
+ "quoteVolume": "3932280581.066103549",
196
+ "usdtVolume": "3932280581.066103549",
197
+ "openUtc": "112100",
198
+ "changeUtc24h": "-0.00504",
199
+ "indexPrice": "111587.6090439271505504",
200
+ "fundingRate": "-0.000002",
201
+ "holdingAmount": "66775.1917",
202
+ "deliveryStartTime": null,
203
+ "deliveryTime": null,
204
+ "deliveryStatus": "",
205
+ "open24h": "109448.3",
206
+ "markPrice": "111537",
207
+ "instId": "BTCUSDT",
208
+ "instType": "futures"
209
+ }
210
+ ]
211
+ """
212
+ return self._get('ticker')
213
+
214
+ @property
215
+ def orders(self) -> DataStore:
216
+ """
217
+ _KEYS = ["instType", "instId", "orderId"]
218
+ .. code:: json
219
+
220
+ [
221
+ {
222
+ "instType": "futures",
223
+ "instId": "BTCUSDT",
224
+ "orderId": "1",
225
+ "clientOid": "1",
226
+ "size": "8.0000",
227
+ "newSize": "500.0000",
228
+ "notional": "8.000000",
229
+ "orderType": "market",
230
+ "force": "gtc",
231
+ "side": "buy",
232
+ "fillPrice": "26256.0",
233
+ "tradeId": "1",
234
+ "baseVolume": "0.0003",
235
+ "fillTime": "1695797773286",
236
+ "fillFee": "-0.00000018",
237
+ "fillFeeCoin": "BTC",
238
+ "tradeScope": "T",
239
+ "accBaseVolume": "0.0003",
240
+ "priceAvg": "26256.0",
241
+ "status": "partially_filled",
242
+ "cTime": "1695797773257",
243
+ "uTime": "1695797773326",
244
+ "stpMode": "cancel_taker",
245
+ "feeDetail": [
246
+ {
247
+ "feeCoin": "BTC",
248
+ "fee": "-0.00000018"
249
+ }
250
+ ],
251
+ "enterPointSource": "WEB"
252
+ }
253
+ ]
254
+ """
255
+ return self._get('orders')
256
+
257
+ @property
258
+ def book(self) -> DataStore:
259
+ """
260
+ _KEYS = ["t", "s", "S", "p"]
261
+
262
+ Data Structure:
263
+
264
+ .. code:: json
265
+
266
+ [
267
+ {
268
+ "t": "futures",
269
+ "s": "BTCUSDT",
270
+ "S": "a",
271
+ "p": "111534.6",
272
+ "q": "8.1762"
273
+ },
274
+ {
275
+ "t": "futures",
276
+ "s": "BTCUSDT",
277
+ "S": "b",
278
+ "p": "111534.5",
279
+ "q": "23.7924"
280
+ }
281
+ ]
282
+ """
283
+ return self._get("book")
@@ -0,0 +1,59 @@
1
+ import pybotters
2
+ from hyperquant.broker.models.bitget import BitgetDataStore
3
+
4
+
5
+ async def test_update():
6
+ async with pybotters.Client() as client:
7
+ store = BitgetDataStore()
8
+ # await store.initialize(
9
+ # client.get("https://api.bitget.com/api/v2/mix/market/contracts?productType=usdt-futures")
10
+ # )
11
+ # print(store.detail.find())
12
+ await store.initialize(
13
+ client.get(
14
+ "https://api.bitget.com/api/v2/mix/market/tickers?productType=usdt-futures"
15
+ )
16
+ )
17
+ print(store.ticker.find({"symbol": "BTCUSDT"}))
18
+
19
+
20
+ async def subscribe_book():
21
+
22
+ async with pybotters.Client() as client:
23
+ store = BitgetDataStore()
24
+ client.ws_connect(
25
+ "wss://ws.bitget.com/v2/ws/public",
26
+ send_json={
27
+ "op": "subscribe",
28
+ "args": [
29
+ {"instType": "SPOT", "channel": "books1", "instId": "BTCUSDT"}
30
+ ]
31
+ },
32
+ hdlr_json=store.onmessage
33
+ )
34
+
35
+ while True:
36
+ await asyncio.sleep(1)
37
+ print(store.book.find())
38
+
39
+ from hyperquant.broker.bitget import Bitget
40
+ async def test_broker_update():
41
+
42
+ async with pybotters.Client() as client:
43
+ bg = Bitget(client)
44
+ store = BitgetDataStore()
45
+ await bg.update('all')
46
+ print(bg.store.detail.find())
47
+
48
+ async def test_broker_sub_orderbook():
49
+ async with pybotters.Client() as client:
50
+ bg = Bitget(client)
51
+ await bg.sub_orderbook(['BTCUSDT', 'ETHUSDT'])
52
+ while True:
53
+ await asyncio.sleep(1)
54
+ print(bg.store.book.find())
55
+
56
+ if __name__ == "__main__":
57
+ import asyncio
58
+
59
+ asyncio.run(test_broker_sub_orderbook())
@@ -530,7 +530,7 @@ wheels = [
530
530
 
531
531
  [[package]]
532
532
  name = "hyperquant"
533
- version = "0.68"
533
+ version = "0.7"
534
534
  source = { editable = "." }
535
535
  dependencies = [
536
536
  { name = "aiohttp" },
@@ -1,9 +0,0 @@
1
- from decimal import ROUND_HALF_UP, Decimal
2
-
3
-
4
- def fmt_value(price: float, tick: float) -> str:
5
- tick_dec = Decimal(str(tick))
6
- price_dec = Decimal(str(price))
7
- return str(
8
- (price_dec / tick_dec).quantize(Decimal("1"), rounding=ROUND_HALF_UP) * tick_dec
9
- )
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