hyperquant 0.5__py3-none-any.whl → 0.6__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,183 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import Any, Iterable, Literal
5
+
6
+ import pybotters
7
+
8
+ from .models.edgex import EdgexDataStore
9
+
10
+
11
+ class Edgex:
12
+ """
13
+ Edgex 公共 API (HTTP/WS) 封装。
14
+
15
+ 说明
16
+ - 当前仅包含公共行情数据(不包含私有接口)。
17
+ - 订单簿频道命名规则:``depth.{contractId}.{level}``。
18
+ 成功订阅后,服务器会先推送一次完整快照(depthType=SNAPSHOT),之后持续推送增量(depthType=CHANGED)。
19
+ 解析后的结果存入 ``EdgexDataStore.book``。
20
+
21
+ 参数
22
+ - client: ``pybotters.Client`` 实例
23
+ - api_url: REST 基地址;默认使用 Edgex 官方 testnet 站点
24
+ - ws_url: WebSocket 基地址;如不提供,则默认使用官方文档地址。
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ client: pybotters.Client,
30
+ *,
31
+ api_url: str | None = None,
32
+ ) -> None:
33
+ self.client = client
34
+ self.store = EdgexDataStore()
35
+ # 公共端点可能因环境/地区不同而变化,允许外部覆盖。
36
+ self.api_url = api_url or "https://pro.edgex.exchange"
37
+ self.ws_url = "wss://quote.edgex.exchange"
38
+
39
+ async def __aenter__(self) -> "Edgex":
40
+ # 初始化基础合约元数据,便于后续使用 tickSize 等字段。
41
+ await self.update_detail()
42
+ return self
43
+
44
+ async def __aexit__(
45
+ self,
46
+ exc_type: type[BaseException] | None,
47
+ exc: BaseException | None,
48
+ tb: BaseException | None,
49
+ ) -> None:
50
+ # Edgex 当前没有需要关闭的资源;保持接口与 Ourbit 等类一致。
51
+ return None
52
+
53
+ async def update_detail(self) -> dict[str, Any]:
54
+ """Fetch and cache contract metadata via the public REST endpoint."""
55
+
56
+ url = self._resolve_api_path("/api/v1/public/meta/getMetaData")
57
+ res = await self.client.get(url)
58
+ res.raise_for_status()
59
+ data = await res.json()
60
+
61
+ if data.get("code") != "SUCCESS": # pragma: no cover - defensive guard
62
+ raise RuntimeError(f"Failed to fetch Edgex metadata: {data}")
63
+
64
+ self.store._apply_metadata(data)
65
+ return data
66
+
67
+ def _resolve_api_path(self, path: str) -> str:
68
+ base = (self.api_url or "").rstrip("/")
69
+ return f"{base}{path}"
70
+
71
+ async def sub_orderbook(
72
+ self,
73
+ contract_ids: str | Iterable[str] | None = None,
74
+ *,
75
+ symbols: str | Iterable[str] | None = None,
76
+ level: int = 15,
77
+ ws_url: str | None = None,
78
+ ) -> None:
79
+ """订阅指定合约 ID 或交易对名的订单簿(遵循 Edgex 协议)。
80
+
81
+ 规范
82
+ - 默认 WS 端点:wss://quote.edgex.exchange(可通过参数/实例覆盖)
83
+ - 每个频道的订阅报文:
84
+ {"type": "subscribe", "channel": "depth.{contractId}.{level}"}
85
+ - 服务端在订阅成功后,会先推送一次快照,再持续推送增量。
86
+ """
87
+
88
+ ids: list[str] = []
89
+ if contract_ids is not None:
90
+ if isinstance(contract_ids, str):
91
+ ids.extend([contract_ids])
92
+ else:
93
+ ids.extend(contract_ids)
94
+
95
+ if symbols is not None:
96
+ if isinstance(symbols, str):
97
+ lookup_symbols = [symbols]
98
+ else:
99
+ lookup_symbols = list(symbols)
100
+
101
+ for symbol in lookup_symbols:
102
+ matches = self.store.detail.find({"contractName": symbol})
103
+ if not matches:
104
+ raise ValueError(f"Unknown Edgex symbol: {symbol}")
105
+ ids.append(str(matches[0]["contractId"]))
106
+
107
+ if not ids:
108
+ raise ValueError("contract_ids or symbols must be provided")
109
+
110
+ channels = [f"depth.{cid}.{level}" for cid in ids]
111
+
112
+ # 优先使用参数 ws_url,其次使用实例的 ws_url,最后使用默认地址。
113
+ url = f"{self.ws_url}/api/v1/public/ws?timestamp=" + str(int(time.time() * 1000))
114
+
115
+ # 根据文档:每个频道一条订阅指令,允许一次发送多个订阅对象。
116
+ payload = [{"type": "subscribe", "channel": ch} for ch in channels]
117
+
118
+ wsapp = self.client.ws_connect(url, send_json=payload, hdlr_json=self.store.onmessage)
119
+ # 等待 WS 完成握手再返回,确保订阅报文成功发送。
120
+ await wsapp._event.wait()
121
+
122
+ async def sub_ticker(
123
+ self,
124
+ contract_ids: str | Iterable[str] | None = None,
125
+ *,
126
+ symbols: str | Iterable[str] | None = None,
127
+ all_contracts: bool = False,
128
+ periodic: bool = False,
129
+ ws_url: str | None = None,
130
+ ) -> None:
131
+ """订阅 24 小时行情推送。
132
+
133
+ 参数
134
+ - contract_ids / symbols: 指定单个或多个合约;二者至少提供一个。
135
+ - all_contracts: 订阅 ``ticker.all``(或 ``ticker.all.1s``)。
136
+ - periodic: 与 ``all_contracts`` 配合,true 则订阅 ``ticker.all.1s``。
137
+ """
138
+
139
+ channels: list[str] = []
140
+
141
+ if all_contracts:
142
+ channel = "ticker.all.1s" if periodic else "ticker.all"
143
+ channels.append(channel)
144
+ else:
145
+ ids: list[str] = []
146
+ if contract_ids is not None:
147
+ if isinstance(contract_ids, str):
148
+ ids.append(contract_ids)
149
+ else:
150
+ ids.extend(contract_ids)
151
+
152
+ if symbols is not None:
153
+ if isinstance(symbols, str):
154
+ lookup_symbols = [symbols]
155
+ else:
156
+ lookup_symbols = list(symbols)
157
+
158
+ for symbol in lookup_symbols:
159
+ matches = self.store.detail.find({"contractName": symbol})
160
+ if not matches:
161
+ raise ValueError(f"Unknown Edgex symbol: {symbol}")
162
+ ids.append(str(matches[0]["contractId"]))
163
+
164
+ if not ids:
165
+ raise ValueError("Provide contract_ids/symbols or set all_contracts=True")
166
+
167
+ channels.extend(f"ticker.{cid}" for cid in ids)
168
+
169
+ url = ws_url or f"{self.ws_url}/api/v1/public/ws?timestamp=" + str(int(time.time() * 1000))
170
+ payload = [{"type": "subscribe", "channel": ch} for ch in channels]
171
+
172
+ wsapp = self.client.ws_connect(url, send_json=payload, hdlr_json=self.store.onmessage)
173
+ await wsapp._event.wait()
174
+
175
+
176
+ async def __aexit__(
177
+ self,
178
+ exc_type: type[BaseException] | None,
179
+ exc: BaseException | None,
180
+ tb: BaseException | None,
181
+ ) -> None:
182
+ # Edgex 当前没有需要关闭的资源;保持接口与 Ourbit 等类一致。
183
+ return None
@@ -0,0 +1,513 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import Any, Awaitable, TYPE_CHECKING
5
+
6
+ from aiohttp import ClientResponse
7
+ from pybotters.store import DataStore, DataStoreCollection
8
+
9
+ if TYPE_CHECKING:
10
+ from pybotters.typedefs import Item
11
+ from pybotters.ws import ClientWebSocketResponse
12
+
13
+
14
+ class Book(DataStore):
15
+ """Order book data store for the Edgex websocket feed."""
16
+
17
+ _KEYS = ["c", "S", "p"]
18
+
19
+ def _init(self) -> None:
20
+ self._version: int | str | None = None
21
+ self.limit: int | None = None
22
+
23
+ def _on_message(self, msg: dict[str, Any]) -> None:
24
+ content = msg.get("content") or {}
25
+ entries = content.get("data") or []
26
+ data_type = (content.get("dataType") or "").lower()
27
+
28
+ for entry in entries:
29
+ contract_id = entry.get("contractId")
30
+ if contract_id is None:
31
+ continue
32
+
33
+ contract_name = entry.get("contractName")
34
+ end_version = entry.get("endVersion")
35
+ depth_type = (entry.get("depthType") or "").lower()
36
+
37
+ is_snapshot = data_type == "snapshot" or depth_type == "snapshot"
38
+
39
+ if is_snapshot:
40
+ self._handle_snapshot(
41
+ contract_id,
42
+ contract_name,
43
+ entry,
44
+ )
45
+ else:
46
+ self._handle_delta(
47
+ contract_id,
48
+ contract_name,
49
+ entry,
50
+ )
51
+
52
+ if end_version is not None:
53
+ self._version = self._normalize_version(end_version)
54
+
55
+ def _handle_snapshot(
56
+ self,
57
+ contract_id: str,
58
+ contract_name: str | None,
59
+ entry: dict[str, Any],
60
+ ) -> None:
61
+ asks = entry.get("asks") or []
62
+ bids = entry.get("bids") or []
63
+
64
+ self._find_and_delete({"c": contract_id})
65
+
66
+ payload: list[dict[str, Any]] = []
67
+ payload.extend(
68
+ self._build_items(
69
+ contract_id,
70
+ contract_name,
71
+ "a",
72
+ asks,
73
+ )
74
+ )
75
+ payload.extend(
76
+ self._build_items(
77
+ contract_id,
78
+ contract_name,
79
+ "b",
80
+ bids,
81
+ )
82
+ )
83
+
84
+ if payload:
85
+ self._insert(payload)
86
+ self._trim(contract_id, contract_name)
87
+
88
+ def _handle_delta(
89
+ self,
90
+ contract_id: str,
91
+ contract_name: str | None,
92
+ entry: dict[str, Any],
93
+ ) -> None:
94
+ updates: list[dict[str, Any]] = []
95
+ deletes: list[dict[str, Any]] = []
96
+
97
+ asks = entry.get("asks") or []
98
+ bids = entry.get("bids") or []
99
+
100
+ for side, levels in (("a", asks), ("b", bids)):
101
+ for row in levels:
102
+ price, size = self._extract_price_size(row)
103
+ criteria = {"c": contract_id, "S": side, "p": price}
104
+
105
+ if not size or float(size) == 0.0:
106
+ deletes.append(criteria)
107
+ continue
108
+
109
+ updates.append(
110
+ {
111
+ "c": contract_id,
112
+ "S": side,
113
+ "p": price,
114
+ "q": size,
115
+ "s": self._symbol(contract_id, contract_name),
116
+ }
117
+ )
118
+
119
+ if deletes:
120
+ self._delete(deletes)
121
+ if updates:
122
+ self._update(updates)
123
+ self._trim(contract_id, contract_name)
124
+
125
+
126
+ def _build_items(
127
+ self,
128
+ contract_id: str,
129
+ contract_name: str | None,
130
+ side: str,
131
+ rows: list[dict[str, Any]],
132
+ ) -> list[dict[str, Any]]:
133
+ items: list[dict[str, Any]] = []
134
+ for row in rows:
135
+ price, size = self._extract_price_size(row)
136
+ if not size or float(size) == 0.0:
137
+ continue
138
+ items.append(
139
+ {
140
+ "c": contract_id,
141
+ "S": side,
142
+ "p": price,
143
+ "q": size,
144
+ "s": self._symbol(contract_id, contract_name),
145
+ }
146
+ )
147
+ return items
148
+
149
+ @staticmethod
150
+ def _normalize_version(value: Any) -> int | str:
151
+ if value is None:
152
+ return value
153
+ try:
154
+ return int(value)
155
+ except (TypeError, ValueError):
156
+ return str(value)
157
+
158
+ @staticmethod
159
+ def _to_str(value: Any) -> str | None:
160
+ if value is None:
161
+ return None
162
+ return str(value)
163
+
164
+ @staticmethod
165
+ def _extract_price_size(row: dict[str, Any]) -> tuple[str, str]:
166
+ return str(row["price"]), str(row["size"])
167
+
168
+ def _trim(self, contract_id: str, contract_name: str | None) -> None:
169
+ if self.limit is None:
170
+ return
171
+
172
+ query: dict[str, Any]
173
+ symbol = self._symbol(contract_id, contract_name)
174
+ if symbol:
175
+ query = {"s": symbol}
176
+ else:
177
+ query = {"c": contract_id}
178
+
179
+ sort_data = self.sorted(query, self.limit)
180
+ asks = sort_data.get("a", [])
181
+ bids = sort_data.get("b", [])
182
+
183
+ self._find_and_delete(query)
184
+
185
+ trimmed = asks + bids
186
+ if trimmed:
187
+ self._insert(trimmed)
188
+
189
+ @staticmethod
190
+ def _symbol(contract_id: str, contract_name: str | None) -> str:
191
+ if contract_name:
192
+ return str(contract_name)
193
+ return str(contract_id)
194
+
195
+ @property
196
+ def version(self) -> int | str | None:
197
+ """返回当前缓存的订单簿版本号。"""
198
+ return self._version
199
+
200
+ def sorted(
201
+ self,
202
+ query: dict[str, Any] | None = None,
203
+ limit: int | None = None,
204
+ ) -> dict[str, list[dict[str, Any]]]:
205
+ """按买卖方向与价格排序后的订单簿视图。"""
206
+ return self._sorted(
207
+ item_key="S",
208
+ item_asc_key="a",
209
+ item_desc_key="b",
210
+ sort_key="p",
211
+ query=query,
212
+ limit=limit,
213
+ )
214
+
215
+
216
+ class Ticker(DataStore):
217
+ """24 小时行情推送数据。"""
218
+
219
+ _KEYS = ["c"]
220
+
221
+ def _on_message(self, msg: dict[str, Any]) -> None:
222
+ content = msg.get("content") or {}
223
+ entries = content.get("data") or []
224
+ data_type = (content.get("dataType") or "").lower()
225
+
226
+ for entry in entries:
227
+ item = self._format(entry)
228
+ if item is None:
229
+ continue
230
+
231
+ criteria = {"c": item["c"]}
232
+ if data_type == "snapshot":
233
+ self._find_and_delete(criteria)
234
+ self._insert([item])
235
+ else:
236
+ self._update([item])
237
+
238
+ def _format(self, entry: dict[str, Any]) -> dict[str, Any] | None:
239
+ contract_id = entry.get("contractId")
240
+ if contract_id is None:
241
+ return None
242
+
243
+ item: dict[str, Any] = {"c": str(contract_id)}
244
+
245
+ name = entry.get("contractName")
246
+ if name is not None:
247
+ item["s"] = str(name)
248
+
249
+ fields = [
250
+ "priceChange",
251
+ "priceChangePercent",
252
+ "trades",
253
+ "size",
254
+ "value",
255
+ "high",
256
+ "low",
257
+ "open",
258
+ "close",
259
+ "highTime",
260
+ "lowTime",
261
+ "startTime",
262
+ "endTime",
263
+ "lastPrice",
264
+ "indexPrice",
265
+ "oraclePrice",
266
+ "openInterest",
267
+ "fundingRate",
268
+ "fundingTime",
269
+ "nextFundingTime",
270
+ "bestAskPrice",
271
+ "bestBidPrice",
272
+ ]
273
+
274
+ for key in fields:
275
+ value = entry.get(key)
276
+ if value is not None:
277
+ item[key] = str(value)
278
+
279
+ return item
280
+
281
+
282
+ class CoinMeta(DataStore):
283
+ """Coin metadata (precision, StarkEx info, etc.)."""
284
+
285
+ _KEYS = ["coinId"]
286
+
287
+ def _onresponse(self, data: dict[str, Any]) -> None:
288
+ coins = (data.get("data") or {}).get("coinList") or []
289
+ items: list[dict[str, Any]] = []
290
+
291
+ for coin in coins:
292
+ coin_id = coin.get("coinId")
293
+ if coin_id is None:
294
+ continue
295
+ items.append(
296
+ {
297
+ "coinId": str(coin_id),
298
+ "coinName": coin.get("coinName"),
299
+ "stepSize": coin.get("stepSize"),
300
+ "showStepSize": coin.get("showStepSize"),
301
+ "starkExAssetId": coin.get("starkExAssetId"),
302
+ }
303
+ )
304
+
305
+ self._clear()
306
+ if items:
307
+ self._insert(items)
308
+
309
+
310
+ class ContractMeta(DataStore):
311
+ """Per-contract trading parameters from the metadata endpoint."""
312
+
313
+ _KEYS = ["contractId"]
314
+
315
+ _FIELDS = (
316
+ "contractName",
317
+ "baseCoinId",
318
+ "quoteCoinId",
319
+ "tickSize",
320
+ "stepSize",
321
+ "minOrderSize",
322
+ "maxOrderSize",
323
+ "defaultTakerFeeRate",
324
+ "defaultMakerFeeRate",
325
+ "enableTrade",
326
+ "fundingInterestRate",
327
+ "fundingImpactMarginNotional",
328
+ "fundingRateIntervalMin",
329
+ "starkExSyntheticAssetId",
330
+ "starkExResolution",
331
+ )
332
+
333
+ def _onresponse(self, data: dict[str, Any]) -> None:
334
+ contracts = (data.get("data") or {}).get("contractList") or []
335
+ items: list[dict[str, Any]] = []
336
+
337
+ for contract in contracts:
338
+ contract_id = contract.get("contractId")
339
+ if contract_id is None:
340
+ continue
341
+
342
+ payload = {"contractId": str(contract_id)}
343
+ for key in self._FIELDS:
344
+ payload[key] = contract.get(key)
345
+ payload["riskTierList"] = self._simplify_risk_tiers(contract.get("riskTierList"))
346
+
347
+ items.append(payload)
348
+
349
+ self._clear()
350
+ if items:
351
+ self._insert(items)
352
+
353
+ @staticmethod
354
+ def _simplify_risk_tiers(risk_tiers: Any) -> list[dict[str, Any]]:
355
+ items: list[dict[str, Any]] = []
356
+ for tier in risk_tiers or []:
357
+ items.append(
358
+ {
359
+ "tier": tier.get("tier"),
360
+ "positionValueUpperBound": tier.get("positionValueUpperBound"),
361
+ "maxLeverage": tier.get("maxLeverage"),
362
+ "maintenanceMarginRate": tier.get("maintenanceMarginRate"),
363
+ "starkExRisk": tier.get("starkExRisk"),
364
+ "starkExUpperBound": tier.get("starkExUpperBound"),
365
+ }
366
+ )
367
+ return items
368
+
369
+ class EdgexDataStore(DataStoreCollection):
370
+ """Edgex DataStore collection exposing the order book feed."""
371
+
372
+ def _init(self) -> None:
373
+ self._create("book", datastore_class=Book)
374
+ self._create("ticker", datastore_class=Ticker)
375
+ self._create("meta_coin", datastore_class=CoinMeta)
376
+ self._create("detail", datastore_class=ContractMeta)
377
+
378
+ @property
379
+ def book(self) -> Book:
380
+ """
381
+ 获取 Edgex 合约订单簿数据流。
382
+
383
+ .. code:: json
384
+
385
+ [
386
+ {
387
+ "c": "10000001", # 合约 ID
388
+ "s": "BTCUSD",
389
+ "S": "a", # 方向 a=卖 b=买
390
+ "p": "117388.2", # 价格
391
+ "q": "12.230", # 数量
392
+ }
393
+ ]
394
+ """
395
+ return self._get("book")
396
+
397
+
398
+
399
+ @property
400
+ def coins(self) -> CoinMeta:
401
+ """
402
+ 获取币种精度及 StarkEx 资产信息列表。
403
+
404
+ .. code:: json
405
+
406
+ [
407
+ {
408
+ "coinId": "1000",
409
+ "coinName": "USDT",
410
+ "stepSize": "0.000001",
411
+ "showStepSize": "0.0001",
412
+ "starkExAssetId": "0x33bda5c9..."
413
+ }
414
+ ]
415
+ """
416
+ return self._get("meta_coin")
417
+
418
+ @property
419
+ def detail(self) -> ContractMeta:
420
+ """
421
+ 获取合约级别的交易参数。
422
+
423
+ .. code:: json
424
+
425
+ [
426
+ {
427
+ "contractId": "10000001",
428
+ "contractName": "BTCUSDT",
429
+ "baseCoinId": "1001",
430
+ "quoteCoinId": "1000",
431
+ "tickSize": "0.1",
432
+ "stepSize": "0.001",
433
+ "minOrderSize": "0.001",
434
+ "maxOrderSize": "50.000",
435
+ "defaultMakerFeeRate": "0.0002",
436
+ "defaultTakerFeeRate": "0.00055",
437
+ "enableTrade": true,
438
+ "fundingInterestRate": "0.0003",
439
+ "fundingImpactMarginNotional": "10",
440
+ "fundingRateIntervalMin": "240",
441
+ "starkExSyntheticAssetId": "0x42544332...",
442
+ "starkExResolution": "0x2540be400",
443
+ "riskTierList": [
444
+ {
445
+ "tier": 1,
446
+ "positionValueUpperBound": "50000",
447
+ "maxLeverage": "100",
448
+ "maintenanceMarginRate": "0.005",
449
+ "starkExRisk": "21474837",
450
+ "starkExUpperBound": "214748364800000000000"
451
+ }
452
+ ]
453
+ }
454
+ ]
455
+ """
456
+ return self._get("detail")
457
+
458
+ @property
459
+ def ticker(self) -> Ticker:
460
+ """
461
+ 获取 24 小时行情推送。
462
+
463
+ .. code:: json
464
+
465
+ [
466
+ {
467
+ "c": "10000001", # 合约 ID
468
+ "s": "BTCUSD", # 合约名称
469
+ "lastPrice": "117400", # 最新价
470
+ "priceChange": "200", # 涨跌额
471
+ "priceChangePercent": "0.0172", # 涨跌幅
472
+ "size": "1250", # 24h 成交量
473
+ "value": "147000000", # 24h 成交额
474
+ "high": "118000", # 24h 最高价
475
+ "low": "116500", # 低价
476
+ "open": "116800", # 开盘价
477
+ "close": "117400", # 收盘价
478
+ "indexPrice": "117350", # 指数价
479
+ "oraclePrice": "117360.12", # 预言机价
480
+ "openInterest": "50000", # 持仓量
481
+ "fundingRate": "0.000234", # 当前资金费率
482
+ "fundingTime": "1758240000000", # 上一次结算时间
483
+ "nextFundingTime": "1758254400000", # 下一次结算时间
484
+ "bestAskPrice": "117410", # 卖一价
485
+ "bestBidPrice": "117400" # 买一价
486
+ }
487
+ ]
488
+ """
489
+ return self._get("ticker")
490
+
491
+ async def initialize(self, *aws: Awaitable["ClientResponse"]) -> None:
492
+ """Populate metadata stores from awaited HTTP responses."""
493
+
494
+ for fut in asyncio.as_completed(aws):
495
+ res = await fut
496
+ data = await res.json()
497
+ if res.url.path == "/api/v1/public/meta/getMetaData":
498
+ self._apply_metadata(data)
499
+
500
+ def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
501
+ channel = (msg.get("channel") or "").lower()
502
+ msg_type = (msg.get("type") or "").lower()
503
+
504
+ if "depth" in channel and msg_type in {"quote-event", "payload"}:
505
+ self.book._on_message(msg)
506
+
507
+ if channel.startswith("ticker") and msg_type in {"payload", "quote-event"}:
508
+ self.ticker._on_message(msg)
509
+
510
+
511
+ def _apply_metadata(self, data: dict[str, Any]) -> None:
512
+ self.coins._onresponse(data)
513
+ self.detail._onresponse(data)
@@ -22,19 +22,20 @@ class Book(DataStore):
22
22
  Channel: push.depth.step
23
23
 
24
24
  用于存储和管理订单簿深度数据,包含买卖盘的价格和数量信息
25
- Keys: ["symbol", "side", "px"]
26
- - symbol: 交易对符号
27
- - side: 买卖方向 (A: ask卖出, B: bid买入)
28
- - px: 价格
25
+ Keys: ["s", "S", "p"]
26
+ - s: 交易对符号
27
+ - S: 买卖方向 (A: ask卖出, B: bid买入)
28
+ - p: 价格
29
29
 
30
30
 
31
31
  """
