vnpy_okx 2025.12.28__py3-none-any.whl → 2026.2.1__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.
vnpy_okx/__init__.py CHANGED
@@ -23,7 +23,7 @@
23
23
  from .okx_gateway import OkxGateway
24
24
 
25
25
 
26
- __version__ = "2025.12.28"
26
+ __version__ = "2026.02.01"
27
27
 
28
28
 
29
29
  __all__ = ["OkxGateway"]
vnpy_okx/okx_gateway.py CHANGED
@@ -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
- base, quote, expiry = name.split("-")
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[packet["id"]]
1487
- order.status = Status.REJECTED
1488
- self.gateway.on_order(order)
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
- "instId": contract.name,
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 = {"instId": contract.name}
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vnpy_okx
3
- Version: 2025.12.28
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-2025.12.28-blueviolet.svg"/>
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"/>
@@ -0,0 +1,7 @@
1
+ vnpy_okx/__init__.py,sha256=0d6DqmtG5dW0pHCM4ZFYu5SnQCgfN0oX_dtGSLxyrzg,1248
2
+ vnpy_okx/okx_gateway.py,sha256=9-DyVRsUCSL5iLut6NYqve_W-frI9bOsiuGoHzW4aW8,73708
3
+ vnpy_okx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ vnpy_okx-2026.2.1.dist-info/METADATA,sha256=1icb0W6UmQQRUf191UsPMenh5c5PdcNW0PHcLwgAZY8,2831
5
+ vnpy_okx-2026.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
6
+ vnpy_okx-2026.2.1.dist-info/licenses/LICENSE,sha256=vKkW-EmD7w-5lDDjg15L8feZ1nkQeNpEHfyO2v9tprs,1099
7
+ vnpy_okx-2026.2.1.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- vnpy_okx/__init__.py,sha256=q5WNRYb_KdEzXSfonARFuNujBAS4jL-z77Lu2pm8beY,1248
2
- vnpy_okx/okx_gateway.py,sha256=eqKESbdBr5Av4TJ7HhQgp6jJjmZjgtgP6ivICOIeozI,72352
3
- vnpy_okx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- vnpy_okx-2025.12.28.dist-info/METADATA,sha256=gGPXJ52UM7FZ8oW0EkNWu3NU4k4rJZsS8iSOHay2sJk,2841
5
- vnpy_okx-2025.12.28.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
6
- vnpy_okx-2025.12.28.dist-info/licenses/LICENSE,sha256=vKkW-EmD7w-5lDDjg15L8feZ1nkQeNpEHfyO2v9tprs,1099
7
- vnpy_okx-2025.12.28.dist-info/RECORD,,