zephyrdark bc484fcb07 feat: add market data API endpoints
- GET /api/market/stocks/{ticker}
- GET /api/market/stocks/{ticker}/prices
- GET /api/market/search

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 09:01:48 +09:00

113 lines
3.1 KiB
Python

"""
Market data API endpoints.
"""
from typing import List, Optional
from datetime import date, timedelta
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.api.deps import CurrentUser
from app.models.stock import Stock, Valuation, Price, Sector
from app.schemas.strategy import StockInfo, StockSearchResult, PriceData
router = APIRouter(prefix="/api/market", tags=["market"])
@router.get("/stocks/{ticker}", response_model=StockInfo)
async def get_stock(
ticker: str,
current_user: CurrentUser,
db: Session = Depends(get_db),
):
"""Get stock information."""
stock = db.query(Stock).filter(Stock.ticker == ticker).first()
if not stock:
raise HTTPException(status_code=404, detail="Stock not found")
valuation = (
db.query(Valuation)
.filter(Valuation.ticker == ticker)
.order_by(Valuation.base_date.desc())
.first()
)
sector = db.query(Sector).filter(Sector.ticker == ticker).first()
return StockInfo(
ticker=stock.ticker,
name=stock.name,
market=stock.market,
sector_name=sector.sector_name if sector else None,
stock_type=stock.stock_type.value if stock.stock_type else "common",
close_price=stock.close_price,
market_cap=int(stock.market_cap / 100_000_000) if stock.market_cap else None,
per=valuation.per if valuation else None,
pbr=valuation.pbr if valuation else None,
psr=valuation.psr if valuation else None,
pcr=valuation.pcr if valuation else None,
dividend_yield=valuation.dividend_yield if valuation else None,
eps=stock.eps,
bps=stock.bps,
base_date=stock.base_date,
)
@router.get("/stocks/{ticker}/prices", response_model=List[PriceData])
async def get_stock_prices(
ticker: str,
current_user: CurrentUser,
db: Session = Depends(get_db),
days: int = Query(default=365, ge=1, le=3650),
):
"""Get historical prices for a stock."""
start_date = date.today() - timedelta(days=days)
prices = (
db.query(Price)
.filter(Price.ticker == ticker)
.filter(Price.date >= start_date)
.order_by(Price.date.desc())
.all()
)
return [
PriceData(
date=p.date,
open=p.open,
high=p.high,
low=p.low,
close=p.close,
volume=p.volume,
)
for p in prices
]
@router.get("/search", response_model=List[StockSearchResult])
async def search_stocks(
q: str = Query(..., min_length=1),
current_user: CurrentUser = None,
db: Session = Depends(get_db),
limit: int = Query(default=20, ge=1, le=100),
):
"""Search stocks by ticker or name."""
stocks = (
db.query(Stock)
.filter(
(Stock.ticker.ilike(f"%{q}%")) | (Stock.name.ilike(f"%{q}%"))
)
.limit(limit)
.all()
)
return [
StockSearchResult(
ticker=s.ticker,
name=s.name,
market=s.market,
)
for s in stocks
]