hyperquant 0.4__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.
- hyperquant/broker/edgex.py +183 -0
- hyperquant/broker/models/edgex.py +513 -0
- hyperquant/broker/models/ourbit.py +270 -169
- hyperquant/broker/ourbit.py +173 -123
- hyperquant/core.py +13 -6
- hyperquant/logkit.py +16 -1
- {hyperquant-0.4.dist-info → hyperquant-0.6.dist-info}/METADATA +1 -1
- {hyperquant-0.4.dist-info → hyperquant-0.6.dist-info}/RECORD +9 -7
- {hyperquant-0.4.dist-info → hyperquant-0.6.dist-info}/WHEEL +0 -0
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,27 +70,29 @@ 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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
"
|
82
|
-
"
|
73
|
+
step_str = str(Decimal(str(step)).normalize())
|
74
|
+
|
75
|
+
send_jsons.append(
|
76
|
+
{
|
77
|
+
"method": "sub.depth.step",
|
78
|
+
"param": {
|
79
|
+
"symbol": symbol,
|
80
|
+
"step": step_str,
|
81
|
+
},
|
83
82
|
}
|
84
|
-
|
83
|
+
)
|
85
84
|
|
86
85
|
await self.client.ws_connect(
|
87
|
-
self.ws_url,
|
88
|
-
send_json=send_jsons,
|
89
|
-
hdlr_json=self.store.onmessage
|
86
|
+
self.ws_url, send_json=send_jsons, hdlr_json=self.store.onmessage
|
90
87
|
)
|
91
88
|
|
92
89
|
async def sub_personal(self):
|
93
|
-
self.client.ws_connect(
|
90
|
+
wsapp = self.client.ws_connect(
|
94
91
|
self.ws_url,
|
95
|
-
send_json={
|
96
|
-
hdlr_json=self.store.onmessage
|
92
|
+
send_json={"method": "sub.personal.user.preference"},
|
93
|
+
hdlr_json=self.store.onmessage,
|
97
94
|
)
|
95
|
+
await wsapp._event.wait()
|
98
96
|
|
99
97
|
def ret_content(self, res: pybotters.FetchResult):
|
100
98
|
match res.data:
|
@@ -102,13 +100,15 @@ class OurbitSwap:
|
|
102
100
|
return res.data["data"]
|
103
101
|
case _:
|
104
102
|
raise Exception(f"Failed api {res.response.url}: {res.data}")
|
105
|
-
|
106
103
|
|
107
104
|
def fmt_price(self, symbol, price: float) -> float:
|
108
105
|
tick = self.store.detail.find({"symbol": symbol})[0].get("tick_size")
|
109
106
|
tick_dec = Decimal(str(tick))
|
110
107
|
price_dec = Decimal(str(price))
|
111
|
-
return float(
|
108
|
+
return float(
|
109
|
+
(price_dec / tick_dec).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
|
110
|
+
* tick_dec
|
111
|
+
)
|
112
112
|
|
113
113
|
async def place_order(
|
114
114
|
self,
|
@@ -120,6 +120,7 @@ class OurbitSwap:
|
|
120
120
|
usdt_amount: Optional[float] = None,
|
121
121
|
leverage: Optional[int] = 20,
|
122
122
|
position_id: Optional[int] = None,
|
123
|
+
quantity: float = None, # 兼容参数,不使用
|
123
124
|
):
|
124
125
|
"""
|
125
126
|
size为合约张数, openType 1 为逐仓, 2为全仓
|
@@ -135,14 +136,13 @@ class OurbitSwap:
|
|
135
136
|
raise ValueError("params err")
|
136
137
|
|
137
138
|
max_lev = self.store.detail.find({"symbol": symbol})[0].get("max_lev")
|
138
|
-
|
139
|
+
|
139
140
|
if usdt_amount is not None:
|
140
141
|
cs = self.store.detail.find({"symbol": symbol})[0].get("contract_sz")
|
141
142
|
size = max(int(usdt_amount / cs / price), 1)
|
142
143
|
|
143
144
|
if price is not None:
|
144
145
|
price = self.fmt_price(symbol, price)
|
145
|
-
|
146
146
|
|
147
147
|
leverage = min(max_lev, leverage)
|
148
148
|
|
@@ -165,22 +165,28 @@ class OurbitSwap:
|
|
165
165
|
data["price"] = str(price)
|
166
166
|
|
167
167
|
if "close" in side:
|
168
|
-
if side ==
|
168
|
+
if side == "close_buy":
|
169
169
|
data["side"] = 2
|
170
|
-
elif side ==
|
170
|
+
elif side == "close_sell":
|
171
171
|
data["side"] = 4
|
172
|
-
|
172
|
+
|
173
173
|
if position_id is None:
|
174
174
|
raise ValueError("position_id is required for closing position")
|
175
175
|
data["positionId"] = position_id
|
176
|
-
|
177
|
-
|
178
|
-
res = await self.client.fetch(
|
176
|
+
|
177
|
+
res = await self.client.fetch(
|
179
178
|
"POST", f"{self.api_url}/api/v1/private/order/create", data=data
|
180
179
|
)
|
181
|
-
|
182
|
-
|
183
|
-
|
180
|
+
# 'orderId' =
|
181
|
+
# '226474723700166962'
|
182
|
+
# 'ts' =
|
183
|
+
# 1758034181833
|
184
|
+
ret_c = self.ret_content(res)
|
185
|
+
# 只返回 orderId
|
186
|
+
return ret_c["orderId"]
|
187
|
+
|
188
|
+
async def place_tpsl(
|
189
|
+
self,
|
184
190
|
position_id: int,
|
185
191
|
take_profit: Optional[float] = None,
|
186
192
|
stop_loss: Optional[float] = None,
|
@@ -214,12 +220,9 @@ class OurbitSwap:
|
|
214
220
|
data["takeProfitPrice"] = take_profit
|
215
221
|
if stop_loss is not None:
|
216
222
|
data["stopLossPrice"] = stop_loss
|
217
|
-
|
218
223
|
|
219
224
|
res = await self.client.fetch(
|
220
|
-
"POST",
|
221
|
-
f"{self.api_url}/api/v1/private/stoporder/place",
|
222
|
-
data=data
|
225
|
+
"POST", f"{self.api_url}/api/v1/private/stoporder/place", data=data
|
223
226
|
)
|
224
227
|
|
225
228
|
return self.ret_content(res)
|
@@ -312,7 +315,7 @@ class OurbitSwap:
|
|
312
315
|
|
313
316
|
class OurbitSpot:
|
314
317
|
|
315
|
-
def __init__(self, client: pybotters.Client):
|
318
|
+
def __init__(self, client: pybotters.Client, personal_msg_cb: callable=None):
|
316
319
|
"""
|
317
320
|
✅ 完成:
|
318
321
|
下单, 撤单, 查询资金, 查询持有订单, 查询历史订单
|
@@ -322,6 +325,7 @@ class OurbitSpot:
|
|
322
325
|
self.store = OurbitSpotDataStore()
|
323
326
|
self.api_url = "https://www.ourbit.com"
|
324
327
|
self.ws_url = "wss://www.ourbit.com/ws"
|
328
|
+
self.personal_msg_cb = personal_msg_cb
|
325
329
|
|
326
330
|
async def __aenter__(self) -> "OurbitSpot":
|
327
331
|
client = self.client
|
@@ -330,171 +334,217 @@ class OurbitSpot:
|
|
330
334
|
)
|
331
335
|
return self
|
332
336
|
|
333
|
-
async def update(
|
337
|
+
async def update(
|
338
|
+
self, update_type: Literal["orders", "balance", "ticker", "book", "all"] = "all"
|
339
|
+
):
|
334
340
|
|
335
341
|
all_urls = [
|
336
342
|
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
343
|
f"{self.api_url}/api/assetbussiness/asset/spot/statistic",
|
338
|
-
f"{self.api_url}/api/platform/spot/market/v2/tickers"
|
344
|
+
f"{self.api_url}/api/platform/spot/market/v2/tickers",
|
339
345
|
]
|
340
346
|
|
341
347
|
# orderTypes=1%2C2%2C3%2C4%2C5%2C100&pageNum=1&pageSize=100&states=0%2C1%2C3
|
342
|
-
|
348
|
+
|
343
349
|
url_map = {
|
344
350
|
"orders": [all_urls[0]],
|
345
351
|
"balance": [all_urls[1]],
|
346
352
|
"ticker": [all_urls[2]],
|
347
|
-
"all": all_urls
|
353
|
+
"all": all_urls,
|
348
354
|
}
|
349
355
|
|
350
356
|
try:
|
351
357
|
urls = url_map[update_type]
|
352
358
|
except KeyError:
|
353
359
|
raise ValueError(f"Unknown update type: {update_type}")
|
354
|
-
|
360
|
+
|
355
361
|
# 直接传协程进去,initialize 会自己 await
|
356
362
|
await self.store.initialize(*(self.client.get(url) for url in urls))
|
357
363
|
|
358
|
-
|
359
364
|
async def sub_personal(self):
|
360
365
|
"""订阅个人频道"""
|
361
366
|
# 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")
|
367
|
+
res = await self.client.fetch("GET", f"{self.api_url}/ucenter/api/ws_token")
|
367
368
|
|
369
|
+
token = res.data["data"].get("wsToken")
|
368
370
|
|
369
|
-
self.client.ws_connect(
|
371
|
+
app = self.client.ws_connect(
|
370
372
|
f"{self.ws_url}?wsToken={token}&platform=web",
|
371
373
|
send_json={
|
372
374
|
"method": "SUBSCRIPTION",
|
373
375
|
"params": [
|
374
376
|
"spot@private.orders",
|
375
377
|
"spot@private.trigger.orders",
|
376
|
-
"spot@private.balances"
|
378
|
+
"spot@private.balances",
|
377
379
|
],
|
378
|
-
"id": 1
|
380
|
+
"id": 1,
|
379
381
|
},
|
380
|
-
hdlr_json=
|
382
|
+
hdlr_json=(
|
383
|
+
self.store.onmessage
|
384
|
+
if self.personal_msg_cb is None
|
385
|
+
else [self.store.onmessage, self.personal_msg_cb]
|
386
|
+
),
|
381
387
|
)
|
382
388
|
|
389
|
+
await app._event.wait()
|
390
|
+
|
383
391
|
async def sub_orderbook(self, symbols: str | list[str]):
|
384
392
|
"""订阅订单簿深度数据
|
385
|
-
|
393
|
+
|
386
394
|
Args:
|
387
395
|
symbols: 交易对符号,可以是单个字符串或字符串列表
|
388
396
|
"""
|
397
|
+
import logging
|
398
|
+
|
399
|
+
logger = logging.getLogger("OurbitSpot")
|
400
|
+
|
389
401
|
if isinstance(symbols, str):
|
390
402
|
symbols = [symbols]
|
391
403
|
|
392
|
-
# 并发获取每个交易对的初始深度数据
|
393
|
-
tasks = [
|
394
|
-
self.client.fetch('GET', f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}")
|
395
|
-
for symbol in symbols
|
396
|
-
]
|
397
|
-
|
398
|
-
# 等待所有请求完成
|
399
|
-
responses = await asyncio.gather(*tasks)
|
400
|
-
|
401
|
-
# 处理响应数据
|
402
|
-
for response in responses:
|
403
|
-
self.store.book._onresponse(response.data)
|
404
|
-
|
405
404
|
# 构建订阅参数
|
406
405
|
subscription_params = []
|
407
406
|
for symbol in symbols:
|
408
407
|
subscription_params.append(f"spot@public.increase.aggre.depth@{symbol}")
|
409
408
|
|
410
|
-
|
411
409
|
# 一次sub20个,超过需要分开订阅
|
412
410
|
for i in range(0, len(subscription_params), 20):
|
413
|
-
self.client.ws_connect(
|
414
|
-
|
411
|
+
wsapp = self.client.ws_connect(
|
412
|
+
"wss://www.ourbit.com/ws?platform=web",
|
415
413
|
send_json={
|
416
414
|
"method": "SUBSCRIPTION",
|
417
|
-
"params": subscription_params[i:i + 20],
|
418
|
-
"id": 2
|
415
|
+
"params": subscription_params[i : i + 20],
|
416
|
+
"id": 2,
|
419
417
|
},
|
420
|
-
hdlr_json=self.store.onmessage
|
418
|
+
hdlr_json=self.store.onmessage,
|
419
|
+
)
|
420
|
+
await wsapp._event.wait()
|
421
|
+
|
422
|
+
# await asyncio.sleep(1) # 等待ws连接稳定
|
423
|
+
|
424
|
+
# 并发获取每个交易对的初始深度数据
|
425
|
+
tasks = [
|
426
|
+
self.client.fetch(
|
427
|
+
"GET", f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}"
|
421
428
|
)
|
429
|
+
for symbol in symbols
|
430
|
+
]
|
431
|
+
|
432
|
+
# 等待所有请求完成
|
433
|
+
responses = await asyncio.gather(*tasks)
|
434
|
+
|
435
|
+
# 处理响应数据
|
436
|
+
for idx, response in enumerate(responses):
|
437
|
+
symbol = symbols[idx]
|
438
|
+
self.store.book._onresponse(response.data)
|
439
|
+
|
440
|
+
async def check_loss():
|
441
|
+
await asyncio.sleep(1)
|
442
|
+
while True:
|
443
|
+
loss = self.store.book.loss
|
444
|
+
for symbol, is_loss in loss.items():
|
445
|
+
if is_loss:
|
446
|
+
resp = await self.client.fetch(
|
447
|
+
"GET",
|
448
|
+
f"{self.api_url}/api/platform/spot/market/depth?symbol={symbol}",
|
449
|
+
)
|
450
|
+
self.store.book._onresponse(resp.data)
|
451
|
+
await asyncio.sleep(1)
|
452
|
+
|
453
|
+
asyncio.create_task(check_loss())
|
422
454
|
|
423
455
|
async def place_order(
|
424
456
|
self,
|
425
457
|
symbol: str,
|
426
458
|
side: Literal["buy", "sell"],
|
427
|
-
price: float,
|
459
|
+
price: float = None,
|
428
460
|
quantity: float = None,
|
429
461
|
order_type: Literal["market", "limit"] = "limit",
|
430
|
-
usdt_amount: float = None
|
462
|
+
usdt_amount: float = None,
|
431
463
|
):
|
432
464
|
"""现货下单
|
433
|
-
|
465
|
+
|
434
466
|
Args:
|
435
467
|
symbol: 交易对,如 "SOL_USDT"
|
436
468
|
side: 买卖方向 "buy" 或 "sell"
|
437
|
-
price:
|
469
|
+
price: 价格,市价单可为None
|
438
470
|
quantity: 数量
|
439
471
|
order_type: 订单类型 "market" 或 "limit"
|
440
472
|
usdt_amount: USDT金额,如果指定则根据价格计算数量
|
441
|
-
|
473
|
+
|
442
474
|
Returns:
|
443
475
|
订单响应数据
|
444
476
|
"""
|
477
|
+
# 参数检查
|
478
|
+
if order_type == "limit" and price is None:
|
479
|
+
raise ValueError("Limit orders require a price")
|
480
|
+
if quantity is None and usdt_amount is None:
|
481
|
+
raise ValueError("Either quantity or usdt_amount must be specified")
|
482
|
+
|
445
483
|
# 解析交易对
|
446
|
-
|
484
|
+
parts = symbol.split("_")
|
485
|
+
if len(parts) != 2:
|
486
|
+
raise ValueError(f"Invalid symbol format: {symbol}")
|
447
487
|
|
448
|
-
|
449
|
-
'name': currency
|
450
|
-
})
|
488
|
+
currency, market = parts
|
451
489
|
|
490
|
+
# 获取交易对详情
|
491
|
+
detail = self.store.detail.get({"name": currency})
|
452
492
|
if not detail:
|
453
493
|
raise ValueError(f"Unknown currency: {currency}")
|
454
494
|
|
455
|
-
price_scale = detail.get(
|
456
|
-
quantity_scale = detail.get(
|
495
|
+
price_scale = detail.get("price_scale")
|
496
|
+
quantity_scale = detail.get("quantity_scale")
|
457
497
|
|
458
|
-
|
459
|
-
# 如果指定了USDT金额,重新计算数量
|
460
|
-
if usdt_amount is not None:
|
461
|
-
if side == "buy":
|
462
|
-
quantity = usdt_amount / price
|
463
|
-
else:
|
464
|
-
# 卖出时usdt_amount表示要卖出的币种价值
|
465
|
-
quantity = usdt_amount / price
|
466
|
-
|
467
|
-
# 格式化价格和数量
|
468
|
-
if price_scale is not None:
|
469
|
-
price = round(price, price_scale)
|
470
|
-
|
471
|
-
if quantity_scale is not None:
|
472
|
-
quantity = round(quantity, quantity_scale)
|
473
|
-
|
474
498
|
# 构建请求数据
|
475
|
-
data = {
|
476
|
-
|
477
|
-
|
478
|
-
"tradeType": side.upper(),
|
479
|
-
"quantity": str(quantity),
|
480
|
-
}
|
481
|
-
|
499
|
+
data = {"currency": currency, "market": market, "tradeType": side.upper()}
|
500
|
+
|
501
|
+
# 处理市价单和限价单的不同参数
|
482
502
|
if order_type == "limit":
|
483
503
|
data["orderType"] = "LIMIT_ORDER"
|
484
|
-
data["price"] = str(
|
504
|
+
data["price"] = str(
|
505
|
+
round(price, price_scale) if price_scale is not None else price
|
506
|
+
)
|
507
|
+
|
508
|
+
# 计算并设置数量
|
509
|
+
if quantity is None and usdt_amount is not None and price:
|
510
|
+
quantity = usdt_amount / price
|
511
|
+
|
512
|
+
if quantity_scale is not None:
|
513
|
+
quantity = round(quantity, quantity_scale)
|
514
|
+
data["quantity"] = str(quantity)
|
515
|
+
|
485
516
|
elif order_type == "market":
|
486
517
|
data["orderType"] = "MARKET_ORDER"
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
518
|
+
|
519
|
+
# 市价单可以使用数量或金额,但不能同时使用
|
520
|
+
if usdt_amount is not None:
|
521
|
+
data["amount"] = str(usdt_amount)
|
522
|
+
else:
|
523
|
+
if quantity_scale is not None:
|
524
|
+
quantity = round(quantity, quantity_scale)
|
525
|
+
data["quantity"] = str(quantity)
|
526
|
+
|
527
|
+
if price:
|
528
|
+
data["price"] = str(price)
|
529
|
+
|
530
|
+
# 确定API端点
|
531
|
+
url = f'{self.api_url}/api/platform/spot/{"v4/order/place" if order_type == "market" else "order/place"}'
|
532
|
+
# print(f"Placing {symbol}: {data}")
|
533
|
+
# 发送请求
|
534
|
+
res = await self.client.fetch("POST", url, json=data)
|
535
|
+
|
495
536
|
# 处理响应
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
537
|
+
if res.data.get("msg") == "success":
|
538
|
+
return res.data["data"]
|
539
|
+
raise Exception(f"Failed to place order: {res.data}")
|
540
|
+
|
541
|
+
async def cancel_orders(self, order_ids: list[str]):
|
542
|
+
|
543
|
+
for order_id in order_ids:
|
544
|
+
url = f"{self.api_url}/api/platform/spot/order/cancel/v2?orderId={order_id}"
|
545
|
+
await self.client.fetch("DELETE", url)
|
546
|
+
|
547
|
+
async def cancel_order(self, order_id: str):
|
548
|
+
|
549
|
+
url = f"{self.api_url}/api/platform/spot/order/cancel/v2?orderId={order_id}"
|
550
|
+
res = await self.client.fetch("DELETE", url)
|
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
|
@@ -332,6 +335,9 @@ class Exchange(ExchangeBase):
|
|
332
335
|
self.account[symbol]['realised_profit'] += profit
|
333
336
|
self.account[symbol]['amount'] -= -direction * cover_amount
|
334
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
|
+
|
335
341
|
self.account[symbol]['hold_price'] = 0 if self.account[symbol]['amount'] == 0 else self.account[symbol]['hold_price']
|
336
342
|
|
337
343
|
if open_amount > 0:
|
@@ -343,6 +349,7 @@ class Exchange(ExchangeBase):
|
|
343
349
|
|
344
350
|
if kwargs:
|
345
351
|
self.opt.update(kwargs)
|
352
|
+
self.account[symbol].update(kwargs)
|
346
353
|
|
347
354
|
# 记录账户总资产到 history
|
348
355
|
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.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=
|
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
|
-
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
|
+
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=
|
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
|
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.
|
20
|
-
hyperquant-0.
|
21
|
-
hyperquant-0.
|
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,,
|
File without changes
|