All checks were successful
Deploy to Production / deploy (push) Successful in 1m33s
Pydantic v2's model_dump(mode="json") serializes Decimal as strings (e.g., "33.33" instead of 33.33), causing frontend crashes when calling .toFixed() on string values. Introduced FloatDecimal type alias with PlainSerializer to ensure Decimal fields are serialized as floats in JSON responses. Also fixed frontend Transaction interface to match backend field names (created_at → executed_at, transaction_type → tx_type). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
3.6 KiB
Python
135 lines
3.6 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 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
|