""" Price service for fetching stock prices. This is a mock implementation that uses DB data. Can be replaced with real OpenAPI implementation later. """ from datetime import date, timedelta from decimal import Decimal from typing import Dict, List, Optional from sqlalchemy.orm import Session from sqlalchemy import func from app.models.stock import Price class PriceData: """Price data point.""" def __init__( self, ticker: str, date: date, open: Decimal, high: Decimal, low: Decimal, close: Decimal, volume: int, ): self.ticker = ticker self.date = date self.open = open self.high = high self.low = low self.close = close self.volume = volume class PriceService: """ Service for fetching stock prices. Current implementation uses DB data (prices table). Can be extended to use real-time OpenAPI in the future. """ def __init__(self, db: Session): self.db = db def get_current_price(self, ticker: str) -> Optional[Decimal]: """ Get current price for a single ticker. Returns the most recent closing price from DB. """ result = ( self.db.query(Price.close) .filter(Price.ticker == ticker) .order_by(Price.date.desc()) .first() ) return Decimal(str(result[0])) if result else None def get_current_prices(self, tickers: List[str]) -> Dict[str, Decimal]: """ Get current prices for multiple tickers. Returns a dict mapping ticker to most recent closing price. """ if not tickers: return {} # Subquery to get max date for each ticker subquery = ( self.db.query( Price.ticker, func.max(Price.date).label('max_date') ) .filter(Price.ticker.in_(tickers)) .group_by(Price.ticker) .subquery() ) # Get prices at max date results = ( self.db.query(Price.ticker, Price.close) .join( subquery, (Price.ticker == subquery.c.ticker) & (Price.date == subquery.c.max_date) ) .all() ) return {ticker: Decimal(str(close)) for ticker, close in results} def get_price_history( self, ticker: str, start_date: date, end_date: date, ) -> List[PriceData]: """ Get price history for a ticker within date range. """ results = ( self.db.query(Price) .filter( Price.ticker == ticker, Price.date >= start_date, Price.date <= end_date, ) .order_by(Price.date) .all() ) return [ PriceData( ticker=p.ticker, date=p.date, open=Decimal(str(p.open)), high=Decimal(str(p.high)), low=Decimal(str(p.low)), close=Decimal(str(p.close)), volume=p.volume, ) for p in results ] def get_price_at_date(self, ticker: str, target_date: date) -> Optional[Decimal]: """ Get closing price at specific date. If no price on exact date, returns most recent price before that date. """ result = ( self.db.query(Price.close) .filter( Price.ticker == ticker, Price.date <= target_date, ) .order_by(Price.date.desc()) .first() ) return Decimal(str(result[0])) if result else None def get_prices_at_date( self, tickers: List[str], target_date: date, ) -> Dict[str, Decimal]: """ Get closing prices for multiple tickers at specific date. """ if not tickers: return {} # Subquery to get max date <= target_date for each ticker subquery = ( self.db.query( Price.ticker, func.max(Price.date).label('max_date') ) .filter( Price.ticker.in_(tickers), Price.date <= target_date, ) .group_by(Price.ticker) .subquery() ) # Get prices at those dates results = ( self.db.query(Price.ticker, Price.close) .join( subquery, (Price.ticker == subquery.c.ticker) & (Price.date == subquery.c.max_date) ) .all() ) return {ticker: Decimal(str(close)) for ticker, close in results}