|
import requests |
|
from fastapi import FastAPI, HTTPException, status |
|
from pydantic import BaseModel |
|
from typing import List, Dict, Optional, Any |
|
|
|
|
|
from data_ingestion.api_loader import get_daily_adjusted_prices, DataIngestionError |
|
import logging |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" |
|
) |
|
logger = logging.getLogger(__name__) |
|
|
|
app = FastAPI(title="API Agent") |
|
|
|
|
|
class MarketDataRequest(BaseModel): |
|
tickers: List[str] |
|
start_date: Optional[str] = None |
|
end_date: Optional[str] = None |
|
data_type: Optional[str] = "adjClose" |
|
|
|
|
|
@app.post("/get_market_data") |
|
def get_market_data(request: MarketDataRequest): |
|
""" |
|
Fetches daily adjusted market data by calling the data_ingestion layer (FMP). |
|
Returns adjusted close prices per ticker per date. |
|
""" |
|
result: Dict[str, Dict[str, float]] = {} |
|
errors: Dict[str, str] = {} |
|
warnings: Dict[str, str] = {} |
|
|
|
key = ( |
|
request.data_type |
|
if request.data_type in ["open", "high", "low", "close", "adjClose", "volume"] |
|
else "adjClose" |
|
) |
|
|
|
for ticker in request.tickers: |
|
try: |
|
raw = get_daily_adjusted_prices(ticker) |
|
|
|
time_series: Dict[str, Any] = {} |
|
if isinstance(raw, dict): |
|
time_series = raw |
|
elif isinstance(raw, list): |
|
logger.warning( |
|
f"Loader returned list for {ticker}; filtering dict entries." |
|
) |
|
for rec in raw: |
|
if isinstance(rec, dict) and "date" in rec: |
|
date_val = rec["date"] |
|
time_series[date_val] = rec |
|
else: |
|
logger.warning( |
|
f"Skipping non-dict or missing-date entry for {ticker}: {rec}" |
|
) |
|
else: |
|
raise DataIngestionError( |
|
f"Unexpected format from loader for {ticker}: {type(raw)}" |
|
) |
|
|
|
ticker_prices: Dict[str, float] = {} |
|
for date_str, values in time_series.items(): |
|
if not isinstance(values, dict): |
|
warnings.setdefault(ticker, "") |
|
warnings[ticker] += f" Non-dict for {date_str}; skipped." |
|
continue |
|
if key not in values: |
|
warnings.setdefault(ticker, "") |
|
warnings[ticker] += f" Missing '{key}' on {date_str}." |
|
continue |
|
try: |
|
ticker_prices[date_str] = float(values[key]) |
|
except (TypeError, ValueError): |
|
warnings.setdefault(ticker, "") |
|
warnings[ticker] += f" Invalid '{key}' value on {date_str}." |
|
|
|
if ticker_prices: |
|
result[ticker] = ticker_prices |
|
logger.info(f"Fetched {len(ticker_prices)} points for {ticker}.") |
|
else: |
|
warnings.setdefault(ticker, "") |
|
warnings[ticker] += " No valid data points found." |
|
|
|
except (requests.RequestException, DataIngestionError) as err: |
|
errors[ticker] = str(err) |
|
logger.error(f"Error fetching {ticker}: {err}") |
|
except Exception as err: |
|
errors[ticker] = f"Unexpected error for {ticker}: {err}" |
|
logger.error(errors[ticker]) |
|
|
|
if not result and errors: |
|
raise HTTPException( |
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
|
detail="Failed to fetch market data for all tickers.", |
|
) |
|
|
|
return {"result": result, "errors": errors, "warnings": warnings} |
|
|