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
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)",
|
|
},
|
|
)
|