feat: calendar eco
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
<RefreshCw className="w-4 h-4 text-indigo-400" /> Calendar Auto-Sync
|
||||
</h2>
|
||||
<div className="text-xs text-slate-400 mb-3">
|
||||
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).
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="text-xs text-slate-500 whitespace-nowrap">Refresh every</label>
|
||||
@@ -1637,11 +1639,12 @@ export default function Config() {
|
||||
{showKeys ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{[
|
||||
{ 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 }) => (
|
||||
<div key={label}>
|
||||
<label className="text-xs text-slate-500 mb-1 block">{label}</label>
|
||||
@@ -1657,7 +1660,7 @@ export default function Config() {
|
||||
</div>
|
||||
<button
|
||||
onClick={saveKeys}
|
||||
disabled={savingKeys || (!newsapiKey && !eiaKey && !fredKey)}
|
||||
disabled={savingKeys || (!newsapiKey && !eiaKey && !fredKey && !fmpKey)}
|
||||
className="mt-3 flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
|
||||
<Save className="w-4 h-4" />
|
||||
{savingKeys ? 'Saving...' : 'Save keys'}
|
||||
|
||||
Reference in New Issue
Block a user