diff --git a/backend/services/calendar_sync.py b/backend/services/calendar_sync.py index 8192a6c..eb27720 100644 --- a/backend/services/calendar_sync.py +++ b/backend/services/calendar_sync.py @@ -1,7 +1,9 @@ """ -Unified calendar sync — ForexFactory only. +Unified calendar sync. 1. FF live JSON (nfs.faireconomy.media) → this week + next week (actual values, precise) 2. FF HTML scraper → weeks 3..N ahead (upcoming forecasts) + 3. FMP API fallback → weeks 3..N ahead, only if step 2 got blocked (ForexFactory now + 403s the scraper — likely Cloudflare) AND an FMP key is configured (services.fmp_calendar) Called by the background loop in main.py and manually via POST /api/eco/calendar-sync. """ import logging @@ -44,6 +46,18 @@ def calendar_sync(weeks_ahead: int = 8) -> Dict[str, Any]: if scrape_weeks > 0: r_scrape = scrape_upcoming(weeks_ahead=scrape_weeks) result["scrape"] = r_scrape + + # FF's scraper gets 403'd (Cloudflare) often enough that "weeks 3..N" can + # silently come back empty — fall back to FMP for that same range when it does. + if not r_scrape.get("total_upserted"): + from services.fmp_calendar import check_fmp_key, fetch_upcoming + if check_fmp_key()["configured"]: + r_fmp = fetch_upcoming(weeks_ahead=weeks_ahead) + result["fmp_fallback"] = r_fmp + if r_fmp.get("total_upserted"): + source = "ff+fmp" + else: + result["fmp_fallback"] = {"skipped": "no_key"} else: result["scrape"] = {"skipped": True} diff --git a/frontend/src/pages/Config.tsx b/frontend/src/pages/Config.tsx index abc9096..fa95490 100644 --- a/frontend/src/pages/Config.tsx +++ b/frontend/src/pages/Config.tsx @@ -798,6 +798,7 @@ export default function Config() { const [newsapiKey, setNewsapiKey] = useState('') const [eiaKey, setEiaKey] = useState('') const [fredKey, setFredKey] = useState('') + const [fmpKey, setFmpKey] = useState('') const [calendarRefreshH, setCalendarRefreshH] = useState(6) const [showKeys, setShowKeys] = useState(false) const [savedMsg, setSavedMsg] = useState('') @@ -960,11 +961,12 @@ export default function Config() { if (newsapiKey) keys.newsapi_key = newsapiKey if (eiaKey) keys.eia_api_key = eiaKey if (fredKey) keys.fred_api_key = fredKey + if (fmpKey) keys.fmp_api_key = fmpKey updateApiKeys(keys, { onSuccess: () => { setSavedMsg('API keys saved') setTimeout(() => setSavedMsg(''), 2000) - setOpenaiKey(''); setNewsapiKey(''); setEiaKey(''); setFredKey('') + setOpenaiKey(''); setNewsapiKey(''); setEiaKey(''); setFredKey(''); setFmpKey('') } }) } @@ -1597,7 +1599,7 @@ export default function Config() { Calendar Auto-Sync
- Interval between automatic economic calendar syncs (FXStreet primary, Forex Factory fallback). + Interval between automatic economic calendar syncs (Forex Factory live JSON + HTML scraper; FMP API used as fallback for weeks 3+ if the FF scraper gets blocked — needs an FMP key below).
@@ -1637,11 +1639,12 @@ export default function Config() { {showKeys ? : }
-
+
{[ { label: 'NewsAPI Key', value: newsapiKey, setter: setNewsapiKey, placeholder: 'Get at newsapi.org' }, { label: 'EIA API Key', value: eiaKey, setter: setEiaKey, placeholder: 'Get at eia.gov' }, { label: 'FRED API Key', value: fredKey, setter: setFredKey, placeholder: 'Get at fred.stlouisfed.org' }, + { label: 'FMP API Key', value: fmpKey, setter: setFmpKey, placeholder: 'Calendar fallback — financialmodelingprep.com' }, ].map(({ label, value, setter, placeholder }) => (
@@ -1657,7 +1660,7 @@ export default function Config() {