feat: add database models for users, portfolios, and market data
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4c667677c9
commit
46c9f752ee
@ -0,0 +1,47 @@
|
||||
from app.models.user import User
|
||||
from app.models.portfolio import (
|
||||
Portfolio,
|
||||
PortfolioType,
|
||||
Target,
|
||||
Holding,
|
||||
Transaction,
|
||||
TransactionType,
|
||||
PortfolioSnapshot,
|
||||
SnapshotHolding,
|
||||
)
|
||||
from app.models.stock import (
|
||||
Stock,
|
||||
StockType,
|
||||
Sector,
|
||||
Valuation,
|
||||
Price,
|
||||
Financial,
|
||||
ReportType,
|
||||
ETF,
|
||||
ETFPrice,
|
||||
AssetClass,
|
||||
JobLog,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"User",
|
||||
"Portfolio",
|
||||
"PortfolioType",
|
||||
"Target",
|
||||
"Holding",
|
||||
"Transaction",
|
||||
"TransactionType",
|
||||
"PortfolioSnapshot",
|
||||
"SnapshotHolding",
|
||||
"Stock",
|
||||
"StockType",
|
||||
"Sector",
|
||||
"Valuation",
|
||||
"Price",
|
||||
"Financial",
|
||||
"ReportType",
|
||||
"ETF",
|
||||
"ETFPrice",
|
||||
"AssetClass",
|
||||
"JobLog",
|
||||
]
|
||||
99
backend/app/models/portfolio.py
Normal file
99
backend/app/models/portfolio.py
Normal file
@ -0,0 +1,99 @@
|
||||
"""
|
||||
Portfolio related models.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, Numeric, DateTime, Date,
|
||||
ForeignKey, Text, Enum as SQLEnum
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
import enum
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class PortfolioType(str, enum.Enum):
|
||||
PENSION = "pension"
|
||||
GENERAL = "general"
|
||||
|
||||
|
||||
class TransactionType(str, enum.Enum):
|
||||
BUY = "buy"
|
||||
SELL = "sell"
|
||||
|
||||
|
||||
class Portfolio(Base):
|
||||
__tablename__ = "portfolios"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
portfolio_type = Column(SQLEnum(PortfolioType), default=PortfolioType.GENERAL)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
targets = relationship("Target", back_populates="portfolio", cascade="all, delete-orphan")
|
||||
holdings = relationship("Holding", back_populates="portfolio", cascade="all, delete-orphan")
|
||||
transactions = relationship("Transaction", back_populates="portfolio", cascade="all, delete-orphan")
|
||||
snapshots = relationship("PortfolioSnapshot", back_populates="portfolio", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class Target(Base):
|
||||
__tablename__ = "targets"
|
||||
|
||||
portfolio_id = Column(Integer, ForeignKey("portfolios.id"), primary_key=True)
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
target_ratio = Column(Numeric(5, 2), nullable=False)
|
||||
|
||||
portfolio = relationship("Portfolio", back_populates="targets")
|
||||
|
||||
|
||||
class Holding(Base):
|
||||
__tablename__ = "holdings"
|
||||
|
||||
portfolio_id = Column(Integer, ForeignKey("portfolios.id"), primary_key=True)
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
quantity = Column(Integer, nullable=False, default=0)
|
||||
avg_price = Column(Numeric(12, 2), nullable=False, default=0)
|
||||
|
||||
portfolio = relationship("Portfolio", back_populates="holdings")
|
||||
|
||||
|
||||
class Transaction(Base):
|
||||
__tablename__ = "transactions"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
portfolio_id = Column(Integer, ForeignKey("portfolios.id"), nullable=False)
|
||||
ticker = Column(String(20), nullable=False)
|
||||
tx_type = Column(SQLEnum(TransactionType), nullable=False)
|
||||
quantity = Column(Integer, nullable=False)
|
||||
price = Column(Numeric(12, 2), nullable=False)
|
||||
executed_at = Column(DateTime, nullable=False)
|
||||
memo = Column(Text, nullable=True)
|
||||
|
||||
portfolio = relationship("Portfolio", back_populates="transactions")
|
||||
|
||||
|
||||
class PortfolioSnapshot(Base):
|
||||
__tablename__ = "portfolio_snapshots"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
portfolio_id = Column(Integer, ForeignKey("portfolios.id"), nullable=False)
|
||||
total_value = Column(Numeric(15, 2), nullable=False)
|
||||
snapshot_date = Column(Date, nullable=False)
|
||||
|
||||
portfolio = relationship("Portfolio", back_populates="snapshots")
|
||||
holdings = relationship("SnapshotHolding", back_populates="snapshot", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class SnapshotHolding(Base):
|
||||
__tablename__ = "snapshot_holdings"
|
||||
|
||||
snapshot_id = Column(Integer, ForeignKey("portfolio_snapshots.id"), primary_key=True)
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
quantity = Column(Integer, nullable=False)
|
||||
price = Column(Numeric(12, 2), nullable=False)
|
||||
value = Column(Numeric(15, 2), nullable=False)
|
||||
current_ratio = Column(Numeric(5, 2), nullable=False)
|
||||
|
||||
snapshot = relationship("PortfolioSnapshot", back_populates="holdings")
|
||||
123
backend/app/models/stock.py
Normal file
123
backend/app/models/stock.py
Normal file
@ -0,0 +1,123 @@
|
||||
"""
|
||||
Stock and market data models.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, Numeric, DateTime, Date,
|
||||
BigInteger, Text, Enum as SQLEnum
|
||||
)
|
||||
import enum
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class StockType(str, enum.Enum):
|
||||
COMMON = "common"
|
||||
SPAC = "spac"
|
||||
PREFERRED = "preferred"
|
||||
REIT = "reit"
|
||||
OTHER = "other"
|
||||
|
||||
|
||||
class ReportType(str, enum.Enum):
|
||||
ANNUAL = "annual"
|
||||
QUARTERLY = "quarterly"
|
||||
|
||||
|
||||
class AssetClass(str, enum.Enum):
|
||||
EQUITY = "equity"
|
||||
BOND = "bond"
|
||||
GOLD = "gold"
|
||||
MIXED = "mixed"
|
||||
|
||||
|
||||
class Stock(Base):
|
||||
__tablename__ = "stocks"
|
||||
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
market = Column(String(20), nullable=False)
|
||||
close_price = Column(Numeric(12, 2), nullable=True)
|
||||
market_cap = Column(BigInteger, nullable=True)
|
||||
eps = Column(Numeric(12, 2), nullable=True)
|
||||
forward_eps = Column(Numeric(12, 2), nullable=True)
|
||||
bps = Column(Numeric(12, 2), nullable=True)
|
||||
dividend_per_share = Column(Numeric(12, 2), nullable=True)
|
||||
stock_type = Column(SQLEnum(StockType), default=StockType.COMMON)
|
||||
base_date = Column(Date, nullable=False)
|
||||
|
||||
|
||||
class Sector(Base):
|
||||
__tablename__ = "sectors"
|
||||
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
sector_code = Column(String(10), nullable=False)
|
||||
company_name = Column(String(100), nullable=False)
|
||||
sector_name = Column(String(50), nullable=False)
|
||||
base_date = Column(Date, nullable=False)
|
||||
|
||||
|
||||
class Valuation(Base):
|
||||
__tablename__ = "valuations"
|
||||
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
base_date = Column(Date, primary_key=True)
|
||||
per = Column(Numeric(10, 2), nullable=True)
|
||||
pbr = Column(Numeric(10, 2), nullable=True)
|
||||
psr = Column(Numeric(10, 2), nullable=True)
|
||||
pcr = Column(Numeric(10, 2), nullable=True)
|
||||
dividend_yield = Column(Numeric(6, 2), nullable=True)
|
||||
|
||||
|
||||
class Price(Base):
|
||||
__tablename__ = "prices"
|
||||
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
date = Column(Date, primary_key=True)
|
||||
open = Column(Numeric(12, 2), nullable=False)
|
||||
high = Column(Numeric(12, 2), nullable=False)
|
||||
low = Column(Numeric(12, 2), nullable=False)
|
||||
close = Column(Numeric(12, 2), nullable=False)
|
||||
volume = Column(BigInteger, nullable=False)
|
||||
|
||||
|
||||
class Financial(Base):
|
||||
__tablename__ = "financials"
|
||||
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
base_date = Column(Date, primary_key=True)
|
||||
report_type = Column(SQLEnum(ReportType), primary_key=True)
|
||||
account = Column(String(50), primary_key=True)
|
||||
value = Column(Numeric(20, 2), nullable=True)
|
||||
|
||||
|
||||
class ETF(Base):
|
||||
__tablename__ = "etfs"
|
||||
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
asset_class = Column(SQLEnum(AssetClass), nullable=False)
|
||||
market = Column(String(20), nullable=False)
|
||||
expense_ratio = Column(Numeric(5, 4), nullable=True)
|
||||
|
||||
|
||||
class ETFPrice(Base):
|
||||
__tablename__ = "etf_prices"
|
||||
|
||||
ticker = Column(String(20), primary_key=True)
|
||||
date = Column(Date, primary_key=True)
|
||||
close = Column(Numeric(12, 2), nullable=False)
|
||||
nav = Column(Numeric(12, 2), nullable=True)
|
||||
volume = Column(BigInteger, nullable=True)
|
||||
|
||||
|
||||
class JobLog(Base):
|
||||
__tablename__ = "job_logs"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
job_name = Column(String(50), nullable=False)
|
||||
status = Column(String(20), nullable=False)
|
||||
started_at = Column(DateTime, default=datetime.utcnow)
|
||||
finished_at = Column(DateTime, nullable=True)
|
||||
records_count = Column(Integer, nullable=True)
|
||||
error_msg = Column(Text, nullable=True)
|
||||
17
backend/app/models/user.py
Normal file
17
backend/app/models/user.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""
|
||||
User model for authentication.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, DateTime
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
username = Column(String(50), unique=True, index=True, nullable=False)
|
||||
email = Column(String(100), unique=True, index=True, nullable=False)
|
||||
hashed_password = Column(String(255), nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
Loading…
x
Reference in New Issue
Block a user