|
|
|
from pydantic import BaseModel, Field, ConfigDict |
|
from typing import Optional, List |
|
from datetime import date, datetime |
|
from decimal import Decimal |
|
|
|
|
|
|
|
class PortfolioBase(BaseModel): |
|
id: int |
|
name: str |
|
description: Optional[str] = None |
|
is_active: bool |
|
created_at: datetime |
|
updated_at: datetime |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
class PortfolioCreate(BaseModel): |
|
name: str = Field( |
|
..., min_length=1, max_length=100, description="Name of the portfolio" |
|
) |
|
description: Optional[str] = Field( |
|
None, description="Optional description for the portfolio" |
|
) |
|
|
|
|
|
class PortfolioUpdate(BaseModel): |
|
name: Optional[str] = Field( |
|
None, min_length=1, max_length=100, description="New name for the portfolio" |
|
) |
|
description: Optional[str] = Field( |
|
None, description="New description for the portfolio" |
|
) |
|
is_active: Optional[bool] = Field( |
|
None, description="Set portfolio active or inactive status" |
|
) |
|
|
|
|
|
class PortfolioListResponse(BaseModel): |
|
portfolios: List[PortfolioBase] |
|
total_count: int |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
|
|
class StockHoldingBase(BaseModel): |
|
stock_id: int = Field(..., description="Internal ID of the stock master record") |
|
quantity: Decimal = Field(..., gt=0, description="Number of shares held") |
|
purchase_price: Decimal = Field( |
|
..., |
|
gt=0, |
|
description="Average price per share at purchase for the aggregated holding", |
|
) |
|
purchase_date: date = Field( |
|
..., description="Representative date of stock purchase (e.g., latest buy)" |
|
) |
|
notes: Optional[str] = Field(None, description="Additional notes for this holding") |
|
|
|
|
|
class StockHoldingCreate(StockHoldingBase): |
|
|
|
pass |
|
|
|
|
|
class StockHoldingUpdate(BaseModel): |
|
|
|
|
|
quantity: Optional[Decimal] = Field( |
|
None, gt=0, description="Updated total number of shares (use with caution)" |
|
) |
|
purchase_price: Optional[Decimal] = Field( |
|
None, |
|
gt=0, |
|
description="Updated average purchase price per share (use with caution)", |
|
) |
|
purchase_date: Optional[date] = Field( |
|
None, description="Updated representative purchase date" |
|
) |
|
notes: Optional[str] = Field(None, description="Updated notes") |
|
|
|
|
|
class StockHoldingResponse(StockHoldingBase): |
|
id: int = Field( |
|
..., description="Unique ID of the PortfolioStock (aggregated holding) record" |
|
) |
|
stock_symbol: str = Field(..., description="Ticker symbol of the stock") |
|
stock_name: str = Field(..., description="Name of the stock company") |
|
current_price: Optional[Decimal] = Field( |
|
None, description="Current market price per share" |
|
) |
|
market_value: Optional[Decimal] = Field( |
|
None, description="Total current market value of the holding" |
|
) |
|
gain_loss: Optional[Decimal] = Field(None, description="Absolute gain or loss") |
|
gain_loss_percentage: Optional[Decimal] = Field( |
|
None, description="Percentage gain or loss" |
|
) |
|
created_at: datetime |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
class StockSellSchema(BaseModel): |
|
quantity: Decimal = Field(..., gt=0, description="Number of shares to sell") |
|
sell_price: Decimal = Field( |
|
..., gt=0, description="Price per share at which stock was sold" |
|
) |
|
sell_date: date = Field(..., description="Date of the sale") |
|
notes: Optional[str] = Field( |
|
None, description="Additional notes for the sell transaction" |
|
) |
|
|
|
|
|
|
|
class UTTHoldingBase(BaseModel): |
|
utt_fund_id: int = Field( |
|
..., description="Internal ID of the UTT fund master record" |
|
) |
|
units_held: Decimal = Field(..., gt=0, description="Number of units held") |
|
purchase_price: Decimal = Field( |
|
..., |
|
gt=0, |
|
description="Average price per unit at purchase (NAV) for the aggregated holding", |
|
) |
|
purchase_date: date = Field( |
|
..., description="Representative date of UTT purchase (e.g., latest buy)" |
|
) |
|
notes: Optional[str] = Field(None, description="Additional notes for this holding") |
|
|
|
|
|
class UTTHoldingCreate(UTTHoldingBase): |
|
|
|
pass |
|
|
|
|
|
class UTTHoldingUpdate(BaseModel): |
|
units_held: Optional[Decimal] = Field( |
|
None, gt=0, description="Updated number of units held (use with caution)" |
|
) |
|
purchase_price: Optional[Decimal] = Field( |
|
None, |
|
gt=0, |
|
description="Updated average purchase price per unit (use with caution)", |
|
) |
|
purchase_date: Optional[date] = Field( |
|
None, description="Updated representative purchase date" |
|
) |
|
notes: Optional[str] = Field(None, description="Updated notes") |
|
|
|
|
|
class UTTHoldingResponse(UTTHoldingBase): |
|
id: int = Field( |
|
..., description="Unique ID of the PortfolioUTT (aggregated holding) record" |
|
) |
|
fund_symbol: str = Field(..., description="Symbol of the UTT fund") |
|
fund_name: str = Field(..., description="Name of the UTT fund") |
|
current_nav: Optional[Decimal] = Field( |
|
None, description="Current Net Asset Value (NAV) per unit" |
|
) |
|
market_value: Optional[Decimal] = Field( |
|
None, description="Total current market value of the holding" |
|
) |
|
gain_loss: Optional[Decimal] = Field(None, description="Absolute gain or loss") |
|
gain_loss_percentage: Optional[Decimal] = Field( |
|
None, description="Percentage gain or loss" |
|
) |
|
created_at: datetime |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
class UTTSellSchema(BaseModel): |
|
units_to_sell: Decimal = Field( |
|
..., gt=0, description="Number of UTT units to sell" |
|
) |
|
sell_price: Decimal = Field( |
|
..., gt=0, description="Price per unit at which UTT was sold (NAV)" |
|
) |
|
sell_date: date = Field(..., description="Date of the sale") |
|
notes: Optional[str] = Field( |
|
None, description="Additional notes for the sell transaction" |
|
) |
|
|
|
|
|
|
|
class BondHoldingBase(BaseModel): |
|
|
|
face_value_held: Decimal = Field( |
|
..., gt=0, description="Total face value of the bond held" |
|
) |
|
auction_number: Optional[int] = Field( |
|
None, description="Auction number if applicable (e.g., for government bonds)" |
|
) |
|
auction_date: Optional[date] = Field( |
|
None, description="Auction date if applicable (e.g., for government bonds)" |
|
) |
|
purchase_price: Decimal = Field( |
|
..., |
|
gt=0, |
|
description="TOTAL purchase price paid for the entire face_value_held (aggregated holding).", |
|
) |
|
purchase_date: date = Field( |
|
..., description="Representative date of bond purchase (e.g., latest buy)" |
|
) |
|
notes: Optional[str] = Field(None, description="Additional notes for this holding") |
|
|
|
|
|
class BondHoldingCreate(BondHoldingBase): |
|
|
|
pass |
|
|
|
|
|
class BondHoldingUpdate(BaseModel): |
|
face_value_held: Optional[Decimal] = Field( |
|
None, gt=0, description="Updated total face value held" |
|
) |
|
purchase_price: Optional[Decimal] = Field( |
|
None, |
|
gt=0, |
|
description="Updated TOTAL purchase price for the new face_value_held (use with caution)", |
|
) |
|
purchase_date: Optional[date] = Field( |
|
None, description="Updated representative purchase date" |
|
) |
|
notes: Optional[str] = Field(None, description="Updated notes") |
|
|
|
|
|
class BondHoldingResponse(BondHoldingBase): |
|
id: int = Field( |
|
..., description="Unique ID of the PortfolioBond (aggregated holding) record" |
|
) |
|
instrument_type: str = Field(..., description="Type of bond instrument") |
|
auction_number: Optional[int] = Field( |
|
None, description="Auction number if applicable" |
|
) |
|
maturity_date: date = Field(..., description="Maturity date of the bond") |
|
current_price: Optional[Decimal] = Field( |
|
None, |
|
description="Current market price (e.g., percentage of face value like 99.5)", |
|
) |
|
market_value: Optional[Decimal] = Field( |
|
None, description="Total current market value of the holding" |
|
) |
|
accrued_interest: Optional[Decimal] = Field( |
|
None, description="Accrued interest on the bond" |
|
) |
|
yield_to_maturity: Optional[Decimal] = Field( |
|
None, description="Yield to maturity of the bond" |
|
) |
|
gain_loss: Optional[Decimal] = Field( |
|
None, description="Absolute gain or loss on principal" |
|
) |
|
created_at: datetime |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
class BondSellSchema(BaseModel): |
|
face_value_to_sell: Decimal = Field( |
|
..., gt=0, description="Face value of the bond portion being sold" |
|
) |
|
sell_price: Decimal = Field( |
|
..., gt=0, description="TOTAL selling proceeds for the face_value_to_sell." |
|
) |
|
sell_date: date = Field(..., description="Date of the sale") |
|
notes: Optional[str] = Field( |
|
None, description="Additional notes for the sell transaction" |
|
) |
|
|
|
|
|
|
|
class CalendarEventBase(BaseModel): |
|
event_date: date |
|
event_type: str = Field(..., max_length=50) |
|
title: str = Field(..., max_length=200) |
|
description: Optional[str] = None |
|
asset_type: Optional[str] = Field(None, max_length=10) |
|
asset_id: Optional[int] = None |
|
estimated_amount: Optional[Decimal] = None |
|
|
|
|
|
class CalendarEventCreate(CalendarEventBase): |
|
pass |
|
|
|
|
|
class CalendarEventResponse(CalendarEventBase): |
|
id: int |
|
is_completed: bool = Field(False) |
|
created_at: datetime |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
|
|
class TransactionBase(BaseModel): |
|
transaction_type: str = Field(..., max_length=20) |
|
asset_type: str = Field(..., max_length=10) |
|
asset_id: Optional[int] = None |
|
asset_name: Optional[str] = Field(None, max_length=100) |
|
quantity: Optional[Decimal] = None |
|
price: Optional[Decimal] = Field(None, ge=0) |
|
transaction_date: date |
|
notes: Optional[str] = None |
|
|
|
|
|
class TransactionCreate(TransactionBase): |
|
total_amount: Decimal |
|
|
|
|
|
class TransactionResponse(TransactionBase): |
|
id: int |
|
total_amount: Decimal |
|
created_at: datetime |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
|
|
class AssetAllocation(BaseModel): |
|
stocks_percentage: Decimal = Field(Decimal("0.0"), ge=0, le=100) |
|
bonds_percentage: Decimal = Field(Decimal("0.0"), ge=0, le=100) |
|
utts_percentage: Decimal = Field(Decimal("0.0"), ge=0, le=100) |
|
cash_percentage: Decimal = Field(Decimal("0.0"), ge=0, le=100) |
|
total_value: Decimal |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
class CalendarEventResponse(BaseModel): |
|
event_date: date |
|
event_type: str |
|
asset_symbol: str |
|
asset_name: str |
|
estimated_amount: Decimal |
|
notes: Optional[str] = None |
|
|
|
model_config = ConfigDict( |
|
from_attributes=True, |
|
) |
|
|
|
|
|
class TransactionDetailResponse(BaseModel): |
|
id: int |
|
transaction_type: str |
|
asset_type: str |
|
asset_id: int |
|
quantity: Decimal |
|
price: Decimal |
|
total_amount: Decimal |
|
transaction_date: date |
|
notes: Optional[str] = None |
|
created_at: datetime |
|
|
|
|
|
asset_name: Optional[str] = None |
|
asset_symbol: Optional[str] = None |
|
|
|
model_config = ConfigDict( |
|
from_attributes=True, |
|
) |
|
|
|
|
|
class PortfolioSummary(BaseModel): |
|
portfolio: PortfolioBase |
|
total_market_value: Decimal |
|
total_cost_basis: Decimal |
|
overall_unrealized_gain_loss: Decimal |
|
overall_unrealized_gain_loss_percentage: Decimal |
|
stock_holdings: List[StockHoldingResponse] = Field(default_factory=list) |
|
utt_holdings: List[UTTHoldingResponse] = Field(default_factory=list) |
|
bond_holdings: List[BondHoldingResponse] = Field(default_factory=list) |
|
asset_allocation: AssetAllocation |
|
recent_transactions: List[TransactionResponse] = Field(default_factory=list) |
|
upcoming_events: List[CalendarEventResponse] = Field(default_factory=list) |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
class AssetPerformanceDetail(BaseModel): |
|
asset_id: Optional[int] = None |
|
name: str |
|
return_value: Decimal |
|
asset_type: Optional[str] = None |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
class PortfolioPerformance(BaseModel): |
|
portfolio_id: int |
|
period: str |
|
start_value: Decimal |
|
end_value: Decimal |
|
absolute_return: Decimal |
|
percentage_return: Decimal |
|
best_performer: Optional[AssetPerformanceDetail] = None |
|
worst_performer: Optional[AssetPerformanceDetail] = None |
|
|
|
model_config = ConfigDict(from_attributes=True) |
|
|
|
|
|
class PositionResponse(BaseModel): |
|
asset_id: int |
|
asset_type: str |
|
asset_name: str |
|
asset_symbol: str |
|
quantity: Decimal |
|
avg_buy_price: Decimal |
|
total_invested: Decimal |
|
current_price: Decimal |
|
current_value: Decimal |
|
profit_loss: Decimal |
|
profit_loss_percent: float |
|
|
|
model_config = ConfigDict( |
|
from_attributes=True, |
|
) |
|
|