Spaces:
Sleeping
Sleeping
| """ | |
| Data & Market Router. | |
| Endpoints for price data, company info, market metadata, | |
| news, features, and data ingestion triggers. | |
| """ | |
| from __future__ import annotations | |
| from typing import List, Optional | |
| from fastapi import APIRouter, Depends, Query | |
| from app.dependencies import get_current_user | |
| from app.schemas.market import ( | |
| AssetSearchResult, | |
| ExchangeOut, | |
| FeatureRequest, | |
| FeatureResponse, | |
| NewsArticleOut, | |
| NewsSearchRequest, | |
| PriceHistoryRequest, | |
| PriceHistoryResponse, | |
| ) | |
| from app.services.data_ingestion.market_metadata import market_metadata | |
| from app.services.data_ingestion.news import news_adapter | |
| from app.services.data_ingestion.yahoo import yahoo_adapter | |
| from app.services.feature_engineering.pipeline import feature_pipeline | |
| router = APIRouter(prefix="/data", tags=["Market Data"]) | |
| # ββ Ticker Search ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def search_tickers( | |
| q: str = Query("", description="Search query for tickers"), | |
| limit: int = Query(15, ge=1, le=50), | |
| ): | |
| """Search for tickers across all global markets (stocks, ETFs, futures, options, indices, crypto).""" | |
| if not q or len(q.strip()) < 1: | |
| return {"results": []} | |
| results = await yahoo_adapter.search_tickers(q.strip(), limit=limit) | |
| return {"results": results} | |
| # ββ Exchanges ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def list_exchanges(): | |
| """Get all supported exchanges.""" | |
| return market_metadata.get_all_exchanges() | |
| async def get_exchange(code: str): | |
| """Get exchange details by code.""" | |
| exc = market_metadata.get_exchange(code) | |
| if not exc: | |
| return {"error": f"Exchange '{code}' not found"} | |
| return exc | |
| async def list_markets(): | |
| """Get all supported market regions with their popular tickers.""" | |
| markets = market_metadata.get_all_markets() | |
| result = {} | |
| for m in markets: | |
| result[m] = { | |
| "tickers": market_metadata.get_popular_tickers(m), | |
| "exchanges": [ | |
| e for e in market_metadata.get_all_exchanges() | |
| if any(m.lower() in e.get("country", "").lower() for _ in [1]) | |
| ], | |
| } | |
| return result | |
| async def list_sectors(): | |
| """Get GICS sector classification.""" | |
| return {"sectors": market_metadata.get_sectors()} | |
| async def popular_tickers(market: str = "US"): | |
| """Get popular tickers for a market region.""" | |
| return {"market": market, "tickers": market_metadata.get_popular_tickers(market)} | |
| # ββ Prices βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def get_price_history( | |
| ticker: str, | |
| period: str = Query("1y", description="1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, max"), | |
| interval: str = Query("1d", description="1d, 1wk, 1mo"), | |
| ): | |
| """Fetch historical OHLCV price data for a ticker.""" | |
| data = await yahoo_adapter.fetch_price_history(ticker, period=period, interval=interval) | |
| return data | |
| async def get_company_info(ticker: str): | |
| """Fetch detailed company information.""" | |
| return await yahoo_adapter.fetch_company_info(ticker) | |
| # ββ Features βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def compute_features(request: FeatureRequest): | |
| """Compute technical features for a ticker.""" | |
| df = await yahoo_adapter.get_price_dataframe(request.ticker, period=request.period) | |
| if df.empty: | |
| return {"ticker": request.ticker, "data": [], "error": "No price data available"} | |
| featured = feature_pipeline.compute_all_features(df, request.features) | |
| # Convert to response format | |
| feature_cols = [c for c in featured.columns if c not in ["Open", "High", "Low", "Close", "Volume", "Dividends", "Stock Splits"]] | |
| data = [] | |
| for idx, row in featured.iterrows(): | |
| date_str = idx.strftime("%Y-%m-%d") if hasattr(idx, "strftime") else str(idx) | |
| features_dict = {col: round(float(row[col]), 6) if not (row[col] != row[col]) else None for col in feature_cols} | |
| data.append({"date": date_str, "features": features_dict}) | |
| return { | |
| "ticker": request.ticker, | |
| "data": data[-60:], # Last 60 data points | |
| "available_features": feature_cols, | |
| } | |
| # ββ News βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def search_news(request: NewsSearchRequest): | |
| """Search for news articles.""" | |
| return await news_adapter.fetch_articles( | |
| query=request.query, | |
| from_date=request.from_date, | |
| to_date=request.to_date, | |
| sources=request.sources, | |
| page_size=request.page_size, | |
| ) | |
| async def get_headlines( | |
| country: str = "us", | |
| category: Optional[str] = None, | |
| ): | |
| """Get top news headlines.""" | |
| return await news_adapter.fetch_headlines(country=country, category=category) | |