hyperquant 0.3__py3-none-any.whl → 0.5__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/auth.py +47 -10
- hyperquant/broker/hyperliquid.py +0 -2
- hyperquant/broker/models/ourbit.py +687 -83
- hyperquant/broker/ourbit.py +336 -31
- hyperquant/broker/ws.py +37 -1
- hyperquant/core.py +10 -6
- hyperquant/logkit.py +16 -1
- {hyperquant-0.3.dist-info → hyperquant-0.5.dist-info}/METADATA +1 -1
- {hyperquant-0.3.dist-info → hyperquant-0.5.dist-info}/RECORD +10 -10
- {hyperquant-0.3.dist-info → hyperquant-0.5.dist-info}/WHEEL +0 -0
hyperquant/broker/ourbit.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
import asyncio
|
1
2
|
from typing import Literal, Optional
|
2
3
|
import pybotters
|
3
|
-
from .models.ourbit import OurbitSwapDataStore
|
4
|
+
from .models.ourbit import OurbitSwapDataStore, OurbitSpotDataStore
|
5
|
+
from decimal import Decimal, ROUND_HALF_UP
|
4
6
|
|
5
7
|
|
6
8
|
class OurbitSwap:
|
@@ -24,7 +26,8 @@ class OurbitSwap:
|
|
24
26
|
return self
|
25
27
|
|
26
28
|
async def update(
|
27
|
-
self,
|
29
|
+
self,
|
30
|
+
update_type: Literal["position", "orders", "balance", "ticker", "all"] = "all",
|
28
31
|
):
|
29
32
|
"""由于交易所很多不支持ws推送,这里使用Rest"""
|
30
33
|
all_urls = [
|
@@ -32,6 +35,7 @@ class OurbitSwap:
|
|
32
35
|
f"{self.api_url}/api/v1/private/order/list/open_orders?page_size=200",
|
33
36
|
f"{self.api_url}/api/v1/private/account/assets",
|
34
37
|
f"{self.api_url}/api/v1/contract/ticker",
|
38
|
+
f"{self.api_url}/api/platform/spot/market/v2/symbols",
|
35
39
|
]
|
36
40
|
|
37
41
|
url_map = {
|
@@ -53,16 +57,11 @@ class OurbitSwap:
|
|
53
57
|
async def sub_tickers(self):
|
54
58
|
self.client.ws_connect(
|
55
59
|
self.ws_url,
|
56
|
-
send_json={
|
57
|
-
|
58
|
-
"param": {
|
59
|
-
"timezone": "UTC+8"
|
60
|
-
}
|
61
|
-
},
|
62
|
-
hdlr_json=self.store.onmessage
|
60
|
+
send_json={"method": "sub.tickers", "param": {"timezone": "UTC+8"}},
|
61
|
+
hdlr_json=self.store.onmessage,
|
63
62
|
)
|
64
63
|
|
65
|
-
async def
|
64
|
+
async def sub_orderbook(self, symbols: str | list[str]):
|
66
65
|
if isinstance(symbols, str):
|
67
66
|
symbols = [symbols]
|
68
67
|
|
@@ -71,19 +70,23 @@ class OurbitSwap:
|
|
71
70
|
|
72
71
|
for symbol in symbols:
|
73
72
|
step = self.store.detail.find({"symbol": symbol})[0].get("tick_size")
|
74
|
-
|
75
|
-
send_jsons.append(
|
76
|
-
|
77
|
-
|
78
|
-
"symbol": symbol,
|
79
|
-
"step": str(step)
|
73
|
+
|
74
|
+
send_jsons.append(
|
75
|
+
{
|
76
|
+
"method": "sub.depth.step",
|
77
|
+
"param": {"symbol": symbol, "step": str(step)},
|
80
78
|
}
|
81
|
-
|
79
|
+
)
|
82
80
|
|
83
81
|
await self.client.ws_connect(
|
82
|
+
self.ws_url, send_json=send_jsons, hdlr_json=self.store.onmessage
|
83
|
+
)
|
84
|
+
|
85
|
+
async def sub_personal(self):
|
86
|
+
self.client.ws_connect(
|
84
87
|
self.ws_url,
|
85
|
-
send_json=
|
86
|
-
hdlr_json=self.store.onmessage
|
88
|
+
send_json={"method": "sub.personal.user.preference"},
|
89
|
+
hdlr_json=self.store.onmessage,
|
87
90
|
)
|
88
91
|
|
89
92
|
def ret_content(self, res: pybotters.FetchResult):
|
@@ -92,12 +95,20 @@ class OurbitSwap:
|
|
92
95
|
return res.data["data"]
|
93
96
|
case _:
|
94
97
|
raise Exception(f"Failed api {res.response.url}: {res.data}")
|
95
|
-
|
98
|
+
|
99
|
+
def fmt_price(self, symbol, price: float) -> float:
|
100
|
+
tick = self.store.detail.find({"symbol": symbol})[0].get("tick_size")
|
101
|
+
tick_dec = Decimal(str(tick))
|
102
|
+
price_dec = Decimal(str(price))
|
103
|
+
return float(
|
104
|
+
(price_dec / tick_dec).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
|
105
|
+
* tick_dec
|
106
|
+
)
|
96
107
|
|
97
108
|
async def place_order(
|
98
109
|
self,
|
99
110
|
symbol: str,
|
100
|
-
side: Literal["buy", "sell", "
|
111
|
+
side: Literal["buy", "sell", "close_buy", "close_sell"],
|
101
112
|
size: float = None,
|
102
113
|
price: float = None,
|
103
114
|
order_type: Literal["market", "limit_GTC", "limit_IOC"] = "market",
|
@@ -105,23 +116,34 @@ class OurbitSwap:
|
|
105
116
|
leverage: Optional[int] = 20,
|
106
117
|
position_id: Optional[int] = None,
|
107
118
|
):
|
108
|
-
"""
|
119
|
+
"""
|
120
|
+
size为合约张数, openType 1 为逐仓, 2为全仓
|
121
|
+
|
122
|
+
.. code ::
|
123
|
+
{
|
124
|
+
"orderId": "219602019841167810",
|
125
|
+
"ts": 1756395601543
|
126
|
+
}
|
127
|
+
|
128
|
+
"""
|
109
129
|
if (size is None) == (usdt_amount is None):
|
110
130
|
raise ValueError("params err")
|
111
131
|
|
112
132
|
max_lev = self.store.detail.find({"symbol": symbol})[0].get("max_lev")
|
113
|
-
|
133
|
+
|
114
134
|
if usdt_amount is not None:
|
115
135
|
cs = self.store.detail.find({"symbol": symbol})[0].get("contract_sz")
|
116
136
|
size = max(int(usdt_amount / cs / price), 1)
|
117
|
-
|
118
137
|
|
119
|
-
|
138
|
+
if price is not None:
|
139
|
+
price = self.fmt_price(symbol, price)
|
140
|
+
|
141
|
+
leverage = min(max_lev, leverage)
|
120
142
|
|
121
143
|
data = {
|
122
144
|
"symbol": symbol,
|
123
145
|
"side": 1 if side == "buy" else 3,
|
124
|
-
"openType":
|
146
|
+
"openType": 2,
|
125
147
|
"type": "5",
|
126
148
|
"vol": size,
|
127
149
|
"leverage": leverage,
|
@@ -136,17 +158,64 @@ class OurbitSwap:
|
|
136
158
|
data["type"] = "1"
|
137
159
|
data["price"] = str(price)
|
138
160
|
|
139
|
-
if
|
140
|
-
|
161
|
+
if "close" in side:
|
162
|
+
if side == "close_buy":
|
163
|
+
data["side"] = 2
|
164
|
+
elif side == "close_sell":
|
165
|
+
data["side"] = 4
|
166
|
+
|
141
167
|
if position_id is None:
|
142
168
|
raise ValueError("position_id is required for closing position")
|
143
169
|
data["positionId"] = position_id
|
144
|
-
|
145
|
-
|
170
|
+
# import time
|
171
|
+
# print(time.time(), '下单')
|
172
|
+
res = await self.client.fetch(
|
146
173
|
"POST", f"{self.api_url}/api/v1/private/order/create", data=data
|
147
174
|
)
|
148
175
|
return self.ret_content(res)
|
149
176
|
|
177
|
+
async def place_tpsl(
|
178
|
+
self,
|
179
|
+
position_id: int,
|
180
|
+
take_profit: Optional[float] = None,
|
181
|
+
stop_loss: Optional[float] = None,
|
182
|
+
):
|
183
|
+
"""
|
184
|
+
position_id 持仓ID
|
185
|
+
|
186
|
+
.. code:: json
|
187
|
+
|
188
|
+
{
|
189
|
+
"success": true,
|
190
|
+
"code": 0,
|
191
|
+
"data": 2280508
|
192
|
+
}
|
193
|
+
"""
|
194
|
+
if (take_profit is None) and (stop_loss is None):
|
195
|
+
raise ValueError("params err")
|
196
|
+
|
197
|
+
data = {
|
198
|
+
"positionId": position_id,
|
199
|
+
"profitTrend": "1",
|
200
|
+
"lossTrend": "1",
|
201
|
+
"profitLossVolType": "SAME",
|
202
|
+
"volType": 2,
|
203
|
+
"takeProfitReverse": 2,
|
204
|
+
"stopLossReverse": 2,
|
205
|
+
"priceProtect": "0",
|
206
|
+
}
|
207
|
+
|
208
|
+
if take_profit is not None:
|
209
|
+
data["takeProfitPrice"] = take_profit
|
210
|
+
if stop_loss is not None:
|
211
|
+
data["stopLossPrice"] = stop_loss
|
212
|
+
|
213
|
+
res = await self.client.fetch(
|
214
|
+
"POST", f"{self.api_url}/api/v1/private/stoporder/place", data=data
|
215
|
+
)
|
216
|
+
|
217
|
+
return self.ret_content(res)
|
218
|
+
|
150
219
|
async def cancel_orders(self, order_ids: list[str]):
|
151
220
|
res = await self.client.fetch(
|
152
221
|
"POST",
|
@@ -230,5 +299,241 @@ class OurbitSwap:
|
|
230
299
|
"GET",
|
231
300
|
f"{self.api_url}/api/v1/private/order/deal_details/{order_id}",
|
232
301
|
)
|
233
|
-
|
234
302
|
return self.ret_content(res)
|
303
|
+
|
304
|
+
|
305
|
+
class OurbitSpot:
|
306
|
+
|
307
|
+
def __init__(self, client: pybotters.Client, personal_msg_cb: callable=None):
|
308
|
+
"""
|
309
|
+
✅ 完成:
|
310
|
+
下单, 撤单, 查询资金, 查询持有订单, 查询历史订单
|
311
|
+
|
312
|
+
"""
|
313
|
+
self.client = client
|
314
|
+
self.store = OurbitSpotDataStore()
|
315
|
+
self.api_url = "https://www.ourbit.com"
|
316
|
+
self.ws_url = "wss://www.ourbit.com/ws"
|
317
|
+
self.personal_msg_cb = personal_msg_cb
|
318
|
+
|
319
|
+
async def __aenter__(self) -> "OurbitSpot":
|
320
|
+
client = self.client
|
321
|
+
await self.store.initialize(
|
322
|
+
client.get(f"{self.api_url}/api/platform/spot/market/v2/symbols")
|
323
|
+
)
|
324
|
+
return self
|
325
|
+
|
326
|
+
async def update(
|
327
|
+
self, update_type: Literal["orders", "balance", "ticker", "book", "all"] = "all"
|
328
|
+
):
|
329
|
+
|
330
|
+
all_urls = [
|
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",
|
332
|
+
f"{self.api_url}/api/assetbussiness/asset/spot/statistic",
|
333
|
+
f"{self.api_url}/api/platform/spot/market/v2/tickers",
|
334
|
+
]
|
335
|
+
|
336
|
+
# orderTypes=1%2C2%2C3%2C4%2C5%2C100&pageNum=1&pageSize=100&states=0%2C1%2C3
|
337
|
+
|
338
|
+
url_map = {
|
339
|
+
"orders": [all_urls[0]],
|
340
|
+
"balance": [all_urls[1]],
|
341
|
+
"ticker": [all_urls[2]],
|
342
|
+
"all": all_urls,
|
343
|
+
}
|
344
|
+
|
345
|
+
try:
|
346
|
+
urls = url_map[update_type]
|
347
|
+
except KeyError:
|
348
|
+
raise ValueError(f"Unknown update type: {update_type}")
|
349
|
+
|
350
|
+
# 直接传协程进去,initialize 会自己 await
|
351
|
+
await self.store.initialize(*(self.client.get(url) for url in urls))
|
352
|
+
|
353
|
+
async def sub_personal(self):
|
354
|
+
"""订阅个人频道"""
|
355
|
+
# https://www.ourbit.com/ucenter/api/ws_token
|
356
|
+
res = await self.client.fetch("GET", f"{self.api_url}/ucenter/api/ws_token")
|
357
|
+
|
358
|
+
token = res.data["data"].get("wsToken")
|
359
|
+
|
360
|
+
app = self.client.ws_connect(
|
361
|
+
f"{self.ws_url}?wsToken={token}&platform=web",
|
362
|
+
send_json={
|
363
|
+
"method": "SUBSCRIPTION",
|
364
|
+
"params": [
|
365
|
+
"spot@private.orders",
|
366
|
+
"spot@private.trigger.orders",
|
367
|
+
"spot@private.balances",
|
368
|
+
],
|
369
|
+
"id": 1,
|
370
|
+
},
|
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
|
+
),
|
376
|
+
)
|
377
|
+
|
378
|
+
await app._event.wait()
|
379
|
+
|
380
|
+
async def sub_orderbook(self, symbols: str | list[str]):
|
381
|
+
"""订阅订单簿深度数据
|
382
|
+
|
383
|
+
Args:
|
384
|
+
symbols: 交易对符号,可以是单个字符串或字符串列表
|
385
|
+
"""
|
386
|
+
import logging
|
387
|
+
|
388
|
+
logger = logging.getLogger("OurbitSpot")
|
389
|
+
|
390
|
+
if isinstance(symbols, str):
|
391
|
+
symbols = [symbols]
|
392
|
+
|
393
|
+
# 构建订阅参数
|
394
|
+
subscription_params = []
|
395
|
+
for symbol in symbols:
|
396
|
+
subscription_params.append(f"spot@public.increase.aggre.depth@{symbol}")
|
397
|
+
|
398
|
+
# 一次sub20个,超过需要分开订阅
|
399
|
+
for i in range(0, len(subscription_params), 20):
|
400
|
+
wsapp = self.client.ws_connect(
|
401
|
+
"wss://www.ourbit.com/ws?platform=web",
|
402
|
+
send_json={
|
403
|
+
"method": "SUBSCRIPTION",
|
404
|
+
"params": subscription_params[i : i + 20],
|
405
|
+
"id": 2,
|
406
|
+
},
|
407
|
+
hdlr_json=self.store.onmessage,
|
408
|
+
)
|
409
|
+
await wsapp._event.wait()
|
410
|
+
|
411
|
+
# await asyncio.sleep(1) # 等待ws连接稳定
|
412
|
+
|
413
|
+
# 并发获取每个交易对的初始深度数据
|
414
|
+
tasks = [
|
415
|
+
self.client.fetch(
|
416
|
+
"GET", f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}"
|
417
|
+
)
|
418
|
+
for symbol in symbols
|
419
|
+
]
|
420
|
+
|
421
|
+
# 等待所有请求完成
|
422
|
+
responses = await asyncio.gather(*tasks)
|
423
|
+
|
424
|
+
# 处理响应数据
|
425
|
+
for idx, response in enumerate(responses):
|
426
|
+
symbol = symbols[idx]
|
427
|
+
self.store.book._onresponse(response.data)
|
428
|
+
|
429
|
+
async def check_loss():
|
430
|
+
await asyncio.sleep(1)
|
431
|
+
while True:
|
432
|
+
loss = self.store.book.loss
|
433
|
+
for symbol, is_loss in loss.items():
|
434
|
+
if is_loss:
|
435
|
+
resp = await self.client.fetch(
|
436
|
+
"GET",
|
437
|
+
f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}",
|
438
|
+
)
|
439
|
+
self.store.book._onresponse(resp.data)
|
440
|
+
await asyncio.sleep(1)
|
441
|
+
|
442
|
+
asyncio.create_task(check_loss())
|
443
|
+
|
444
|
+
async def place_order(
|
445
|
+
self,
|
446
|
+
symbol: str,
|
447
|
+
side: Literal["buy", "sell"],
|
448
|
+
price: float = None,
|
449
|
+
quantity: float = None,
|
450
|
+
order_type: Literal["market", "limit"] = "limit",
|
451
|
+
usdt_amount: float = None,
|
452
|
+
):
|
453
|
+
"""现货下单
|
454
|
+
|
455
|
+
Args:
|
456
|
+
symbol: 交易对,如 "SOL_USDT"
|
457
|
+
side: 买卖方向 "buy" 或 "sell"
|
458
|
+
price: 价格,市价单可为None
|
459
|
+
quantity: 数量
|
460
|
+
order_type: 订单类型 "market" 或 "limit"
|
461
|
+
usdt_amount: USDT金额,如果指定则根据价格计算数量
|
462
|
+
|
463
|
+
Returns:
|
464
|
+
订单响应数据
|
465
|
+
"""
|
466
|
+
# 参数检查
|
467
|
+
if order_type == "limit" and price is None:
|
468
|
+
raise ValueError("Limit orders require a price")
|
469
|
+
if quantity is None and usdt_amount is None:
|
470
|
+
raise ValueError("Either quantity or usdt_amount must be specified")
|
471
|
+
|
472
|
+
# 解析交易对
|
473
|
+
parts = symbol.split("_")
|
474
|
+
if len(parts) != 2:
|
475
|
+
raise ValueError(f"Invalid symbol format: {symbol}")
|
476
|
+
|
477
|
+
currency, market = parts
|
478
|
+
|
479
|
+
# 获取交易对详情
|
480
|
+
detail = self.store.detail.get({"name": currency})
|
481
|
+
if not detail:
|
482
|
+
raise ValueError(f"Unknown currency: {currency}")
|
483
|
+
|
484
|
+
price_scale = detail.get("price_scale")
|
485
|
+
quantity_scale = detail.get("quantity_scale")
|
486
|
+
|
487
|
+
# 构建请求数据
|
488
|
+
data = {"currency": currency, "market": market, "tradeType": side.upper()}
|
489
|
+
|
490
|
+
# 处理市价单和限价单的不同参数
|
491
|
+
if order_type == "limit":
|
492
|
+
data["orderType"] = "LIMIT_ORDER"
|
493
|
+
data["price"] = str(
|
494
|
+
round(price, price_scale) if price_scale is not None else price
|
495
|
+
)
|
496
|
+
|
497
|
+
# 计算并设置数量
|
498
|
+
if quantity is None and usdt_amount is not None and price:
|
499
|
+
quantity = usdt_amount / price
|
500
|
+
|
501
|
+
if quantity_scale is not None:
|
502
|
+
quantity = round(quantity, quantity_scale)
|
503
|
+
data["quantity"] = str(quantity)
|
504
|
+
|
505
|
+
elif order_type == "market":
|
506
|
+
data["orderType"] = "MARKET_ORDER"
|
507
|
+
|
508
|
+
# 市价单可以使用数量或金额,但不能同时使用
|
509
|
+
if usdt_amount is not None:
|
510
|
+
data["amount"] = str(usdt_amount)
|
511
|
+
else:
|
512
|
+
if quantity_scale is not None:
|
513
|
+
quantity = round(quantity, quantity_scale)
|
514
|
+
data["quantity"] = str(quantity)
|
515
|
+
|
516
|
+
if price:
|
517
|
+
data["price"] = str(price)
|
518
|
+
|
519
|
+
# 确定API端点
|
520
|
+
url = f'{self.api_url}/api/platform/spot/{"v4/order/place" if order_type == "market" else "order/place"}'
|
521
|
+
# print(f"Placing {symbol}: {data}")
|
522
|
+
# 发送请求
|
523
|
+
res = await self.client.fetch("POST", url, json=data)
|
524
|
+
|
525
|
+
# 处理响应
|
526
|
+
if res.data.get("msg") == "success":
|
527
|
+
return res.data["data"]
|
528
|
+
raise Exception(f"Failed to place order: {res.data}")
|
529
|
+
|
530
|
+
async def cancel_orders(self, order_ids: list[str]):
|
531
|
+
|
532
|
+
for order_id in order_ids:
|
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)
|
hyperquant/broker/ws.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
import asyncio
|
2
2
|
import pybotters
|
3
|
+
from pybotters.ws import ClientWebSocketResponse, logger
|
4
|
+
from pybotters.auth import Hosts
|
5
|
+
import yarl
|
3
6
|
|
4
7
|
|
5
8
|
class Heartbeat:
|
@@ -8,5 +11,38 @@ class Heartbeat:
|
|
8
11
|
while not ws.closed:
|
9
12
|
await ws.send_str('{"method":"ping"}')
|
10
13
|
await asyncio.sleep(10.0)
|
14
|
+
|
15
|
+
async def ourbit_spot(ws: pybotters.ws.ClientWebSocketResponse):
|
16
|
+
while not ws.closed:
|
17
|
+
await ws.send_str('{"method":"ping"}')
|
18
|
+
await asyncio.sleep(10.0)
|
19
|
+
|
20
|
+
pybotters.ws.HeartbeatHosts.items['futures.ourbit.com'] = Heartbeat.ourbit
|
21
|
+
pybotters.ws.HeartbeatHosts.items['www.ourbit.com'] = Heartbeat.ourbit_spot
|
22
|
+
|
23
|
+
class WssAuth:
|
24
|
+
@staticmethod
|
25
|
+
async def ourbit(ws: ClientWebSocketResponse):
|
26
|
+
key: str = ws._response._session.__dict__["_apis"][
|
27
|
+
pybotters.ws.AuthHosts.items[ws._response.url.host].name
|
28
|
+
][0]
|
29
|
+
await ws.send_json(
|
30
|
+
{
|
31
|
+
"method": "login",
|
32
|
+
"param": {
|
33
|
+
"token": key
|
34
|
+
}
|
35
|
+
}
|
36
|
+
)
|
37
|
+
async for msg in ws:
|
38
|
+
# {"channel":"rs.login","data":"success","ts":1756470267848}
|
39
|
+
data = msg.json()
|
40
|
+
if data.get("channel") == "rs.login":
|
41
|
+
if data.get("data") == "success":
|
42
|
+
break
|
43
|
+
else:
|
44
|
+
logger.warning(f"WebSocket login failed: {data}")
|
45
|
+
|
46
|
+
|
11
47
|
|
12
|
-
pybotters.ws.
|
48
|
+
pybotters.ws.AuthHosts.items['futures.ourbit.com'] = pybotters.auth.Item("ourbit", WssAuth.ourbit)
|
hyperquant/core.py
CHANGED
@@ -269,10 +269,10 @@ class ExchangeBase:
|
|
269
269
|
)
|
270
270
|
|
271
271
|
class Exchange(ExchangeBase):
|
272
|
-
def __init__(self, trade_symbols, fee=0.0002, initial_balance=10000, recorded=False):
|
272
|
+
def __init__(self, trade_symbols:list=[], fee=0.0002, initial_balance=10000, recorded=False):
|
273
273
|
super().__init__(initial_balance=initial_balance, recorded=recorded)
|
274
274
|
self.fee = fee
|
275
|
-
self.trade_symbols = trade_symbols
|
275
|
+
self.trade_symbols:list = trade_symbols
|
276
276
|
self.id_gen = 0
|
277
277
|
self.account['USDT'].update({
|
278
278
|
'hold': 0,
|
@@ -280,8 +280,12 @@ class Exchange(ExchangeBase):
|
|
280
280
|
'short': 0
|
281
281
|
})
|
282
282
|
for symbol in trade_symbols:
|
283
|
-
self.account[symbol] =
|
284
|
-
|
283
|
+
self.account[symbol] = self._act_template
|
284
|
+
|
285
|
+
@property
|
286
|
+
def _act_template(self):
|
287
|
+
return {'amount': 0, 'hold_price': 0, 'value': 0, 'price': 0,
|
288
|
+
'realised_profit': 0, 'unrealised_profit': 0, 'fee': 0}.copy()
|
285
289
|
|
286
290
|
def Trade(self, symbol, direction, price, amount, **kwargs):
|
287
291
|
if self.recorded and 'time' not in kwargs:
|
@@ -307,8 +311,7 @@ class Exchange(ExchangeBase):
|
|
307
311
|
|
308
312
|
if symbol not in self.trade_symbols:
|
309
313
|
self.trade_symbols.append(symbol)
|
310
|
-
self.account[symbol] =
|
311
|
-
'realised_profit': 0, 'unrealised_profit': 0, 'fee': 0}
|
314
|
+
self.account[symbol] = self._act_template
|
312
315
|
|
313
316
|
cover_amount = 0 if direction * self.account[symbol]['amount'] >= 0 else min(abs(self.account[symbol]['amount']), amount)
|
314
317
|
open_amount = amount - cover_amount
|
@@ -343,6 +346,7 @@ class Exchange(ExchangeBase):
|
|
343
346
|
|
344
347
|
if kwargs:
|
345
348
|
self.opt.update(kwargs)
|
349
|
+
self.account[symbol].update(kwargs)
|
346
350
|
|
347
351
|
# 记录账户总资产到 history
|
348
352
|
if self.recorded:
|
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.5
|
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,21 @@
|
|
1
1
|
hyperquant/__init__.py,sha256=UpjiX4LS5jmrBc2kE8RiLR02eCfD8JDQrR1q8zkLNcQ,161
|
2
|
-
hyperquant/core.py,sha256=
|
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
|
-
hyperquant/broker/auth.py,sha256=
|
8
|
-
hyperquant/broker/hyperliquid.py,sha256=
|
9
|
-
hyperquant/broker/ourbit.py,sha256=
|
10
|
-
hyperquant/broker/ws.py,sha256=
|
7
|
+
hyperquant/broker/auth.py,sha256=oA9Yw1I59-u0Tnoj2e4wUup5q8V5T2qpga5RKbiAiZI,2614
|
8
|
+
hyperquant/broker/hyperliquid.py,sha256=7MxbI9OyIBcImDelPJu-8Nd53WXjxPB5TwE6gsjHbto,23252
|
9
|
+
hyperquant/broker/ourbit.py,sha256=Fza_nhfoSCf1Ulm7UHOlt969Wm37bKja9P5xpN93XqY,17902
|
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=QuKxUYlJzRC4zr0lNiz3dpireConbsRyOtOvA0_VTVA,39978
|
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.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,,
|
File without changes
|