hyperquant 0.7__py3-none-any.whl → 0.8__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.
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- import itertools
5
4
  import logging
6
- import time
7
- from typing import Any, Iterable, Literal
5
+ import uuid
6
+ from typing import Any, Literal
8
7
 
9
8
  import pybotters
10
9
 
@@ -14,9 +13,11 @@ from .lib.util import fmt_value
14
13
  logger = logging.getLogger(__name__)
15
14
 
16
15
 
17
-
18
16
  class Bitget:
19
- """Bitget public market-data client (REST + WS)."""
17
+ """Bitget public/privileged client (REST + WS).
18
+
19
+ 默认只支持单向持仓(One-way mode)。
20
+ """
20
21
 
21
22
  def __init__(
22
23
  self,
@@ -32,70 +33,279 @@ class Bitget:
32
33
  self.ws_url = ws_url or "wss://ws.bitget.com/v2/ws/public"
33
34
  self.ws_url_private = ws_url or "wss://ws.bitget.com/v2/ws/private"
34
35
 
35
- self._ws_app = None
36
-
36
+ self.ws_app = None
37
+ self.has_sub_personal = False
38
+
37
39
 
38
40
  async def __aenter__(self) -> "Bitget":
39
41
  await self.update("detail")
40
42
  return self
41
43
 
42
- async def __aexit__(self, exc_type, exc, tb) -> None:
44
+ async def __aexit__(self, exc_type, exc, tb) -> None: # pragma: no cover - symmetry
43
45
  pass
44
46
 
47
+ async def sub_personal(self) -> None:
48
+ sub_msg = {
49
+ "op": "subscribe",
50
+ "args": [
51
+ {"instType": "USDT-FUTURES", "channel": "orders", "instId": "default"},
52
+ {
53
+ "instType": "USDT-FUTURES",
54
+ "channel": "positions",
55
+ "instId": "default",
56
+ },
57
+ {"instType": "USDT-FUTURES", "channel": "account", "coin": "default"},
58
+ ],
59
+ }
60
+ self.ws_app = await self._ensure_private_ws()
61
+
62
+
63
+ await self.ws_app.current_ws.send_json(sub_msg)
64
+
65
+
66
+ self.has_sub_personal = True
67
+
45
68
  async def update(
46
69
  self,
47
- update_type: Literal["detail", 'all'] = "all",
70
+ update_type: Literal["detail", "ticker", "all"] = "all",
48
71
  ) -> None:
49
- fet = []
50
- if update_type in ("detail", "all"):
51
- fet.append(
72
+ """Refresh cached REST resources."""
73
+
74
+ requests: list[Any] = []
75
+
76
+ if update_type in {"detail", "all"}:
77
+ requests.append(
52
78
  self.client.get(
53
- f"{self.rest_api}/api/v2/mix/market/contracts?productType=usdt-futures",
79
+ f"{self.rest_api}/api/v2/mix/market/contracts",
80
+ params={"productType": "usdt-futures"},
54
81
  )
55
82
  )
56
83
 
57
- await self.store.initialize(*fet)
84
+ if update_type in {"ticker", "all"}:
85
+ requests.append(
86
+ self.client.get(
87
+ f"{self.rest_api}/api/v2/mix/market/tickers",
88
+ params={"productType": "usdt-futures"},
89
+ )
90
+ )
91
+
92
+ if not requests:
93
+ raise ValueError(f"update_type err: {update_type}")
94
+
95
+ await self.store.initialize(*requests)
58
96
 
59
97
  async def place_order(
60
98
  self,
61
99
  symbol: str,
62
100
  *,
63
- direction: Literal["buy", "sell", "0", "1"],
101
+ direction: Literal["buy", "sell", "long", "short", "0", "1"],
64
102
  volume: float,
65
103
  price: float | None = None,
66
- order_type: Literal["market", "limit_ioc", "limit_gtc"] = "market",
67
- offset_flag: Literal["open", "close", "0", "1"] = "open",
68
- exchange_id: str = "Exchange",
69
- product_group: str = "SwapU",
70
- order_proportion: str = "0.0000",
71
- client_order_id: str | None = None,
72
- ) -> dict[str, Any]:
73
- pass
104
+ order_type: Literal[
105
+ "market",
106
+ "limit_gtc",
107
+ "limit_ioc",
108
+ "limit_fok",
109
+ "limit_post_only",
110
+ "limit",
111
+ ] = "market",
112
+ margin_mode: Literal["isolated", "crossed"] = "crossed",
113
+ product_type: str = "USDT-FUTURES",
114
+ margin_coin: str = "USDT",
115
+ reduce_only: bool | None = None,
116
+ offset_flag: Literal["open", "close", "0", "1"] | None = None,
117
+ client_order_id: str | None = None
118
+ ) -> dict[str, Any] | None:
119
+ """
120
+ 请求成功返回示例:
121
+
122
+ .. code:: json
123
+
124
+ {
125
+ "clientOid": "121211212122",
126
+ "orderId": "121211212122"
127
+ }
128
+ """
129
+
130
+ side = self._normalize_direction(direction)
131
+ order_type_code, force_code = self._resolve_order_type(order_type)
132
+
133
+ if reduce_only is None:
134
+ reduce_only = self._normalize_offset(offset_flag)
135
+
136
+ detail = self._get_detail_entry(symbol)
137
+ volume_str = self._format_with_step(
138
+ volume, detail.get("step_size") or detail.get("stepSize")
139
+ )
140
+
141
+ payload: dict[str, Any] = {
142
+ "symbol": symbol,
143
+ "productType": product_type,
144
+ "marginMode": margin_mode,
145
+ "marginCoin": margin_coin,
146
+ "side": side,
147
+ "size": volume_str,
148
+ "orderType": order_type_code,
149
+ }
150
+
151
+ if force_code:
152
+ payload["force"] = force_code
153
+
154
+ if order_type_code == "limit":
155
+ if price is None:
156
+ raise ValueError("price is required for Bitget limit orders")
157
+ payload["price"] = self._format_with_step(
158
+ price,
159
+ detail.get("tick_size") or detail.get("tickSize"),
160
+ )
161
+ elif price is not None:
162
+ logger.debug("Price %.8f ignored for market order", price)
163
+
164
+ if reduce_only is True:
165
+ payload["reduceOnly"] = "YES"
166
+ elif reduce_only is False:
167
+ payload["reduceOnly"] = "NO"
168
+
169
+ if client_order_id:
170
+ payload["clientOid"] = client_order_id
171
+
172
+ res = await self.client.post(
173
+ f"{self.rest_api}/api/v2/mix/order/place-order",
174
+ data=payload,
175
+ )
176
+ data = await res.json()
177
+ return self._ensure_ok("place_order", data)
74
178
 
75
179
  async def cancel_order(
76
180
  self,
77
181
  order_sys_id: str,
78
182
  *,
79
- action_flag: str | int = "1",
183
+ symbol: str,
184
+ margin_mode: Literal["isolated", "crossed"],
185
+ product_type: str = "USDT-FUTURES",
186
+ margin_coin: str = "USDT",
187
+ client_order_id: str | None = None,
80
188
  ) -> dict[str, Any]:
81
- pass
189
+ """Cancel an order via ``POST /api/v2/mix/order/cancel-order``."""
82
190
 
191
+ payload = {
192
+ "symbol": symbol,
193
+ "productType": product_type,
194
+ "marginMode": margin_mode,
195
+ "marginCoin": margin_coin,
196
+ }
83
197
 
84
- async def sub_orderbook(self, symbols: list[str], channel: str = 'books1') -> None:
85
- """订阅指定交易对的订单簿(遵循 LBank 协议)。
86
- """
198
+ if client_order_id:
199
+ payload["clientOid"] = client_order_id
200
+ else:
201
+ payload["orderId"] = order_sys_id
87
202
 
88
- submsg = {
89
- "op": "subscribe",
90
- "args": []
91
- }
203
+ res = await self.client.post(
204
+ f"{self.rest_api}/api/v2/mix/order/cancel-order",
205
+ data=payload,
206
+ )
207
+ data = await res.json()
208
+ return self._ensure_ok("cancel_order", data)
209
+
210
+ async def sub_orderbook(self, symbols: list[str], channel: str = "books1") -> None:
211
+ """Subscribe to Bitget order-book snapshots/updates."""
212
+
213
+ submsg = {"op": "subscribe", "args": []}
92
214
  for symbol in symbols:
93
215
  submsg["args"].append(
94
- {"instType": "SPOT", "channel": channel, "instId": symbol}
216
+ {"instType": "USDT-FUTURES", "channel": channel, "instId": symbol}
95
217
  )
96
218
 
97
219
  self.client.ws_connect(
98
220
  self.ws_url,
99
221
  send_json=submsg,
100
- hdlr_json=self.store.onmessage
101
- )
222
+ hdlr_json=self.store.onmessage,
223
+ )
224
+
225
+ def _get_detail_entry(self, symbol: str) -> dict[str, Any]:
226
+ detail = self.store.detail.get({"symbol": symbol})
227
+ if not detail:
228
+ raise ValueError(
229
+ f"Unknown Bitget instrument: {symbol}. Call update('detail') first or provide valid symbol."
230
+ )
231
+ return detail
232
+
233
+ async def _ensure_private_ws(self):
234
+ wsqueue = pybotters.WebSocketQueue()
235
+ ws_app = self.client.ws_connect(
236
+ self.ws_url_private,
237
+ hdlr_json=self.store.onmessage,
238
+ )
239
+ # async for msg in wsqueue:
240
+ # print(msg)
241
+
242
+ await ws_app._event.wait()
243
+ await ws_app.current_ws._wait_authtask()
244
+ return ws_app
245
+
246
+ @staticmethod
247
+ def _format_with_step(value: float, step: Any) -> str:
248
+ if step in (None, 0, "0"):
249
+ return str(value)
250
+ try:
251
+ step_float = float(step)
252
+ except (TypeError, ValueError): # pragma: no cover - defensive guard
253
+ return str(value)
254
+ if step_float <= 0:
255
+ return str(value)
256
+ return fmt_value(value, step_float)
257
+
258
+ @staticmethod
259
+ def _normalize_direction(direction: str) -> str:
260
+ mapping = {
261
+ "buy": "buy",
262
+ "long": "buy",
263
+ "0": "buy",
264
+ "sell": "sell",
265
+ "short": "sell",
266
+ "1": "sell",
267
+ }
268
+ key = str(direction).lower()
269
+ try:
270
+ return mapping[key]
271
+ except KeyError as exc: # pragma: no cover - guard
272
+ raise ValueError(f"Unsupported direction: {direction}") from exc
273
+
274
+ @staticmethod
275
+ def _normalize_offset(
276
+ offset: Literal["open", "close", "0", "1"] | None,
277
+ ) -> bool | None:
278
+ if offset is None:
279
+ return None
280
+ mapping = {
281
+ "open": False,
282
+ "0": False,
283
+ "close": True,
284
+ "1": True,
285
+ }
286
+ key = str(offset).lower()
287
+ if key in mapping:
288
+ return mapping[key]
289
+ raise ValueError(f"Unsupported offset_flag: {offset}")
290
+
291
+ @staticmethod
292
+ def _resolve_order_type(order_type: str) -> tuple[str, str | None]:
293
+ mapping = {
294
+ "market": ("market", None),
295
+ "limit": ("limit", "gtc"),
296
+ "limit_gtc": ("limit", "gtc"),
297
+ "limit_ioc": ("limit", "ioc"),
298
+ "limit_fok": ("limit", "fok"),
299
+ "limit_post_only": ("limit", "post_only"),
300
+ }
301
+ key = str(order_type).lower()
302
+ try:
303
+ return mapping[key]
304
+ except KeyError as exc: # pragma: no cover - guard
305
+ raise ValueError(f"Unsupported order_type: {order_type}") from exc
306
+
307
+ @staticmethod
308
+ def _ensure_ok(operation: str, data: Any) -> dict[str, Any]:
309
+ if not isinstance(data, dict) or data.get("code") != "00000":
310
+ raise RuntimeError(f"{operation} failed: {data}")
311
+ return data.get("data") or {}
@@ -136,6 +136,162 @@ class Lbank:
136
136
 
