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:
@@ -1,8 +1,9 @@
|
||||
"""
|
||||
Weekly scheduler for institutional reports:
|
||||
Scheduler for institutional reports:
|
||||
- CFTC COT: released every Friday ~15:30 ET → fetch Saturday UTC
|
||||
- EIA Petroleum Weekly: released every Wednesday ~10:30 ET → fetch Wednesday afternoon UTC
|
||||
Checks once per hour; only fetches when the day matches and report not yet fetched this week.
|
||||
- Earnings / VX curve / Central bank RSS / Sentiment: fetch daily (after 8am UTC)
|
||||
Checks once per hour; only fetches when the day matches and report not yet fetched today.
|
||||
"""
|
||||
import logging
|
||||
import threading
|
||||
@@ -17,17 +18,20 @@ _CHECK_INTERVAL_S = 3600 # check every hour
|
||||
|
||||
|
||||
def _should_fetch_cot() -> bool:
|
||||
"""Saturday UTC = day after COT release."""
|
||||
now = datetime.utcnow()
|
||||
return now.weekday() == 5 # Saturday
|
||||
|
||||
|
||||
def _should_fetch_eia() -> bool:
|
||||
"""Wednesday afternoon UTC."""
|
||||
now = datetime.utcnow()
|
||||
return now.weekday() == 2 and now.hour >= 16 # Wednesday ≥16:00 UTC
|
||||
|
||||
|
||||
def _should_fetch_daily() -> bool:
|
||||
"""Earnings, VX, central bank, sentiment — fetch once per day after 8am UTC."""
|
||||
return datetime.utcnow().hour >= 8
|
||||
|
||||
|
||||
def _last_fetch_date(report_type: str) -> Optional[str]:
|
||||
try:
|
||||
from services.database import get_conn
|
||||
@@ -44,44 +48,67 @@ def _last_fetch_date(report_type: str) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def _fetch_and_save(label: str, fetch_fn, *args):
|
||||
"""Generic helper: call fetch_fn(*args), save result if not None."""
|
||||
try:
|
||||
from routers.institutional import save_institutional_report
|
||||
report = fetch_fn(*args)
|
||||
if report:
|
||||
save_institutional_report(report)
|
||||
logger.info(f"[InstitutionalScheduler] {label} saved: {report['report_date']}")
|
||||
else:
|
||||
logger.info(f"[InstitutionalScheduler] {label} returned no data")
|
||||
except Exception as e:
|
||||
logger.warning(f"[InstitutionalScheduler] {label} fetch failed: {e}")
|
||||
|
||||
|
||||
def _run_loop():
|
||||
logger.info("[InstitutionalScheduler] Started")
|
||||
while not _stop_event.is_set():
|
||||
try:
|
||||
today = datetime.utcnow().strftime("%Y-%m-%d")
|
||||
|
||||
if _should_fetch_cot():
|
||||
last = _last_fetch_date("cot")
|
||||
if last != today:
|
||||
logger.info("[InstitutionalScheduler] Fetching COT...")
|
||||
try:
|
||||
from services.cot_fetcher import fetch_cot_report
|
||||
from routers.institutional import save_institutional_report
|
||||
report = fetch_cot_report()
|
||||
if report:
|
||||
save_institutional_report(report)
|
||||
logger.info(f"[InstitutionalScheduler] COT saved: {report['report_date']}")
|
||||
except Exception as e:
|
||||
logger.warning(f"[InstitutionalScheduler] COT fetch failed: {e}")
|
||||
# ── Weekly: COT (Saturday) ──────────────────────────────────────
|
||||
if _should_fetch_cot() and _last_fetch_date("cot") != today:
|
||||
logger.info("[InstitutionalScheduler] Fetching COT...")
|
||||
from services.cot_fetcher import fetch_cot_report
|
||||
_fetch_and_save("COT", fetch_cot_report)
|
||||
|
||||
if _should_fetch_eia():
|
||||
last = _last_fetch_date("eia")
|
||||
if last != today:
|
||||
logger.info("[InstitutionalScheduler] Fetching EIA...")
|
||||
try:
|
||||
from services.database import get_config
|
||||
from services.eia_fetcher import fetch_eia_report
|
||||
from routers.institutional import save_institutional_report
|
||||
key = get_config("eia_api_key") or ""
|
||||
if key:
|
||||
report = fetch_eia_report(key)
|
||||
if report:
|
||||
save_institutional_report(report)
|
||||
logger.info(f"[InstitutionalScheduler] EIA saved: {report['report_date']}")
|
||||
else:
|
||||
logger.info("[InstitutionalScheduler] EIA skipped — no API key configured")
|
||||
except Exception as e:
|
||||
logger.warning(f"[InstitutionalScheduler] EIA fetch failed: {e}")
|
||||
# ── Weekly: EIA (Wednesday ≥16:00 UTC) ─────────────────────────
|
||||
if _should_fetch_eia() and _last_fetch_date("eia") != today:
|
||||
logger.info("[InstitutionalScheduler] Fetching EIA...")
|
||||
try:
|
||||
from services.database import get_config
|
||||
from services.eia_fetcher import fetch_eia_report
|
||||
key = get_config("eia_api_key") or ""
|
||||
if key:
|
||||
_fetch_and_save("EIA", fetch_eia_report, key)
|
||||
else:
|
||||
logger.info("[InstitutionalScheduler] EIA skipped — no API key configured")
|
||||
except Exception as e:
|
||||
logger.warning(f"[InstitutionalScheduler] EIA fetch failed: {e}")
|
||||
|
||||
# ── Daily: Earnings, VX curve, Central banks, Sentiment ─────────
|
||||
if _should_fetch_daily():
|
||||
if _last_fetch_date("earnings") != today:
|
||||
logger.info("[InstitutionalScheduler] Fetching Earnings...")
|
||||
from services.earnings_fetcher import fetch_earnings_report
|
||||
_fetch_and_save("Earnings", fetch_earnings_report)
|
||||
|
||||
if _last_fetch_date("vx_curve") != today:
|
||||
logger.info("[InstitutionalScheduler] Fetching VX term structure...")
|
||||
from services.vx_fetcher import fetch_vx_report
|
||||
_fetch_and_save("VX", fetch_vx_report)
|
||||
|
||||
if _last_fetch_date("central_bank") != today:
|
||||
logger.info("[InstitutionalScheduler] Fetching Central Bank RSS...")
|
||||
from services.central_bank_fetcher import fetch_central_bank_reports
|
||||
_fetch_and_save("CentralBank", fetch_central_bank_reports)
|
||||
|
||||
if _last_fetch_date("sentiment") != today:
|
||||
logger.info("[InstitutionalScheduler] Fetching Sentiment...")
|
||||
from services.sentiment_fetcher import fetch_sentiment_report
|
||||
_fetch_and_save("Sentiment", fetch_sentiment_report)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[InstitutionalScheduler] Loop error: {e}")
|
||||
|
||||
Reference in New Issue
Block a user