32
32
 
33
- _KEYS = ["symbol", "side", "i"]
33
+ _KEYS = ["s", "S", "p"]
34
34
 
35
35
  def _init(self) -> None:
36
36
  # super().__init__()
37
37
  self._time: int | None = None
38
+ self.limit = 1
38
39
 
39
40
  def _on_message(self, msg: dict[str, Any]) -> None:
40
41
 
@@ -43,29 +44,29 @@ class Book(DataStore):
43
44
  asks = data.get("asks", [])
44
45
  bids = data.get("bids", [])
45
46
  # 提速 默认 5当前
46
- asks = asks[:5]
47
- bids = bids[:5]
47
+ asks = asks[:self.limit]
48
+ bids = bids[:self.limit]
48
49
 
49
50
  timestamp = data.get("ct") # 使用服务器时间
50
51
 
51
52
  data_to_insert: list[Item] = []
52
53
 
53
54
  # 先删除旧的订单簿数据
54
- self._find_and_delete({"symbol": symbol})
55
+ self._find_and_delete({"s": symbol})
55
56
 
56
57
  # 处理买卖盘数据
57
- for side_id, levels in (("B", bids), ("A", asks)):
58
+ for side_id, levels in (("b", bids), ("a", asks)):
58
59
  for i, level in enumerate(levels):