137
137
  await self.store.initialize(*requests)
138
138
 
139
+ async def query_trade(
140
+ self,
141
+ order_id: str | None = None,
142
+ *,
143
+ product_group: str = "SwapU",
144
+ page_index: int = 1,
145
+ page_size: int = 20,
146
+ ) -> list[dict[str, Any]]:
147
+ """Fetch trade executions linked to a given OrderSysID.
148
+
149
+ Example response payload::
150
+
151
+ [
152
+ {
153
+ "TradeUnitID": "e1b03fb1-6849-464f-a",
154
+ "ProductGroup": "SwapU",
155
+ "CloseProfit": 0,
156
+ "BusinessNo": 1001770339345505,
157
+ "TradeID": "1000162046503720",
158
+ "PositionID": "1000632926272299",
159
+ "DeriveSource": "0",
160
+ "OrderID": "",
161
+ "Direction": "0",
162
+ "InstrumentID": "SOLUSDT",
163
+ "OffsetFlag": "0",
164
+ "Remark": "def",
165
+ "DdlnTime": "0",
166
+ "UseMargin": 0.054213,
167
+ "Currency": "USDT",
168
+ "Turnover": 5.4213,
169
+ "SettlementGroup": "SwapU",
170
+ "Leverage": 100,
171
+ "OrderSysID": "1000632948114584",
172
+ "ExchangeID": "Exchange",
173
+ "AccountID": "e1b03fb1-6849-464f-a986-94b9a6e625e6",
174
+ "TradeTime": 1760161085,
175
+ "Fee": 0.00325278,
176
+ "OrderPrice": 180.89,
177
+ "InsertTime": 1760161085,
178
+ "MemberID": "e1b03fb1-6849-464f-a986-94b9a6e625e6",
179
+ "MatchRole": "1",
180
+ "ClearCurrency": "USDT",
181
+ "Price": 180.71,
182
+ "Volume": 0.03,
183
+ "OpenPrice": 182.94,
184
+ "MasterAccountID": "",
185
+ "PriceCurrency": "USDT",
186
+ "FeeCurrency": "USDT"
187
+ }
188
+ ]
189
+ """
190
+
191
+ if not order_id:
192
+ raise ValueError("order_id is required to query order executions")
193
+
194
+ params = {
195
+ "ProductGroup": product_group,
196
+ "OrderSysID": order_id,
197
+ "pageIndex": page_index,
198
+ "pageSize": page_size,
199
+ }
200
+
201
+ res = await self.client.get(
202
+ f"{self.front_api}/cfd/query/v1.0/Trade",
203
+ params=params,
204
+ headers=self._rest_headers,
205
+ )
206
+ data = await res.json()
207
+ payload = self._ensure_ok("query_trade", data)
208
+
209
+ if isinstance(payload, dict):
210
+ rows = payload.get("data")
211
+ if isinstance(rows, list):
212
+ return rows
213
+ elif isinstance(payload, list): # pragma: no cover - defensive fallback
214
+ return payload
215
+
216
+ return []
217
+
218
+ async def query_order(
219
+ self,
220
+ order_id: str | None = None,
221
+ *,
222
+ product_group: str = "SwapU",
223
+ page_index: int = 1,
224
+ page_size: int = 20,
225
+ ) -> dict[str, Any]:
226
+ """
227
+ 返回值示例:
228
+
229
+ .. code:: json
230
+
231
+ {
232
+ "order_id": "1000632478428573",
233
+ "instrument_id": "SOLUSDT",
234
+ "position_id": "1000632478428573",
235
+ "direction": "0",
236
+ "offset_flag": "0",
237
+ "trade_time": 1760123456,
238
+ "avg_price": 182.5,
239
+ "volume": 0.03,
240
+ "turnover": 5.475,
241
+ "fee": 0.003285,
242
+ "trade_count": 1
243
+ }
244
+
245
+ 如果没有订单成交返回
246
+ {
247
+ "order_id": "1000632478428573",
248
+ "trade_count": 0
249
+ }
250
+ """
251
+
252
+ if not order_id:
253
+ raise ValueError("order_id is required to query order statistics")
254
+
255
+ trades = await self.query_trade(
256
+ order_id,
257
+ product_group=product_group,
258
+ page_index=page_index,
259
+ page_size=page_size,
260
+ )
261
+
262
+ if not trades:
263
+ return {
264
+ "order_id": order_id,
265
+ "trade_count": 0,
266
+ }
267
+
268
+ def _to_float(value: Any) -> float:
269
+ try:
270
+ return float(value)
271
+ except (TypeError, ValueError):
272
+ return 0.0
273
+
274
+ total_volume = sum(_to_float(trade.get("Volume")) for trade in trades)
275
+ total_turnover = sum(_to_float(trade.get("Turnover")) for trade in trades)
276
+ total_fee = sum(_to_float(trade.get("Fee")) for trade in trades)
277
+
278
+ avg_price = total_turnover / total_volume if total_volume else None
279
+ last_trade = trades[-1]
280
+
281
+ return {
282
+ "order_id": order_id,
283
+ "instrument_id": last_trade.get("InstrumentID"),
284
+ "position_id": last_trade.get("PositionID"),
285
+ "direction": last_trade.get("Direction"),
286
+ "offset_flag": last_trade.get("OffsetFlag"),
287
+ "trade_time": last_trade.get("TradeTime"),
288
+ "avg_price": avg_price,
289
+ "volume": total_volume,
290
+ "turnover": total_turnover,
291
+ "fee": total_fee,
292
+ "trade_count": len(trades),
293
+ }
294
+
139
295
  def _resolve_instrument(self) -> str | None:
