"""数据中台 ORM 模型(SQLAlchemy 2.0)。""" from __future__ import annotations import datetime as dt from sqlalchemy import (BigInteger, Date, DateTime, Float, Integer, String, Text, UniqueConstraint, func) from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column class Base(DeclarativeBase): pass class Security(Base): """证券基础信息。""" __tablename__ = "securities" code: Mapped[str] = mapped_column(String(12), primary_key=True) name: Mapped[str] = mapped_column(String(40)) market: Mapped[str] = mapped_column(String(8), default="A") updated_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class DailyQuote(Base): """个股日线(前复权)。""" __tablename__ = "quotes_daily" __table_args__ = (UniqueConstraint("code", "date", name="uq_quote_code_date"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(12), index=True) date: Mapped[dt.date] = mapped_column(Date, index=True) open: Mapped[float] = mapped_column(Float) high: Mapped[float] = mapped_column(Float) low: Mapped[float] = mapped_column(Float) close: Mapped[float] = mapped_column(Float) volume: Mapped[int] = mapped_column(BigInteger, default=0) amount: Mapped[float] = mapped_column(Float, default=0.0) class IndexDaily(Base): """指数日线。""" __tablename__ = "index_daily" __table_args__ = (UniqueConstraint("code", "date", name="uq_index_code_date"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(12), index=True) name: Mapped[str] = mapped_column(String(40), default="") date: Mapped[dt.date] = mapped_column(Date, index=True) open: Mapped[float] = mapped_column(Float) high: Mapped[float] = mapped_column(Float) low: Mapped[float] = mapped_column(Float) close: Mapped[float] = mapped_column(Float) volume: Mapped[int] = mapped_column(BigInteger, default=0) class SectorDaily(Base): """板块每日快照。""" __tablename__ = "sector_daily" __table_args__ = (UniqueConstraint("date", "name", name="uq_sector_date_name"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) date: Mapped[dt.date] = mapped_column(Date, index=True) name: Mapped[str] = mapped_column(String(40)) pct: Mapped[float] = mapped_column(Float, default=0.0) amount: Mapped[float] = mapped_column(Float, default=0.0) count: Mapped[int] = mapped_column(Integer, default=0) leader: Mapped[str] = mapped_column(String(40), default="") class FundFlowDaily(Base): """行业资金流每日快照。""" __tablename__ = "fund_flow_daily" __table_args__ = (UniqueConstraint("date", "name", name="uq_fund_date_name"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) date: Mapped[dt.date] = mapped_column(Date, index=True) name: Mapped[str] = mapped_column(String(40)) net: Mapped[float] = mapped_column(Float, default=0.0) pct: Mapped[float] = mapped_column(Float, default=0.0) class SentimentDaily(Base): """市场情绪每日快照。""" __tablename__ = "sentiment_daily" date: Mapped[dt.date] = mapped_column(Date, primary_key=True) up: Mapped[int] = mapped_column(Integer, default=0) down: Mapped[int] = mapped_column(Integer, default=0) flat: Mapped[int] = mapped_column(Integer, default=0) limit_up: Mapped[int] = mapped_column(Integer, default=0) limit_down: Mapped[int] = mapped_column(Integer, default=0) class DragonTiger(Base): """龙虎榜明细。""" __tablename__ = "dragon_tiger" __table_args__ = (UniqueConstraint("date", "code", "reason", name="uq_lhb"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) date: Mapped[dt.date] = mapped_column(Date, index=True) code: Mapped[str] = mapped_column(String(12)) name: Mapped[str] = mapped_column(String(40), default="") pct: Mapped[float] = mapped_column(Float, default=0.0) net: Mapped[float] = mapped_column(Float, default=0.0) reason: Mapped[str] = mapped_column(String(120), default="") class StockMetric(Base): """个股最新因子快照(供全市场选股快速查询)。""" __tablename__ = "stock_metrics" code: Mapped[str] = mapped_column(String(12), primary_key=True) name: Mapped[str] = mapped_column(String(40), default="") date: Mapped[dt.date] = mapped_column(Date, index=True) close: Mapped[float] = mapped_column(Float, default=0.0) pct: Mapped[float] = mapped_column(Float, default=0.0, index=True) ma5: Mapped[float] = mapped_column(Float, default=0.0) ma10: Mapped[float] = mapped_column(Float, default=0.0) ma20: Mapped[float] = mapped_column(Float, default=0.0) ma60: Mapped[float] = mapped_column(Float, default=0.0) vol_ratio: Mapped[float] = mapped_column(Float, default=0.0) ret5: Mapped[float] = mapped_column(Float, default=0.0, index=True) ret20: Mapped[float] = mapped_column(Float, default=0.0) ret60: Mapped[float] = mapped_column(Float, default=0.0) pos60: Mapped[float] = mapped_column(Float, default=0.0) # 0~1,60日价格分位 rsi14: Mapped[float] = mapped_column(Float, default=0.0) macd_gold: Mapped[bool] = mapped_column(default=False) ma_bull: Mapped[bool] = mapped_column(default=False) up_streak: Mapped[int] = mapped_column(Integer, default=0) amount: Mapped[float] = mapped_column(Float, default=0.0) class Trade(Base): """交易记录(用于持仓盈亏与归因)。""" __tablename__ = "trades" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) date: Mapped[dt.date] = mapped_column(Date, index=True) code: Mapped[str] = mapped_column(String(12), index=True) name: Mapped[str] = mapped_column(String(40), default="") side: Mapped[str] = mapped_column(String(4)) # buy / sell price: Mapped[float] = mapped_column(Float) qty: Mapped[int] = mapped_column(Integer) fee: Mapped[float] = mapped_column(Float, default=0.0) reason: Mapped[str] = mapped_column(String(60), default="") emotion: Mapped[str] = mapped_column(String(20), default="") created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class AlertRule(Base): """预警规则。""" __tablename__ = "alert_rules" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(12), index=True) name: Mapped[str] = mapped_column(String(40), default="") kind: Mapped[str] = mapped_column(String(20)) # price_above/price_below/pct_above/pct_below threshold: Mapped[float] = mapped_column(Float) channel: Mapped[str] = mapped_column(String(20), default="站内") note: Mapped[str] = mapped_column(String(80), default="") status: Mapped[str] = mapped_column(String(12), default="active") # active/triggered last_value: Mapped[float] = mapped_column(Float, default=0.0) created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) triggered_at: Mapped[dt.datetime | None] = mapped_column(DateTime, nullable=True) class AlertEvent(Base): """预警触发事件(站内通知)。""" __tablename__ = "alert_events" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) rule_id: Mapped[int] = mapped_column(Integer, index=True) code: Mapped[str] = mapped_column(String(12)) name: Mapped[str] = mapped_column(String(40), default="") message: Mapped[str] = mapped_column(String(160)) value: Mapped[float] = mapped_column(Float, default=0.0) read: Mapped[bool] = mapped_column(default=False) created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class DailyReport(Base): """AI 自动复盘日报(收盘后生成,可推送)。""" __tablename__ = "daily_reports" date: Mapped[dt.date] = mapped_column(Date, primary_key=True) source: Mapped[str] = mapped_column(String(8), default="rule") # llm / rule title: Mapped[str] = mapped_column(String(80), default="") content: Mapped[str] = mapped_column(Text, default="") # markdown 正文 pushed: Mapped[bool] = mapped_column(default=False) created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class SignalStat(Base): """信号历史胜率(基于全市场历史日线回测的统计,支撑 AI 证据链的『历史命中率』)。""" __tablename__ = "signal_stats" __table_args__ = (UniqueConstraint("signal", "horizon", name="uq_signal_horizon"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) signal: Mapped[str] = mapped_column(String(24), index=True) horizon: Mapped[int] = mapped_column(Integer, default=5) # 向后 N 个交易日 samples: Mapped[int] = mapped_column(Integer, default=0) win_rate: Mapped[float] = mapped_column(Float, default=0.0) # 上涨占比 % avg_ret: Mapped[float] = mapped_column(Float, default=0.0) # 平均收益 % updated_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class Prediction(Base): """AI 诊断/预测留痕,N 日后核验真实涨跌,形成可回溯的『实测准确率』。""" __tablename__ = "predictions" __table_args__ = (UniqueConstraint("code", "date", "kind", name="uq_pred"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) date: Mapped[dt.date] = mapped_column(Date, index=True) code: Mapped[str] = mapped_column(String(12), index=True) name: Mapped[str] = mapped_column(String(40), default="") kind: Mapped[str] = mapped_column(String(16), default="diagnose") score: Mapped[float] = mapped_column(Float, default=0.0) confidence: Mapped[float] = mapped_column(Float, default=0.0) direction: Mapped[str] = mapped_column(String(6), default="flat") # up/down/flat horizon: Mapped[int] = mapped_column(Integer, default=5) base_close: Mapped[float] = mapped_column(Float, default=0.0) actual_ret: Mapped[float] = mapped_column(Float, default=0.0) status: Mapped[str] = mapped_column(String(8), default="open") # open/closed hit: Mapped[bool | None] = mapped_column(nullable=True) created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class JobRun(Base): """定时/手动任务执行日志。""" __tablename__ = "job_runs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) job: Mapped[str] = mapped_column(String(40)) status: Mapped[str] = mapped_column(String(16)) # running/success/error started_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) finished_at: Mapped[dt.datetime | None] = mapped_column(DateTime, nullable=True) message: Mapped[str] = mapped_column(Text, default="") class SelectorStrategy(Base): """选股策略保存。""" __tablename__ = "selector_strategies" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(80)) description: Mapped[str] = mapped_column(String(200), default="") strategy_json: Mapped[str] = mapped_column(Text) # JSON格式的策略定义 is_preset: Mapped[bool] = mapped_column(default=False) # 是否预设策略 created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class SelectorAlert(Base): """选股条件预警。""" __tablename__ = "selector_alerts" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) strategy_id: Mapped[int] = mapped_column(Integer, index=True) strategy_name: Mapped[str] = mapped_column(String(80)) status: Mapped[str] = mapped_column(String(12), default="active") # active/paused last_checked: Mapped[dt.datetime | None] = mapped_column(DateTime, nullable=True) last_count: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class SocialPost(Base): """社区帖子。""" __tablename__ = "social_posts" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) source: Mapped[str] = mapped_column(String(20), index=True) # eastmoney/xueqiu/guba post_id: Mapped[str] = mapped_column(String(100), unique=True) code: Mapped[str] = mapped_column(String(12), index=True, default="") title: Mapped[str] = mapped_column(String(200)) content: Mapped[str] = mapped_column(Text, default="") author: Mapped[str] = mapped_column(String(80), default="") comment_count: Mapped[int] = mapped_column(Integer, default=0) view_count: Mapped[int] = mapped_column(Integer, default=0) sentiment: Mapped[str] = mapped_column(String(20), default="neutral") # bullish/bearish/neutral keywords: Mapped[str] = mapped_column(String(200), default="") # 逗号分隔 created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now(), index=True) class SentimentIndex(Base): """社区情绪指数(每日)。""" __tablename__ = "sentiment_index" date: Mapped[dt.date] = mapped_column(Date, primary_key=True) bullish_count: Mapped[int] = mapped_column(Integer, default=0) bearish_count: Mapped[int] = mapped_column(Integer, default=0) neutral_count: Mapped[int] = mapped_column(Integer, default=0) bullish_ratio: Mapped[float] = mapped_column(Float, default=0.0) # 0-100 total_posts: Mapped[int] = mapped_column(Integer, default=0) top_keywords: Mapped[str] = mapped_column(String(500), default="") # JSON格式 updated_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class CorporateEvent(Base): """公司事件(财报、增减持、限售解禁等)。""" __tablename__ = "corporate_events" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(12), index=True) name: Mapped[str] = mapped_column(String(40), default="") event_type: Mapped[str] = mapped_column(String(20), index=True) # earnings/insider/unlock/dividend event_date: Mapped[dt.date] = mapped_column(Date, index=True) title: Mapped[str] = mapped_column(String(200)) description: Mapped[str] = mapped_column(Text, default="") amount: Mapped[float] = mapped_column(Float, default=0.0) # 金额(亿元) impact: Mapped[str] = mapped_column(String(20), default="neutral") # positive/negative/neutral created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class PolicyEvent(Base): """行业政策事件。""" __tablename__ = "policy_events" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) sector: Mapped[str] = mapped_column(String(40), index=True) # 受影响板块 event_date: Mapped[dt.date] = mapped_column(Date, index=True) title: Mapped[str] = mapped_column(String(200)) content: Mapped[str] = mapped_column(Text, default="") policy_type: Mapped[str] = mapped_column(String(40)) # subsidy/restriction/support/regulation impact: Mapped[str] = mapped_column(String(20), default="neutral") affected_stocks: Mapped[str] = mapped_column(String(500), default="") # 逗号分隔的股票代码 created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class FinancialReport(Base): """财务报表数据。""" __tablename__ = "financial_reports" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(12), index=True) name: Mapped[str] = mapped_column(String(40), default="") report_date: Mapped[dt.date] = mapped_column(Date, index=True) # 报告期 publish_date: Mapped[dt.date] = mapped_column(Date, index=True) # 发布日期 report_type: Mapped[str] = mapped_column(String(20)) # Q1/Q2/Q3/annual # 核心指标 revenue: Mapped[float] = mapped_column(Float, default=0.0) # 营收(亿元) net_profit: Mapped[float] = mapped_column(Float, default=0.0) # 净利润(亿元) roe: Mapped[float] = mapped_column(Float, default=0.0) # 净资产收益率(%) gross_margin: Mapped[float] = mapped_column(Float, default=0.0) # 毛利率(%) revenue_growth: Mapped[float] = mapped_column(Float, default=0.0) # 营收同比增长(%) profit_growth: Mapped[float] = mapped_column(Float, default=0.0) # 净利润同比增长(%) # 风险指标 inventory: Mapped[float] = mapped_column(Float, default=0.0) # 存货(亿元) receivable: Mapped[float] = mapped_column(Float, default=0.0) # 应收账款(亿元) debt_ratio: Mapped[float] = mapped_column(Float, default=0.0) # 资产负债率(%) # AI摘要 ai_summary: Mapped[str] = mapped_column(String(500), default="") created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now()) class IntradayEvent(Base): """盘中异动事件记录。""" __tablename__ = "intraday_events" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(12), index=True) name: Mapped[str] = mapped_column(String(40), default="") event_type: Mapped[str] = mapped_column(String(20), index=True) # surge/volume_break/limit_open/consecutive/big_order price: Mapped[float] = mapped_column(Float, default=0.0) pct: Mapped[float] = mapped_column(Float, default=0.0) volume_ratio: Mapped[float] = mapped_column(Float, default=0.0) amount: Mapped[float] = mapped_column(Float, default=0.0) # 对于big_order是单笔金额 description: Mapped[str] = mapped_column(String(200), default="") detected_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now(), index=True) notified: Mapped[bool] = mapped_column(default=False)