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.user import UserBase, UserCreate, UserResponse
|
||||||
from app.schemas.auth import Token, TokenPayload, LoginRequest
|
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__ = [
|
__all__ = [
|
||||||
"UserBase",
|
"UserBase",
|
||||||
@ -8,4 +17,11 @@ __all__ = [
|
|||||||
"Token",
|
"Token",
|
||||||
"TokenPayload",
|
"TokenPayload",
|
||||||
"LoginRequest",
|
"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