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

"""
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 (SEC XBRL) vs estimated (Alpha Vantage consensus).\n\n"
"**데이터 소스**:\n"
"- Reported EPS: SEC EDGAR XBRL (무료, 2009년~)\n"
"- Estimated EPS: Alpha Vantage EARNINGS API (`ALPHA_VANTAGE_API_KEY` 설정 시)\n\n"
"**surprise** = reported_eps - estimated_eps (애널리스트 컨센서스 대비).\n"
"API 키 미설정 시 estimated_eps 없이 reported_eps만 반환."
),
)
@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=80, description="Number of recent quarters (max 80 = ~20 years)"),
force_refresh: bool = Query(False, description="Bypass cache and re-fetch from SEC EDGAR"),
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)",
},
)