140
296
  detail_entries = self.store.detail.find()
141
297
  if detail_entries:
@@ -212,7 +368,71 @@ class Lbank:
212
368
  order_proportion: str = "0.0000",
213
369
  client_order_id: str | None = None,
214
370
  ) -> dict[str, Any]:
215
- """Create an order using documented REST parameters."""
371
+ """Create an order using documented REST parameters.
372
+
373
+ 返回示例:
374
+
375
+ .. code:: json
376
+
377
+ {
378
+ "offsetFlag": "5",
379
+ "orderType": "1",
380
+ "reserveMode": "0",
381
+ "fee": "0.0066042",
382
+ "frozenFee": "0",
383
+ "ddlnTime": "0",
384
+ "userID": "lbank_exchange_user",
385
+ "masterAccountID": "",
386
+ "exchangeID": "Exchange",
387
+ "accountID": "e1b03fb1-6849-464f-a986-94b9a6e625e6",
388
+ "orderSysID": "1000633129818889",
389
+ "volumeRemain": "0",
390
+ "price": "183.36",
391
+ "businessValue": "1760183423813",
392
+ "frozenMargin": "0",
393
+ "instrumentID": "SOLUSDT",
394
+ "posiDirection": "2",
395
+ "volumeMode": "1",
396
+ "volume": "0.06",
397
+ "insertTime": "1760183423",
398
+ "copyMemberID": "",
399
+ "position": "0.06",
400
+ "tradePrice": "183.45",
401
+ "leverage": "100",
402
+ "businessResult": "",
403
+ "availableUse": "0",
404
+ "orderStatus": "1",
405
+ "openPrice": "182.94",
406
+ "frozenMoney": "0",
407
+ "remark": "def",
408
+ "reserveUse": "0",
409
+ "sessionNo": "41",
410
+ "isCrossMargin": "1",
411
+ "closeProfit": "0.0306",
412
+ "businessNo": "1001770756852986", # 订单有成交会并入仓位 businessNo
413
+ "relatedOrderSysID": "",
414
+ "positionID": "1000632926272299",
415
+ "mockResp": false,
416
+ "deriveSource": "0",
417
+ "copyOrderID": "",
418
+ "currency": "USDT",
419
+ "turnover": "11.007",
420
+ "frontNo": "-68",
421
+ "direction": "1",
422
+ "orderPriceType": "4",
423
+ "volumeCancled": "0",
424
+ "updateTime": "1760183423",
425
+ "localID": "1000633129818889",
426
+ "volumeTraded": "0.06",
427
+ "appid": "WEB",
428
+ "tradeUnitID": "e1b03fb1-6849-464f-a",
429
+ "businessType": "P",
430
+ "memberID": "e1b03fb1-6849-464f-a986-94b9a6e625e6",
431
+ "timeCondition": "0",
432
+ "copyProfit": "0"
433
+ }
434
+
435
+ """
216
436
 
