From a1639472057c7d37b50dc10a2199b3311192b484 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Mon, 27 Apr 2026 17:46:54 +0200 Subject: [PATCH] feat(mcp-deribit): leverage_cap module with TDD --- .../src/mcp_deribit/leverage_cap.py | 56 +++++++++++++++++++ .../mcp-deribit/tests/test_leverage_cap.py | 51 +++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 services/mcp-deribit/src/mcp_deribit/leverage_cap.py create mode 100644 services/mcp-deribit/tests/test_leverage_cap.py diff --git a/services/mcp-deribit/src/mcp_deribit/leverage_cap.py b/services/mcp-deribit/src/mcp_deribit/leverage_cap.py new file mode 100644 index 0000000..d04dd51 --- /dev/null +++ b/services/mcp-deribit/src/mcp_deribit/leverage_cap.py @@ -0,0 +1,56 @@ +"""Leverage cap server-side per place_order. + +Cap letto dal secret JSON via campo `max_leverage`. Default 1 (cash) se assente. +""" +from __future__ import annotations + +from fastapi import HTTPException + + +def get_max_leverage(creds: dict) -> int: + """Legge max_leverage dal secret. Default 1 se mancante.""" + raw = creds.get("max_leverage", 1) + try: + value = int(raw) + except (TypeError, ValueError): + value = 1 + return max(1, value) + + +def enforce_leverage( + requested: int | float | None, + *, + creds: dict, + exchange: str, +) -> int: + """Verifica e applica leverage cap. Ritorna leverage applicabile. + + Solleva HTTPException(403, LEVERAGE_CAP_EXCEEDED) se requested > cap. + Se requested is None, applica il cap come default. + """ + cap = get_max_leverage(creds) + if requested is None: + return cap + lev = int(requested) + if lev < 1: + raise HTTPException( + status_code=403, + detail={ + "error": "LEVERAGE_CAP_EXCEEDED", + "exchange": exchange, + "requested": lev, + "max": cap, + "reason": "leverage must be >= 1", + }, + ) + if lev > cap: + raise HTTPException( + status_code=403, + detail={ + "error": "LEVERAGE_CAP_EXCEEDED", + "exchange": exchange, + "requested": lev, + "max": cap, + }, + ) + return lev diff --git a/services/mcp-deribit/tests/test_leverage_cap.py b/services/mcp-deribit/tests/test_leverage_cap.py new file mode 100644 index 0000000..0e5f73c --- /dev/null +++ b/services/mcp-deribit/tests/test_leverage_cap.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import pytest +from fastapi import HTTPException + +from mcp_deribit.leverage_cap import enforce_leverage, get_max_leverage + + +def test_get_max_leverage_returns_creds_value(): + creds = {"max_leverage": 5} + assert get_max_leverage(creds) == 5 + + +def test_get_max_leverage_default_when_missing(): + """Default 1 (cash) se il secret non ha max_leverage.""" + assert get_max_leverage({}) == 1 + + +def test_enforce_leverage_pass_under_cap(): + creds = {"max_leverage": 3} + enforce_leverage(2, creds=creds, exchange="deribit") # no raise + + +def test_enforce_leverage_pass_at_cap(): + creds = {"max_leverage": 3} + enforce_leverage(3, creds=creds, exchange="deribit") # no raise + + +def test_enforce_leverage_reject_over_cap(): + creds = {"max_leverage": 3} + with pytest.raises(HTTPException) as exc: + enforce_leverage(10, creds=creds, exchange="deribit") + assert exc.value.status_code == 403 + assert exc.value.detail["error"] == "LEVERAGE_CAP_EXCEEDED" + assert exc.value.detail["exchange"] == "deribit" + assert exc.value.detail["requested"] == 10 + assert exc.value.detail["max"] == 3 + + +def test_enforce_leverage_reject_when_below_one(): + creds = {"max_leverage": 3} + with pytest.raises(HTTPException) as exc: + enforce_leverage(0, creds=creds, exchange="deribit") + assert exc.value.status_code == 403 + + +def test_enforce_leverage_default_when_none(): + """Se requested รจ None, applica il cap come default.""" + creds = {"max_leverage": 3} + result = enforce_leverage(None, creds=creds, exchange="deribit") + assert result == 3