mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-18 21:08:07 +00:00
- Add missing numpy, scipy, dbnomics to requirements.txt (fixes ImportError on fresh install) - Sync claude.md with actual codebase: §3 file structure (37 services, 21 routers), §5 API endpoints (92 routes), §6 frontend pages (12), §13 TODO status - Update README.md with current architecture (92 API routes, 21 routers, 37 services), multi-asset overview, research grid, macro dashboard, screener+backtest, multi-jurisdiction filings, and 2026-03-26 changelog entry - Add new routers: dart, edinet, fmp, macro, research - Add new services: cache, dart_fetcher, dart_filing_service, economic_calendar, ecos_fetcher, edinet_filing_service, fmp_client, global_macro_quadrant, kpi_history_service, macro_cycle, macro_fetcher, oecd_cycle, peer_comparison_service, research_dashboard, smart_money_service, yield_fx_service - Add new frontend: macro page, screener+backtest, research grid components, overview (Equity/ETF/Commodity), filings (SEC/DART/EDINET), error boundaries - Remove 6 unused services: copilot_context, crypto_fetcher, fx_fetcher, gemini_analysis, market_data, technical_analysis - Remove obsolete docs: .agent/, AGENT.md, ATLAS_EVALUATION.md, docs/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
133 lines
3.8 KiB
Python
133 lines
3.8 KiB
Python
"""Macro series: FRED (public CSV), optional ECOS (Bank of Korea).
|
|
|
|
FRED uses ``https://fred.stlouisfed.org/graph/fredgraph.csv`` (no API key) so we
|
|
avoid pandas-datareader / legacy dependencies on Python 3.12+.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import io
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
from typing import Any, Dict, List, Optional
|
|
from urllib.request import Request, urlopen
|
|
|
|
from server.utils.safe_float import _safe_float
|
|
|
|
_FRED_CSV = "https://fred.stlouisfed.org/graph/fredgraph.csv"
|
|
_USER_AGENT = "ATLAS-Terminal/1.0 (macro)"
|
|
|
|
|
|
def _parse_iso(s: Optional[str], default: datetime) -> datetime:
|
|
if not s:
|
|
return default
|
|
try:
|
|
return datetime.strptime(s[:10], "%Y-%m-%d")
|
|
except ValueError:
|
|
return default
|
|
|
|
|
|
def fetch_fred_series(
|
|
series_id: str,
|
|
start: Optional[str] = None,
|
|
end: Optional[str] = None,
|
|
) -> List[Dict[str, Any]]:
|
|
"""Download a FRED series via the public graph CSV export."""
|
|
sid = (series_id or "").strip().upper()
|
|
if not sid:
|
|
return []
|
|
|
|
end_dt = _parse_iso(end, datetime.utcnow())
|
|
start_dt = _parse_iso(start, end_dt - timedelta(days=365 * 10))
|
|
|
|
url = f"{_FRED_CSV}?id={sid}"
|
|
try:
|
|
req = Request(url, headers={"User-Agent": _USER_AGENT})
|
|
with urlopen(req, timeout=45) as resp:
|
|
text = resp.read().decode("utf-8", errors="replace")
|
|
except Exception:
|
|
return []
|
|
|
|
rows: List[Dict[str, Any]] = []
|
|
reader = csv.DictReader(io.StringIO(text))
|
|
value_col = sid
|
|
for rec in reader:
|
|
ds = (rec.get("observation_date") or rec.get("DATE") or "").strip()[:10]
|
|
if not ds:
|
|
continue
|
|
try:
|
|
dt = datetime.strptime(ds, "%Y-%m-%d")
|
|
except ValueError:
|
|
continue
|
|
if dt < start_dt or dt > end_dt:
|
|
continue
|
|
raw = rec.get(value_col) or rec.get(sid) or next(
|
|
(rec[k] for k in rec if k not in ("observation_date", "DATE") and rec.get(k)),
|
|
"",
|
|
)
|
|
fv = _safe_float(str(raw).replace(",", ""))
|
|
if fv is not None:
|
|
rows.append({"date": ds, "value": fv})
|
|
return rows
|
|
|
|
|
|
def fetch_oecd_mei() -> List[Dict[str, Any]]:
|
|
"""OECD MEI via pandas-datareader was removed (Py3.13+). Return empty for now."""
|
|
return []
|
|
|
|
|
|
def fetch_ecos_series(
|
|
stat_code: str,
|
|
cycle: str = "M",
|
|
start_ym: str = "201501",
|
|
end_ym: Optional[str] = None,
|
|
) -> List[Dict[str, Any]]:
|
|
"""Bank of Korea ECOS Open API (requires ``ECOS_API_KEY``)."""
|
|
import json
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
key = (os.getenv("ECOS_API_KEY") or "").strip()
|
|
if not key:
|
|
return []
|
|
|
|
code = (stat_code or "").strip()
|
|
if not code:
|
|
return []
|
|
|
|
end_m = end_ym or datetime.utcnow().strftime("%Y%m")
|
|
path = (
|
|
f"/api/StatisticSearch/{urllib.parse.quote(key, safe='')}/json/kr/1/1000/"
|
|
f"{code}/{cycle}/{start_ym}/{end_m}/"
|
|
)
|
|
url = "https://ecos.bok.or.kr" + path
|
|
try:
|
|
req = urllib.request.Request(url, headers={"User-Agent": _USER_AGENT})
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
raw = resp.read().decode("utf-8", errors="replace")
|
|
except Exception:
|
|
return []
|
|
|
|
try:
|
|
payload = json.loads(raw)
|
|
except json.JSONDecodeError:
|
|
return []
|
|
|
|
row_block = (
|
|
payload.get("StatisticSearch", {}).get("row", [])
|
|
)
|
|
if not isinstance(row_block, list):
|
|
return []
|
|
|
|
out: List[Dict[str, Any]] = []
|
|
for r in row_block:
|
|
if not isinstance(r, dict):
|
|
continue
|
|
t = r.get("TIME") or r.get("time")
|
|
v = r.get("DATA_VALUE") or r.get("DATA")
|
|
fv = _safe_float(v)
|
|
if t and fv is not None:
|
|
out.append({"period": str(t), "value": fv})
|
|
return out
|