zephyrdark 65bc4cb623 feat: add KJBStrategy ranking class and API endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:12:18 +09:00

140 lines
3.7 KiB
Python

"""
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
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