lint: ruff clean services/ (autofix + manual + ignore E741)
ci / ruff lint (push) Successful in 15s
ci / validate compose + Caddyfile (push) Successful in 2m6s
ci / mypy mcp_common (push) Successful in 30s
ci / pytest (push) Successful in 34s
ci / build & push to registry (push) Failing after 47s

- 24 autofix safe (SIM105 contextlib.suppress, F401 unused imports,
  I001 import order, B007 unused loop var, F811 redef, F841 unused).
- 15 unsafe-fix (UP038 X|Y in isinstance, SIM108 ternary, ecc.).
- Manual fix: SIM102 nested if in deribit term_structure, E402 imports
  in test_cot.py + sentiment server.py.
- Ignore E741 (variabili 'l' in list comprehensions deribit/client.py
  — stilistico, non bug).

Tests: 478/478 verdi.
This commit is contained in:
AdrianoDev
2026-04-29 08:44:12 +02:00
parent 910f80c99b
commit 9da2e12473
15 changed files with 29 additions and 41 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ target-version = "py313"
[tool.ruff.lint] [tool.ruff.lint]
select = ["E", "F", "I", "W", "UP", "B", "SIM"] select = ["E", "F", "I", "W", "UP", "B", "SIM"]
ignore = ["E501"] ignore = ["E501", "E741"]
[tool.ruff.lint.flake8-bugbear] [tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = [ extend-immutable-calls = [
+2 -3
View File
@@ -21,6 +21,7 @@ Claude Code config esempio:
""" """
from __future__ import annotations from __future__ import annotations
import contextlib
from typing import Any from typing import Any
import httpx import httpx
@@ -63,10 +64,8 @@ def _derive_input_schemas(app: FastAPI, tool_names: list[str]) -> dict[str, dict
if pname == "return": if pname == "return":
continue continue
if isinstance(ann, type) and issubclass(ann, BaseModel): if isinstance(ann, type) and issubclass(ann, BaseModel):
try: with contextlib.suppress(Exception):
out[name] = ann.model_json_schema() out[name] = ann.model_json_schema()
except Exception:
pass
break break
return out return out
@@ -36,10 +36,7 @@ def orderbook_imbalance(
ask_vol = sum(q for _, q in top_asks) ask_vol = sum(q for _, q in top_asks)
total = bid_vol + ask_vol total = bid_vol + ask_vol
if total == 0: ratio = None if total == 0 else (bid_vol - ask_vol) / total
ratio = None
else:
ratio = (bid_vol - ask_vol) / total
# Microprice: best bid, best ask. Weighted by opposite-side size. # Microprice: best bid, best ask. Weighted by opposite-side size.
microprice = None microprice = None
-1
View File
@@ -4,7 +4,6 @@ import asyncio
import httpx import httpx
import pytest import pytest
from mcp_common.http import async_client, call_with_retry from mcp_common.http import async_client, call_with_retry
+2 -2
View File
@@ -112,7 +112,7 @@ def test_vol_cone_returns_percentiles_per_window():
closes = _gbm_series(mu=0.0, sigma=0.5, n=400) closes = _gbm_series(mu=0.0, sigma=0.5, n=400)
out = vol_cone(closes, windows=[10, 30, 60]) out = vol_cone(closes, windows=[10, 30, 60])
assert set(out.keys()) == {10, 30, 60} assert set(out.keys()) == {10, 30, 60}
for w, stats in out.items(): for _w, stats in out.items():
assert "current" in stats assert "current" in stats
assert "p10" in stats and "p50" in stats and "p90" in stats assert "p10" in stats and "p50" in stats and "p90" in stats
assert stats["p10"] <= stats["p50"] <= stats["p90"] assert stats["p10"] <= stats["p50"] <= stats["p90"]
@@ -200,7 +200,7 @@ def test_autocorrelation_white_noise_low():
assert len(out) == 5 assert len(out) == 5
# white noise → all autocorr ≈ 0 (within ±2/sqrt(N)) # white noise → all autocorr ≈ 0 (within ±2/sqrt(N))
bound = 2.0 / math.sqrt(len(rets)) bound = 2.0 / math.sqrt(len(rets))
for lag, val in out.items(): for _lag, val in out.items():
assert abs(val) < bound * 2 # generous assert abs(val) < bound * 2 # generous
+3 -3
View File
@@ -71,13 +71,13 @@ def _asset_class_enum(ac: str) -> AssetClass:
def _serialize(obj: Any) -> Any: def _serialize(obj: Any) -> Any:
"""Recursively convert pydantic/datetime objects → json-safe.""" """Recursively convert pydantic/datetime objects → json-safe."""
if obj is None or isinstance(obj, (str, int, float, bool)): if obj is None or isinstance(obj, str | int | float | bool):
return obj return obj
if isinstance(obj, (_dt.datetime, _dt.date)): if isinstance(obj, _dt.datetime | _dt.date):
return obj.isoformat() return obj.isoformat()
if isinstance(obj, dict): if isinstance(obj, dict):
return {k: _serialize(v) for k, v in obj.items()} return {k: _serialize(v) for k, v in obj.items()}
if isinstance(obj, (list, tuple)): if isinstance(obj, list | tuple):
return [_serialize(v) for v in obj] return [_serialize(v) for v in obj]
if hasattr(obj, "model_dump"): if hasattr(obj, "model_dump"):
return _serialize(obj.model_dump()) return _serialize(obj.model_dump())
@@ -1,10 +1,10 @@
from __future__ import annotations from __future__ import annotations
import contextlib
import time import time
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any from typing import Any
import httpx
from mcp_common import indicators as ind from mcp_common import indicators as ind
from mcp_common import microstructure as micro from mcp_common import microstructure as micro
from mcp_common import options as opt from mcp_common import options as opt
@@ -196,10 +196,8 @@ class DeribitClient:
name = s.get("instrument_name") name = s.get("instrument_name")
oi = s.get("open_interest") oi = s.get("open_interest")
if name and oi is not None: if name and oi is not None:
try: with contextlib.suppress(TypeError, ValueError):
oi_by_name[name] = float(oi) oi_by_name[name] = float(oi)
except (TypeError, ValueError):
pass
all_items = raw.get("result") or [] all_items = raw.get("result") or []
filtered: list[dict] = [] filtered: list[dict] = []
@@ -882,10 +880,9 @@ class DeribitClient:
shape = "backwardation" shape = "backwardation"
short_term = next((x for x in ts if 8 <= x["dte"] <= 14), None) short_term = next((x for x in ts if 8 <= x["dte"] <= 14), None)
mid_term = next((x for x in ts if 35 <= x["dte"] <= 45), None) mid_term = next((x for x in ts if 35 <= x["dte"] <= 45), None)
if short_term and mid_term: if short_term and mid_term and mid_term["atm_iv"] - short_term["atm_iv"] > 5:
if mid_term["atm_iv"] - short_term["atm_iv"] > 5: contango_steep = True
contango_steep = True calendar_opp = True
calendar_opp = True
return { return {
"currency": currency, "currency": currency,
@@ -1131,7 +1128,7 @@ class DeribitClient:
structure = self._guess_structure(enriched) structure = self._guess_structure(enriched)
notional = sum(l["quantity"] * spot for l in enriched) if spot else 0.0 sum(l["quantity"] * spot for l in enriched) if spot else 0.0
fee_per_leg = min(0.0003 * (spot or 1) * sum(l["quantity"] for l in enriched), fee_per_leg = min(0.0003 * (spot or 1) * sum(l["quantity"] for l in enriched),
0.125 * abs(net_premium)) if spot else 0.0 0.125 * abs(net_premium)) if spot else 0.0
fees_open = round(fee_per_leg, 4) fees_open = round(fee_per_leg, 4)
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import contextlib
import os import os
from fastapi import Depends, FastAPI, HTTPException from fastapi import Depends, FastAPI, HTTPException
@@ -272,10 +273,8 @@ def create_app(
@asynccontextmanager @asynccontextmanager
async def _lifespan(_app: FastAPI): async def _lifespan(_app: FastAPI):
for inst in ("BTC-PERPETUAL", "ETH-PERPETUAL"): for inst in ("BTC-PERPETUAL", "ETH-PERPETUAL"):
try: with contextlib.suppress(Exception):
await client.set_leverage(inst, cap_default) await client.set_leverage(inst, cap_default)
except Exception:
pass
yield yield
app = build_app( app = build_app(
@@ -551,10 +550,8 @@ def create_app(
_check(principal, core=True) _check(principal, core=True)
lev = _enforce_leverage(body.leverage, creds=creds, exchange="deribit") lev = _enforce_leverage(body.leverage, creds=creds, exchange="deribit")
if lev != cap_default: if lev != cap_default:
try: with contextlib.suppress(Exception):
await client.set_leverage(body.instrument_name, lev) await client.set_leverage(body.instrument_name, lev)
except Exception:
pass
result = await client.place_order( result = await client.place_order(
instrument_name=body.instrument_name, instrument_name=body.instrument_name,
side=body.side, side=body.side,
@@ -582,10 +579,8 @@ def create_app(
lev = _enforce_leverage(body.leverage, creds=creds, exchange="deribit") lev = _enforce_leverage(body.leverage, creds=creds, exchange="deribit")
if lev != cap_default: if lev != cap_default:
for leg in body.legs: for leg in body.legs:
try: with contextlib.suppress(Exception):
await client.set_leverage(leg.instrument_name, lev) await client.set_leverage(leg.instrument_name, lev)
except Exception:
pass
result = await client.place_combo_order( result = await client.place_combo_order(
legs=[leg.model_dump() for leg in body.legs], legs=[leg.model_dump() for leg in body.legs],
side=body.side, side=body.side,
@@ -6,7 +6,6 @@ import asyncio
import datetime as _dt import datetime as _dt
from typing import Any from typing import Any
import httpx
from mcp_common import indicators as ind from mcp_common import indicators as ind
from mcp_common.http import async_client from mcp_common.http import async_client
@@ -5,6 +5,7 @@ from typing import Any
import httpx import httpx
from mcp_common.http import async_client from mcp_common.http import async_client
from mcp_macro.cot import classify_extreme, compute_percentile, parse_disagg_row, parse_tff_row from mcp_macro.cot import classify_extreme, compute_percentile, parse_disagg_row, parse_tff_row
from mcp_macro.cot_contracts import ( from mcp_macro.cot_contracts import (
ALL_DISAGG_SYMBOLS, ALL_DISAGG_SYMBOLS,
+6 -4
View File
@@ -1,6 +1,11 @@
from __future__ import annotations from __future__ import annotations
from mcp_macro.cot import classify_extreme, compute_percentile from mcp_macro.cot import (
classify_extreme,
compute_percentile,
parse_disagg_row,
parse_tff_row,
)
def test_compute_percentile_basic(): def test_compute_percentile_basic():
@@ -44,9 +49,6 @@ def test_classify_extreme_none_input():
assert classify_extreme(None) == "neutral" assert classify_extreme(None) == "neutral"
from mcp_macro.cot import parse_disagg_row, parse_tff_row
# Payload Socrata reale (subset campi rilevanti, valori arbitrari per test) # Payload Socrata reale (subset campi rilevanti, valori arbitrari per test)
TFF_SOCRATA_ROW = { TFF_SOCRATA_ROW = {
"report_date_as_yyyy_mm_dd": "2026-04-22T00:00:00.000", "report_date_as_yyyy_mm_dd": "2026-04-22T00:00:00.000",
@@ -366,6 +366,7 @@ async def test_fetch_cot_disagg_unknown_symbol():
async def test_fetch_cot_extreme_positioning_flags_outliers(monkeypatch): async def test_fetch_cot_extreme_positioning_flags_outliers(monkeypatch):
"""Mock fetch_cot_tff e fetch_cot_disagg per simulare history e ultimo punto.""" """Mock fetch_cot_tff e fetch_cot_disagg per simulare history e ultimo punto."""
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from mcp_macro import fetchers as f from mcp_macro import fetchers as f
# Simula una serie ES dove ultimo lev_funds_net è in basso (extreme_short) # Simula una serie ES dove ultimo lev_funds_net è in basso (extreme_short)
@@ -127,7 +127,6 @@ def test_get_market_overview_no_auth_401(http):
assert r.status_code == 401 assert r.status_code == 401
from unittest.mock import AsyncMock, patch
def test_get_cot_tff_core_ok(http): def test_get_cot_tff_core_ok(http):
@@ -5,7 +5,6 @@ import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from typing import Any from typing import Any
import httpx
from mcp_common.http import async_client from mcp_common.http import async_client
CRYPTOPANIC_URL = "https://cryptopanic.com/api/v1/posts/" CRYPTOPANIC_URL = "https://cryptopanic.com/api/v1/posts/"
@@ -9,8 +9,6 @@ from mcp_common.mcp_bridge import mount_mcp_endpoint
from mcp_common.server import build_app from mcp_common.server import build_app
from pydantic import BaseModel from pydantic import BaseModel
logger = logging.getLogger(__name__)
from mcp_sentiment.fetchers import ( from mcp_sentiment.fetchers import (
fetch_cointegration_pairs, fetch_cointegration_pairs,
fetch_cross_exchange_funding, fetch_cross_exchange_funding,
@@ -23,6 +21,8 @@ from mcp_sentiment.fetchers import (
fetch_world_news, fetch_world_news,
) )
logger = logging.getLogger(__name__)
# --- Body models --- # --- Body models ---
class GetCryptoNewsReq(BaseModel): class GetCryptoNewsReq(BaseModel):