59
60
  # level格式: [price, size, count]
60
61
  if len(level) >= 3:
61
62
  price, size, count = level[0:3]
62
63
  data_to_insert.append(
63
64
  {
64
- "symbol": symbol,
65
- "side": side_id,
66
- "px": str(price),
67
- "sz": str(size),
68
- "count": count,
65
+ "s": symbol,
66
+ "S": side_id,
67
+ "p": str(price),
68
+ "q": str(size),
69
+ "ct": count,
69
70
  "i": i
70
71
  }
71
72
  )
@@ -79,31 +80,17 @@ class Book(DataStore):
79
80
  """返回最后更新时间"""
80
81
  return self._time
81
82
 
82
- @property
83
- def sorted(self) -> dict[str, list[Item]]:
84
- """获取排序后的订单簿数据
85
-
86
- Returns:
87
- 返回按价格排序的买卖盘数据,卖盘升序,买盘降序
88
-
89
- .. code-block:: python
83
+ def sorted(
84
+ self, query: Item | None = None, limit: int | None = None
85
+ ) -> dict[str, list[Item]]:
90
86
 
91
- {
92
- "asks": [
93
- {"symbol": "BTC_USDT", "side": "A", "px": "110152.5", "sz": "53539", "count": 1},
94
- {"symbol": "BTC_USDT", "side": "A", "px": "110152.6", "sz": "95513", "count": 2}
95
- ],
96
- "bids": [
97
- {"symbol": "BTC_USDT", "side": "B", "px": "110152.4", "sz": "76311", "count": 1},
98
- {"symbol": "BTC_USDT", "side": "B", "px": "110152.3", "sz": "104688", "count": 2}
99
- ]
100
- }
101
- """
102
87
  return self._sorted(
103
- item_key="side",
104
- item_asc_key="A", # asks 升序
105
- item_desc_key="B", # bids 降序
106
- sort_key="px",
88
+ item_key="S",
89
+ item_asc_key="a", # asks 升序
90
+ item_desc_key="b", # bids 降序
91
+ sort_key="p",
92
+ query=query,
93
+ limit=limit,
107
94
  )
