Explorar el Código

fix: validate leverage and instrument identity

lxy hace 1 mes
padre
commit
4c45e41127
Se han modificado 2 ficheros con 81 adiciones y 4 borrados
  1. 8 0
      okx_codex_trader/okx_client.py
  2. 73 4
      tests/test_okx_client.py

+ 8 - 0
okx_codex_trader/okx_client.py

@@ -162,6 +162,8 @@ class OkxClient:
         )
         instrument = self._first_item(data)
         try:
+            if instrument.get("instId") != symbol or instrument.get("instType") != "SWAP":
+                raise self._invalid_payload()
             return InstrumentMeta(
                 ct_val=_parse_finite_float(instrument["ctVal"]),
                 lot_sz=_parse_finite_float(instrument["lotSz"]),
@@ -185,6 +187,12 @@ class OkxClient:
             raise ValueError("hedge mode is required")
 
     def set_leverage(self, symbol: str, leverage: int, pos_side: str) -> None:
+        if not symbol.endswith("-SWAP"):
+            raise ValueError("swap instrument is required")
+        if leverage < 1 or leverage > 3:
+            raise ValueError("leverage is invalid")
+        if pos_side not in {"long", "short"}:
+            raise ValueError("pos_side is invalid")
         self._request(
             "POST",
             "/api/v5/account/set-leverage",

+ 73 - 4
tests/test_okx_client.py

@@ -109,14 +109,14 @@ def descending_candles_response() -> DummyResponse:
     )
 
 
-def instrument_response() -> DummyResponse:
+def instrument_response(symbol: str = "BTC-USDT-SWAP") -> DummyResponse:
     return DummyResponse(
         {
             "code": "0",
             "msg": "",
             "data": [
                 {
-                    "instId": "BTC-USDT-SWAP",
+                    "instId": symbol,
                     "instType": "SWAP",
                     "ctVal": "0.001",
                     "lotSz": "1",
@@ -279,6 +279,42 @@ def instrument_with_non_finite_numeric_response() -> DummyResponse:
     )
 
 
+def instrument_with_wrong_symbol_response() -> DummyResponse:
+    return DummyResponse(
+        {
+            "code": "0",
+            "msg": "",
+            "data": [
+                {
+                    "instId": "ETH-USDT-SWAP",
+                    "instType": "SWAP",
+                    "ctVal": "0.001",
+                    "lotSz": "1",
+                    "minSz": "1",
+                }
+            ],
+        }
+    )
+
+
+def instrument_with_wrong_type_response() -> DummyResponse:
+    return DummyResponse(
+        {
+            "code": "0",
+            "msg": "",
+            "data": [
+                {
+                    "instId": "BTC-USDT-SWAP",
+                    "instType": "FUTURES",
+                    "ctVal": "0.001",
+                    "lotSz": "1",
+                    "minSz": "1",
+                }
+            ],
+        }
+    )
+
+
 def ticker_with_non_finite_numeric_response() -> DummyResponse:
     return DummyResponse({"code": "0", "msg": "", "data": [{"instId": "BTC-USDT-SWAP", "last": "Infinity"}]})
 
@@ -364,7 +400,7 @@ def test_signed_demo_request_attaches_headers():
 def test_signed_post_request_uses_actual_serialized_body_bytes():
     session = DummySession(
         [
-            instrument_response(),
+            instrument_response(symbol="ETH-USDT-SWAP"),
             account_config_response(pos_mode="long_short_mode"),
             leverage_response(),
             place_order_response(),
@@ -503,7 +539,7 @@ def test_place_demo_order_validates_size_before_setting_leverage():
 def test_limit_short_order_uses_sell_and_short_pos_side():
     session = DummySession(
         [
-            instrument_response(),
+            instrument_response(symbol="ETH-USDT-SWAP"),
             account_config_response(pos_mode="long_short_mode"),
             leverage_response(),
             place_order_response(),
@@ -642,6 +678,22 @@ def test_get_last_price_rejects_non_finite_numeric_field():
         client.get_last_price(symbol="BTC-USDT-SWAP")
 
 
+def test_get_instrument_meta_rejects_mismatched_symbol():
+    session = DummySession([instrument_with_wrong_symbol_response()])
+    client = OkxClient(config=sample_config(), session=session)
+
+    with pytest.raises(ValueError, match="okx response payload is invalid"):
+        client.get_instrument_meta(symbol="BTC-USDT-SWAP")
+
+
+def test_get_instrument_meta_rejects_non_swap_type():
+    session = DummySession([instrument_with_wrong_type_response()])
+    client = OkxClient(config=sample_config(), session=session)
+
+    with pytest.raises(ValueError, match="okx response payload is invalid"):
+        client.get_instrument_meta(symbol="BTC-USDT-SWAP")
+
+
 def test_place_demo_order_raises_when_order_id_is_missing():
     session = DummySession(
         [
@@ -676,6 +728,23 @@ def test_place_demo_order_rejects_invalid_leverage_before_okx():
     assert session.request_paths == []
 
 
+@pytest.mark.parametrize(
+    ("symbol", "leverage", "pos_side", "expected_message"),
+    [
+        ("BTC-USDT", 2, "long", "swap instrument is required"),
+        ("BTC-USDT-SWAP", 4, "long", "leverage is invalid"),
+        ("BTC-USDT-SWAP", 2, "net", "pos_side is invalid"),
+    ],
+)
+def test_set_leverage_validates_public_boundary_inputs(symbol, leverage, pos_side, expected_message):
+    session = DummySession([])
+    client = OkxClient(config=sample_config(), session=session)
+
+    with pytest.raises(ValueError, match=expected_message):
+        client.set_leverage(symbol=symbol, leverage=leverage, pos_side=pos_side)
+    assert session.request_paths == []
+
+
 def test_place_demo_order_rejects_unknown_action_before_okx():
     session = DummySession([])
     signal = TradeSignal(