""" Quant strategy related Pydantic schemas. """ from datetime import date from decimal import Decimal from typing import Optional, List, Dict from pydantic import BaseModel, Field from app.schemas.portfolio import FloatDecimal class FactorWeights(BaseModel): """Factor weights for multi-factor strategy.""" value: FloatDecimal = Field(default=Decimal("0.25"), ge=0, le=1) quality: FloatDecimal = Field(default=Decimal("0.25"), ge=0, le=1) momentum: FloatDecimal = Field(default=Decimal("0.25"), ge=0, le=1) low_vol: FloatDecimal = Field(default=Decimal("0.25"), ge=0, le=1) class UniverseFilter(BaseModel): """Stock universe filtering criteria.""" markets: List[str] = ["KOSPI", "KOSDAQ"] min_market_cap: Optional[int] = None # in 억원 max_market_cap: Optional[int] = None exclude_stock_types: List[str] = ["spac", "preferred", "reit"] exclude_sectors: List[str] = [] class StrategyRequest(BaseModel): """Base request for running a strategy.""" universe: UniverseFilter = UniverseFilter() top_n: int = Field(default=30, ge=1, le=100) base_date: Optional[date] = None dc_only: bool = False class MultiFactorRequest(StrategyRequest): """Multi-factor strategy request.""" weights: FactorWeights = FactorWeights() class QualityRequest(StrategyRequest): """Super Quality strategy request.""" min_fscore: int = Field(default=7, ge=0, le=9) class ValueMomentumRequest(StrategyRequest): """Value-Momentum strategy request.""" value_weight: FloatDecimal = Field(default=Decimal("0.5"), ge=0, le=1) momentum_weight: FloatDecimal = Field(default=Decimal("0.5"), ge=0, le=1) class KJBRequest(StrategyRequest): """KJB strategy request.""" pass class StockFactor(BaseModel): """Factor scores for a single stock.""" ticker: str name: str market: str sector_name: Optional[str] = None market_cap: Optional[int] = None close_price: Optional[FloatDecimal] = None # Raw metrics per: Optional[FloatDecimal] = None pbr: Optional[FloatDecimal] = None psr: Optional[FloatDecimal] = None pcr: Optional[FloatDecimal] = None dividend_yield: Optional[FloatDecimal] = None roe: Optional[FloatDecimal] = None # Factor scores (z-scores) value_score: Optional[FloatDecimal] = None quality_score: Optional[FloatDecimal] = None momentum_score: Optional[FloatDecimal] = None # Composite total_score: Optional[FloatDecimal] = None rank: Optional[int] = None fscore: Optional[int] = None class StrategyResult(BaseModel): """Result from running a strategy.""" strategy_name: str base_date: date universe_count: int result_count: int stocks: List[StockFactor] class StockInfo(BaseModel): """Detailed stock information.""" ticker: str name: str market: str sector_name: Optional[str] = None stock_type: str close_price: Optional[FloatDecimal] = None market_cap: Optional[int] = None # Valuation per: Optional[FloatDecimal] = None pbr: Optional[FloatDecimal] = None psr: Optional[FloatDecimal] = None pcr: Optional[FloatDecimal] = None dividend_yield: Optional[FloatDecimal] = None # Per-share data eps: Optional[FloatDecimal] = None bps: Optional[FloatDecimal] = None base_date: Optional[date] = None class Config: from_attributes = True class StockSearchResult(BaseModel): """Stock search result.""" ticker: str name: str market: str class PriceData(BaseModel): """Price data point.""" date: date open: FloatDecimal high: FloatDecimal low: FloatDecimal close: FloatDecimal volume: int class Config: from_attributes = True