test1 / data /finance_fetchers.py
davidkim205's picture
fundamental 내용 추가
2cc39e6
import yfinance as yf
from datetime import datetime
from typing import Any
def _safe_value(data, key, default=None):
val = data.get(key, default)
return default if (val is None or val != val) else val
def _to_billions(value):
if value is None:
return None
try:
return round(float(value) / 1e9, 2)
except (TypeError, ValueError):
return None
def _format_date_yyyymmdd(value):
if value is None:
return None
if isinstance(value, (int, float)):
try:
return datetime.fromtimestamp(value).strftime("%Y-%m-%d")
except (OverflowError, OSError, ValueError):
return None
text = str(value).strip()
return text[:10] if text else None
def fetch_price_data(ticker, period="1y"):
stock = yf.Ticker(ticker)
hist = stock.history(period=period)
if hist.empty:
return {}
close = hist["Close"]
current = close.iloc[-1]
week_ago = close.iloc[-6] if len(close) >= 6 else close.iloc[0]
month_ago = close.iloc[-22] if len(close) >= 22 else close.iloc[0]
year_start = close.iloc[0]
return {
"current_price": round(float(current), 2),
"change_1w_pct": round((current / week_ago - 1) * 100, 2),
"change_1m_pct": round((current / month_ago - 1) * 100, 2),
"change_ytd_pct": round((current / year_start - 1) * 100, 2),
"52w_high": round(float(close.max()), 2),
"52w_low": round(float(close.min()), 2),
"avg_volume_30d": int(hist["Volume"].tail(30).mean()),
}
def fetch_fundamentals(ticker):
info = yf.Ticker(ticker).info or {}
return {
"company_name": _safe_value(info, "longName", ticker),
"symbol": _safe_value(info, "symbol", ticker),
"exchange": _safe_value(info, "exchange", "N/A"),
"quote_type": _safe_value(info, "quoteType"),
"currency": _safe_value(info, "currency"),
"sector": _safe_value(info, "sector", "N/A"),
"industry": _safe_value(info, "industry", "N/A"),
"country": _safe_value(info, "country"),
"city": _safe_value(info, "city"),
"website": _safe_value(info, "website"),
"full_time_employees": _safe_value(info, "fullTimeEmployees"),
"market_cap_b": _to_billions(_safe_value(info, "marketCap")),
"enterprise_value_b": _to_billions(_safe_value(info, "enterpriseValue")),
"shares_outstanding_b": _to_billions(_safe_value(info, "sharesOutstanding")),
"float_shares_b": _to_billions(_safe_value(info, "floatShares")),
"pe_ratio": _safe_value(info, "trailingPE"),
"forward_pe": _safe_value(info, "forwardPE"),
"pb_ratio": _safe_value(info, "priceToBook"),
"ps_ratio": _safe_value(info, "priceToSalesTrailing12Months"),
"ev_to_ebitda": _safe_value(info, "enterpriseToEbitda"),
"trailing_eps": _safe_value(info, "trailingEps"),
"forward_eps": _safe_value(info, "forwardEps"),
"roe": _safe_value(info, "returnOnEquity"),
"roa": _safe_value(info, "returnOnAssets"),
"gross_margin": _safe_value(info, "grossMargins"),
"ebitda_margin": _safe_value(info, "ebitdaMargins"),
"operating_margin": _safe_value(info, "operatingMargins"),
"profit_margin": _safe_value(info, "profitMargins"),
"revenue_growth": _safe_value(info, "revenueGrowth"),
"earnings_growth": _safe_value(info, "earningsGrowth"),
"debt_to_equity": _safe_value(info, "debtToEquity"),
"current_ratio": _safe_value(info, "currentRatio"),
"quick_ratio": _safe_value(info, "quickRatio"),
"total_cash_b": _to_billions(_safe_value(info, "totalCash")),
"total_debt_b": _to_billions(_safe_value(info, "totalDebt")),
"operating_cashflow_b": _to_billions(_safe_value(info, "operatingCashflow")),
"free_cashflow_b": _to_billions(_safe_value(info, "freeCashflow")),
"dividend_rate": _safe_value(info, "dividendRate"),
"dividend_yield": _safe_value(info, "dividendYield"),
"payout_ratio": _safe_value(info, "payoutRatio"),
"ex_dividend_date": _format_date_yyyymmdd(_safe_value(info, "exDividendDate")),
"held_percent_insiders": _safe_value(info, "heldPercentInsiders"),
"held_percent_institutions": _safe_value(info, "heldPercentInstitutions"),
"short_ratio": _safe_value(info, "shortRatio"),
"short_percent_float": _safe_value(info, "shortPercentOfFloat"),
"analyst_target": _safe_value(info, "targetMeanPrice"),
"target_high_price": _safe_value(info, "targetHighPrice"),
"target_low_price": _safe_value(info, "targetLowPrice"),
"analyst_opinion_count": _safe_value(info, "numberOfAnalystOpinions"),
"recommendation": _safe_value(info, "recommendationKey"),
"beta": _safe_value(info, "beta"),
"description": (_safe_value(info, "longBusinessSummary") or "")[:400],
}
def fetch_news(ticker, max_items=8):
try:
raw_news = yf.Ticker(ticker).news or []
snippets = []
for item in raw_news[:max_items]:
content = item.get("content", {})
title = content.get("title") or item.get("title", "")
summary = content.get("summary") or ""
pub_raw = content.get("pubDate") or item.get("providerPublishTime", "")
pub_dt = _parse_pub_date(pub_raw)
if title:
line = f"[{pub_dt}] {title}"
if summary:
line += f" — {summary[:120]}"
snippets.append(line)
return snippets
except Exception as e:
print(f" 뉴스 수집 오류: {e}")
return []
def _parse_pub_date(pub_raw):
if isinstance(pub_raw, int):
return datetime.fromtimestamp(pub_raw).strftime("%Y-%m-%d")
if isinstance(pub_raw, str) and pub_raw:
return pub_raw[:10]
return "날짜 미상"