108
95
 
109
96
 
@@ -146,15 +133,19 @@ class Orders(DataStore):
146
133
  def _fmt(self, order:dict):
147
134
  return {
148
135
  "order_id": order.get("orderId"),
136
+ "position_id": order.get("positionId"),
149
137
  "symbol": order.get("symbol"),
150
- "px": order.get("price"),
138
+ "price": order.get("price"),
151
139
  "vol": order.get("vol"),
152
140
  "lev": order.get("leverage"),
153
141
  "side": "buy" if order.get("side") == 1 else "sell",
154
- "deal_vol": order.get("dealVol"),
155
- "deal_avg_px": order.get("dealAvgPrice"),
142
+ "deal_quantity": order.get("dealVol"),
143
+ "avg_price": order.get("dealAvgPrice"),
156
144
  "create_ts": order.get("createTime"),
157
145
  "update_ts": order.get("updateTime"),
146
+ "fee": order.get("makerFee"),
147
+ "profit": order.get("profit"),
148
+ "used_margin": order.get("usedMargin"),
158
149
  "state": "open"
159
150
  }
160
151
 
@@ -192,6 +183,13 @@ class Orders(DataStore):
192
183
  self._find_and_delete({
193
184
  "order_id": order.get("order_id")
194
185
  })
186
+ else:
187
+ order = self._fmt(data)
188
+ order["state"] = f"unknown_{state}"
189
+ self._update([order])
190
+ self._find_and_delete({
191
+ "order_id": order.get("order_id")
192
+ })
195
193
 
