Initial commit: stock analysis backend and prototype UI.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
223
backend/models.py
Normal file
223
backend/models.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""数据中台 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="")
|
||||
Reference in New Issue
Block a user