diff --git a/src/cerbero_mcp/__main__.py b/src/cerbero_mcp/__main__.py index 52797e4..eb18a2a 100644 --- a/src/cerbero_mcp/__main__.py +++ b/src/cerbero_mcp/__main__.py @@ -10,6 +10,7 @@ Boot: from __future__ import annotations from contextlib import asynccontextmanager +from typing import Literal, cast import uvicorn from fastapi import FastAPI @@ -40,7 +41,9 @@ def _make_app(settings: Settings) -> FastAPI: app.state.settings = settings async def builder(exchange: str, env: str): - return await build_client(settings, exchange, env) + return await build_client( + settings, exchange, cast(Literal["testnet", "mainnet"], env) + ) app.state.registry = ClientRegistry(builder=builder) @@ -65,7 +68,7 @@ def _make_app(settings: Settings) -> FastAPI: def main() -> None: configure_root_logging() - settings = Settings() + settings = Settings() # type: ignore[call-arg] app = _make_app(settings) uvicorn.run( app, diff --git a/src/cerbero_mcp/exchanges/alpaca/client.py b/src/cerbero_mcp/exchanges/alpaca/client.py index 5e3fb83..5af0988 100644 --- a/src/cerbero_mcp/exchanges/alpaca/client.py +++ b/src/cerbero_mcp/exchanges/alpaca/client.py @@ -130,14 +130,14 @@ class AlpacaClient: async def get_account(self) -> dict: acc = await self._run(self._trading.get_account) - return _serialize(acc) + return _serialize(acc) # type: ignore[no-any-return] async def get_positions(self) -> list[dict]: pos = await self._run(self._trading.get_all_positions) return [_serialize(p) for p in pos] async def get_activities(self, limit: int = 50) -> list[dict]: - acts = await self._run(self._trading.get_account_activities) + acts = await self._run(self._trading.get_account_activities) # type: ignore[union-attr] data = [_serialize(a) for a in acts] return data[:limit] @@ -148,7 +148,7 @@ class AlpacaClient: ) -> list[dict]: req = GetAssetsRequest( asset_class=_asset_class_enum(asset_class), - status=status, + status=status, # type: ignore[arg-type] ) assets = await self._run(self._trading.get_all_assets, req) return [_serialize(a) for a in assets[:500]] @@ -175,10 +175,10 @@ class AlpacaClient: "timestamp": _serialize(getattr(trade, "timestamp", None)), } if ac == "crypto": - req = CryptoLatestTradeRequest(symbol_or_symbols=symbol) + req = CryptoLatestTradeRequest(symbol_or_symbols=symbol) # type: ignore[assignment] data = await self._run(self._crypto.get_crypto_latest_trade, req) trade = data.get(symbol) - q_req = CryptoLatestQuoteRequest(symbol_or_symbols=symbol) + q_req = CryptoLatestQuoteRequest(symbol_or_symbols=symbol) # type: ignore[assignment] qdata = await self._run(self._crypto.get_crypto_latest_quote, q_req) quote = qdata.get(symbol) return { @@ -190,7 +190,7 @@ class AlpacaClient: "timestamp": _serialize(getattr(trade, "timestamp", None)), } if ac == "options": - req = OptionLatestQuoteRequest(symbol_or_symbols=symbol) + req = OptionLatestQuoteRequest(symbol_or_symbols=symbol) # type: ignore[assignment] data = await self._run(self._option.get_option_latest_quote, req) quote = data.get(symbol) return { @@ -224,13 +224,13 @@ class AlpacaClient: ) data = await self._run(self._stock.get_stock_bars, req) elif ac == "crypto": - req = CryptoBarsRequest( + req = CryptoBarsRequest( # type: ignore[assignment] symbol_or_symbols=symbol, timeframe=tf, start=start_dt, end=end_dt, limit=limit, ) data = await self._run(self._crypto.get_crypto_bars, req) elif ac == "options": - req = OptionBarsRequest( + req = OptionBarsRequest( # type: ignore[assignment] symbol_or_symbols=symbol, timeframe=tf, start=start_dt, end=end_dt, limit=limit, ) @@ -255,7 +255,7 @@ class AlpacaClient: async def get_snapshot(self, symbol: str) -> dict: req = StockSnapshotRequest(symbol_or_symbols=symbol) data = await self._run(self._stock.get_stock_snapshot, req) - return _serialize(data.get(symbol)) + return _serialize(data.get(symbol)) # type: ignore[no-any-return] async def get_option_chain( self, @@ -301,23 +301,23 @@ class AlpacaClient: "time_in_force": tif_enum, } if qty is not None: - common["qty"] = qty + common["qty"] = qty # type: ignore[assignment] if notional is not None: - common["notional"] = notional + common["notional"] = notional # type: ignore[assignment] if ot == "market": req = MarketOrderRequest(**common) elif ot == "limit": if limit_price is None: raise ValueError("limit_price required for limit order") - req = LimitOrderRequest(**common, limit_price=limit_price) + req = LimitOrderRequest(**common, limit_price=limit_price) # type: ignore[assignment] elif ot == "stop": if stop_price is None: raise ValueError("stop_price required for stop order") - req = StopOrderRequest(**common, stop_price=stop_price) + req = StopOrderRequest(**common, stop_price=stop_price) # type: ignore[assignment] else: raise ValueError(f"unsupported order_type: {order_type}") order = await self._run(self._trading.submit_order, req) - return _serialize(order) + return _serialize(order) # type: ignore[no-any-return] async def amend_order( self, @@ -338,7 +338,7 @@ class AlpacaClient: kwargs["time_in_force"] = TimeInForce(tif.lower()) req = ReplaceOrderRequest(**kwargs) order = await self._run(self._trading.replace_order_by_id, order_id, req) - return _serialize(order) + return _serialize(order) # type: ignore[no-any-return] async def cancel_order(self, order_id: str) -> dict: await self._run(self._trading.cancel_order_by_id, order_id) @@ -364,7 +364,7 @@ class AlpacaClient: order = await self._run( self._trading.close_position, symbol, close_options=req ) - return _serialize(order) + return _serialize(order) # type: ignore[no-any-return] async def close_all_positions(self, cancel_orders: bool = True) -> list[dict]: resp = await self._run( @@ -376,7 +376,7 @@ class AlpacaClient: async def get_clock(self) -> dict: clock = await self._run(self._trading.get_clock) - return _serialize(clock) + return _serialize(clock) # type: ignore[no-any-return] async def get_calendar( self, start: str | None = None, end: str | None = None diff --git a/src/cerbero_mcp/exchanges/deribit/client.py b/src/cerbero_mcp/exchanges/deribit/client.py index f335d8e..8a502ce 100644 --- a/src/cerbero_mcp/exchanges/deribit/client.py +++ b/src/cerbero_mcp/exchanges/deribit/client.py @@ -87,10 +87,10 @@ class DeribitClient: resp = await http.get(url, params=request_params, headers=headers) data = resp.json() if "result" in data: - return data + return data # type: ignore[no-any-return] return {"result": None, "error": error_msg} - return data + return data # type: ignore[no-any-return] # ── Read tools ─────────────────────────────────────────────── @@ -190,9 +190,9 @@ class DeribitClient: self._request("public/get_book_summary_by_currency", summary_params), return_exceptions=True, ) - raw = instruments_raw if isinstance(instruments_raw, dict) else {} + raw = instruments_raw if isinstance(instruments_raw, dict) else {} # type: ignore[has-type] summary_items = ( - summary_raw.get("result") if isinstance(summary_raw, dict) else None + summary_raw.get("result") if isinstance(summary_raw, dict) else None # type: ignore[has-type] ) or [] oi_by_name: dict[str, float] = {} for s in summary_items: @@ -1200,8 +1200,8 @@ class DeribitClient: if len({l["expiry"] for l in legs}) == 2 and len(strikes) == 1: return "calendar spread" if n == 4: - types = [l["type"] for l in legs] - if types.count("P") == 2 and types.count("C") == 2: + types_list = [l["type"] for l in legs] + if types_list.count("P") == 2 and types_list.count("C") == 2: return "iron condor" return "custom" @@ -1484,7 +1484,7 @@ class DeribitClient: if not values_sorted: return None idx = int(round((len(values_sorted) - 1) * p)) - return values_sorted[idx] + return values_sorted[idx] # type: ignore[no-any-return] mean = sum(values) / len(values) if values else None return { @@ -1567,7 +1567,7 @@ class DeribitClient: r = raw.get("result") if r is None: return {"error": raw.get("error", "unknown"), "state": "error"} - return r + return r # type: ignore[no-any-return] async def place_combo_order( self, diff --git a/src/cerbero_mcp/exchanges/deribit/tools.py b/src/cerbero_mcp/exchanges/deribit/tools.py index 4bf2ce3..f9b35eb 100644 --- a/src/cerbero_mcp/exchanges/deribit/tools.py +++ b/src/cerbero_mcp/exchanges/deribit/tools.py @@ -296,10 +296,12 @@ async def environment_info( async def get_ticker(client: DeribitClient, params: GetTickerReq) -> dict: + assert params.instrument_name is not None # validator garantisce non-None return await client.get_ticker(params.instrument_name) async def get_ticker_batch(client: DeribitClient, params: GetTickerBatchReq) -> dict: + assert params.instrument_names is not None # validator garantisce non-None return await client.get_ticker_batch(params.instrument_names) @@ -327,7 +329,7 @@ async def get_orderbook_imbalance( return await client.get_orderbook_imbalance(params.instrument_name, params.depth) -async def get_positions(client: DeribitClient, params: GetPositionsReq) -> dict: +async def get_positions(client: DeribitClient, params: GetPositionsReq) -> list: return await client.get_positions(params.currency) @@ -337,7 +339,7 @@ async def get_account_summary( return await client.get_account_summary(params.currency) -async def get_trade_history(client: DeribitClient, params: GetTradeHistoryReq) -> dict: +async def get_trade_history(client: DeribitClient, params: GetTradeHistoryReq) -> list: return await client.get_trade_history(params.limit, params.instrument_name) diff --git a/src/cerbero_mcp/exchanges/hyperliquid/client.py b/src/cerbero_mcp/exchanges/hyperliquid/client.py index 72b9df2..f254c0a 100644 --- a/src/cerbero_mcp/exchanges/hyperliquid/client.py +++ b/src/cerbero_mcp/exchanges/hyperliquid/client.py @@ -317,7 +317,7 @@ class HyperliquidClient: ) if resp.status_code == 200: res = resp.json().get("result") or {} - first = next(iter(res.values()), {}) + first: dict = next(iter(res.values()), {}) price = (first.get("c") or [None])[0] spot_price = float(price) if price else None spot_source = "kraken" @@ -466,8 +466,10 @@ class HyperliquidClient: mark = ticker.get("mark_price", 0) price = round(mark * 1.03, 1) if is_buy else round(mark * 0.97, 1) elif type in ("stop_market", "stop_loss"): + assert price is not None ot = {"trigger": {"triggerPx": float(price), "isMarket": True, "tpsl": "sl"}} elif type == "take_profit": + assert price is not None ot = {"trigger": {"triggerPx": float(price), "isMarket": True, "tpsl": "tp"}} else: ot = {"limit": {"tif": "Gtc"}} diff --git a/src/cerbero_mcp/exchanges/hyperliquid/tools.py b/src/cerbero_mcp/exchanges/hyperliquid/tools.py index 496196d..ea0f332 100644 --- a/src/cerbero_mcp/exchanges/hyperliquid/tools.py +++ b/src/cerbero_mcp/exchanges/hyperliquid/tools.py @@ -247,6 +247,9 @@ async def get_trade_history( async def get_historical( client: HyperliquidClient, params: GetHistoricalReq ) -> dict: + assert params.instrument is not None # validator garantisce non-None + assert params.start_date is not None # validator garantisce non-None + assert params.end_date is not None # validator garantisce non-None return await client.get_historical( params.instrument, params.start_date, params.end_date, params.resolution ) @@ -273,6 +276,9 @@ async def basis_spot_perp( async def get_indicators( client: HyperliquidClient, params: GetIndicatorsReq ) -> dict: + assert params.instrument is not None # validator garantisce non-None + assert params.start_date is not None # validator garantisce non-None + assert params.end_date is not None # validator garantisce non-None return await client.get_indicators( params.instrument, params.indicators, diff --git a/src/cerbero_mcp/exchanges/macro/fetchers.py b/src/cerbero_mcp/exchanges/macro/fetchers.py index fcd3f7d..1a08f03 100644 --- a/src/cerbero_mcp/exchanges/macro/fetchers.py +++ b/src/cerbero_mcp/exchanges/macro/fetchers.py @@ -89,7 +89,7 @@ async def fetch_asset_price(ticker: str) -> dict[str, Any]: now = time.monotonic() cached = _ASSET_CACHE.get(key) if cached and (now - cached["ts"]) < _ASSET_CACHE_TTL: - return cached["data"] + return cached["data"] # type: ignore[no-any-return] mapping = ASSET_TICKER_MAP.get(key) if not mapping: @@ -137,7 +137,7 @@ async def fetch_treasury_yields() -> dict[str, Any]: now = time.monotonic() if _TREASURY_CACHE["data"] and (now - _TREASURY_CACHE["ts"]) < _TREASURY_TTL: - return _TREASURY_CACHE["data"] + return _TREASURY_CACHE["data"] # type: ignore[no-any-return] symbols = [ ("us2y", "^UST2YR"), @@ -155,7 +155,7 @@ async def fetch_treasury_yields() -> dict[str, Any]: spread = None if yields.get("us10y") is not None and yields.get("us2y") is not None: - spread = round(yields["us10y"] - yields["us2y"], 3) + spread = round(yields["us10y"] - yields["us2y"], 3) # type: ignore[operator] shape = "unknown" if spread is not None: if spread > 0.25: @@ -586,7 +586,7 @@ async def fetch_market_overview() -> dict[str, Any]: now = time.monotonic() if _MARKET_CACHE["data"] is not None and (now - _MARKET_CACHE["ts"]) < _MARKET_CACHE_TTL: - return _MARKET_CACHE["data"] + return _MARKET_CACHE["data"] # type: ignore[no-any-return] async with async_client(timeout=10.0) as client: global_data: dict[str, Any] = {} diff --git a/src/cerbero_mcp/exchanges/sentiment/fetchers.py b/src/cerbero_mcp/exchanges/sentiment/fetchers.py index 398ec69..319744f 100644 --- a/src/cerbero_mcp/exchanges/sentiment/fetchers.py +++ b/src/cerbero_mcp/exchanges/sentiment/fetchers.py @@ -134,7 +134,7 @@ async def fetch_crypto_news(api_key: str = "", limit: int = 20) -> dict[str, Any providers_failed.append(name) continue providers_ok.append(name) - for item in res: + for item in res: # type: ignore[union-attr] if "provider" not in item: item["provider"] = name all_items.append(item) @@ -643,7 +643,7 @@ async def fetch_oi_history(asset: str = "BTC", period: str = "5m", limit: int = past = next((p for p in reversed(points) if p["timestamp"] <= cutoff_ts), None) if past is None or past["oi"] == 0: return None - return round(100.0 * (current["oi"] - past["oi"]) / past["oi"], 3) + return round(100.0 * (current["oi"] - past["oi"]) / past["oi"], 3) # type: ignore[no-any-return] return { "asset": asset, diff --git a/src/cerbero_mcp/routers/alpaca.py b/src/cerbero_mcp/routers/alpaca.py index 967653f..38fc2a1 100644 --- a/src/cerbero_mcp/routers/alpaca.py +++ b/src/cerbero_mcp/routers/alpaca.py @@ -6,7 +6,7 @@ dal middleware in `cerbero_mcp.auth`; qui leggiamo solo `request.state.environme """ from __future__ import annotations -from typing import Literal +from typing import Literal, cast from fastapi import APIRouter, Depends, Request @@ -18,14 +18,14 @@ Environment = Literal["testnet", "mainnet"] def get_environment(request: Request) -> Environment: - return request.state.environment + return cast(Environment, request.state.environment) async def get_alpaca_client( request: Request, env: Environment = Depends(get_environment) ) -> AlpacaClient: registry: ClientRegistry = request.app.state.registry - return await registry.get("alpaca", env) + return cast(AlpacaClient, await registry.get("alpaca", env)) def _build_creds(request: Request) -> dict: diff --git a/src/cerbero_mcp/routers/bybit.py b/src/cerbero_mcp/routers/bybit.py index 6af4465..0beb88e 100644 --- a/src/cerbero_mcp/routers/bybit.py +++ b/src/cerbero_mcp/routers/bybit.py @@ -6,7 +6,7 @@ dal middleware in `cerbero_mcp.auth`; qui leggiamo solo `request.state.environme """ from __future__ import annotations -from typing import Literal +from typing import Literal, cast from fastapi import APIRouter, Depends, Request @@ -18,14 +18,14 @@ Environment = Literal["testnet", "mainnet"] def get_environment(request: Request) -> Environment: - return request.state.environment + return cast(Environment, request.state.environment) async def get_bybit_client( request: Request, env: Environment = Depends(get_environment) ) -> BybitClient: registry: ClientRegistry = request.app.state.registry - return await registry.get("bybit", env) + return cast(BybitClient, await registry.get("bybit", env)) def _build_creds(request: Request) -> dict: diff --git a/src/cerbero_mcp/routers/deribit.py b/src/cerbero_mcp/routers/deribit.py index 059e1c3..8fdaf74 100644 --- a/src/cerbero_mcp/routers/deribit.py +++ b/src/cerbero_mcp/routers/deribit.py @@ -6,7 +6,7 @@ dal middleware in `cerbero_mcp.auth`; qui leggiamo solo `request.state.environme """ from __future__ import annotations -from typing import Literal +from typing import Literal, cast from fastapi import APIRouter, Depends, Request @@ -18,14 +18,14 @@ Environment = Literal["testnet", "mainnet"] def get_environment(request: Request) -> Environment: - return request.state.environment + return cast(Environment, request.state.environment) async def get_deribit_client( request: Request, env: Environment = Depends(get_environment) ) -> DeribitClient: registry: ClientRegistry = request.app.state.registry - return await registry.get("deribit", env) + return cast(DeribitClient, await registry.get("deribit", env)) def _build_creds(request: Request) -> dict: diff --git a/src/cerbero_mcp/routers/hyperliquid.py b/src/cerbero_mcp/routers/hyperliquid.py index d546795..39cbd62 100644 --- a/src/cerbero_mcp/routers/hyperliquid.py +++ b/src/cerbero_mcp/routers/hyperliquid.py @@ -6,7 +6,7 @@ dal middleware in `cerbero_mcp.auth`; qui leggiamo solo `request.state.environme """ from __future__ import annotations -from typing import Literal +from typing import Literal, cast from fastapi import APIRouter, Depends, Request @@ -18,14 +18,14 @@ Environment = Literal["testnet", "mainnet"] def get_environment(request: Request) -> Environment: - return request.state.environment + return cast(Environment, request.state.environment) async def get_hyperliquid_client( request: Request, env: Environment = Depends(get_environment) ) -> HyperliquidClient: registry: ClientRegistry = request.app.state.registry - return await registry.get("hyperliquid", env) + return cast(HyperliquidClient, await registry.get("hyperliquid", env)) def _build_creds(request: Request) -> dict: diff --git a/src/cerbero_mcp/routers/macro.py b/src/cerbero_mcp/routers/macro.py index 06d73fd..ce69b45 100644 --- a/src/cerbero_mcp/routers/macro.py +++ b/src/cerbero_mcp/routers/macro.py @@ -6,7 +6,7 @@ Tutti i tool sono READ — niente write, niente leverage_cap. """ from __future__ import annotations -from typing import Literal +from typing import Literal, cast from fastapi import APIRouter, Depends, Request @@ -18,14 +18,14 @@ Environment = Literal["testnet", "mainnet"] def get_environment(request: Request) -> Environment: - return request.state.environment + return cast(Environment, request.state.environment) async def get_macro_client( request: Request, env: Environment = Depends(get_environment) ) -> MacroClient: registry: ClientRegistry = request.app.state.registry - return await registry.get("macro", env) + return cast(MacroClient, await registry.get("macro", env)) def make_router() -> APIRouter: diff --git a/src/cerbero_mcp/routers/sentiment.py b/src/cerbero_mcp/routers/sentiment.py index 5a02e49..ca69b19 100644 --- a/src/cerbero_mcp/routers/sentiment.py +++ b/src/cerbero_mcp/routers/sentiment.py @@ -7,7 +7,7 @@ niente write, niente leverage_cap. """ from __future__ import annotations -from typing import Literal +from typing import Literal, cast from fastapi import APIRouter, Depends, Request @@ -19,14 +19,14 @@ Environment = Literal["testnet", "mainnet"] def get_environment(request: Request) -> Environment: - return request.state.environment + return cast(Environment, request.state.environment) async def get_sentiment_client( request: Request, env: Environment = Depends(get_environment) ) -> SentimentClient: registry: ClientRegistry = request.app.state.registry - return await registry.get("sentiment", env) + return cast(SentimentClient, await registry.get("sentiment", env)) def make_router() -> APIRouter: diff --git a/src/cerbero_mcp/server.py b/src/cerbero_mcp/server.py index 0471ff8..e793ba9 100644 --- a/src/cerbero_mcp/server.py +++ b/src/cerbero_mcp/server.py @@ -201,5 +201,5 @@ def build_app( app.openapi_schema = schema return schema - app.openapi = _custom_openapi + app.openapi = _custom_openapi # type: ignore[method-assign] return app diff --git a/src/cerbero_mcp/settings.py b/src/cerbero_mcp/settings.py index 2999695..6f60283 100644 --- a/src/cerbero_mcp/settings.py +++ b/src/cerbero_mcp/settings.py @@ -99,9 +99,9 @@ class Settings(_Sub): testnet_token: SecretStr mainnet_token: SecretStr - deribit: DeribitSettings = Field(default_factory=DeribitSettings) - bybit: BybitSettings = Field(default_factory=BybitSettings) - hyperliquid: HyperliquidSettings = Field(default_factory=HyperliquidSettings) - alpaca: AlpacaSettings = Field(default_factory=AlpacaSettings) - macro: MacroSettings = Field(default_factory=MacroSettings) - sentiment: SentimentSettings = Field(default_factory=SentimentSettings) + deribit: DeribitSettings = Field(default_factory=lambda: DeribitSettings()) # type: ignore[call-arg] + bybit: BybitSettings = Field(default_factory=lambda: BybitSettings()) # type: ignore[call-arg] + hyperliquid: HyperliquidSettings = Field(default_factory=lambda: HyperliquidSettings()) # type: ignore[call-arg] + alpaca: AlpacaSettings = Field(default_factory=lambda: AlpacaSettings()) # type: ignore[call-arg] + macro: MacroSettings = Field(default_factory=lambda: MacroSettings()) # type: ignore[call-arg] + sentiment: SentimentSettings = Field(default_factory=lambda: SentimentSettings()) # type: ignore[call-arg]