196
194
  class Detail(DataStore):
197
195
  _KEYS = ["symbol"]
@@ -214,7 +212,8 @@ class Detail(DataStore):
214
212
  "io": detail.get("io"),
215
213
  "contract_sz": detail.get("cs"),
216
214
  "minv": detail.get("minV"),
217
- "maxv": detail.get("maxV")
215
+ "maxv": detail.get("maxV"),
216
+ "online_time": detail.get("tcd")
218
217
  }
219
218
  )
220
219
  self._update(data_to_insert)
@@ -225,27 +224,27 @@ class Position(DataStore):
225
224
 
226
225
  def _fmt(self, position:dict):
227
226
  return {
228
- "position_id": position.get("positionId"),
229
- "symbol": position.get("symbol"),
230
- "side": "short" if position.get("positionType") == 2 else "long",
231
- "open_type": position.get("openType"),
232
- "state": position.get("state"),
233
- "hold_vol": position.get("holdVol"),
234
- "frozen_vol": position.get("frozenVol"),
235
- "close_vol": position.get("closeVol"),
236
- "hold_avg_price": position.get("holdAvgPriceFullyScale"),
237
- "open_avg_price": position.get("openAvgPriceFullyScale"),
238
- "close_avg_price": str(position.get("closeAvgPrice")),
239
- "liquidate_price": str(position.get("liquidatePrice")),
240
- "oim": position.get("oim"),
241
- "im": position.get("im"),
242
- "hold_fee": position.get("holdFee"),
243
- "realised": position.get("realised"),
244
- "leverage": position.get("leverage"),
245
- "margin_ratio": position.get("marginRatio"),
246
- "create_ts": position.get("createTime"),
247
- "update_ts": position.get("updateTime"),
248
- }
227
+ "position_id": position.get("positionId"),
228
+ "symbol": position.get("symbol"),
229
+ "side": "short" if position.get("positionType") == 2 else "long",
230
+ "open_type": position.get("openType"),
231
+ "state": position.get("state"),
232
+ "hold_vol": position.get("holdVol"),
233
+ "frozen_vol": position.get("frozenVol"),
234
+ "close_vol": position.get("closeVol"),
235
+ "hold_avg_price": position.get("holdAvgPriceFullyScale"),
236
+ "open_avg_price": position.get("openAvgPriceFullyScale"),
237
+ "close_avg_price": str(position.get("closeAvgPrice")),
238
+ "liquidate_price": str(position.get("liquidatePrice")),
239
+ "oim": position.get("oim"),
240
+ "im": position.get("im"),
241
+ "hold_fee": position.get("holdFee"),
242
+ "realised": position.get("realised"),
243
+ "leverage": position.get("leverage"),
244
+ "margin_ratio": position.get("marginRatio"),
245
+ "create_ts": position.get("createTime"),
246
+ "update_ts": position.get("updateTime"),
247
+ }
249
248
 
