From 7290900dc736181c8982d28c78765b41de2f8983 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Sat, 9 May 2026 19:27:34 +0200 Subject: [PATCH] feat(cerbero): tools wrapper for Phase 1 indicator subset Espone sma/rsi/atr/macd/realized_vol/funding_rate sopra CerberoClient delegando a call_tool(exchange, tool, args). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/multi_swarm/cerbero/tools.py | 60 ++++++++++++++++++++++++++++++++ tests/unit/test_cerbero_tools.py | 32 +++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/multi_swarm/cerbero/tools.py create mode 100644 tests/unit/test_cerbero_tools.py diff --git a/src/multi_swarm/cerbero/tools.py b/src/multi_swarm/cerbero/tools.py new file mode 100644 index 0000000..e2550cc --- /dev/null +++ b/src/multi_swarm/cerbero/tools.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +from typing import Any + +from .client import CerberoClient + + +class CerberoTools: + """Sottoinsieme di tool MCP esposti agli agenti in Phase 1.""" + + def __init__(self, client: CerberoClient): + self._client = client + + def sma(self, exchange: str, symbol: str, timeframe: str, length: int) -> Any: + return self._client.call_tool( + exchange, "sma", {"symbol": symbol, "timeframe": timeframe, "length": length} + ) + + def rsi(self, exchange: str, symbol: str, timeframe: str, length: int = 14) -> Any: + return self._client.call_tool( + exchange, "rsi", {"symbol": symbol, "timeframe": timeframe, "length": length} + ) + + def atr(self, exchange: str, symbol: str, timeframe: str, length: int = 14) -> Any: + return self._client.call_tool( + exchange, "atr", {"symbol": symbol, "timeframe": timeframe, "length": length} + ) + + def macd( + self, + exchange: str, + symbol: str, + timeframe: str, + fast: int = 12, + slow: int = 26, + signal: int = 9, + ) -> Any: + return self._client.call_tool( + exchange, + "macd", + { + "symbol": symbol, + "timeframe": timeframe, + "fast": fast, + "slow": slow, + "signal": signal, + }, + ) + + def realized_vol( + self, exchange: str, symbol: str, timeframe: str, window: int = 24 + ) -> Any: + return self._client.call_tool( + exchange, + "realized_vol", + {"symbol": symbol, "timeframe": timeframe, "window": window}, + ) + + def funding_rate(self, exchange: str, symbol: str) -> Any: + return self._client.call_tool(exchange, "funding_rate", {"symbol": symbol}) diff --git a/tests/unit/test_cerbero_tools.py b/tests/unit/test_cerbero_tools.py new file mode 100644 index 0000000..06cfdb5 --- /dev/null +++ b/tests/unit/test_cerbero_tools.py @@ -0,0 +1,32 @@ +import pytest + +from multi_swarm.cerbero.tools import CerberoTools + + +def test_tools_dispatch_sma(mocker): + fake_client = mocker.MagicMock() + fake_client.call_tool.return_value = {"value": 100.0} + t = CerberoTools(fake_client) + out = t.sma(exchange="bybit", symbol="BTCUSDT", timeframe="1h", length=20) + fake_client.call_tool.assert_called_once_with( + "bybit", "sma", {"symbol": "BTCUSDT", "timeframe": "1h", "length": 20} + ) + assert out == {"value": 100.0} + + +def test_tools_dispatch_rsi(mocker): + fake_client = mocker.MagicMock() + fake_client.call_tool.return_value = {"value": 55.0} + t = CerberoTools(fake_client) + out = t.rsi(exchange="bybit", symbol="BTCUSDT", timeframe="1h", length=14) + fake_client.call_tool.assert_called_once_with( + "bybit", "rsi", {"symbol": "BTCUSDT", "timeframe": "1h", "length": 14} + ) + assert out == {"value": 55.0} + + +def test_tools_unknown_raises(mocker): + fake_client = mocker.MagicMock() + t = CerberoTools(fake_client) + with pytest.raises(AttributeError): + t.nonexistent_tool() # type: ignore[attr-defined]