hyperquant 0.68__py3-none-any.whl → 0.71__py3-none-any.whl

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.
@@ -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
+ )
@@ -241,7 +241,8 @@ class Lbank:
241
241
  if price_type_code == "0":
242
242
  payload["Price"] = price_str
243
243
  elif price is not None:
244
- logger.warning("Price is ignored for market orders")
244
+ # logger.warning("Price is ignored for market orders")
245
+ pass
245
246
 
246
247
  # if client_order_id:
247
248
  # payload["LocalID"] = client_order_id
@@ -7,3 +7,16 @@ def fmt_value(price: float, tick: float) -> str:
7
7
  return str(
8
8
  (price_dec / tick_dec).quantize(Decimal("1"), rounding=ROUND_HALF_UP) * tick_dec
9
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")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.68
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
@@ -5,15 +5,17 @@ hyperquant/draw.py,sha256=up_lQ3pHeVLoNOyh9vPjgNwjD0M-6_IetSGviQUgjhY,54624
5
5
  hyperquant/logkit.py,sha256=nUo7nx5eONvK39GOhWwS41zNRL756P2J7-5xGzwXnTY,8462
6
6
  hyperquant/notikit.py,sha256=x5yAZ_tAvLQRXcRbcg-VabCaN45LUhvlTZnUqkIqfAA,3596
7
7
  hyperquant/broker/auth.py,sha256=Wst7mTBuUS2BQ5hZd0a8FNNs5Uc01ac9WzJpseTuyAY,7673
8
+ hyperquant/broker/bitget.py,sha256=G13WRACqxuHfxNh0nKB-11VF02XDsgb0vM1g19eOo2A,2861
8
9
  hyperquant/broker/edgex.py,sha256=TqUO2KRPLN_UaxvtLL6HnA9dAQXC1sGxOfqTHd6W5k8,18378
9
10
  hyperquant/broker/hyperliquid.py,sha256=7MxbI9OyIBcImDelPJu-8Nd53WXjxPB5TwE6gsjHbto,23252
10
- hyperquant/broker/lbank.py,sha256=7o0xaCuUuIGDUX93-1tnWBH39QGohHpqpL-KFa2OiJc,11662
11
+ hyperquant/broker/lbank.py,sha256=dZUbi0a_Vhkp4pJ1V11X6nEM7I4HhQIVRgpSMeGcAMU,11681
11
12
  hyperquant/broker/ourbit.py,sha256=NUcDSIttf-HGWzoW1uBTrGLPHlkuemMjYCm91MigTno,18228
12
13
  hyperquant/broker/ws.py,sha256=9Zu5JSLj-ylYEVmFmRwvZDDnVYKwb37cLHfZzA0AZGc,2200
13
14
  hyperquant/broker/lib/edgex_sign.py,sha256=lLUCmY8HHRLfLKyGrlTJYaBlSHPsIMWg3EZnQJKcmyk,95785
14
15
  hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb84D8,8296
15
16
  hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
16
- hyperquant/broker/lib/util.py,sha256=u02kGb-7LMCi32UNLeKoPaZBZ2LBEjx72KRkaKX0yQg,275
17
+ hyperquant/broker/lib/util.py,sha256=iMU1qF0CHj5zzlIMEQGwjz-qtEVosEe7slXOCuB7Rcw,566
18
+ hyperquant/broker/models/bitget.py,sha256=swKYa7-jRoCSQxg0AUTZ7lPEhD1vxYIKm49eaIPwMxU,8961
17
19
  hyperquant/broker/models/edgex.py,sha256=vPAkceal44cjTYKQ_0BoNAskOpmkno_Yo1KxgMLPc6Y,33954
18
20
  hyperquant/broker/models/hyperliquid.py,sha256=c4r5739ibZfnk69RxPjQl902AVuUOwT8RNvKsMtwXBY,9459
19
21
  hyperquant/broker/models/lbank.py,sha256=ZCD1dOUMyWPT8lKDj6C6LcHEof2d0JN384McURzLA-s,18868
@@ -22,6 +24,6 @@ hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw
22
24
  hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
23
25
  hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
24
26
  hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
25
- hyperquant-0.68.dist-info/METADATA,sha256=-5HFGYBVdzJALnWhh4AFh62i_3cPqw_CGs5wF3XXjEk,4317
26
- hyperquant-0.68.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- hyperquant-0.68.dist-info/RECORD,,
27
+ hyperquant-0.71.dist-info/METADATA,sha256=ldQcPawGZVC-9RTCaAFt4lE5SAw26fPVjbEfjk04xLE,4317
28
+ hyperquant-0.71.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
+ hyperquant-0.71.dist-info/RECORD,,