217
437
  direction_code = self._normalize_direction(direction)
218
438
  offset_code = self._normalize_offset(offset_flag)
@@ -244,9 +464,7 @@ class Lbank:
244
464
  # logger.warning("Price is ignored for market orders")
245
465
  pass
246
466
 
247
- # if client_order_id:
248
- # payload["LocalID"] = client_order_id
249
- print(payload)
467
+
250
468
  res = await self.client.post(
251
469
  f"{self.front_api}/cfd/cff/v1/SendOrderInsert",
252
470
  json=payload,
@@ -277,6 +495,22 @@ class Lbank:
277
495
  if not isinstance(data, dict) or data.get("code") != 200:
278
496
  raise RuntimeError(f"{operation} failed: {data}")
279
497
  return data.get("data") or {}
498
+
499
+
500
+ async def set_position_mode(self, mode: Literal["hedge", "oneway"] = "oneway") -> dict[str, Any]:
501
+ """设置持仓模式到单向持仓或对冲持仓"""
502
+
503
+ mode_code = "2" if mode == "oneway" else "1"
504
+ payload = {
505
+ "PositionType": mode_code,
506
+ }
507
+ res = await self.client.post(
508
+ f"{self.front_api}/cfd/action/v1.0/SendMemberAction",
509
+ json=payload,
510
+ headers=self._rest_headers,
511
+ )
512
+ data = await res.json()
513
+ return self._ensure_ok("set_position_mode", data)
280
514
 
281
515
  @staticmethod
282
516
  def _normalize_direction(direction: str) -> str:
@@ -351,4 +585,4 @@ class Lbank:
351
585
  batch = send_jsons[i:i+5]
352
586
  await asyncio.gather(*(sub(send_json) for send_json in batch))
353
587
  if i + 5 < len(send_jsons):
354
- await asyncio.sleep(0.1)
588
+ await asyncio.sleep(0.3)
@@ -16,13 +16,18 @@ class Detail(DataStore):
16
16
  _KEYS = ["symbol"]
17
17
 
18
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
19
+ step_place = entry.get("volume_place", 1)
20
+ tick_place = entry.get("price_place", 1)
21
+
22
+ step_size = place_to_step(step_place)
23
+ tick_size = place_to_step(tick_place)
24
+
25
+ entry["stepSize"] = step_size
26
+ entry["tickSize"] = tick_size
27
+ # expose snake_case aliases for downstream callers keeping legacy naming
28
+ entry["step_size"] = step_size
29
+ entry["tick_size"] = tick_size
30
+
26
31
  return entry
27
32
 
28
33
  def _onresponse(self, data: list[dict[str, Any]] | dict[str, Any] | None) -> None:
@@ -280,4 +285,75 @@ class BitgetDataStore(BitgetV2DataStore):
280
285
  }
