feat: 4 remaining institutional reports — Earnings, VX curve, Central Bank RSS, Sentiment

New fetchers (no API keys required):
- earnings_fetcher.py: yfinance EPS calendar + surprise tracking for 23 geo-relevant tickers
- vx_fetcher.py: VIX term structure (^VIX/^VXV/^VXMT) + CBOE delayed futures, regime detection
- central_bank_fetcher.py: Fed + ECB RSS feeds, keyword-based hawkish/dovish classification
- sentiment_fetcher.py: CNN Fear & Greed (primary) + NAAIM + AAII (optional fallbacks)

Wiring:
- institutional_scheduler.py: all 4 now scheduled daily (≥08:00 UTC), deduplicated per day
- institutional.py /refresh: all 6 types handled with _run() helper
- ai_analyzer.py build_institutional_block(): limit 6→12, generic header text
- InstitutionalReports.tsx: 6-type color map, individual refresh buttons, expanded filters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-22 14:26:19 +02:00
parent d178615c74
commit acc8bef29d
8 changed files with 1193 additions and 103 deletions

View File

@@ -151,40 +151,49 @@ def get_report(report_id: int):
@router.post("/refresh")
def refresh_reports(report_type: Optional[str] = Query(None)):
"""Trigger a live fetch of COT and/or EIA reports."""
"""Trigger a live fetch of any/all institutional report types."""
results: Dict[str, str] = {}
if not report_type or report_type == "cot":
def _run(label: str, fetch_fn, *args):
try:
from services.cot_fetcher import fetch_cot_report
report = fetch_cot_report()
report = fetch_fn(*args)
if report:
save_institutional_report(report)
results["cot"] = "ok"
logger.info(f"[Institutional] COT report saved: {report['report_date']}")
results[label] = "ok"
logger.info(f"[Institutional] {label} saved: {report['report_date']}")
else:
results["cot"] = "no_data"
results[label] = "no_data"
except Exception as e:
logger.warning(f"[Institutional] COT refresh failed: {e}")
results["cot"] = f"error: {str(e)[:100]}"
logger.warning(f"[Institutional] {label} refresh failed: {e}")
results[label] = f"error: {str(e)[:100]}"
if not report_type or report_type == "cot":
from services.cot_fetcher import fetch_cot_report
_run("cot", fetch_cot_report)
if not report_type or report_type == "eia":
try:
eia_key = get_config("eia_api_key") or ""
if eia_key:
from services.eia_fetcher import fetch_eia_report
report = fetch_eia_report(eia_key)
if report:
save_institutional_report(report)
results["eia"] = "ok"
logger.info(f"[Institutional] EIA report saved: {report['report_date']}")
else:
results["eia"] = "no_data"
else:
results["eia"] = "no_api_key"
except Exception as e:
logger.warning(f"[Institutional] EIA refresh failed: {e}")
results["eia"] = f"error: {str(e)[:100]}"
eia_key = get_config("eia_api_key") or ""
if eia_key:
from services.eia_fetcher import fetch_eia_report
_run("eia", fetch_eia_report, eia_key)
else:
results["eia"] = "no_api_key"
if not report_type or report_type == "earnings":
from services.earnings_fetcher import fetch_earnings_report
_run("earnings", fetch_earnings_report)
if not report_type or report_type == "vx_curve":
from services.vx_fetcher import fetch_vx_report
_run("vx_curve", fetch_vx_report)
if not report_type or report_type == "central_bank":
from services.central_bank_fetcher import fetch_central_bank_reports
_run("central_bank", fetch_central_bank_reports)
if not report_type or report_type == "sentiment":
from services.sentiment_fetcher import fetch_sentiment_report
_run("sentiment", fetch_sentiment_report)
return {"status": "done", "results": results}