hyperquant 0.26__py3-none-any.whl → 0.32__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,502 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ from typing import TYPE_CHECKING, Any, Awaitable
6
+
7
+ import aiohttp
8
+ from pybotters.store import DataStore, DataStoreCollection
9
+
10
+ if TYPE_CHECKING:
11
+ from pybotters.typedefs import Item
12
+ from pybotters.ws import ClientWebSocketResponse
13
+
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class Book(DataStore):
19
+ """深度数据存储类,用于处理订单簿深度信息
20
+
21
+ Channel: push.depth.step
22
+
23
+ 用于存储和管理订单簿深度数据,包含买卖盘的价格和数量信息
24
+ Keys: ["symbol", "side", "px"]
25
+ - symbol: 交易对符号
26
+ - side: 买卖方向 (A: ask卖出, B: bid买入)
27
+ - px: 价格
28
+
29
+
30
+ """
31
+
32
+ _KEYS = ["symbol", "side", "px"]
33
+
34
+ def _init(self) -> None:
35
+ # super().__init__()
36
+ self._time: int | None = None
37
+
38
+ def _on_message(self, msg: dict[str, Any]) -> None:
39
+
40
+ symbol = msg.get("symbol")
41
+ data = msg.get("data", {})
42
+ asks = data.get("asks", [])
43
+ bids = data.get("bids", [])
44
+ timestamp = data.get("ct") # 使用服务器时间
45
+
46
+ data_to_insert: list[Item] = []
47
+
48
+ # 先删除旧的订单簿数据
49
+ self._find_and_delete({"symbol": symbol})
50
+
51
+ # 处理买卖盘数据
52
+ for side_id, levels in (("B", bids), ("A", asks)):
53
+ for level in levels:
54
+ # level格式: [price, size, count]
55
+ if len(level) >= 3:
56
+ price, size, count = level[0:3]
57
+ data_to_insert.append(
58
+ {
59
+ "symbol": symbol,
60
+ "side": side_id,
61
+ "px": str(price),
62
+ "sz": str(size),
63
+ "count": count,
64
+ }
65
+ )
66
+
67
+ # 插入新的订单簿数据
68
+ self._insert(data_to_insert)
69
+ self._time = timestamp
70
+
71
+ @property
72
+ def time(self) -> int | None:
73
+ """返回最后更新时间"""
74
+ return self._time
75
+
76
+ @property
77
+ def sorted(self) -> dict[str, list[Item]]:
78
+ """获取排序后的订单簿数据
79
+
80
+ Returns:
81
+ 返回按价格排序的买卖盘数据,卖盘升序,买盘降序
82
+
83
+ .. code-block:: python
84
+
85
+ {
86
+ "asks": [
87
+ {"symbol": "BTC_USDT", "side": "A", "px": "110152.5", "sz": "53539", "count": 1},
88
+ {"symbol": "BTC_USDT", "side": "A", "px": "110152.6", "sz": "95513", "count": 2}
89
+ ],
90
+ "bids": [
91
+ {"symbol": "BTC_USDT", "side": "B", "px": "110152.4", "sz": "76311", "count": 1},
92
+ {"symbol": "BTC_USDT", "side": "B", "px": "110152.3", "sz": "104688", "count": 2}
93
+ ]
94
+ }
95
+ """
96
+ return self._sorted(
97
+ item_key="side",
98
+ item_asc_key="A", # asks 升序
99
+ item_desc_key="B", # bids 降序
100
+ sort_key="px",
101
+ )
102
+
103
+
104
+ class Ticker(DataStore):
105
+ _KEYS = ["symbol"]
106
+
107
+ def _on_message(self, data: dict[str, Any]):
108
+ self._onresponse(data)
109
+
110
+ def _onresponse(self, data: dict[str, Any]):
111
+ tickers = data.get("data", [])
112
+ if tickers:
113
+ data_to_insert: list[Item] = []
114
+ for ticker in tickers:
115
+ ticker: dict[str, Any] = ticker
116
+ for ticker in tickers:
117
+ data_to_insert.append(
118
+ {
119
+ "amount24": ticker.get("amount24"),
120
+ "fair_price": ticker.get("fairPrice"),
121
+ "high24_price": ticker.get("high24Price"),
122
+ "index_price": ticker.get("indexPrice"),
123
+ "last_price": ticker.get("lastPrice"),
124
+ "lower24_price": ticker.get("lower24Price"),
125
+ "max_bid_price": ticker.get("maxBidPrice"),
126
+ "min_ask_price": ticker.get("minAskPrice"),
127
+ "rise_fall_rate": ticker.get("riseFallRate"),
128
+ "symbol": ticker.get("symbol"),
129
+ "timestamp": ticker.get("timestamp"),
130
+ "volume24": ticker.get("volume24"),
131
+ }
132
+ )
133
+ # self._clear()
134
+ self._insert(data_to_insert)
135
+
136
+
137
+ class Orders(DataStore):
138
+ _KEYS = ["order_id"]
139
+
140
+ # {'success': True, 'code': 0, 'data': [{'orderId': '219108574599630976', 'symbol': 'SOL_USDT', 'positionId': 0, 'price': 190, 'priceStr': '190', 'vol': 1, 'leverage': 20, 'side': 1, 'category': 1, 'orderType': 1, 'dealAvgPrice': 0, 'dealAvgPriceStr': '0', 'dealVol': 0, 'orderMargin': 0.09652, 'takerFee': 0, 'makerFee': 0, 'profit': 0, 'feeCurrency': 'USDT', 'openType': 1, 'state': 2, 'externalOid': '_m_2228b23a75204e1982b301e44d439cbb', 'errorCode': 0, 'usedMargin': 0, 'createTime': 1756277955008, 'updateTime': 1756277955037, 'positionMode': 1, 'version': 1, 'showCancelReason': 0, 'showProfitRateShare': 0, 'voucher': False}]}
141
+ def _onresponse(self, data: dict[str, Any]):
142
+ orders = data.get("data", [])
143
+ if orders:
144
+ data_to_insert: list[Item] = []
145
+ for order in orders:
146
+ order: dict[str, Any] = order
147
+
148
+ data_to_insert.append(
149
+ {
150
+ "order_id": order.get("orderId"),
151
+ "symbol": order.get("symbol"),
152
+ "px": order.get("priceStr"),
153
+ "vol": order.get("vol"),
154
+ "lev": order.get("leverage"),
155
+ "side": "buy" if order.get("side") == 1 else "sell",
156
+ "deal_vol": order.get("dealVol"),
157
+ "deal_avg_px": order.get("dealAvgPriceStr"),
158
+ "create_ts": order.get("createTime"),
159
+ "update_ts": order.get("updateTime"),
160
+ }
161
+ )
162
+
163
+ self._clear()
164
+ self._update(data_to_insert)
165
+
166
+
167
+ class Detail(DataStore):
168
+ _KEYS = ["symbol"]
169
+
170
+ def _on_message(self, data: dict[str, Any]):
171
+ self._onresponse(data)
172
+
173
+ def _onresponse(self, data: dict[str, Any]):
174
+ details: dict = data.get("data", {})
175
+ data_to_insert: list[Item] = []
176
+ if details:
177
+ for detail in details:
178
+ data_to_insert.append(
179
+ {
180
+ "symbol": detail.get("symbol"),
181
+ "ft": detail.get("ft"),
182
+ "max_lev": detail.get("maxL"),
183
+ "tick_size": detail.get("pu"),
184
+ "vol_unit": detail.get("vu"),
185
+ "io": detail.get("io"),
186
+ "contract_sz": detail.get("cs"),
187
+ "minv": detail.get("minV"),
188
+ "maxv": detail.get("maxV")
189
+ }
190
+ )
191
+ self._update(data_to_insert)
192
+
193
+ class Position(DataStore):
194
+ _KEYS = ["position_id"]
195
+ # {"success":true,"code":0,"data":[{"positionId":5355366,"symbol":"SOL_USDT","positionType":1,"openType":1,"state":1,"holdVol":1,"frozenVol":0,"closeVol":0,"holdAvgPrice":203.44,"holdAvgPriceFullyScale":"203.44","openAvgPrice":203.44,"openAvgPriceFullyScale":"203.44","closeAvgPrice":0,"liquidatePrice":194.07,"oim":0.10253376,"im":0.10253376,"holdFee":0,"realised":-0.0008,"leverage":20,"marginRatio":0.0998,"createTime":1756275984696,"updateTime":1756275984696,"autoAddIm":false,"version":1,"profitRatio":0,"newOpenAvgPrice":203.44,"newCloseAvgPrice":0,"closeProfitLoss":0,"fee":0.00081376}]}
196
+ def _onresponse(self, data: dict[str, Any]):
197
+ positions = data.get("data", [])
198
+ if positions:
199
+ data_to_insert: list[Item] = []
200
+ for position in positions:
201
+ position: dict[str, Any] = position
202
+
203
+ data_to_insert.append(
204
+ {
205
+ "position_id": position.get("positionId"),
206
+ "symbol": position.get("symbol"),
207
+ "side": "short" if position.get("positionType") == 2 else "long",
208
+ "open_type": position.get("openType"),
209
+ "state": position.get("state"),
210
+ "hold_vol": position.get("holdVol"),
211
+ "frozen_vol": position.get("frozenVol"),
212
+ "close_vol": position.get("closeVol"),
213
+ "hold_avg_price": position.get("holdAvgPriceFullyScale"),
214
+ "open_avg_price": position.get("openAvgPriceFullyScale"),
215
+ "close_avg_price": str(position.get("closeAvgPrice")),
216
+ "liquidate_price": str(position.get("liquidatePrice")),
217
+ "oim": position.get("oim"),
218
+ "im": position.get("im"),
219
+ "hold_fee": position.get("holdFee"),
220
+ "realised": position.get("realised"),
221
+ "leverage": position.get("leverage"),
222
+ "margin_ratio": position.get("marginRatio"),
223
+ "create_ts": position.get("createTime"),
224
+ "update_ts": position.get("updateTime"),
225
+ }
226
+ )
227
+
228
+ self._clear()
229
+ self._insert(data_to_insert)
230
+
231
+ class Balance(DataStore):
232
+ _KEYS = ["currency"]
233
+
234
+ def _onresponse(self, data: dict[str, Any]):
235
+ balances = data.get("data", [])
236
+ if balances:
237
+ data_to_insert: list[Item] = []
238
+ for balance in balances:
239
+ balance: dict[str, Any] = balance
240
+ data_to_insert.append({
241
+ "currency": balance.get("currency"),
242
+ "position_margin": balance.get("positionMargin"),
243
+ "available_balance": balance.get("availableBalance"),
244
+ "cash_balance": balance.get("cashBalance"),
245
+ "frozen_balance": balance.get("frozenBalance"),
246
+ "equity": balance.get("equity"),
247
+ "unrealized": balance.get("unrealized"),
248
+ "bonus": balance.get("bonus"),
249
+ "last_bonus": balance.get("lastBonus"),
250
+ "wallet_balance": balance.get("walletBalance"),
251
+ "voucher": balance.get("voucher"),
252
+ "voucher_using": balance.get("voucherUsing"),
253
+ })
254
+ self._clear()
255
+ self._insert(data_to_insert)
256
+
257
+ class OurbitSwapDataStore(DataStoreCollection):
258
+ """
259
+ Ourbit DataStoreCollection
260
+
261
+ REST API:
262
+ - 地址: https://futures.ourbit.com
263
+ - 合约详情
264
+ GET /api/v1/contract/detailV2?client=web
265
+ - ticker
266
+ GET /api/v1/contract/ticker
267
+ - open_orders
268
+ GET /api/v1/private/order/list/open_orders?page_size=200
269
+ - open_positions
270
+ GET /api/v1/private/position/open_positions
271
+
272
+ WebSocket API:
273
+ - 地址: wss://futures.ourbit.com/edge or /ws
274
+ - 支持频道:
275
+ * 深度数据(Book): push.depth.step
276
+ * 行情数据(Ticker): push.tickers
277
+
278
+ 示例订阅 JSON:
279
+
280
+ .. code:: json
281
+
282
+ {
283
+ "method": "sub.depth.step",
284
+ "param": {
285
+ "symbol": "BTC_USDT",
286
+ "step": "0.1"
287
+ }
288
+ }
289
+
290
+ .. code:: json
291
+
292
+ {
293
+ "method": "sub.tickers",
294
+ "param": {
295
+ "timezone": "UTC+8"
296
+ }
297
+ }
298
+
299
+ TODO:
300
+ - 添加 trades、ticker、candle 等其他数据流
301
+ """
302
+
303
+ def _init(self) -> None:
304
+ self._create("book", datastore_class=Book)
305
+ self._create("detail", datastore_class=Detail)
306
+ self._create("ticker", datastore_class=Ticker)
307
+ self._create("orders", datastore_class=Orders)
308
+ self._create("position", datastore_class=Position)
309
+ self._create("balance", datastore_class=Balance)
310
+ # TODO: 添加其他数据流,如 trades, ticker, candle 等
311
+
312
+ def onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
313
+ channel = msg.get("channel")
314
+
315
+ if channel == "push.depth.step":
316
+ self.book._on_message(msg)
317
+ if channel == "push.tickers":
318
+ self.ticker._on_message(msg)
319
+ else:
320
+ logger.debug(f"未知的channel: {channel}")
321
+
322
+ async def initialize(self, *aws: Awaitable[aiohttp.ClientResponse]) -> None:
323
+ """Initialize DataStore from HTTP response data."""
324
+ for f in asyncio.as_completed(aws):
325
+ res = await f
326
+ data = await res.json()
327
+ if res.url.path == "/api/v1/contract/detailV2":
328
+ self.detail._onresponse(data)
329
+ if res.url.path == "/api/v1/contract/ticker":
330
+ self.ticker._onresponse(data)
331
+ if res.url.path == "/api/v1/private/order/list/open_orders":
332
+ self.orders._onresponse(data)
333
+ if res.url.path == "/api/v1/private/position/open_positions":
334
+ self.position._onresponse(data)
335
+ if res.url.path == "/api/v1/private/account/assets":
336
+ self.balance._onresponse(data)
337
+
338
+ @property
339
+ def detail(self) -> Detail:
340
+ """合约详情
341
+ Data structure:
342
+ .. code:: python
343
+ [
344
+ {
345
+ "symbol": "BTC_USDT", # 交易对
346
+ "ft": 100, # 合约面值
347
+ "max_lev": 100, # 最大杠杆
348
+ "tick_size": 0.1, # 最小变动价位
349
+ "vol_unit": 1, # 合约单位
350
+ "io": ["binance", "mexc"], # 交易所列表
351
+ "contract_sz": 1,
352
+ "minv": 1,
353
+ "maxv": 10000
354
+
355
+ }
356
+ ]
357
+ """
358
+ return self._get("detail", Detail)
359
+
360
+ @property
361
+ def book(self) -> Book:
362
+ """订单簿深度数据流
363
+
364
+ Data type: Mutable
365
+
366
+ Keys: ("symbol", "side", "px")
367
+
368
+ Data structure:
369
+
370
+ .. code:: python
371
+
372
+ [
373
+ {
374
+ "symbol": "BTC_USDT", # 交易对
375
+ "side": "A", # 卖出方向
376
+ "px": "110152.5", # 价格
377
+ "sz": "53539", # 数量
378
+ "count": 1 # 订单数量
379
+ },
380
+ {
381
+ "symbol": "BTC_USDT", # 交易对
382
+ "side": "B", # 买入方向
383
+ "px": "110152.4", # 价格
384
+ "sz": "76311", # 数量
385
+ "count": 1 # 订单数量
386
+ }
387
+ ]
388
+ """
389
+ return self._get("book", Book)
390
+
391
+ @property
392
+ def ticker(self) -> Ticker:
393
+ """市场行情数据流
394
+
395
+ Data type: Mutable
396
+
397
+ Keys: ("symbol",)
398
+
399
+ Data structure:
400
+
401
+ .. code:: python
402
+
403
+ [
404
+ {
405
+ "symbol": "BTC_USDT", # 交易对
406
+ "last_price": "110152.5", # 最新价格
407
+ "index_price": "110000.0", # 指数价格
408
+ "fair_price": "110100.0", # 公允价格
409
+ "high24_price": "115000.0", # 24小时最高价
410
+ "lower24_price": "105000.0", # 24小时最低价
411
+ "volume24": "1500", # 24小时交易量
412
+ "amount24": "165000000", # 24小时交易额
413
+ "rise_fall_rate": "0.05", # 涨跌幅
414
+ "max_bid_price": "110150.0", # 买一价
415
+ "min_ask_price": "110155.0", # 卖一价
416
+ "timestamp": 1625247600000 # 时间戳
417
+ }
418
+ ]
419
+ """
420
+ return self._get("ticker", Ticker)
421
+
422
+ @property
423
+ def orders(self) -> Orders:
424
+ """
425
+ 订单数据
426
+ Data structure:
427
+
428
+ .. code:: json
429
+
430
+ [
431
+ {
432
+ "id": "123456",
433
+ "symbol": "BTC_USDT",
434
+ "side": "buy",
435
+ "price": "110152.5",
436
+ "size": "0.1",
437
+ "status": "open",
438
+ "create_ts": 1625247600000,
439
+ "update_ts": 1625247600000
440
+ }
441
+ ]
442
+ """
443
+ return self._get("orders", Orders)
444
+
445
+ @property
446
+ def position(self) -> Position:
447
+ """
448
+ 持仓数据
449
+
450
+ Data structure:
451
+ .. code:: python
452
+ [
453
+ {
454
+ "position_id": "123456",
455
+ "symbol": "BTC_USDT",
456
+ "side": "long",
457
+ "open_type": "limit",
458
+ "state": "open",
459
+ "hold_vol": "0.1",
460
+ "frozen_vol": "0.0",
461
+ "close_vol": "0.0",
462
+ "hold_avg_price": "110152.5",
463
+ "open_avg_price": "110152.5",
464
+ "close_avg_price": "0.0",
465
+ "liquidate_price": "100000.0",
466
+ "oim": "0.0",
467
+ "im": "0.0",
468
+ "hold_fee": "0.0",
469
+ "realised": "0.0",
470
+ "leverage": "10",
471
+ "margin_ratio": "0.1",
472
+ "create_ts": 1625247600000,
473
+ "update_ts": 1625247600000
474
+ }
475
+ ]
476
+ """
477
+ return self._get("position", Position)
478
+
479
+ @property
480
+ def balance(self) -> Balance:
481
+ """账户余额数据
482
+
483
+ Data structure:
484
+ .. code:: python
485
+ [
486
+ {
487
+ "currency": "USDT", # 币种
488
+ "position_margin": 0.3052, # 持仓保证金
489
+ "available_balance": 19.7284, # 可用余额
490
+ "cash_balance": 19.7284, # 现金余额
491
+ "frozen_balance": 0, # 冻结余额
492
+ "equity": 19.9442, # 权益
493
+ "unrealized": -0.0895, # 未实现盈亏
494
+ "bonus": 0, # 奖励
495
+ "last_bonus": 0, # 最后奖励
496
+ "wallet_balance": 20.0337, # 钱包余额
497
+ "voucher": 0, # 代金券
498
+ "voucher_using": 0 # 使用中的代金券
499
+ }
500
+ ]
501
+ """
502
+ return self._get("balance", Balance)