vnpy_okx 2025.12.22__tar.gz → 2026.1.11__tar.gz
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.
- {vnpy_okx-2025.12.22 → vnpy_okx-2026.1.11}/PKG-INFO +2 -2
- {vnpy_okx-2025.12.22 → vnpy_okx-2026.1.11}/README.md +1 -1
- {vnpy_okx-2025.12.22 → vnpy_okx-2026.1.11}/vnpy_okx/__init__.py +1 -1
- {vnpy_okx-2025.12.22 → vnpy_okx-2026.1.11}/vnpy_okx/okx_gateway.py +35 -18
- {vnpy_okx-2025.12.22 → vnpy_okx-2026.1.11}/.gitignore +0 -0
- {vnpy_okx-2025.12.22 → vnpy_okx-2026.1.11}/LICENSE +0 -0
- {vnpy_okx-2025.12.22 → vnpy_okx-2026.1.11}/pyproject.toml +0 -0
- {vnpy_okx-2025.12.22 → vnpy_okx-2026.1.11}/vnpy_okx/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vnpy_okx
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2026.1.11
|
|
4
4
|
Summary: OKX trading gateway for VeighNa.
|
|
5
5
|
Project-URL: Homepage, https://www.github.com/veighna-global
|
|
6
6
|
Project-URL: Source, https://www.github.com/veighna-global
|
|
@@ -33,7 +33,7 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
35
|
<p align="center">
|
|
36
|
-
<img src ="https://img.shields.io/badge/version-2025.12.
|
|
36
|
+
<img src ="https://img.shields.io/badge/version-2025.12.28-blueviolet.svg"/>
|
|
37
37
|
<img src ="https://img.shields.io/badge/platform-windows|linux|macos-yellow.svg"/>
|
|
38
38
|
<img src ="https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg" />
|
|
39
39
|
<img src ="https://img.shields.io/github/license/veighna-global/vnpy_okx.svg?color=orange"/>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<img src ="https://img.shields.io/badge/version-2025.12.
|
|
8
|
+
<img src ="https://img.shields.io/badge/version-2025.12.28-blueviolet.svg"/>
|
|
9
9
|
<img src ="https://img.shields.io/badge/platform-windows|linux|macos-yellow.svg"/>
|
|
10
10
|
<img src ="https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg" />
|
|
11
11
|
<img src ="https://img.shields.io/github/license/veighna-global/vnpy_okx.svg?color=orange"/>
|
|
@@ -10,6 +10,7 @@ from types import TracebackType
|
|
|
10
10
|
from collections.abc import Callable
|
|
11
11
|
from time import sleep
|
|
12
12
|
from decimal import Decimal
|
|
13
|
+
from typing import cast
|
|
13
14
|
|
|
14
15
|
from vnpy.event import EventEngine, Event, EVENT_TIMER
|
|
15
16
|
from vnpy.trader.constant import (
|
|
@@ -116,7 +117,7 @@ class OkxGateway(BaseGateway):
|
|
|
116
117
|
"Margin Currency": ""
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
exchanges: Exchange = [Exchange.GLOBAL]
|
|
120
|
+
exchanges: list[Exchange] = [Exchange.GLOBAL]
|
|
120
121
|
|
|
121
122
|
def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
|
|
122
123
|
"""
|
|
@@ -339,7 +340,7 @@ class OkxGateway(BaseGateway):
|
|
|
339
340
|
self.orders[order.orderid] = order
|
|
340
341
|
super().on_order(order)
|
|
341
342
|
|
|
342
|
-
def get_order(self, orderid: str) -> OrderData:
|
|
343
|
+
def get_order(self, orderid: str) -> OrderData | None:
|
|
343
344
|
"""
|
|
344
345
|
Get previously saved order by order id.
|
|
345
346
|
|
|
@@ -401,7 +402,7 @@ class OkxGateway(BaseGateway):
|
|
|
401
402
|
Returns:
|
|
402
403
|
OrderData: VeighNa order object
|
|
403
404
|
"""
|
|
404
|
-
contract: ContractData = self.get_contract_by_name(data["instId"])
|
|
405
|
+
contract: ContractData = cast(ContractData, self.get_contract_by_name(data["instId"]))
|
|
405
406
|
|
|
406
407
|
order_id: str = data["clOrdId"]
|
|
407
408
|
if order_id:
|
|
@@ -439,7 +440,7 @@ class OkxGateway(BaseGateway):
|
|
|
439
440
|
Returns:
|
|
440
441
|
OrderData: VeighNa order object
|
|
441
442
|
"""
|
|
442
|
-
contract: ContractData = self.get_contract_by_name(data["sprdId"])
|
|
443
|
+
contract: ContractData = cast(ContractData, self.get_contract_by_name(data["sprdId"]))
|
|
443
444
|
|
|
444
445
|
order_id: str = data["clOrdId"]
|
|
445
446
|
if order_id:
|
|
@@ -764,6 +765,7 @@ class RestApi(RestClient):
|
|
|
764
765
|
net_position=net_position,
|
|
765
766
|
gateway_name=self.gateway_name,
|
|
766
767
|
)
|
|
768
|
+
contract.extra = d
|
|
767
769
|
|
|
768
770
|
self.gateway.on_contract(contract)
|
|
769
771
|
|
|
@@ -795,7 +797,7 @@ class RestApi(RestClient):
|
|
|
795
797
|
leg_symbols: list[str] = []
|
|
796
798
|
for leg in d["legs"]:
|
|
797
799
|
leg_name: str = leg["instId"]
|
|
798
|
-
leg_contract: ContractData = self.gateway.get_contract_by_name(leg_name)
|
|
800
|
+
leg_contract: ContractData = cast(ContractData, self.gateway.get_contract_by_name(leg_name))
|
|
799
801
|
leg_symbols.append(leg_contract.symbol)
|
|
800
802
|
|
|
801
803
|
contract: ContractData = ContractData(
|
|
@@ -877,6 +879,11 @@ class RestApi(RestClient):
|
|
|
877
879
|
self.gateway.write_log(f"Query kline history failed, symbol not found: {req.symbol}")
|
|
878
880
|
return []
|
|
879
881
|
|
|
882
|
+
# Validate interval is not None
|
|
883
|
+
if not req.interval:
|
|
884
|
+
self.gateway.write_log(f"Query kline history failed, interval not found: {req.symbol}")
|
|
885
|
+
return []
|
|
886
|
+
|
|
880
887
|
# Initialize buffer for storing bars
|
|
881
888
|
buf: dict[datetime, BarData] = {}
|
|
882
889
|
limit: str = "100"
|
|
@@ -913,7 +920,7 @@ class RestApi(RestClient):
|
|
|
913
920
|
break
|
|
914
921
|
else:
|
|
915
922
|
data: dict = resp.json()
|
|
916
|
-
bar_data: list = data.get("data", None)
|
|
923
|
+
bar_data: list | None = data.get("data", None)
|
|
917
924
|
|
|
918
925
|
if not bar_data:
|
|
919
926
|
msg: str = data.get("msg", "No data returned.")
|
|
@@ -1395,7 +1402,7 @@ class PrivateApi(WebsocketApi):
|
|
|
1395
1402
|
# Process trade data for filled or partially filled orders
|
|
1396
1403
|
# Round trade volume number to meet minimum volume precision
|
|
1397
1404
|
trade_volume: float = float(d["fillSz"])
|
|
1398
|
-
contract: ContractData = self.gateway.get_contract_by_symbol(order.symbol)
|
|
1405
|
+
contract: ContractData | None = self.gateway.get_contract_by_symbol(order.symbol)
|
|
1399
1406
|
if contract:
|
|
1400
1407
|
trade_volume = round_to(trade_volume, contract.min_volume)
|
|
1401
1408
|
|
|
@@ -1451,7 +1458,7 @@ class PrivateApi(WebsocketApi):
|
|
|
1451
1458
|
data: list = packet["data"]
|
|
1452
1459
|
for d in data:
|
|
1453
1460
|
name: str = d["instId"]
|
|
1454
|
-
contract: ContractData = self.gateway.get_contract_by_name(name)
|
|
1461
|
+
contract: ContractData = cast(ContractData, self.gateway.get_contract_by_name(name))
|
|
1455
1462
|
|
|
1456
1463
|
pos: float = float(d["pos"])
|
|
1457
1464
|
price: float = get_float_value(d, "avgPx")
|
|
@@ -1483,9 +1490,11 @@ class PrivateApi(WebsocketApi):
|
|
|
1483
1490
|
# Wrong parameters
|
|
1484
1491
|
if packet["code"] != "0":
|
|
1485
1492
|
if not data:
|
|
1486
|
-
order: OrderData = self.reqid_order_map
|
|
1487
|
-
order
|
|
1488
|
-
|
|
1493
|
+
order: OrderData | None = self.reqid_order_map.get(packet["id"], None)
|
|
1494
|
+
if order:
|
|
1495
|
+
order.status = Status.REJECTED
|
|
1496
|
+
self.gateway.on_order(order)
|
|
1497
|
+
|
|
1489
1498
|
return
|
|
1490
1499
|
|
|
1491
1500
|
# Failed to process
|
|
@@ -1516,10 +1525,11 @@ class PrivateApi(WebsocketApi):
|
|
|
1516
1525
|
"""
|
|
1517
1526
|
# Wrong parameters
|
|
1518
1527
|
if packet["code"] != "0":
|
|
1519
|
-
code: str = packet["code"]
|
|
1520
1528
|
msg: str = packet["msg"]
|
|
1521
|
-
|
|
1522
|
-
|
|
1529
|
+
if msg:
|
|
1530
|
+
code: str = packet["code"]
|
|
1531
|
+
self.gateway.write_log(f"Cancel order failed, status code: {code}, message: {msg}")
|
|
1532
|
+
return
|
|
1523
1533
|
|
|
1524
1534
|
# Failed to process
|
|
1525
1535
|
data: list = packet["data"]
|
|
@@ -1624,9 +1634,15 @@ class PrivateApi(WebsocketApi):
|
|
|
1624
1634
|
"tdMode": "cross" # Only support cross margin mode
|
|
1625
1635
|
}
|
|
1626
1636
|
|
|
1637
|
+
# Add extra field for portfolio margin
|
|
1627
1638
|
if self.margin_currency:
|
|
1628
1639
|
arg["ccy"] = self.margin_currency
|
|
1629
1640
|
|
|
1641
|
+
# Add extra field for spot
|
|
1642
|
+
if "SPOT" in req.symbol:
|
|
1643
|
+
quote_ccy_list: list[str] = contract.extra["tradeQuoteCcyList"] # type: ignore
|
|
1644
|
+
arg["tradeQuoteCcy"] = quote_ccy_list[0]
|
|
1645
|
+
|
|
1630
1646
|
# Create websocket request with unique request ID
|
|
1631
1647
|
self.reqid += 1
|
|
1632
1648
|
packet: dict = {
|
|
@@ -1660,7 +1676,7 @@ class PrivateApi(WebsocketApi):
|
|
|
1660
1676
|
return
|
|
1661
1677
|
|
|
1662
1678
|
# Initialize cancel parameters
|
|
1663
|
-
arg: dict = {}
|
|
1679
|
+
arg: dict = {"instId": contract.name}
|
|
1664
1680
|
|
|
1665
1681
|
# Determine the type of order ID to use for cancellation
|
|
1666
1682
|
# OKX supports both client order ID and exchange order ID for cancellation
|
|
@@ -1954,10 +1970,11 @@ class BusinessApi(WebsocketApi):
|
|
|
1954
1970
|
"""
|
|
1955
1971
|
# Wrong parameters
|
|
1956
1972
|
if packet["code"] != "0":
|
|
1957
|
-
code: str = packet["code"]
|
|
1958
1973
|
msg: str = packet["msg"]
|
|
1959
|
-
|
|
1960
|
-
|
|
1974
|
+
if msg:
|
|
1975
|
+
code: str = packet["code"]
|
|
1976
|
+
self.gateway.write_log(f"Cancel order failed, status code: {code}, message: {msg}")
|
|
1977
|
+
return
|
|
1961
1978
|
|
|
1962
1979
|
# Failed to process
|
|
1963
1980
|
data: list = packet["data"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|