""" Quant strategy API endpoints. """ from typing import Set from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from app.core.database import get_db from app.api.deps import CurrentUser from app.models.stock import ETF from app.schemas.strategy import ( MultiFactorRequest, QualityRequest, ValueMomentumRequest, KJBRequest, StrategyResult, ) from app.services.strategy import MultiFactorStrategy, QualityStrategy, ValueMomentumStrategy, KJBStrategy router = APIRouter(prefix="/api/strategy", tags=["strategy"]) def _filter_dc_only(result: StrategyResult, db: Session) -> StrategyResult: """Filter strategy result to include only ETFs (DC pension investable).""" tickers = [s.ticker for s in result.stocks] etf_tickers: Set[str] = set( row[0] for row in db.query(ETF.ticker).filter(ETF.ticker.in_(tickers)).all() ) if tickers else set() filtered = [s for s in result.stocks if s.ticker in etf_tickers] # Re-rank for i, stock in enumerate(filtered, 1): stock.rank = i return StrategyResult( strategy_name=result.strategy_name, base_date=result.base_date, universe_count=result.universe_count, result_count=len(filtered), stocks=filtered, ) @router.post("/multi-factor", response_model=StrategyResult) async def run_multi_factor( request: MultiFactorRequest, current_user: CurrentUser, db: Session = Depends(get_db), ): """Run multi-factor strategy.""" strategy = MultiFactorStrategy(db) result = strategy.run( universe_filter=request.universe, top_n=request.top_n, base_date=request.base_date, weights=request.weights, ) return _filter_dc_only(result, db) if request.dc_only else result @router.post("/quality", response_model=StrategyResult) async def run_quality( request: QualityRequest, current_user: CurrentUser, db: Session = Depends(get_db), ): """Run super quality strategy.""" strategy = QualityStrategy(db) result = strategy.run( universe_filter=request.universe, top_n=request.top_n, base_date=request.base_date, min_fscore=request.min_fscore, ) return _filter_dc_only(result, db) if request.dc_only else result @router.post("/value-momentum", response_model=StrategyResult) async def run_value_momentum( request: ValueMomentumRequest, current_user: CurrentUser, db: Session = Depends(get_db), ): """Run value-momentum strategy.""" strategy = ValueMomentumStrategy(db) result = strategy.run( universe_filter=request.universe, top_n=request.top_n, base_date=request.base_date, value_weight=request.value_weight, momentum_weight=request.momentum_weight, ) return _filter_dc_only(result, db) if request.dc_only else result @router.post("/kjb", response_model=StrategyResult) async def run_kjb( request: KJBRequest, current_user: CurrentUser, db: Session = Depends(get_db), ): """Run KJB strategy.""" strategy = KJBStrategy(db) result = strategy.run( universe_filter=request.universe, top_n=request.top_n, base_date=request.base_date, ) return _filter_dc_only(result, db) if request.dc_only else result