You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

71 lines
2.6 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
Earnings Surprise endpoints — SEC EDGAR XBRL EPS data
"""
import logging
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import Response
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.schemas.earnings import EarningsSurpriseEntry, EarningsSurpriseResponse
from app.services.earnings_service import EarningsService
from app.utils.cache import with_cache
router = APIRouter()
logger = logging.getLogger("app.api.v1.earnings")
@router.get(
"/surprise/{symbol}",
response_model=EarningsSurpriseResponse,
summary="Get earnings surprise history",
description=(
"Quarterly EPS surprise: reported vs analyst consensus estimate.\n\n"
"**데이터 소스**: yfinance-plus (`Ticker.earnings_dates`). API 키 불필요.\n"
"**커버리지**: ~25분기 (6년+). 첫 조회 시 자동 인덱싱.\n\n"
"**surprise** = reported_eps - estimated_eps.\n"
"**surprise_percentage** = (surprise / estimated) × 100.\n"
"**streak**: 연속 beat (양수) 또는 miss (음수) 횟수."
),
)
@with_cache(namespace="earnings:surprise", ttl=None, key_params=["symbol", "quarters"])
async def get_earnings_surprise(
symbol: str,
response: Response,
quarters: int = Query(8, ge=1, le=40, description="Number of recent quarters (max ~25 available)"),
force_refresh: bool = Query(False, description="Bypass cache and re-fetch from yfinance"),
db: AsyncSession = Depends(get_db),
):
svc = EarningsService()
if force_refresh:
try:
await svc.index_earnings(db, symbol, force_refresh=True)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
try:
rows, stats = await svc.get_earnings_surprise(db, ticker=symbol, quarters=quarters)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Earnings surprise error for {symbol}: {e}")
raise HTTPException(status_code=502, detail=f"Failed to fetch earnings data: {e}")
entries = [EarningsSurpriseEntry.from_orm_obj(r) for r in rows]
return EarningsSurpriseResponse(
symbol=symbol.upper(),
quarters=entries,
streak=stats["streak"],
avg_surprise_pct=stats["avg_surprise_pct"],
metadata={
"data_source": "SEC_XBRL",
"quarters_requested": quarters,
"quarters_returned": len(entries),
"note": "surprise = current_eps - previous_quarter_eps (QoQ change)",
},
)