feat: institutional reports — CFTC COT + EIA petroleum weekly

- New institutional_reports table (DB) with importance, signals per asset class, key points, absorption tracking
- cot_fetcher.py: CFTC Socrata API (6dca-aqww), 7 instruments (Gold/Silver/Copper/WTI/NatGas/SP500/EURUSD), net positioning + 52-week z-score
- eia_fetcher.py: EIA API v2, 4 series (crude/Cushing/gasoline/distillates), WoW surprise detection
- institutional.py router: GET /reports, GET /reports/{id}, POST /refresh, GET /stats
- institutional_scheduler.py: weekly auto-fetch (COT Saturdays, EIA Wednesday afternoons)
- ai_analyzer.py: build_institutional_block() + institutional_block param injected into AI scoring prompt
- auto_cycle.py: inject institutional block into suggestion + scoring, absorption tracking via keyword overlap after each cycle commentary
- InstitutionalReports.tsx: full page with filter bar (type/category/importance/period), cards with key point bullets, EXTREME alerts highlighted, signal badges, absorption badge, trading implications, expandable detail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-22 13:45:07 +02:00
parent 4423ad91db
commit 3edbd6b0b7
12 changed files with 1228 additions and 2 deletions

View File

@@ -14,7 +14,7 @@ Cycle steps:
import logging
import threading
import uuid
from datetime import datetime
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
logger = logging.getLogger(__name__)
@@ -551,6 +551,16 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
except Exception as _cbe:
logger.warning(f"[Cycle] Convergence block build failed (non-blocking): {_cbe}")
# ── Build institutional reports block ──────────────────────────
_institutional_block = ""
try:
from services.ai_analyzer import build_institutional_block
_institutional_block = build_institutional_block(days=7)
if _institutional_block:
logger.info(f"[Cycle {run_id[:16]}] Institutional block injected")
except Exception as _ibe:
logger.warning(f"[Cycle] Institutional block failed (non-blocking): {_ibe}")
suggestions = suggest_patterns_from_market_context(
news, quotes, calendar, macro_regime=macro_regime, geo_score=geo_score_obj,
portfolio_lessons=portfolio_lessons,
@@ -705,6 +715,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
fred_block=_fred_block,
price_discovery_block=_price_discovery_block,
portfolio_context_block=_portfolio_block,
institutional_block=_institutional_block,
run_id=run_id,
)
scored_with_id = [s for s in scored if s.get("pattern_id")]
@@ -991,6 +1002,56 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
_current_status["last_run_at"] = datetime.utcnow().isoformat()
logger.info(f"[Cycle {run_id[:16]}] Completed — {added_count} new patterns, {len(scored)} scored")
# ── Step 6.1: Institutional report absorption tracking ───────────────
try:
from services.database import get_conn as _get_conn
import json as _json
_commentary_text = ""
if commentary:
try:
_c = _json.loads(commentary) if isinstance(commentary, str) else commentary
_commentary_text = " ".join([
str(_c.get("narrative", "")),
str(_c.get("key_catalyst", "")),
" ".join(str(x) for x in (_c.get("risks", []) or [])),
]).lower()
except Exception:
_commentary_text = str(commentary).lower()
if _commentary_text:
_conn = _get_conn()
try:
cutoff = (datetime.utcnow() - timedelta(days=14)).strftime("%Y-%m-%d")
_inst_rows = _conn.execute(
"SELECT id, key_points_json FROM institutional_reports "
"WHERE report_date >= ? AND (absorbed_score IS NULL OR absorbed_score = 0)",
(cutoff,),
).fetchall()
for _ir in _inst_rows:
try:
_kps = _json.loads(_ir["key_points_json"] or "[]")
_kp_words = set(
w for kp in _kps for w in kp.lower().split()
if len(w) > 4
)
if not _kp_words:
continue
_overlap = sum(1 for w in _kp_words if w in _commentary_text)
_score = min(1.0, _overlap / max(len(_kp_words), 1))
if _score > 0:
_conn.execute(
"UPDATE institutional_reports SET absorbed_score=?, injected_in_cycle_id=? WHERE id=?",
(round(_score, 3), run_id, _ir["id"]),
)
except Exception:
pass
_conn.commit()
logger.info(f"[Cycle {run_id[:16]}] Absorption tracked for {len(_inst_rows)} institutional reports")
finally:
_conn.close()
except Exception as _abs_e:
logger.warning(f"[Cycle] Absorption tracking failed (non-blocking): {_abs_e}")
# ── Step 6.5: Generate full cycle report ─────────────────────────────
try:
_cycle_report = _generate_cycle_report(