aboutsummaryrefslogtreecommitdiff
path: root/services/data_service.py
diff options
context:
space:
mode:
authorTyler <tyler@tylerhoang.xyz>2026-03-28 23:01:14 -0700
committerTyler <tyler@tylerhoang.xyz>2026-03-28 23:01:14 -0700
commit23675b39b8055a8568cdcf71f66482b9d0cf90a9 (patch)
tree14e42cf710b47072e904b1c21d7322352ae1823c /services/data_service.py
Initial commit — Prism financial analysis dashboard
Streamlit app with market bar, price chart, financial statements, DCF valuation engine, comparable companies, and news feed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'services/data_service.py')
-rw-r--r--services/data_service.py109
1 files changed, 109 insertions, 0 deletions
diff --git a/services/data_service.py b/services/data_service.py
new file mode 100644
index 0000000..fa9b026
--- /dev/null
+++ b/services/data_service.py
@@ -0,0 +1,109 @@
+"""yfinance wrapper — price history, financial statements, company info."""
+import yfinance as yf
+import pandas as pd
+import streamlit as st
+
+
+@st.cache_data(ttl=60)
+def search_tickers(query: str) -> list[dict]:
+ """Search for tickers by company name or symbol. Returns list of {symbol, name, exchange}."""
+ if not query or len(query.strip()) < 2:
+ return []
+ try:
+ results = yf.Search(query.strip(), max_results=8).quotes
+ out = []
+ for r in results:
+ symbol = r.get("symbol", "")
+ name = r.get("longname") or r.get("shortname") or symbol
+ exchange = r.get("exchange") or r.get("exchDisp", "")
+ if symbol:
+ out.append({"symbol": symbol, "name": name, "exchange": exchange})
+ return out
+ except Exception:
+ return []
+
+
+@st.cache_data(ttl=300)
+def get_company_info(ticker: str) -> dict:
+ """Return company info dict from yfinance."""
+ t = yf.Ticker(ticker.upper())
+ info = t.info or {}
+ return info
+
+
+@st.cache_data(ttl=300)
+def get_price_history(ticker: str, period: str = "1y") -> pd.DataFrame:
+ """Return OHLCV price history."""
+ t = yf.Ticker(ticker.upper())
+ df = t.history(period=period)
+ df.index = pd.to_datetime(df.index)
+ return df
+
+
+@st.cache_data(ttl=3600)
+def get_income_statement(ticker: str, quarterly: bool = False) -> pd.DataFrame:
+ t = yf.Ticker(ticker.upper())
+ df = t.quarterly_income_stmt if quarterly else t.income_stmt
+ return df if df is not None else pd.DataFrame()
+
+
+@st.cache_data(ttl=3600)
+def get_balance_sheet(ticker: str, quarterly: bool = False) -> pd.DataFrame:
+ t = yf.Ticker(ticker.upper())
+ df = t.quarterly_balance_sheet if quarterly else t.balance_sheet
+ return df if df is not None else pd.DataFrame()
+
+
+@st.cache_data(ttl=3600)
+def get_cash_flow(ticker: str, quarterly: bool = False) -> pd.DataFrame:
+ t = yf.Ticker(ticker.upper())
+ df = t.quarterly_cashflow if quarterly else t.cashflow
+ return df if df is not None else pd.DataFrame()
+
+
+@st.cache_data(ttl=300)
+def get_market_indices() -> dict:
+ """Return latest price + day change % for major indices."""
+ symbols = {
+ "S&P 500": "^GSPC",
+ "NASDAQ": "^IXIC",
+ "DOW": "^DJI",
+ "VIX": "^VIX",
+ }
+ result = {}
+ for name, sym in symbols.items():
+ try:
+ t = yf.Ticker(sym)
+ hist = t.history(period="2d")
+ if len(hist) >= 2:
+ prev_close = hist["Close"].iloc[-2]
+ last = hist["Close"].iloc[-1]
+ pct_change = (last - prev_close) / prev_close
+ elif len(hist) == 1:
+ last = hist["Close"].iloc[-1]
+ pct_change = 0.0
+ else:
+ result[name] = {"price": None, "change_pct": None}
+ continue
+ result[name] = {"price": float(last), "change_pct": float(pct_change)}
+ except Exception:
+ result[name] = {"price": None, "change_pct": None}
+ return result
+
+
+@st.cache_data(ttl=3600)
+def get_free_cash_flow_series(ticker: str) -> pd.Series:
+ """Return annual Free Cash Flow series (most recent first)."""
+ t = yf.Ticker(ticker.upper())
+ cf = t.cashflow
+ if cf is None or cf.empty:
+ return pd.Series(dtype=float)
+ if "Free Cash Flow" in cf.index:
+ return cf.loc["Free Cash Flow"].dropna()
+ # Compute from operating CF - capex
+ try:
+ op = cf.loc["Operating Cash Flow"]
+ capex = cf.loc["Capital Expenditure"]
+ return (op + capex).dropna()
+ except KeyError:
+ return pd.Series(dtype=float)