250
249
  def _onresponse(self, data: dict[str, Any]):
251
250
  positions = data.get("data", [])
@@ -405,8 +404,8 @@ class OurbitSwapDataStore(DataStoreCollection):
405
404
  "io": ["binance", "mexc"], # 交易所列表
406
405
  "contract_sz": 1,
407
406
  "minv": 1,
408
- "maxv": 10000
409
-
407
+ "maxv": 10000,
408
+ "online_time": 1625247600000 # 上线时间
410
409
  }
411
410
  ]
412
411
  """
@@ -415,32 +414,37 @@ class OurbitSwapDataStore(DataStoreCollection):
415
414
  @property
416
415
  def book(self) -> Book:
417
416
  """订单簿深度数据流
418
-
417
+
418
+ 提供实时订单簿深度数据,包含买卖双方价格和数量信息
419
+
419
420
  Data type: Mutable
420
-
421
- Keys: ("symbol", "side", "px")
421
+
422
+ Keys: ("s", "S", "p")
423
+ - s: 交易对符号,如 "BTC_USDT"
424
+ - S: 买卖方向,"a" 表示卖单(ask),"b" 表示买单(bid)
425
+ - p: 价格
422
426
 
423
427
  Data structure:
424
428
 
425
429
  .. code:: python
426
430
 
427
431
  [
428
- {
429
- "symbol": "BTC_USDT", # 交易对
430
- "side": "A", # 卖出方向
431
- "px": "110152.5", # 价格
432
- "sz": "53539", # 数量
433
- "count": 1 # 订单数量
434
- "i" 0 # 价格档位索引
435
- },
436
- {
437
- "symbol": "BTC_USDT", # 交易对
438
- "side": "B", # 买入方向
439
- "px": "110152.4", # 价格
440
- "sz": "76311", # 数量
441
- "count": 1 # 订单数量
442
- "i" 0 # 价格档位索引
443
- }
432
+ {
433
+ "s": "BTC_USDT", # 交易对符号
434
+ "S": "a", # 卖单方向(ask)
435
+ "p": "110152.5", # 价格
436
+ "q": "53539", # 数量
437
+ "ct": 1, # 该价格的订单数量
438
+ "i": 0 # 价格档位索引(从0开始)
439
+ },
440
+ {
441
+ "s": "BTC_USDT", # 交易对符号
442
+ "S": "b", # 买单方向(bid)
443
+ "p": "110152.4", # 价格
444
+ "q": "76311", # 数量
445
+ "ct": 1, # 该价格的订单数量
446
+ "i": 0 # 价格档位索引(从0开始)
447
+ }
444
448
  ]
