"""Phase 1 市场数据表模型(SQLAlchemy 2.x 声明式)。 列名与 domain.entities.market 字段一一对应,便于 Repository 双向映射。 Decimal 字段用 Numeric:SQLite 以浮点近似存储,未来 MySQL 下精确。 """ from __future__ import annotations from datetime import date, datetime from decimal import Decimal from sqlalchemy import ( BigInteger, Boolean, Date, DateTime, Integer, Numeric, String, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column from app.infrastructure.persistence.sqlalchemy.base import Base # SQLite 只对 INTEGER PRIMARY KEY 自增;MySQL 下用 BIGINT PK_INT = BigInteger().with_variant(Integer, "sqlite") SYMBOL_LEN = 12 class StockModel(Base): __tablename__ = "stock" id: Mapped[int] = mapped_column(primary_key=True) symbol: Mapped[str] = mapped_column(String(SYMBOL_LEN), unique=True, index=True) name: Mapped[str] = mapped_column(String(64)) industry: Mapped[str | None] = mapped_column(String(64), nullable=True) area: Mapped[str | None] = mapped_column(String(32), nullable=True) market: Mapped[str | None] = mapped_column(String(16), nullable=True) exchange: Mapped[str | None] = mapped_column(String(8), nullable=True) list_date: Mapped[date] = mapped_column(Date) delist_date: Mapped[date | None] = mapped_column(Date, nullable=True) status: Mapped[str] = mapped_column(String(8), default="L") class TradingCalendarModel(Base): __tablename__ = "trading_calendar" id: Mapped[int] = mapped_column(primary_key=True) calendar_date: Mapped[date] = mapped_column(Date, unique=True, index=True) is_open: Mapped[bool] = mapped_column(Boolean, default=True) class StockDailyModel(Base): """不复权日线。""" __tablename__ = "stock_daily" __table_args__ = (UniqueConstraint("symbol", "trade_date", name="uq_daily_symbol_date"),) id: Mapped[int] = mapped_column(PK_INT, primary_key=True, autoincrement=True) symbol: Mapped[str] = mapped_column(String(SYMBOL_LEN), index=True) trade_date: Mapped[date] = mapped_column(Date, index=True) open: Mapped[Decimal | None] = mapped_column(Numeric(12, 4), nullable=True) high: Mapped[Decimal | None] = mapped_column(Numeric(12, 4), nullable=True) low: Mapped[Decimal | None] = mapped_column(Numeric(12, 4), nullable=True) close: Mapped[Decimal | None] = mapped_column(Numeric(12, 4), nullable=True) volume: Mapped[Decimal | None] = mapped_column(Numeric(24, 2), nullable=True) amount: Mapped[Decimal | None] = mapped_column(Numeric(24, 2), nullable=True) class AdjustFactorModel(Base): __tablename__ = "adjust_factor" __table_args__ = (UniqueConstraint("symbol", "trade_date", name="uq_adj_symbol_date"),) id: Mapped[int] = mapped_column(PK_INT, primary_key=True, autoincrement=True) symbol: Mapped[str] = mapped_column(String(SYMBOL_LEN), index=True) trade_date: Mapped[date] = mapped_column(Date, index=True) factor: Mapped[Decimal] = mapped_column(Numeric(20, 6)) class FinancialIndicatorModel(Base): """财务指标快照 —— report_date(报告期) 与 announce_date(公告日) 并存。""" __tablename__ = "financial_indicator" __table_args__ = ( UniqueConstraint("symbol", "report_date", "announce_date", name="uq_fin_sym_rep_ann"), ) id: Mapped[int] = mapped_column(PK_INT, primary_key=True, autoincrement=True) symbol: Mapped[str] = mapped_column(String(SYMBOL_LEN), index=True) report_date: Mapped[date] = mapped_column(Date, index=True) announce_date: Mapped[date] = mapped_column(Date, index=True) eps: Mapped[Decimal | None] = mapped_column(Numeric(12, 4), nullable=True) roe: Mapped[Decimal | None] = mapped_column(Numeric(10, 4), nullable=True) total_revenue: Mapped[Decimal | None] = mapped_column(Numeric(24, 2), nullable=True) net_profit: Mapped[Decimal | None] = mapped_column(Numeric(24, 2), nullable=True) gross_margin: Mapped[Decimal | None] = mapped_column(Numeric(10, 4), nullable=True) class SyncLogModel(Base): __tablename__ = "sync_log" id: Mapped[int] = mapped_column(PK_INT, primary_key=True, autoincrement=True) source: Mapped[str] = mapped_column(String(16), index=True) api: Mapped[str] = mapped_column(String(32)) request_time: Mapped[datetime] = mapped_column(DateTime) success: Mapped[bool] = mapped_column(Boolean) failure_reason: Mapped[str | None] = mapped_column(String(500), nullable=True) row_count: Mapped[int] = mapped_column(default=0) data_start: Mapped[date | None] = mapped_column(Date, nullable=True) data_end: Mapped[date | None] = mapped_column(Date, nullable=True)