hyperquant 0.47__py3-none-any.whl → 0.48__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.
- hyperquant/broker/models/ourbit.py +76 -33
- hyperquant/broker/ourbit.py +91 -88
- hyperquant/logkit.py +16 -1
- {hyperquant-0.47.dist-info → hyperquant-0.48.dist-info}/METADATA +1 -1
- {hyperquant-0.47.dist-info → hyperquant-0.48.dist-info}/RECORD +6 -6
- {hyperquant-0.47.dist-info → hyperquant-0.48.dist-info}/WHEEL +0 -0
@@ -605,17 +605,22 @@ class SpotOrders(DataStore):
|
|
605
605
|
|
606
606
|
|
607
607
|
def _fmt(self, order: dict) -> dict:
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
608
|
+
# 状态映射:1=open, 2=filled(整单成交), 3=partially_filled, 4=canceled
|
609
|
+
state_num = order.get("state") or order.get("status")
|
610
|
+
if state_num == 1:
|
611
|
+
state_txt = "open"
|
612
|
+
elif state_num == 2:
|
613
|
+
state_txt = "filled" # ✔ 2 才是整单成交
|
614
|
+
elif state_num == 3:
|
615
|
+
state_txt = "partially_filled"
|
616
|
+
elif state_num == 4:
|
617
|
+
state_txt = "canceled"
|
618
|
+
else:
|
619
|
+
state_txt = "unknown"
|
615
620
|
|
616
621
|
return {
|
617
|
-
"order_id": order.get("id"),
|
618
|
-
"symbol": order.get("symbol"),
|
622
|
+
"order_id": order.get("id") or order.get("orderId"),
|
623
|
+
"symbol": order.get("symbol") or order.get("s"),
|
619
624
|
"currency": order.get("currency"),
|
620
625
|
"market": order.get("market"),
|
621
626
|
"trade_type": order.get("tradeType"),
|
@@ -626,8 +631,8 @@ class SpotOrders(DataStore):
|
|
626
631
|
"deal_quantity": order.get("dealQuantity"),
|
627
632
|
"deal_amount": order.get("dealAmount"),
|
628
633
|
"avg_price": order.get("avgPrice"),
|
629
|
-
"state":
|
630
|
-
"source": order.get("source"),
|
634
|
+
"state": state_txt,
|
635
|
+
"source": order.get("source") or order.get("internal"),
|
631
636
|
"fee": order.get("fee"),
|
632
637
|
"create_ts": order.get("createTime"),
|
633
638
|
"unique_id": order.get("uniqueId"),
|
@@ -643,9 +648,9 @@ class SpotOrders(DataStore):
|
|
643
648
|
self._insert(items)
|
644
649
|
|
645
650
|
def _on_message(self, msg: dict[str, Any]) -> None:
|
646
|
-
d:dict = msg.get("d", {})
|
651
|
+
d: dict = msg.get("d", {})
|
647
652
|
|
648
|
-
|
653
|
+
# 基础字段
|
649
654
|
item = {
|
650
655
|
"order_id": d.get("id"),
|
651
656
|
"symbol": msg.get("s") or d.get("symbol"),
|
@@ -656,7 +661,6 @@ class SpotOrders(DataStore):
|
|
656
661
|
"amount": d.get("amount"),
|
657
662
|
"remain_quantity": d.get("remainQ"),
|
658
663
|
"remain_amount": d.get("remainA"),
|
659
|
-
"state": d.get("status"),
|
660
664
|
"client_order_id": d.get("clientOrderId"),
|
661
665
|
"is_taker": d.get("isTaker"),
|
662
666
|
"create_ts": d.get("createTime"),
|
@@ -665,31 +669,70 @@ class SpotOrders(DataStore):
|
|
665
669
|
|
666
670
|
state = d.get("status")
|
667
671
|
|
672
|
+
# 新建 / 已挂出
|
668
673
|
if state == 1:
|
669
674
|
item["state"] = "open"
|
670
675
|
self._insert([item])
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
676
|
+
return
|
677
|
+
|
678
|
+
# 成交片段(部分/完全)
|
679
|
+
if state in (2, 3):
|
680
|
+
# 单片段信息(可能多次推送;需做增量累计 + 去重)
|
681
|
+
single_id = d.get("singleDealId")
|
682
|
+
single_px = d.get("singleDealPrice")
|
683
|
+
single_qty = d.get("singleDealQuantity")
|
684
|
+
try:
|
685
|
+
px_i = float(single_px) if single_px is not None else 0.0
|
686
|
+
qty_i = float(single_qty) if single_qty is not None else 0.0
|
687
|
+
except Exception:
|
688
|
+
px_i, qty_i = 0.0, 0.0
|
689
|
+
|
690
|
+
old = self.get({"order_id": d.get("id")})
|
691
|
+
old_qty = float(old.get("deal_quantity") or 0.0) if old else 0.0
|
692
|
+
old_avg = float(old.get("avg_price") or 0.0) if old else 0.0
|
693
|
+
old_last_single = old.get("last_single_id") if old else None
|
694
|
+
|
695
|
+
# 去重:若与上一片段 ID 相同,认为是重复推送,直接按状态更新不累计
|
696
|
+
if old and single_id and old_last_single == single_id:
|
697
|
+
new_qty = old_qty
|
698
|
+
new_avg = old_avg
|
699
|
+
else:
|
700
|
+
# VWAP 累计
|
701
|
+
new_qty = old_qty + qty_i
|
702
|
+
if new_qty > 0:
|
703
|
+
new_avg = (old_avg * old_qty + px_i * qty_i) / new_qty
|
704
|
+
else:
|
705
|
+
new_avg = px_i
|
706
|
+
|
707
|
+
# 写回
|
708
|
+
item.update({
|
709
|
+
"avg_price": str(new_avg) if new_qty > 0 else old.get("avg_price") if old else None,
|
710
|
+
"deal_quantity": str(new_qty) if new_qty > 0 else old.get("deal_quantity") if old else None,
|
711
|
+
"single_id": single_id,
|
712
|
+
"last_single_id": single_id,
|
713
|
+
})
|
714
|
+
|
715
|
+
# 状态文本:2=filled(整单), 3=partially_filled
|
716
|
+
item["state"] = "filled" if state == 2 else "partially_filled"
|
717
|
+
|
718
|
+
self._update([item])
|
719
|
+
|
720
|
+
# 整单成交 → 删除
|
675
721
|
if state == 2:
|
676
|
-
|
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
|
-
})
|
722
|
+
self._find_and_delete({"order_id": d.get("id")})
|
723
|
+
return
|
685
724
|
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
elif state == 4:
|
725
|
+
# 撤单
|
726
|
+
if state == 4:
|
690
727
|
item["state"] = "canceled"
|
691
728
|
self._update([item])
|
692
|
-
self._find_and_delete({
|
729
|
+
self._find_and_delete({"order_id": d.get("id")})
|
730
|
+
return
|
731
|
+
|
732
|
+
# 未知状态:更新后删除,避免脏数据残留
|
733
|
+
item["state"] = "unknown"
|
734
|
+
self._update([item])
|
735
|
+
self._find_and_delete({"order_id": d.get("id")})
|
693
736
|
|
694
737
|
|
695
738
|
|
@@ -789,7 +832,7 @@ class SpotBook(DataStore):
|
|
789
832
|
)
|
790
833
|
|
791
834
|
if not check_con:
|
792
|
-
logger.warning(f"(丢补丁) version:{now_version} fv:{fv} tv:{tv} ")
|
835
|
+
# logger.warning(f"(丢补丁) version:{now_version} fv:{fv} tv:{tv} ")
|
793
836
|
self.loss[symbol] = True # 暂时不这样做
|
794
837
|
|
795
838
|
|
hyperquant/broker/ourbit.py
CHANGED
@@ -26,7 +26,8 @@ class OurbitSwap:
|
|
26
26
|
return self
|
27
27
|
|
28
28
|
async def update(
|
29
|
-
self,
|
29
|
+
self,
|
30
|
+
update_type: Literal["position", "orders", "balance", "ticker", "all"] = "all",
|
30
31
|
):
|
31
32
|
"""由于交易所很多不支持ws推送,这里使用Rest"""
|
32
33
|
all_urls = [
|
@@ -34,7 +35,7 @@ class OurbitSwap:
|
|
34
35
|
f"{self.api_url}/api/v1/private/order/list/open_orders?page_size=200",
|
35
36
|
f"{self.api_url}/api/v1/private/account/assets",
|
36
37
|
f"{self.api_url}/api/v1/contract/ticker",
|
37
|
-
f"{self.api_url}/api/platform/spot/market/v2/symbols"
|
38
|
+
f"{self.api_url}/api/platform/spot/market/v2/symbols",
|
38
39
|
]
|
39
40
|
|
40
41
|
url_map = {
|
@@ -56,13 +57,8 @@ class OurbitSwap:
|
|
56
57
|
async def sub_tickers(self):
|
57
58
|
self.client.ws_connect(
|
58
59
|
self.ws_url,
|
59
|
-
send_json={
|
60
|
-
|
61
|
-
"param": {
|
62
|
-
"timezone": "UTC+8"
|
63
|
-
}
|
64
|
-
},
|
65
|
-
hdlr_json=self.store.onmessage
|
60
|
+
send_json={"method": "sub.tickers", "param": {"timezone": "UTC+8"}},
|
61
|
+
hdlr_json=self.store.onmessage,
|
66
62
|
)
|
67
63
|
|
68
64
|
async def sub_orderbook(self, symbols: str | list[str]):
|
@@ -74,26 +70,23 @@ class OurbitSwap:
|
|
74
70
|
|
75
71
|
for symbol in symbols:
|
76
72
|
step = self.store.detail.find({"symbol": symbol})[0].get("tick_size")
|
77
|
-
|
78
|
-
send_jsons.append(
|
79
|
-
|
80
|
-
|
81
|
-
"symbol": symbol,
|
82
|
-
"step": str(step)
|
73
|
+
|
74
|
+
send_jsons.append(
|
75
|
+
{
|
76
|
+
"method": "sub.depth.step",
|
77
|
+
"param": {"symbol": symbol, "step": str(step)},
|
83
78
|
}
|
84
|
-
|
79
|
+
)
|
85
80
|
|
86
81
|
await self.client.ws_connect(
|
87
|
-
self.ws_url,
|
88
|
-
send_json=send_jsons,
|
89
|
-
hdlr_json=self.store.onmessage
|
82
|
+
self.ws_url, send_json=send_jsons, hdlr_json=self.store.onmessage
|
90
83
|
)
|
91
84
|
|
92
85
|
async def sub_personal(self):
|
93
86
|
self.client.ws_connect(
|
94
87
|
self.ws_url,
|
95
|
-
send_json={
|
96
|
-
hdlr_json=self.store.onmessage
|
88
|
+
send_json={"method": "sub.personal.user.preference"},
|
89
|
+
hdlr_json=self.store.onmessage,
|
97
90
|
)
|
98
91
|
|
99
92
|
def ret_content(self, res: pybotters.FetchResult):
|
@@ -102,13 +95,15 @@ class OurbitSwap:
|
|
102
95
|
return res.data["data"]
|
103
96
|
case _:
|
104
97
|
raise Exception(f"Failed api {res.response.url}: {res.data}")
|
105
|
-
|
106
98
|
|
107
99
|
def fmt_price(self, symbol, price: float) -> float:
|
108
100
|
tick = self.store.detail.find({"symbol": symbol})[0].get("tick_size")
|
109
101
|
tick_dec = Decimal(str(tick))
|
110
102
|
price_dec = Decimal(str(price))
|
111
|
-
return float(
|
103
|
+
return float(
|
104
|
+
(price_dec / tick_dec).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
|
105
|
+
* tick_dec
|
106
|
+
)
|
112
107
|
|
113
108
|
async def place_order(
|
114
109
|
self,
|
@@ -135,14 +130,13 @@ class OurbitSwap:
|
|
135
130
|
raise ValueError("params err")
|
136
131
|
|
137
132
|
max_lev = self.store.detail.find({"symbol": symbol})[0].get("max_lev")
|
138
|
-
|
133
|
+
|
139
134
|
if usdt_amount is not None:
|
140
135
|
cs = self.store.detail.find({"symbol": symbol})[0].get("contract_sz")
|
141
136
|
size = max(int(usdt_amount / cs / price), 1)
|
142
137
|
|
143
138
|
if price is not None:
|
144
139
|
price = self.fmt_price(symbol, price)
|
145
|
-
|
146
140
|
|
147
141
|
leverage = min(max_lev, leverage)
|
148
142
|
|
@@ -165,22 +159,23 @@ class OurbitSwap:
|
|
165
159
|
data["price"] = str(price)
|
166
160
|
|
167
161
|
if "close" in side:
|
168
|
-
if side ==
|
162
|
+
if side == "close_buy":
|
169
163
|
data["side"] = 2
|
170
|
-
elif side ==
|
164
|
+
elif side == "close_sell":
|
171
165
|
data["side"] = 4
|
172
|
-
|
166
|
+
|
173
167
|
if position_id is None:
|
174
168
|
raise ValueError("position_id is required for closing position")
|
175
169
|
data["positionId"] = position_id
|
176
170
|
# import time
|
177
171
|
# print(time.time(), '下单')
|
178
|
-
res =
|
172
|
+
res = await self.client.fetch(
|
179
173
|
"POST", f"{self.api_url}/api/v1/private/order/create", data=data
|
180
174
|
)
|
181
175
|
return self.ret_content(res)
|
182
|
-
|
183
|
-
async def place_tpsl(
|
176
|
+
|
177
|
+
async def place_tpsl(
|
178
|
+
self,
|
184
179
|
position_id: int,
|
185
180
|
take_profit: Optional[float] = None,
|
186
181
|
stop_loss: Optional[float] = None,
|
@@ -214,12 +209,9 @@ class OurbitSwap:
|
|
214
209
|
data["takeProfitPrice"] = take_profit
|
215
210
|
if stop_loss is not None:
|
216
211
|
data["stopLossPrice"] = stop_loss
|
217
|
-
|
218
212
|
|
219
213
|
res = await self.client.fetch(
|
220
|
-
"POST",
|
221
|
-
f"{self.api_url}/api/v1/private/stoporder/place",
|
222
|
-
data=data
|
214
|
+
"POST", f"{self.api_url}/api/v1/private/stoporder/place", data=data
|
223
215
|
)
|
224
216
|
|
225
217
|
return self.ret_content(res)
|
@@ -312,7 +304,7 @@ class OurbitSwap:
|
|
312
304
|
|
313
305
|
class OurbitSpot:
|
314
306
|
|
315
|
-
def __init__(self, client: pybotters.Client):
|
307
|
+
def __init__(self, client: pybotters.Client, personal_msg_cb: callable=None):
|
316
308
|
"""
|
317
309
|
✅ 完成:
|
318
310
|
下单, 撤单, 查询资金, 查询持有订单, 查询历史订单
|
@@ -322,6 +314,7 @@ class OurbitSpot:
|
|
322
314
|
self.store = OurbitSpotDataStore()
|
323
315
|
self.api_url = "https://www.ourbit.com"
|
324
316
|
self.ws_url = "wss://www.ourbit.com/ws"
|
317
|
+
self.personal_msg_cb = personal_msg_cb
|
325
318
|
|
326
319
|
async def __aenter__(self) -> "OurbitSpot":
|
327
320
|
client = self.client
|
@@ -330,41 +323,39 @@ class OurbitSpot:
|
|
330
323
|
)
|
331
324
|
return self
|
332
325
|
|
333
|
-
async def update(
|
326
|
+
async def update(
|
327
|
+
self, update_type: Literal["orders", "balance", "ticker", "book", "all"] = "all"
|
328
|
+
):
|
334
329
|
|
335
330
|
all_urls = [
|
336
331
|
f"{self.api_url}/api/platform/spot/order/current/orders/v2?orderTypes=1%2C2%2C3%2C4%2C5%2C100&pageNum=1&pageSize=100&states=0%2C1%2C3",
|
337
332
|
f"{self.api_url}/api/assetbussiness/asset/spot/statistic",
|
338
|
-
f"{self.api_url}/api/platform/spot/market/v2/tickers"
|
333
|
+
f"{self.api_url}/api/platform/spot/market/v2/tickers",
|
339
334
|
]
|
340
335
|
|
341
336
|
# orderTypes=1%2C2%2C3%2C4%2C5%2C100&pageNum=1&pageSize=100&states=0%2C1%2C3
|
342
|
-
|
337
|
+
|
343
338
|
url_map = {
|
344
339
|
"orders": [all_urls[0]],
|
345
340
|
"balance": [all_urls[1]],
|
346
341
|
"ticker": [all_urls[2]],
|
347
|
-
"all": all_urls
|
342
|
+
"all": all_urls,
|
348
343
|
}
|
349
344
|
|
350
345
|
try:
|
351
346
|
urls = url_map[update_type]
|
352
347
|
except KeyError:
|
353
348
|
raise ValueError(f"Unknown update type: {update_type}")
|
354
|
-
|
349
|
+
|
355
350
|
# 直接传协程进去,initialize 会自己 await
|
356
351
|
await self.store.initialize(*(self.client.get(url) for url in urls))
|
357
352
|
|
358
|
-
|
359
353
|
async def sub_personal(self):
|
360
354
|
"""订阅个人频道"""
|
361
355
|
# https://www.ourbit.com/ucenter/api/ws_token
|
362
|
-
res = await self.client.fetch(
|
363
|
-
'GET', f"{self.api_url}/ucenter/api/ws_token"
|
364
|
-
)
|
365
|
-
|
366
|
-
token = res.data['data'].get("wsToken")
|
356
|
+
res = await self.client.fetch("GET", f"{self.api_url}/ucenter/api/ws_token")
|
367
357
|
|
358
|
+
token = res.data["data"].get("wsToken")
|
368
359
|
|
369
360
|
app = self.client.ws_connect(
|
370
361
|
f"{self.ws_url}?wsToken={token}&platform=web",
|
@@ -373,23 +364,27 @@ class OurbitSpot:
|
|
373
364
|
"params": [
|
374
365
|
"spot@private.orders",
|
375
366
|
"spot@private.trigger.orders",
|
376
|
-
"spot@private.balances"
|
367
|
+
"spot@private.balances",
|
377
368
|
],
|
378
|
-
"id": 1
|
369
|
+
"id": 1,
|
379
370
|
},
|
380
|
-
hdlr_json=
|
371
|
+
hdlr_json=(
|
372
|
+
self.store.onmessage
|
373
|
+
if self.personal_msg_cb is None
|
374
|
+
else [self.store.onmessage, self.personal_msg_cb]
|
375
|
+
),
|
381
376
|
)
|
382
377
|
|
383
378
|
await app._event.wait()
|
384
|
-
|
385
379
|
|
386
380
|
async def sub_orderbook(self, symbols: str | list[str]):
|
387
381
|
"""订阅订单簿深度数据
|
388
|
-
|
382
|
+
|
389
383
|
Args:
|
390
384
|
symbols: 交易对符号,可以是单个字符串或字符串列表
|
391
385
|
"""
|
392
386
|
import logging
|
387
|
+
|
393
388
|
logger = logging.getLogger("OurbitSpot")
|
394
389
|
|
395
390
|
if isinstance(symbols, str):
|
@@ -403,27 +398,29 @@ class OurbitSpot:
|
|
403
398
|
# 一次sub20个,超过需要分开订阅
|
404
399
|
for i in range(0, len(subscription_params), 20):
|
405
400
|
wsapp = self.client.ws_connect(
|
406
|
-
|
401
|
+
"wss://www.ourbit.com/ws?platform=web",
|
407
402
|
send_json={
|
408
403
|
"method": "SUBSCRIPTION",
|
409
|
-
"params": subscription_params[i:i + 20],
|
410
|
-
"id": 2
|
404
|
+
"params": subscription_params[i : i + 20],
|
405
|
+
"id": 2,
|
411
406
|
},
|
412
|
-
hdlr_json=self.store.onmessage
|
407
|
+
hdlr_json=self.store.onmessage,
|
413
408
|
)
|
414
409
|
await wsapp._event.wait()
|
415
|
-
|
410
|
+
|
416
411
|
# await asyncio.sleep(1) # 等待ws连接稳定
|
417
412
|
|
418
413
|
# 并发获取每个交易对的初始深度数据
|
419
414
|
tasks = [
|
420
|
-
self.client.fetch(
|
415
|
+
self.client.fetch(
|
416
|
+
"GET", f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}"
|
417
|
+
)
|
421
418
|
for symbol in symbols
|
422
419
|
]
|
423
|
-
|
420
|
+
|
424
421
|
# 等待所有请求完成
|
425
422
|
responses = await asyncio.gather(*tasks)
|
426
|
-
|
423
|
+
|
427
424
|
# 处理响应数据
|
428
425
|
for idx, response in enumerate(responses):
|
429
426
|
symbol = symbols[idx]
|
@@ -435,13 +432,15 @@ class OurbitSpot:
|
|
435
432
|
loss = self.store.book.loss
|
436
433
|
for symbol, is_loss in loss.items():
|
437
434
|
if is_loss:
|
438
|
-
resp = await self.client.fetch(
|
435
|
+
resp = await self.client.fetch(
|
436
|
+
"GET",
|
437
|
+
f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}",
|
438
|
+
)
|
439
439
|
self.store.book._onresponse(resp.data)
|
440
440
|
await asyncio.sleep(1)
|
441
441
|
|
442
442
|
asyncio.create_task(check_loss())
|
443
443
|
|
444
|
-
|
445
444
|
async def place_order(
|
446
445
|
self,
|
447
446
|
symbol: str,
|
@@ -449,10 +448,10 @@ class OurbitSpot:
|
|
449
448
|
price: float = None,
|
450
449
|
quantity: float = None,
|
451
450
|
order_type: Literal["market", "limit"] = "limit",
|
452
|
-
usdt_amount: float = None
|
451
|
+
usdt_amount: float = None,
|
453
452
|
):
|
454
453
|
"""现货下单
|
455
|
-
|
454
|
+
|
456
455
|
Args:
|
457
456
|
symbol: 交易对,如 "SOL_USDT"
|
458
457
|
side: 买卖方向 "buy" 或 "sell"
|
@@ -460,7 +459,7 @@ class OurbitSpot:
|
|
460
459
|
quantity: 数量
|
461
460
|
order_type: 订单类型 "market" 或 "limit"
|
462
461
|
usdt_amount: USDT金额,如果指定则根据价格计算数量
|
463
|
-
|
462
|
+
|
464
463
|
Returns:
|
465
464
|
订单响应数据
|
466
465
|
"""
|
@@ -469,45 +468,43 @@ class OurbitSpot:
|
|
469
468
|
raise ValueError("Limit orders require a price")
|
470
469
|
if quantity is None and usdt_amount is None:
|
471
470
|
raise ValueError("Either quantity or usdt_amount must be specified")
|
472
|
-
|
471
|
+
|
473
472
|
# 解析交易对
|
474
473
|
parts = symbol.split("_")
|
475
474
|
if len(parts) != 2:
|
476
475
|
raise ValueError(f"Invalid symbol format: {symbol}")
|
477
|
-
|
476
|
+
|
478
477
|
currency, market = parts
|
479
|
-
|
478
|
+
|
480
479
|
# 获取交易对详情
|
481
480
|
detail = self.store.detail.get({"name": currency})
|
482
481
|
if not detail:
|
483
482
|
raise ValueError(f"Unknown currency: {currency}")
|
484
|
-
|
483
|
+
|
485
484
|
price_scale = detail.get("price_scale")
|
486
485
|
quantity_scale = detail.get("quantity_scale")
|
487
|
-
|
486
|
+
|
488
487
|
# 构建请求数据
|
489
|
-
data = {
|
490
|
-
|
491
|
-
"market": market,
|
492
|
-
"tradeType": side.upper()
|
493
|
-
}
|
494
|
-
|
488
|
+
data = {"currency": currency, "market": market, "tradeType": side.upper()}
|
489
|
+
|
495
490
|
# 处理市价单和限价单的不同参数
|
496
491
|
if order_type == "limit":
|
497
492
|
data["orderType"] = "LIMIT_ORDER"
|
498
|
-
data["price"] = str(
|
499
|
-
|
493
|
+
data["price"] = str(
|
494
|
+
round(price, price_scale) if price_scale is not None else price
|
495
|
+
)
|
496
|
+
|
500
497
|
# 计算并设置数量
|
501
498
|
if quantity is None and usdt_amount is not None and price:
|
502
499
|
quantity = usdt_amount / price
|
503
|
-
|
500
|
+
|
504
501
|
if quantity_scale is not None:
|
505
502
|
quantity = round(quantity, quantity_scale)
|
506
503
|
data["quantity"] = str(quantity)
|
507
|
-
|
504
|
+
|
508
505
|
elif order_type == "market":
|
509
506
|
data["orderType"] = "MARKET_ORDER"
|
510
|
-
|
507
|
+
|
511
508
|
# 市价单可以使用数量或金额,但不能同时使用
|
512
509
|
if usdt_amount is not None:
|
513
510
|
data["amount"] = str(usdt_amount)
|
@@ -515,23 +512,29 @@ class OurbitSpot:
|
|
515
512
|
if quantity_scale is not None:
|
516
513
|
quantity = round(quantity, quantity_scale)
|
517
514
|
data["quantity"] = str(quantity)
|
518
|
-
|
515
|
+
|
519
516
|
if price:
|
520
517
|
data["price"] = str(price)
|
521
|
-
|
518
|
+
|
522
519
|
# 确定API端点
|
523
520
|
url = f'{self.api_url}/api/platform/spot/{"v4/order/place" if order_type == "market" else "order/place"}'
|
524
|
-
|
521
|
+
print(f"Placing {symbol}: {data}")
|
525
522
|
# 发送请求
|
526
523
|
res = await self.client.fetch("POST", url, json=data)
|
527
|
-
|
524
|
+
|
528
525
|
# 处理响应
|
529
526
|
if res.data.get("msg") == "success":
|
530
527
|
return res.data["data"]
|
531
528
|
raise Exception(f"Failed to place order: {res.data}")
|
532
|
-
|
529
|
+
|
533
530
|
async def cancel_orders(self, order_ids: list[str]):
|
534
531
|
|
535
532
|
for order_id in order_ids:
|
536
|
-
url = f
|
537
|
-
await self.client.fetch("DELETE", url)
|
533
|
+
url = f"{self.api_url}/api/platform/spot/order/cancel/v2?orderId={order_id}"
|
534
|
+
await self.client.fetch("DELETE", url)
|
535
|
+
|
536
|
+
async def cancel_order(self, order_id: str):
|
537
|
+
|
538
|
+
url = f"{self.api_url}/api/platform/spot/order/cancel/v2?orderId={order_id}"
|
539
|
+
res = await self.client.fetch("DELETE", url)
|
540
|
+
print(res.data)
|
hyperquant/logkit.py
CHANGED
@@ -146,10 +146,25 @@ class MinConsoleHandler(logging.StreamHandler):
|
|
146
146
|
else:
|
147
147
|
super().emit(record)
|
148
148
|
|
149
|
+
# ====================================================================================================
|
150
|
+
# ** NullLogger 类,用于禁用日志 **
|
151
|
+
# ====================================================================================================
|
152
|
+
class NullLogger:
|
153
|
+
def debug(self, *a, **k): pass
|
154
|
+
def info(self, *a, **k): pass
|
155
|
+
def ok(self, *a, **k): pass
|
156
|
+
def warning(self, *a, **k): pass
|
157
|
+
def error(self, *a, **k): pass
|
158
|
+
def critical(self, *a, **k): pass
|
159
|
+
def exception(self, *a, **k): pass
|
160
|
+
def divider(self, *a, **k): pass
|
161
|
+
|
149
162
|
# ====================================================================================================
|
150
163
|
# ** 功能函数 **
|
151
164
|
# ====================================================================================================
|
152
|
-
def get_logger(name=None, file_path=None, show_time=False, use_color=True, timezone="Asia/Shanghai"
|
165
|
+
def get_logger(name=None, file_path=None, show_time=False, use_color=True, timezone="Asia/Shanghai", level: object = None, enable_console: bool = True, enabled: bool = True):
|
166
|
+
if not enabled:
|
167
|
+
return NullLogger()
|
153
168
|
if name is None:
|
154
169
|
name = '_'
|
155
170
|
logger_instance = Logger(name, show_time, use_color, timezone) # 传递时区参数
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hyperquant
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.48
|
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
|
@@ -2,20 +2,20 @@ hyperquant/__init__.py,sha256=UpjiX4LS5jmrBc2kE8RiLR02eCfD8JDQrR1q8zkLNcQ,161
|
|
2
2
|
hyperquant/core.py,sha256=iEI8qTNpyesB_w67SrKXeGoB9JllovBeJKI0EZFYew4,20631
|
3
3
|
hyperquant/db.py,sha256=i2TjkCbmH4Uxo7UTDvOYBfy973gLcGexdzuT_YcSeIE,6678
|
4
4
|
hyperquant/draw.py,sha256=up_lQ3pHeVLoNOyh9vPjgNwjD0M-6_IetSGviQUgjhY,54624
|
5
|
-
hyperquant/logkit.py,sha256=
|
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
8
|
hyperquant/broker/hyperliquid.py,sha256=7MxbI9OyIBcImDelPJu-8Nd53WXjxPB5TwE6gsjHbto,23252
|
9
|
-
hyperquant/broker/ourbit.py,sha256=
|
9
|
+
hyperquant/broker/ourbit.py,sha256=50pmapIscWi7vwIjvWY3m3j9wVgrFjK-fZWeFGSxkT4,17924
|
10
10
|
hyperquant/broker/ws.py,sha256=umRzxwCaZaRIgIq4YY-AuA0wCXFT0uOBmQbIXFY8CK0,1555
|
11
11
|
hyperquant/broker/lib/hpstore.py,sha256=LnLK2zmnwVvhEbLzYI-jz_SfYpO1Dv2u2cJaRAb84D8,8296
|
12
12
|
hyperquant/broker/lib/hyper_types.py,sha256=HqjjzjUekldjEeVn6hxiWA8nevAViC2xHADOzDz9qyw,991
|
13
13
|
hyperquant/broker/models/hyperliquid.py,sha256=c4r5739ibZfnk69RxPjQl902AVuUOwT8RNvKsMtwXBY,9459
|
14
|
-
hyperquant/broker/models/ourbit.py,sha256=
|
14
|
+
hyperquant/broker/models/ourbit.py,sha256=rFJEFwzTravStHsB0kEwP5YhI2EQsn79Vv7aEiCIWLc,41585
|
15
15
|
hyperquant/datavison/_util.py,sha256=92qk4vO856RqycO0YqEIHJlEg-W9XKapDVqAMxe6rbw,533
|
16
16
|
hyperquant/datavison/binance.py,sha256=3yNKTqvt_vUQcxzeX4ocMsI5k6Q6gLZrvgXxAEad6Kc,5001
|
17
17
|
hyperquant/datavison/coinglass.py,sha256=PEjdjISP9QUKD_xzXNzhJ9WFDTlkBrRQlVL-5pxD5mo,10482
|
18
18
|
hyperquant/datavison/okx.py,sha256=yg8WrdQ7wgWHNAInIgsWPM47N3Wkfr253169IPAycAY,6898
|
19
|
-
hyperquant-0.
|
20
|
-
hyperquant-0.
|
21
|
-
hyperquant-0.
|
19
|
+
hyperquant-0.48.dist-info/METADATA,sha256=G1KErGZNZ7Cx8-S35chKSAhKvPKDcpE1Z2DvWlqHJuE,4317
|
20
|
+
hyperquant-0.48.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
21
|
+
hyperquant-0.48.dist-info/RECORD,,
|
File without changes
|