445
449
  """
446
450
  return self._get("book", Book)
@@ -605,17 +609,22 @@ class SpotOrders(DataStore):
605
609
 
606
610
 
607
611
  def _fmt(self, order: dict) -> dict:
608
- state = order.get("state")
609
- if state == 1 or state == 2:
610
- state = "open"
611
- elif state == 3:
612
- state = "filled"
613
- elif state == 4:
614
- state = "canceled"
612
+ # 状态映射:1=open, 2=filled(整单成交), 3=partially_filled, 4=canceled
613
+ state_num = order.get("state") or order.get("status")
614
+ if state_num == 1:
615
+ state_txt = "open"
616
+ elif state_num == 2:
617
+ state_txt = "filled" # ✔ 2 才是整单成交
618
+ elif state_num == 3:
619
+ state_txt = "partially_filled"
620
+ elif state_num == 4:
621
+ state_txt = "canceled"
622
+ else:
623
+ state_txt = "unknown"
615
624
 
616
625
  return {
617
- "order_id": order.get("id"),
618
- "symbol": order.get("symbol"),
626
+ "order_id": order.get("id") or order.get("orderId"),
627
+ "symbol": order.get("symbol") or order.get("s"),
619
628
  "currency": order.get("currency"),
620
629
  "market": order.get("market"),
621
630
  "trade_type": order.get("tradeType"),
@@ -626,15 +635,14 @@ class SpotOrders(DataStore):
626
635
  "deal_quantity": order.get("dealQuantity"),
627
636
  "deal_amount": order.get("dealAmount"),
628
637
  "avg_price": order.get("avgPrice"),
629
- "state": order.get("state"),
630
- "source": order.get("source"),
638
+ "state": state_txt,
639
+ "source": order.get("source") or order.get("internal"),
631
640
  "fee": order.get("fee"),
632
641
  "create_ts": order.get("createTime"),
633
642
  "unique_id": order.get("uniqueId"),
634
643
  }
635
644
 
636
645
 
637
-
638
646
  def _onresponse(self, data: dict[str, Any]):
639
647
  orders = (data.get("data") or {}).get("resultList", [])
640
648
  items = [self._fmt(order) for order in orders]
@@ -643,9 +651,9 @@ class SpotOrders(DataStore):
643
651
  self._insert(items)
644
652
 
645
653
  def _on_message(self, msg: dict[str, Any]) -> None:
646
- d:dict = msg.get("d", {})
654
+ d: dict = msg.get("d", {})
647
655
 
648
-
656
+ # 基础字段
649
657
  item = {
650
658
  "order_id": d.get("id"),
651
659
  "symbol": msg.get("s") or d.get("symbol"),
@@ -656,7 +664,6 @@ class SpotOrders(DataStore):
656
664
  "amount": d.get("amount"),
657
665
  "remain_quantity": d.get("remainQ"),
658
666
  "remain_amount": d.get("remainA"),
659
- "state": d.get("status"),
660
667
  "client_order_id": d.get("clientOrderId"),
661
668
  "is_taker": d.get("isTaker"),
662
669
  "create_ts": d.get("createTime"),
@@ -665,32 +672,78 @@ class SpotOrders(DataStore):
665
672
 
666
673
  state = d.get("status")
667
674
 
668
- if state == 1:
669
- item["state"] = "open"
670
- self._insert([item])
671
-
672
- elif state == 3 or state == 2:
673
- if state == 3:
674
- item["state"] = "partially_filled"
675
+
676
+
677
+ # 成交片段(部分/完全)
678
+ if d.get("singleDealPrice"):
679
+ # 单片段信息(可能多次推送;需做增量累计 + 去重)
680
+ single_id = d.get("singleDealId")
681
+ single_px = d.get("singleDealPrice")
682
+ single_qty = d.get("singleDealQuantity")
683
+ try:
684
+ px_i = float(single_px) if single_px is not None else 0.0
685
+ qty_i = float(single_qty) if single_qty is not None else 0.0
686
+ except Exception:
687
+ px_i, qty_i = 0.0, 0.0
688
+
689
+ old = self.get({"order_id": d.get("id")})
690
+ old_qty = float(old.get("deal_quantity") or 0.0) if old else 0.0
691
+ old_avg = float(old.get("avg_price") or 0.0) if old else 0.0
692
+ old_last_single = old.get("last_single_id") if old else None
693
+
694
+ # 去重:若与上一片段 ID 相同,认为是重复推送,直接按状态更新不累计
695
+ if old and single_id and old_last_single == single_id:
696
+ new_qty = old_qty
697
+ new_avg = old_avg
698
+ else:
699
+ # VWAP 累计
700
+ new_qty = old_qty + qty_i
701
+ if new_qty > 0:
702
+ new_avg = (old_avg * old_qty + px_i * qty_i) / new_qty
703
+ else:
704
+ new_avg = px_i
705
+
706
+ # 写回
707
+ item.update({
708
+ "avg_price": str(new_avg) if new_qty > 0 else old.get("avg_price") if old else None,
709
+ "deal_quantity": str(new_qty) if new_qty > 0 else old.get("deal_quantity") if old else None,
710
+ "single_id": single_id,
711
+ "last_single_id": single_id,
712
+ })
713
+
714
+ # 状态文本:2=filled(整单), 3=partially_filled
715
+ # item["state"] = "filled" if state == 2 else "partially_filled"
675
716
  if state == 2:
676
717
  item["state"] = "filled"
677
-
678
- # 如果这三个字段存在追加
679
- if d.get("singleDealId") and d.get("singleDealPrice") and d.get("singleDealQuantity"):
680
- item.update({
681
- "unique_id": d.get("singleDealId"),
682
- "avg_price": d.get("singleDealPrice"),
683
- "deal_quantity": d.get("singleDealQuantity"),
684
- })
718
+ elif state == 3:
719
+ item["state"] = "partially_filled"
720
+ else:
721
+ item["state"] = "unknown_"+str(state)
685
722
 
686
- self._update([item])
687
- self._find_and_delete({ "order_id": d.get("id") })
688
-
689
- elif state == 4:
690
- item["state"] = "canceled"
691
723
  self._update([item])
692
- self._find_and_delete({ "order_id": d.get("id") })
693
724
 
725
+ # 整单成交 或者 部分取消 → 删除
726
+ if state == 2 or 'unknown' in item["state"]:
727
+ self._find_and_delete({"order_id": d.get("id")})
728
+ return
729
+ else:
730
+ # 新建 / 已挂出
731
+ if state == 1:
732
+ item["state"] = "open"
733
+ self._insert([item])
734
+ return
735
+
736
+ elif state == 4:
737
+ item["state"] = "canceled"
738
+ self._update([item])
739
+ self._find_and_delete({"order_id": d.get("id")})
740
+ return
741
+ else:
742
+
743
+ # 未知状态:更新后删除,避免脏数据残留
744
+ item["state"] = "unknown_"+str(state)
745
+ self._update([item])
746
+ self._find_and_delete({"order_id": d.get("id")})
694
747
 
695
748
 
696
749
 
@@ -722,7 +775,7 @@ class SpotBook(DataStore):
722
775
  # items.sort(key=lambda x: x.get("fv", 0)) # 按 fromVersion 排序
723
776
  # self._find_and_delete({"s": symbol})
724
777
 
725
- # 处理缓存
778
+ # 应为我们先连接的ws, 所以可能有缓存需要去处理
726
779
  items = [item for item in self.cache if item.get("s") == symbol]
727
780
  items.sort(key=lambda x: x.get("fv", 0)) # 按 fromVersion 排序
728
781
  self.cache = [item for item in self.cache if item.get("s") != symbol]
@@ -1032,34 +1085,30 @@ class OurbitSpotDataStore(DataStoreCollection):
1032
1085
  @property
1033
1086
  def orders(self) -> SpotOrders:
1034
1087
  """
1035
- 现货订单数据流(SpotOrders)
1036
-
1037
- Keys: ["order_id"]
1038
-
1039
- 说明:
1040
- - 聚合 REST 当前订单与 WS 增量推送(频道: spot@private.orders)
1041
- - 统一状态值:open, partially_filled, filled, canceled
1088
+ 现货订单数据流
1042
1089
 
1043
1090
  Data structure:
1091
+
1044
1092
  .. code:: python
1093
+
1045
1094
  [
1046
1095
  {
1047
1096
  "order_id": "123456", # 订单ID
1048
1097
  "symbol": "BTC_USDT", # 交易对
1049
1098
  "currency": "USDT", # 币种
1050
1099
  "market": "BTC_USDT", # 市场
1051
- "trade_type": "buy", # buy/sell
1052
- "order_type": "limit", # limit/market
1100
+ "trade_type": "buy", # 交易类型
1101
+ "order_type": "limit", # 订单类型
1053
1102
  "price": "11000.0", # 委托价格
1054
1103
  "quantity": "0.01", # 委托数量
1055
1104
  "amount": "110.0", # 委托金额
1056
- "deal_quantity": "0.01", # 已成交数量(累计)
1057
- "deal_amount": "110.0", # 已成交金额(累计)
1058
- "avg_price": "11000.0", # 成交均价(累计)
1059
- "state": "open", # open/partially_filled/filled/canceled
1060
- "fee": "0.01", # 手续费
1105
+ "deal_quantity": "0.01", # 成交数量
1106
+ "deal_amount": "110.0", # 成交金额
1107
+ "avg_price": "11000.0", # 成交均价
1108
+ "state": "open", # 订单状态
1061
1109
  "source": "api", # 来源
1062
- "create_ts": 1625247600000,# 创建时间戳(毫秒)
1110
+ "fee": "0.01", # 手续费
1111
+ "create_ts": 1625247600000,# 创建时间戳
1063
1112
  "unique_id": "abcdefg" # 唯一标识
1064
1113
  }
1065
1114
  ]
