feat: add portfolio Pydantic schemas
This commit is contained in:
parent
914f0f7367
commit
a3d9819175
@ -1,5 +1,14 @@
|
||||
from app.schemas.user import UserBase, UserCreate, UserResponse
|
||||
from app.schemas.auth import Token, TokenPayload, LoginRequest
|
||||
from app.schemas.portfolio import (
|
||||
TargetCreate, TargetResponse,
|
||||
HoldingCreate, HoldingResponse, HoldingWithValue,
|
||||
TransactionCreate, TransactionResponse,
|
||||
PortfolioCreate, PortfolioUpdate, PortfolioResponse, PortfolioDetail,
|
||||
SnapshotResponse, SnapshotHoldingResponse,
|
||||
RebalanceItem, RebalanceResponse,
|
||||
RebalanceSimulationRequest, RebalanceSimulationResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"UserBase",
|
||||
@ -8,4 +17,11 @@ __all__ = [
|
||||
"Token",
|
||||
"TokenPayload",
|
||||
"LoginRequest",
|
||||
"TargetCreate", "TargetResponse",
|
||||
"HoldingCreate", "HoldingResponse", "HoldingWithValue",
|
||||
"TransactionCreate", "TransactionResponse",
|
||||
"PortfolioCreate", "PortfolioUpdate", "PortfolioResponse", "PortfolioDetail",
|
||||
"SnapshotResponse", "SnapshotHoldingResponse",
|
||||
"RebalanceItem", "RebalanceResponse",
|
||||
"RebalanceSimulationRequest", "RebalanceSimulationResponse",
|
||||
]
|
||||
|
||||
158
backend/app/schemas/portfolio.py
Normal file
158
backend/app/schemas/portfolio.py
Normal file
@ -0,0 +1,158 @@
|
||||
"""
|
||||
Portfolio related Pydantic schemas.
|
||||
"""
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# Target schemas
|
||||
class TargetBase(BaseModel):
|
||||
ticker: str
|
||||
target_ratio: Decimal = Field(..., ge=0, le=100)
|
||||
|
||||
|
||||
class TargetCreate(TargetBase):
|
||||
pass
|
||||
|
||||
|
||||
class TargetResponse(TargetBase):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Holding schemas
|
||||
class HoldingBase(BaseModel):
|
||||
ticker: str
|
||||
quantity: int = Field(..., ge=0)
|
||||
avg_price: Decimal = Field(..., ge=0)
|
||||
|
||||
|
||||
class HoldingCreate(HoldingBase):
|
||||
pass
|
||||
|
||||
|
||||
class HoldingResponse(HoldingBase):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class HoldingWithValue(HoldingResponse):
|
||||
"""Holding with calculated values."""
|
||||
current_price: Decimal | None = None
|
||||
value: Decimal | None = None
|
||||
current_ratio: Decimal | None = None
|
||||
profit_loss: Decimal | None = None
|
||||
profit_loss_ratio: Decimal | None = None
|
||||
|
||||
|
||||
# Transaction schemas
|
||||
class TransactionBase(BaseModel):
|
||||
ticker: str
|
||||
tx_type: str # "buy" or "sell"
|
||||
quantity: int = Field(..., gt=0)
|
||||
price: Decimal = Field(..., gt=0)
|
||||
executed_at: datetime
|
||||
memo: Optional[str] = None
|
||||
|
||||
|
||||
class TransactionCreate(TransactionBase):
|
||||
pass
|
||||
|
||||
|
||||
class TransactionResponse(TransactionBase):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Portfolio schemas
|
||||
class PortfolioBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
portfolio_type: str = "general" # "pension" or "general"
|
||||
|
||||
|
||||
class PortfolioCreate(PortfolioBase):
|
||||
pass
|
||||
|
||||
|
||||
class PortfolioUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=100)
|
||||
portfolio_type: Optional[str] = None
|
||||
|
||||
|
||||
class PortfolioResponse(PortfolioBase):
|
||||
id: int
|
||||
user_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class PortfolioDetail(PortfolioResponse):
|
||||
"""Portfolio with targets and holdings."""
|
||||
targets: List[TargetResponse] = []
|
||||
holdings: List[HoldingWithValue] = []
|
||||
total_value: Decimal | None = None
|
||||
total_invested: Decimal | None = None
|
||||
total_profit_loss: Decimal | None = None
|
||||
|
||||
|
||||
# Snapshot schemas
|
||||
class SnapshotHoldingResponse(BaseModel):
|
||||
ticker: str
|
||||
quantity: int
|
||||
price: Decimal
|
||||
value: Decimal
|
||||
current_ratio: Decimal
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class SnapshotResponse(BaseModel):
|
||||
id: int
|
||||
portfolio_id: int
|
||||
total_value: Decimal
|
||||
snapshot_date: date
|
||||
holdings: List[SnapshotHoldingResponse] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Rebalancing schemas
|
||||
class RebalanceItem(BaseModel):
|
||||
ticker: str
|
||||
name: str | None = None
|
||||
target_ratio: Decimal
|
||||
current_ratio: Decimal
|
||||
current_quantity: int
|
||||
current_value: Decimal
|
||||
target_value: Decimal
|
||||
diff_value: Decimal
|
||||
diff_quantity: int
|
||||
action: str # "buy", "sell", or "hold"
|
||||
|
||||
|
||||
class RebalanceResponse(BaseModel):
|
||||
portfolio_id: int
|
||||
total_value: Decimal
|
||||
items: List[RebalanceItem]
|
||||
|
||||
|
||||
class RebalanceSimulationRequest(BaseModel):
|
||||
additional_amount: Decimal = Field(..., gt=0)
|
||||
|
||||
|
||||
class RebalanceSimulationResponse(BaseModel):
|
||||
portfolio_id: int
|
||||
current_total: Decimal
|
||||
additional_amount: Decimal
|
||||
new_total: Decimal
|
||||
items: List[RebalanceItem]
|
||||
Loading…
x
Reference in New Issue
Block a user