vnpy_okx 2025.12.28__tar.gz → 2026.2.1__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.28 → vnpy_okx-2026.2.1}/PKG-INFO +2 -2
- {vnpy_okx-2025.12.28 → vnpy_okx-2026.2.1}/README.md +1 -1
- {vnpy_okx-2025.12.28 → vnpy_okx-2026.2.1}/vnpy_okx/__init__.py +1 -1
- {vnpy_okx-2025.12.28 → vnpy_okx-2026.2.1}/vnpy_okx/okx_gateway.py +42 -14
- {vnpy_okx-2025.12.28 → vnpy_okx-2026.2.1}/.gitignore +0 -0
- {vnpy_okx-2025.12.28 → vnpy_okx-2026.2.1}/LICENSE +0 -0
- {vnpy_okx-2025.12.28 → vnpy_okx-2026.2.1}/pyproject.toml +0 -0
- {vnpy_okx-2025.12.28 → vnpy_okx-2026.2.1}/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.2.1
|
|
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-
|
|
36
|
+
<img src ="https://img.shields.io/badge/version-uv-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-
|
|
8
|
+
<img src ="https://img.shields.io/badge/version-uv-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:
|
|
@@ -512,6 +513,13 @@ class RestApi(RestClient):
|
|
|
512
513
|
Returns:
|
|
513
514
|
Request: Modified request with authentication parameters
|
|
514
515
|
"""
|
|
516
|
+
# Add simulated trading header for demo server (applies to all requests)
|
|
517
|
+
if self.simulated:
|
|
518
|
+
if not request.headers:
|
|
519
|
+
request.headers = {"x-simulated-trading": "1"}
|
|
520
|
+
else:
|
|
521
|
+
request.headers["x-simulated-trading"] = "1"
|
|
522
|
+
|
|
515
523
|
# Public API does not need to sign
|
|
516
524
|
if "public" in request.path:
|
|
517
525
|
return request
|
|
@@ -739,7 +747,11 @@ class RestApi(RestClient):
|
|
|
739
747
|
base, quote, _ = name.split("-")
|
|
740
748
|
symbol = base + quote + "_SWAP_OKX"
|
|
741
749
|
case Product.FUTURES:
|
|
742
|
-
|
|
750
|
+
symbol_parts: list[str] = name.split("-")
|
|
751
|
+
if len(symbol_parts) < 3:
|
|
752
|
+
continue
|
|
753
|
+
|
|
754
|
+
base, quote, expiry = symbol_parts
|
|
743
755
|
symbol = base + quote + "_" + expiry + "_OKX"
|
|
744
756
|
|
|
745
757
|
if d["tickSz"]:
|
|
@@ -764,6 +776,7 @@ class RestApi(RestClient):
|
|
|
764
776
|
net_position=net_position,
|
|
765
777
|
gateway_name=self.gateway_name,
|
|
766
778
|
)
|
|
779
|
+
contract.extra = d
|
|
767
780
|
|
|
768
781
|
self.gateway.on_contract(contract)
|
|
769
782
|
|
|
@@ -795,7 +808,7 @@ class RestApi(RestClient):
|
|
|
795
808
|
leg_symbols: list[str] = []
|
|
796
809
|
for leg in d["legs"]:
|
|
797
810
|
leg_name: str = leg["instId"]
|
|
798
|
-
leg_contract: ContractData = self.gateway.get_contract_by_name(leg_name)
|
|
811
|
+
leg_contract: ContractData = cast(ContractData, self.gateway.get_contract_by_name(leg_name))
|
|
799
812
|
leg_symbols.append(leg_contract.symbol)
|
|
800
813
|
|
|
801
814
|
contract: ContractData = ContractData(
|
|
@@ -857,6 +870,8 @@ class RestApi(RestClient):
|
|
|
857
870
|
request: Original request object
|
|
858
871
|
"""
|
|
859
872
|
detail: str = self.exception_detail(exc, value, tb, request)
|
|
873
|
+
# Escape curly braces to prevent loguru from interpreting them as format placeholders
|
|
874
|
+
detail = detail.replace("{", "{{").replace("}", "}}")
|
|
860
875
|
|
|
861
876
|
msg: str = f"Exception catched by REST API: {detail}"
|
|
862
877
|
self.gateway.write_log(msg)
|
|
@@ -877,6 +892,11 @@ class RestApi(RestClient):
|
|
|
877
892
|
self.gateway.write_log(f"Query kline history failed, symbol not found: {req.symbol}")
|
|
878
893
|
return []
|
|
879
894
|
|
|
895
|
+
# Validate interval is not None
|
|
896
|
+
if not req.interval:
|
|
897
|
+
self.gateway.write_log(f"Query kline history failed, interval not found: {req.symbol}")
|
|
898
|
+
return []
|
|
899
|
+
|
|
880
900
|
# Initialize buffer for storing bars
|
|
881
901
|
buf: dict[datetime, BarData] = {}
|
|
882
902
|
limit: str = "100"
|
|
@@ -913,7 +933,7 @@ class RestApi(RestClient):
|
|
|
913
933
|
break
|
|
914
934
|
else:
|
|
915
935
|
data: dict = resp.json()
|
|
916
|
-
bar_data: list = data.get("data", None)
|
|
936
|
+
bar_data: list | None = data.get("data", None)
|
|
917
937
|
|
|
918
938
|
if not bar_data:
|
|
919
939
|
msg: str = data.get("msg", "No data returned.")
|
|
@@ -1395,7 +1415,7 @@ class PrivateApi(WebsocketApi):
|
|
|
1395
1415
|
# Process trade data for filled or partially filled orders
|
|
1396
1416
|
# Round trade volume number to meet minimum volume precision
|
|
1397
1417
|
trade_volume: float = float(d["fillSz"])
|
|
1398
|
-
contract: ContractData = self.gateway.get_contract_by_symbol(order.symbol)
|
|
1418
|
+
contract: ContractData | None = self.gateway.get_contract_by_symbol(order.symbol)
|
|
1399
1419
|
if contract:
|
|
1400
1420
|
trade_volume = round_to(trade_volume, contract.min_volume)
|
|
1401
1421
|
|
|
@@ -1451,7 +1471,7 @@ class PrivateApi(WebsocketApi):
|
|
|
1451
1471
|
data: list = packet["data"]
|
|
1452
1472
|
for d in data:
|
|
1453
1473
|
name: str = d["instId"]
|
|
1454
|
-
contract: ContractData = self.gateway.get_contract_by_name(name)
|
|
1474
|
+
contract: ContractData = cast(ContractData, self.gateway.get_contract_by_name(name))
|
|
1455
1475
|
|
|
1456
1476
|
pos: float = float(d["pos"])
|
|
1457
1477
|
price: float = get_float_value(d, "avgPx")
|
|
@@ -1483,9 +1503,11 @@ class PrivateApi(WebsocketApi):
|
|
|
1483
1503
|
# Wrong parameters
|
|
1484
1504
|
if packet["code"] != "0":
|
|
1485
1505
|
if not data:
|
|
1486
|
-
order: OrderData = self.reqid_order_map
|
|
1487
|
-
order
|
|
1488
|
-
|
|
1506
|
+
order: OrderData | None = self.reqid_order_map.get(packet["id"], None)
|
|
1507
|
+
if order:
|
|
1508
|
+
order.status = Status.REJECTED
|
|
1509
|
+
self.gateway.on_order(order)
|
|
1510
|
+
|
|
1489
1511
|
return
|
|
1490
1512
|
|
|
1491
1513
|
# Failed to process
|
|
@@ -1616,7 +1638,7 @@ class PrivateApi(WebsocketApi):
|
|
|
1616
1638
|
|
|
1617
1639
|
# Prepare order parameters for OKX API
|
|
1618
1640
|
arg: dict = {
|
|
1619
|
-
"
|
|
1641
|
+
"instIdCode": contract.extra["instIdCode"], # type: ignore
|
|
1620
1642
|
"clOrdId": orderid,
|
|
1621
1643
|
"side": DIRECTION_VT2OKX[req.direction],
|
|
1622
1644
|
"ordType": ORDERTYPE_VT2OKX[req.type],
|
|
@@ -1625,9 +1647,15 @@ class PrivateApi(WebsocketApi):
|
|
|
1625
1647
|
"tdMode": "cross" # Only support cross margin mode
|
|
1626
1648
|
}
|
|
1627
1649
|
|
|
1650
|
+
# Add extra field for portfolio margin
|
|
1628
1651
|
if self.margin_currency:
|
|
1629
1652
|
arg["ccy"] = self.margin_currency
|
|
1630
1653
|
|
|
1654
|
+
# Add extra field for spot
|
|
1655
|
+
if "SPOT" in req.symbol:
|
|
1656
|
+
quote_ccy_list: list[str] = contract.extra["tradeQuoteCcyList"] # type: ignore
|
|
1657
|
+
arg["tradeQuoteCcy"] = quote_ccy_list[0]
|
|
1658
|
+
|
|
1631
1659
|
# Create websocket request with unique request ID
|
|
1632
1660
|
self.reqid += 1
|
|
1633
1661
|
packet: dict = {
|
|
@@ -1661,7 +1689,7 @@ class PrivateApi(WebsocketApi):
|
|
|
1661
1689
|
return
|
|
1662
1690
|
|
|
1663
1691
|
# Initialize cancel parameters
|
|
1664
|
-
arg: dict = {"
|
|
1692
|
+
arg: dict = {"instIdCode": contract.extra["instIdCode"]} # type: ignore
|
|
1665
1693
|
|
|
1666
1694
|
# Determine the type of order ID to use for cancellation
|
|
1667
1695
|
# OKX supports both client order ID and exchange order ID for cancellation
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|