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() {