jashdoshi77's picture
QuantHedge: Full deployment with Docker + nginx + uvicorn
9d29748
Raw
History Blame Contribute Delete
5.77 kB
"""
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 ────────────────────────────────────────────────────────
@router.get("/search-tickers")
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 ────────────────────────────────────────────────────────────
@router.get("/exchanges", response_model=List[ExchangeOut])
async def list_exchanges():
"""Get all supported exchanges."""
return market_metadata.get_all_exchanges()
@router.get("/exchanges/{code}")
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
@router.get("/markets")
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
@router.get("/sectors")
async def list_sectors():
"""Get GICS sector classification."""
return {"sectors": market_metadata.get_sectors()}
@router.get("/popular-tickers")
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 ───────────────────────────────────────────────────────────────
@router.get("/prices/{ticker}")
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
@router.get("/company/{ticker}")
async def get_company_info(ticker: str):
"""Fetch detailed company information."""
return await yahoo_adapter.fetch_company_info(ticker)
# ── Features ─────────────────────────────────────────────────────────────
@router.post("/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 ─────────────────────────────────────────────────────────────────
@router.post("/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,
)
@router.get("/news/headlines")
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)