@@ -70,11 +70,15 @@ class OurbitSwap:
70
70
 
71
71
  for symbol in symbols:
72
72
  step = self.store.detail.find({"symbol": symbol})[0].get("tick_size")
73
+ step_str = str(Decimal(str(step)).normalize())
73
74
 
74
75
  send_jsons.append(
75
76
  {
76
77
  "method": "sub.depth.step",
77
- "param": {"symbol": symbol, "step": str(step)},
78
+ "param": {
79
+ "symbol": symbol,
80
+ "step": step_str,
81
+ },
78
82
  }
79
83
  )
80
84
 
@@ -83,11 +87,12 @@ class OurbitSwap:
83
87
  )
84
88
 
85
89
  async def sub_personal(self):
86
- self.client.ws_connect(
90
+ wsapp = self.client.ws_connect(
87
91
  self.ws_url,
88
92
  send_json={"method": "sub.personal.user.preference"},
89
93
  hdlr_json=self.store.onmessage,
90
94
  )
95
+ await wsapp._event.wait()
91
96
 
92
97
  def ret_content(self, res: pybotters.FetchResult):
93
98
  match res.data:
@@ -115,6 +120,7 @@ class OurbitSwap:
115
120
  usdt_amount: Optional[float] = None,
116
121
  leverage: Optional[int] = 20,
117
122
  position_id: Optional[int] = None,
123
+ quantity: float = None, # 兼容参数,不使用
118
124
  ):
119
125
  """
120
126
  size为合约张数, openType 1 为逐仓, 2为全仓
@@ -167,12 +173,17 @@ class OurbitSwap:
167
173
  if position_id is None:
168
174
  raise ValueError("position_id is required for closing position")
169
175
  data["positionId"] = position_id
170
- # import time
171
- # print(time.time(), '下单')
176
+
172
177
  res = await self.client.fetch(
173
178
  "POST", f"{self.api_url}/api/v1/private/order/create", data=data
174
179
  )
175
- return self.ret_content(res)
180
+ # 'orderId' =
181
+ # '226474723700166962'
182
+ # 'ts' =
183
+ # 1758034181833
184
+ ret_c = self.ret_content(res)
185
+ # 只返回 orderId
186
+ return ret_c["orderId"]
176
187
 
177
188
  async def place_tpsl(
178
189
  self,
hyperquant/core.py CHANGED
@@ -335,6 +335,9 @@ class Exchange(ExchangeBase):
335
335
  self.account[symbol]['realised_profit'] += profit
336
336
  self.account[symbol]['amount'] -= -direction * cover_amount
337
337
  trade['pos'] = profit # 记录盈亏
338
+
339
+ trade['pos_rate'] = -direction * (price / self.account[symbol]['hold_price'] - 1) if self.account[symbol]['hold_price'] != 0 else 0
340
+
338
341
  self.account[symbol]['hold_price'] = 0 if self.account[symbol]['amount'] == 0 else self.account[symbol]['hold_price']
339
342
 
340
343
  if open_amount > 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.5
3
+ Version: 0.6
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,21 +1,23 @@
1
1
  hyperquant/__init__.py,sha256=UpjiX4LS5jmrBc2kE8RiLR02eCfD8JDQrR1q8zkLNcQ,161
2
- hyperquant/core.py,sha256=iEI8qTNpyesB_w67SrKXeGoB9JllovBeJKI0EZFYew4,20631
2
+ hyperquant/core.py,sha256=RzRFbyImqzBiaA-9lQzvxPfxwcOvScdABZviS4y0kqM,20783
3
3
  hyperquant/db.py,sha256=i2TjkCbmH4Uxo7UTDvOYBfy973gLcGexdzuT_YcSeIE,6678
4
4
  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=oA9Yw1I59-u0Tnoj2e4wUup5q8V5T2qpga5RKbiAiZI,2614
8
+ hyperquant/broker/edgex.py,sha256=qQtc8jZqB5ZODoGGVcG_aIVUlrJX_pRF9EyO927LiVM,6646
8
9
  hyperquant/broker/hyperliquid.py,sha256=7MxbI9OyIBcImDelPJu-8Nd53WXjxPB5TwE6gsjHbto,23252
9
- hyperquant/broker/ourbit.py,sha256=Fza_nhfoSCf1Ulm7UHOlt969Wm37bKja9P5xpN93XqY,17902
10
+ hyperquant/broker/ourbit.py,sha256=NUcDSIttf-HGWzoW1uBTrGLPHlkuemMjYCm91MigTno,18228
10
11
  hyperquant/broker/ws.py,sha256=umRzxwCaZaRIgIq4YY-AuA0wCXFT0uOBmQbIXFY8CK0,1555
11
12
  hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb84D8,8296
12
13
  hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
14
+ hyperquant/broker/models/edgex.py,sha256=KRJB9PIjP555P-GiVIYm01sFtlAWeYlk5y-YuoXhk9k,15606
13
15
  hyperquant/broker/models/hyperliquid.py,sha256=c4r5739ibZfnk69RxPjQl902AVuUOwT8RNvKsMtwXBY,9459
14
- hyperquant/broker/models/ourbit.py,sha256=QuKxUYlJzRC4zr0lNiz3dpireConbsRyOtOvA0_VTVA,39978
16
+ hyperquant/broker/models/ourbit.py,sha256=xMcbuCEXd3XOpPBq0RYF2zpTFNnxPtuNJZCexMZVZ1k,41965
15
17
  hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw,533
16
18
  hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
17
19
  hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
18
20
  hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
19
- hyperquant-0.5.dist-info/METADATA,sha256=A-fC66EHBajjznMPB06z6oaMCWHEy3TTmUUXxK2CLcU,4316
20
- hyperquant-0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- hyperquant-0.5.dist-info/RECORD,,
21
+ hyperquant-0.6.dist-info/METADATA,sha256=lQSDRR5kM9kI_maMt9erlxLv_sPaX3EZ0la5uXj33ys,4316
22
+ hyperquant-0.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ hyperquant-0.6.dist-info/RECORD,,