# COT Report — design spec (mcp-macro) **Data**: 2026-04-27 **Servizio target**: `mcp-macro` **Scope**: aggiungere supporto al Commitment of Traders (COT) report pubblicato dalla CFTC come fonte di posizionamento istituzionale per opzioni, ETF azionari e materie prime. ## 1. Motivazione Il COT settimanale CFTC è uno dei segnali di posizionamento più seguiti per futures sotto la giurisdizione USA (equity, bond, valute, energia, metalli, agricoli). Manca completamente in `mcp-macro`, che oggi copre solo yields, FRED, calendar, equity futures spot prices. L'uso primario nel contesto Cerbero: - **Overlay opzioni Deribit**: BTC ha correlazione strutturale con Nasdaq, e il posizionamento dei *Leveraged Funds* su NQ è un proxy di rischio sistemico equity. Quando i lev funds sono short estremo equity, IV upside premium si comprime → squeeze probabile. - **Segnali ETF**: *Asset Manager net* (TFF) approssima il flow istituzionale long-only (SPY, QQQ). - **Materie prime**: *Producer/Merchant* (hedger commerciale) e *Managed Money* (hedge fund / CTA) sono i veri segnali di top/bottom per oil, gold, copper, agricoli. ## 2. Decisione: due report, non uno Approccio adottato: - **Equity / financial** (S&P, NDX, Russell, treasuries, currencies) → **TFF** (*Traders in Financial Futures*). - **Materie prime** (oil, gold, silver, copper, grains) → **Disaggregated** (futures-only & options combined). - **Legacy** (non-commercial vs commercial) → **escluso**: report obsoleto, troppo aggregato, perde la granularità sui 4 ruoli istituzionali. Motivazione: i due report coprono i 13 simboli watchlist con la massima granularità senza overlap. ## 3. Sorgenti dati API CFTC pubblica (no API key richiesta), endpoint Socrata: `https://publicreporting.cftc.gov/resource/.json`. | Report | Dataset ID | Frequenza | Contenuto | | -------------------------- | ----------- | --------------------------- | -------------------------------------------------------------------- | | TFF F&O combined | `gpe5-46if` | settimanale (ven 15:30 ET) | Dealer/Intermediary, Asset Manager, Leveraged Funds, Other Reportables | | Disaggregated F&O combined | `72hh-3qpy` | settimanale (ven 15:30 ET) | Producer/Merchant, Swap Dealer, Managed Money, Other Reportables | Dati osservati al **martedì** della settimana, pubblicati il **venerdì** seguente alle 15:30 ET. ## 4. Watchlist simboli ### TFF - `ES` (E-mini S&P 500) - `NQ` (E-mini Nasdaq-100) - `RTY` (E-mini Russell 2000) - `ZN` (10-Year T-Note) - `ZB` (30-Year T-Bond) - `6E` (Euro FX) - `6J` (Japanese Yen) - `DX` (US Dollar Index) ### Disaggregated - `CL` (Crude Oil WTI) - `GC` (Gold) - `SI` (Silver) - `HG` (Copper) - `ZW` (Wheat) - `ZC` (Corn) - `ZS` (Soybeans) Mapping `ticker → cftc_contract_market_code` mantenuto in costante nel modulo (es. ES → `13874A`, CL → `067651`). I codici sono pubblici CFTC e non cambiano. ## 5. Tool MCP esposti Tre tool, tutti `reads` (core + observer): ### 5.1 `get_cot_tff(symbol, lookback_weeks=52)` Ritorna serie temporale TFF per un simbolo equity/financial. Output: ```json { "symbol": "ES", "report_type": "tff", "rows": [ { "report_date": "2026-04-22", "dealer_long": 12345, "dealer_short": 23456, "dealer_net": -11111, "asset_mgr_long": 654321, "asset_mgr_short": 200000, "asset_mgr_net": 454321, "lev_funds_long": 100000, "lev_funds_short": 350000, "lev_funds_net": -250000, "other_long": 50000, "other_short": 50000, "other_net": 0, "open_interest": 2500000 } ], "data_timestamp": "2026-04-27T20:00:00Z" } ``` ### 5.2 `get_cot_disaggregated(symbol, lookback_weeks=52)` Stessa shape, campi diversi: `producer_*`, `swap_*`, `managed_money_*`, `other_*`. ### 5.3 `get_cot_extreme_positioning(lookback_weeks=156)` Scanner che restituisce, per ogni simbolo della watchlist, il percentile storico (1y o 3y) dell'ultimo *net position* per il ruolo chiave (Leveraged Funds per TFF, Managed Money per Disaggregated). Flagga estremi a percentili ≤ 5 o ≥ 95. Output: ```json { "lookback_weeks": 156, "extremes": [ { "symbol": "ES", "report_type": "tff", "key_role": "lev_funds", "current_net": -250000, "percentile": 3.2, "signal": "extreme_short", "report_date": "2026-04-22" } ], "data_timestamp": "2026-04-27T20:00:00Z" } ``` `signal` ∈ `{"extreme_short", "extreme_long", "neutral"}`. ## 6. Architettura ```text mcp-macro/ src/mcp_macro/ fetchers.py # esistente — aggiunge fetch_cot_tff, fetch_cot_disaggregated, fetch_cot_extreme_positioning cot_contracts.py # NUOVO — costanti SYMBOL_TO_CFTC_CODE, CFTC_FIELD_MAPPINGS server.py # esistente — aggiunge 3 endpoint + body models tests/ test_cot.py # NUOVO — pure-logic test su parsing + percentile + extreme detection test_fetchers.py # esistente — aggiunge integration test con httpx_mock ``` Logica pura (calcolo percentile, classificazione extreme) in `fetchers` testata indipendentemente dal layer HTTP. I fetcher async usano `mcp_common.http.async_client` (retry transport già in place). ## 7. Cache - Chiamata Socrata risponde tipicamente in 200-800ms. - COT esce settimanalmente venerdì sera ET → cache TTL 1 ora è eccessiva ma sicura. Riusa il pattern `_TREASURY_CACHE` esistente in `fetchers.py` (chiave `(symbol, report_type, lookback_weeks)`). ## 8. Edge cases - **Pre-pubblicazione (es. mercoledì)**: ultimo report è quello della settimana precedente. Niente da gestire — l'API ritorna l'ultimo disponibile. - **Simbolo fuori watchlist**: `get_cot_tff("INVALID")` → 400 con payload `{"error": "unknown_symbol", "available": [...]}`. - **API CFTC down**: retry transport gestisce transient. Su 5xx persistente: ritorna `{"rows": [], "error": "cftc_unavailable"}`. - **Lookback troppo corto** (< 4 settimane) → percentile inattendibile in extreme positioning. Validation Pydantic: `lookback_weeks ≥ 4`. ## 9. Test plan Pure-logic (no HTTP): - `compute_percentile(value, history)` con casi noti. - `classify_extreme(percentile, threshold=5)` → boundary cases. - `parse_tff_row()` e `parse_disaggregated_row()` su payload Socrata mock (campi reali documentati). Integration (httpx_mock): - `fetch_cot_tff("ES", lookback_weeks=52)` con risposta CFTC mock → verifica shape output + ordering rows ASC per data. - `fetch_cot_extreme_positioning()` con dati che includono casi extreme + casi neutral → verifica filtering e signal. ACL test (TestClient): - `POST /tools/get_cot_tff` con core/observer/no-auth → 200/200/401. - `POST /tools/get_cot_extreme_positioning` idem. ## 10. Out of scope (versione 1) - **Storico oltre 3 anni**: l'API CFTC ha tutto da 2010, ma `lookback` default 52w (= 1 anno) e max ragionevole 156w. Storico decennale può essere aggiunto in v2 se serve per backtest. - **Disaggregated futures-only** (dataset diverso da F&O combined): meno usato, skip. - **Notification al rilascio settimanale**: il bot deve schedulare a venerdì 16:00 ET; non è responsabilità del MCP server. - **Legacy report**: escluso (vedi §2). - **Aggregazione cross-symbol** (es. "tutti i metalli combinati"): l'utente compone via tool individuali.