feat: add quant strategy Pydantic schemas

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
zephyrdark 2026-02-03 08:57:05 +09:00
parent 3218e26a5a
commit d671befb90
2 changed files with 142 additions and 0 deletions

View File

@ -9,6 +9,12 @@ from app.schemas.portfolio import (
RebalanceItem, RebalanceResponse,
RebalanceSimulationRequest, RebalanceSimulationResponse,
)
from app.schemas.strategy import (
FactorWeights, UniverseFilter,
StrategyRequest, MultiFactorRequest, QualityRequest, ValueMomentumRequest,
StockFactor, StrategyResult,
StockInfo, StockSearchResult, PriceData,
)
__all__ = [
"UserBase",
@ -24,4 +30,8 @@ __all__ = [
"SnapshotResponse", "SnapshotHoldingResponse",
"RebalanceItem", "RebalanceResponse",
"RebalanceSimulationRequest", "RebalanceSimulationResponse",
"FactorWeights", "UniverseFilter",
"StrategyRequest", "MultiFactorRequest", "QualityRequest", "ValueMomentumRequest",
"StockFactor", "StrategyResult",
"StockInfo", "StockSearchResult", "PriceData",
]

View File

@ -0,0 +1,132 @@
"""
Quant strategy related Pydantic schemas.
"""
from datetime import date
from decimal import Decimal
from typing import Optional, List, Dict
from pydantic import BaseModel, Field
class FactorWeights(BaseModel):
"""Factor weights for multi-factor strategy."""
value: Decimal = Field(default=Decimal("0.25"), ge=0, le=1)
quality: Decimal = Field(default=Decimal("0.25"), ge=0, le=1)
momentum: Decimal = Field(default=Decimal("0.25"), ge=0, le=1)
low_vol: Decimal = 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: Decimal = Field(default=Decimal("0.5"), ge=0, le=1)
momentum_weight: Decimal = Field(default=Decimal("0.5"), ge=0, le=1)
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[Decimal] = None
# Raw metrics
per: Optional[Decimal] = None
pbr: Optional[Decimal] = None
psr: Optional[Decimal] = None
pcr: Optional[Decimal] = None
dividend_yield: Optional[Decimal] = None
roe: Optional[Decimal] = None
# Factor scores (z-scores)
value_score: Optional[Decimal] = None
quality_score: Optional[Decimal] = None
momentum_score: Optional[Decimal] = None
# Composite
total_score: Optional[Decimal] = 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[Decimal] = None
market_cap: Optional[int] = None
# Valuation
per: Optional[Decimal] = None
pbr: Optional[Decimal] = None
psr: Optional[Decimal] = None
pcr: Optional[Decimal] = None
dividend_yield: Optional[Decimal] = None
# Per-share data
eps: Optional[Decimal] = None
bps: Optional[Decimal] = 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: Decimal
high: Decimal
low: Decimal
close: Decimal
volume: int
class Config:
from_attributes = True