mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-21 14:48:05 +00:00
Complete migration from Streamlit to Next.js 14 App Router + FastAPI backend. Frontend (Next.js 14): - 10 pages: Overview, Research, Valuation, Technical, Markets, Earnings, News, Portfolio, Filings, Settings - Terminal Noir dark theme with custom Tailwind config - TradingView Lightweight Charts for candlestick/volume - Valuation: DCF, Sensitivity Matrix, Monte Carlo, Tornado, Reverse DCF - Financial Statements table with YoY growth badges and margin rows - SEC EDGAR inline filing viewer with section tabs - News split-view with iframe article embedding - Technical Analysis with RSI, MACD, Bollinger, Fibonacci, Moving Averages - Earnings beat/miss visualization - AI Copilot chat panel with Gemini integration Backend (FastAPI): - 13 routers: market_data, financials, valuation, technical, earnings, insider, edgar, news, portfolio, analysis, chat, estimates, fx - Services: DCF engine, Monte Carlo simulation, sensitivity analysis, risk metrics, SEC parser, technical indicators - yfinance + yahooquery data sources with fallback pattern - SQLite caching layer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
152 lines
4.1 KiB
Python
152 lines
4.1 KiB
Python
"""FX rate fetcher -- yfinance-based with in-memory TTL cache.
|
|
|
|
Provides current rates and 1-year history for major currency pairs.
|
|
"""
|
|
|
|
import time
|
|
from typing import Any, Dict, List, Optional, Tuple
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# In-memory cache with configurable TTL
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_cache: Dict[str, Any] = {}
|
|
_cache_ts: Dict[str, float] = {}
|
|
_CACHE_TTL = 60 # seconds
|
|
|
|
# Default pairs
|
|
DEFAULT_PAIRS: List[str] = ["USDKRW=X", "GBPUSD=X", "EURUSD=X", "USDJPY=X"]
|
|
|
|
|
|
def _get_cached(key: str) -> Optional[Any]:
|
|
if key in _cache and (time.time() - _cache_ts.get(key, 0)) < _CACHE_TTL:
|
|
return _cache[key]
|
|
return None
|
|
|
|
|
|
def _set_cached(key: str, value: Any) -> None:
|
|
_cache[key] = value
|
|
_cache_ts[key] = time.time()
|
|
|
|
|
|
def _normalise_pair(pair: str) -> str:
|
|
"""Ensure pair is in Yahoo Finance format (e.g. 'USDKRW=X')."""
|
|
p = pair.upper().replace("/", "").strip()
|
|
if not p.endswith("=X"):
|
|
p = f"{p}=X"
|
|
return p
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Current rates
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def fetch_fx_rate(pair: str) -> Optional[float]:
|
|
"""Fetch the latest exchange rate for a single currency pair.
|
|
|
|
Parameters
|
|
----------
|
|
pair:
|
|
Currency pair string, e.g. ``"USDKRW"``, ``"USDKRW=X"``, ``"EUR/USD"``.
|
|
|
|
Returns
|
|
-------
|
|
float or None
|
|
The latest rate, or None if unavailable.
|
|
"""
|
|
symbol = _normalise_pair(pair)
|
|
cache_key = f"fx_rate:{symbol}"
|
|
cached = _get_cached(cache_key)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
try:
|
|
import yfinance as yf
|
|
|
|
ticker = yf.Ticker(symbol)
|
|
fast = getattr(ticker, "fast_info", None)
|
|
if fast:
|
|
price = getattr(fast, "last_price", None)
|
|
if price and float(price) > 0:
|
|
rate = float(price)
|
|
_set_cached(cache_key, rate)
|
|
return rate
|
|
hist = ticker.history(period="1d")
|
|
if hist is not None and not hist.empty:
|
|
rate = float(hist["Close"].iloc[-1])
|
|
_set_cached(cache_key, rate)
|
|
return rate
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
|
|
def fetch_multiple_rates(
|
|
pairs: Optional[List[str]] = None,
|
|
) -> Dict[str, float]:
|
|
"""Fetch current rates for multiple pairs.
|
|
|
|
Parameters
|
|
----------
|
|
pairs:
|
|
List of pair strings. Defaults to DEFAULT_PAIRS.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
Mapping of normalised pair symbol -> rate.
|
|
"""
|
|
pairs = pairs or DEFAULT_PAIRS
|
|
rates: Dict[str, float] = {}
|
|
for pair in pairs:
|
|
rate = fetch_fx_rate(pair)
|
|
if rate is not None:
|
|
key = _normalise_pair(pair).replace("=X", "")
|
|
rates[key] = round(rate, 4)
|
|
return rates
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1-year history
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def fetch_fx_history(
|
|
pair: str,
|
|
period: str = "1y",
|
|
) -> Tuple[List[str], List[float]]:
|
|
"""Fetch historical daily closing rates for a currency pair.
|
|
|
|
Parameters
|
|
----------
|
|
pair:
|
|
Currency pair string.
|
|
period:
|
|
yfinance period string (default ``"1y"``).
|
|
|
|
Returns
|
|
-------
|
|
tuple of (dates, rates)
|
|
dates: list of ISO date strings
|
|
rates: list of float closing prices
|
|
"""
|
|
symbol = _normalise_pair(pair)
|
|
cache_key = f"fx_hist:{symbol}:{period}"
|
|
cached = _get_cached(cache_key)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
try:
|
|
import yfinance as yf
|
|
|
|
ticker = yf.Ticker(symbol)
|
|
hist = ticker.history(period=period)
|
|
if hist is None or hist.empty:
|
|
return ([], [])
|
|
dates = [d.strftime("%Y-%m-%d") for d in hist.index]
|
|
rates = [round(float(v), 4) for v in hist["Close"]]
|
|
result = (dates, rates)
|
|
_set_cached(cache_key, result)
|
|
return result
|
|
except Exception:
|
|
return ([], [])
|