281
286
  ]
282
287
  """
283
- return self._get("book")
288
+ return self._get("book")
289
+
290
+ @property
291
+ def account(self) -> DataStore:
292
+ """
293
+ _KEYS = ["instType", "marginCoin"]
294
+
295
+ Data Structure:
296
+
297
+ .. code:: json
298
+
299
+ [
300
+ {
301
+ "marginCoin": "USDT",
302
+ "frozen": "0.00000000",
303
+ "available": "13.98545761",
304
+ "maxOpenPosAvailable": "13.98545761",
305
+ "maxTransferOut": "13.98545761",
306
+ "equity": "13.98545761",
307
+ "usdtEquity": "13.985457617660",
308
+ "crossedRiskRate": "0",
309
+ "unrealizedPL": "0.000000000000",
310
+ "unionTotalMargin": "100",
311
+ "unionAvailable": "20",
312
+ "unionMm": "15",
313
+ "assetMode": "union"
314
+ }
315
+ ]
316
+ """
317
+ return self._get("account")
318
+
319
+ @property
320
+ def position(self) -> DataStore:
321
+ """
322
+ _KEYS = ["instType", "instId", "posId"]
323
+
324
+ Data Structure:
325
+
326
+ .. code:: json
327
+
328
+ [
329
+ {
330
+ "posId": "1",
331
+ "instId": "ETHUSDT",
332
+ "marginCoin": "USDT",
333
+ "marginSize": "9.5",
334
+ "marginMode": "crossed",
335
+ "holdSide": "short",
336
+ "posMode": "hedge_mode",
337
+ "total": "0.1",
338
+ "available": "0.1",
339
+ "frozen": "0",
340
+ "openPriceAvg": "1900",
341
+ "leverage": 20,
342
+ "achievedProfits": "0",
343
+ "unrealizedPL": "0",
344
+ "unrealizedPLR": "0",
345
+ "liquidationPrice": "5788.108475905242",
346
+ "keepMarginRate": "0.005",
347
+ "marginRate": "0.004416374196",
348
+ "cTime": "1695649246169",
349
+ "breakEvenPrice": "24778.97",
350
+ "totalFee": "1.45",
351
+ "deductedFee": "0.388",
352
+ "markPrice": "2500",
353
+ "assetMode": "union",
354
+ "uTime": "1695711602568",
355
+ "autoMargin": "off"
356
+ }
357
+ ]
358
+ """
359
+ return self._get("position")
@@ -184,7 +184,8 @@ class Orders(DataStore):
184
184
  "side": direction,
185
185
  "offset": offset_flag,
186
186
  "order_type": order_kind,
187
- "price": entry.get("Price"),
187
+ "price": entry.get('TradePrice') or entry.get("Price"),
188
+ "order_price": entry.get("OrderPrice") or entry.get('Price'),
188
189
  "quantity": entry.get("Volume"),
189
190
  "filled": entry.get("VolumeTraded"),
190
191
  "remaining": entry.get("VolumeRemain"),
@@ -270,14 +271,20 @@ class Position(DataStore):
270
271
  if not entry:
271
272
  return None
272
273
  position_id = entry.get("PositionID")
274
+ bus_id = entry.get("BusinessNo")
273
275
  if not position_id:
274
276
  return None
275
-
276
- direction_code = str(entry.get("PosiDirection")) if entry.get("PosiDirection") is not None else None
277
- side = self._POS_DIRECTION_MAP.get(direction_code, direction_code)
278
-
277
+
278
+ q = float(entry.get("Position", 0))
279
+ side = "net"
280
+ if q > 0:
281
+ side = "long"
282
+ elif q < 0:
283
+ side = "short"
284
+
279
285
  return {
280
286
  "position_id": position_id,
287
+ "bus_id": bus_id,
281
288
  "symbol": entry.get("InstrumentID"),
282
289
  "side": side,
283
290
  "quantity": entry.get("Position"),
@@ -291,6 +298,7 @@ class Position(DataStore):
291
298
  "realized_pnl": entry.get("CloseProfit"),
292
299
  "update_time": entry.get("UpdateTime"),
293
300
  "insert_time": entry.get("InsertTime"),
301
+ "begin_time": entry.get("BeginTime")
294
302
  }
295
303
 
296
304
  def _onresponse(self, data: dict[str, Any] | None) -> None:
@@ -472,6 +480,7 @@ class LbankDataStore(DataStoreCollection):
472
480
  [
473
481
  {
474
482
  "position_id": <仓位ID>,
483
+ "bus_id": <订单ID覆盖>,
475
484
  "symbol": <合约ID>,
476
485
  "side": "long" / "short" / "net",
477
486
  "quantity": <持仓数量>,
@@ -484,7 +493,8 @@ class LbankDataStore(DataStoreCollection):
484
493
  "unrealized_pnl": <未实现盈亏>,
485
494
  "realized_pnl": <已实现盈亏>,
486
495
  "update_time": <更新时间>,
487
- "insert_time": <插入时间>
496
+ "insert_time": <插入时间>,
497
+ "begin_time": <持仓开始时间>
488
498
  },
489
499
  ...
490
500
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.7
3
+ Version: 0.8
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,25 +5,25 @@ 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=866OuWUek0xJM8qWa7Bh4GDvcwUe3JVrx5mQMv2WEJE,2616
8
+ hyperquant/broker/bitget.py,sha256=X_S0LKZ7FZAEb6oEMr1vdGP1fondzK74BhmNTpRDSEA,9488
9
9
  hyperquant/broker/edgex.py,sha256=TqUO2KRPLN_UaxvtLL6HnA9dAQXC1sGxOfqTHd6W5k8,18378
10
10
  hyperquant/broker/hyperliquid.py,sha256=7MxbI9OyIBcImDelPJu-8Nd53WXjxPB5TwE6gsjHbto,23252
11
- hyperquant/broker/lbank.py,sha256=dZUbi0a_Vhkp4pJ1V11X6nEM7I4HhQIVRgpSMeGcAMU,11681
11
+ hyperquant/broker/lbank.py,sha256=ynlBA7TAPjsT_aaAh_G4jse8gNLYdmuQQcDaWSjcxL8,19559
12
12
  hyperquant/broker/ourbit.py,sha256=NUcDSIttf-HGWzoW1uBTrGLPHlkuemMjYCm91MigTno,18228
13
13
  hyperquant/broker/ws.py,sha256=9Zu5JSLj-ylYEVmFmRwvZDDnVYKwb37cLHfZzA0AZGc,2200
14
14
  hyperquant/broker/lib/edgex_sign.py,sha256=lLUCmY8HHRLfLKyGrlTJYaBlSHPsIMWg3EZnQJKcmyk,95785
15
15
  hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb84D8,8296
16
16
  hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
17
17
  hyperquant/broker/lib/util.py,sha256=iMU1qF0CHj5zzlIMEQGwjz-qtEVosEe7slXOCuB7Rcw,566
18
- hyperquant/broker/models/bitget.py,sha256=swKYa7-jRoCSQxg0AUTZ7lPEhD1vxYIKm49eaIPwMxU,8961
18
+ hyperquant/broker/models/bitget.py,sha256=0RwDY75KrJb-c-oYoMxbqxWfsILe-n_Npojz4UFUq7c,11389
19
19
  hyperquant/broker/models/edgex.py,sha256=vPAkceal44cjTYKQ_0BoNAskOpmkno_Yo1KxgMLPc6Y,33954
20
20
  hyperquant/broker/models/hyperliquid.py,sha256=c4r5739ibZfnk69RxPjQl902AVuUOwT8RNvKsMtwXBY,9459
21
- hyperquant/broker/models/lbank.py,sha256=ZCD1dOUMyWPT8lKDj6C6LcHEof2d0JN384McURzLA-s,18868
21
+ hyperquant/broker/models/lbank.py,sha256=vHkNKxIMzpoC_EwcZnEOPOupizF92yGWi9GKxvYYFUQ,19181
22
22
  hyperquant/broker/models/ourbit.py,sha256=xMcbuCEXd3XOpPBq0RYF2zpTFNnxPtuNJZCexMZVZ1k,41965
23
23
  hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw,533
24
24
  hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
25
25
  hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
26
26
  hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
27
- hyperquant-0.7.dist-info/METADATA,sha256=DZvCivcjbSzGsuRtDvwuaAiaG2zw61z_13guzOR2rsQ,4316
28
- hyperquant-0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
- hyperquant-0.7.dist-info/RECORD,,
27
+ hyperquant-0.8.dist-info/METADATA,sha256=sZJCGMDc0ZTKhOfpK8IK7H0XkgKnhQJMX-Fai7aqahM,4316
28
+ hyperquant-0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
+ hyperquant-0.8.dist-info/RECORD,,