Files
stock_cursor_v0/backend/models.py
2026-06-16 03:00:06 +08:00

451 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""数据中台 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 SectorLeader(Base):
"""板块每日龙头股前5按成交额"""
__tablename__ = "sector_leaders"
__table_args__ = (UniqueConstraint("date", "sector", "code", name="uq_sector_leader"),)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
date: Mapped[dt.date] = mapped_column(Date, index=True)
sector: Mapped[str] = mapped_column(String(40), index=True)
code: Mapped[str] = mapped_column(String(12))
name: Mapped[str] = mapped_column(String(40))
pct: Mapped[float] = mapped_column(Float, default=0.0)
price: Mapped[float] = mapped_column(Float, default=0.0)
amount: Mapped[float] = mapped_column(Float, default=0.0)
rank: Mapped[int] = mapped_column(Integer, default=0)
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~160日价格分位
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)
class PaperAccount(Base):
"""模拟盘账户。"""
__tablename__ = "paper_accounts"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(50), default="默认模拟盘")
initial_cash: Mapped[float] = mapped_column(Float, default=1_000_000.0)
cash: Mapped[float] = mapped_column(Float, default=1_000_000.0)
is_active: Mapped[bool] = mapped_column(default=True)
created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now())
class PaperTrade(Base):
"""模拟盘交易记录。"""
__tablename__ = "paper_trades"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
account_id: Mapped[int] = mapped_column(Integer, index=True, default=1)
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)
cash_before: Mapped[float] = mapped_column(Float, default=0.0)
cash_after: Mapped[float] = mapped_column(Float, default=0.0)
reason: Mapped[str] = mapped_column(String(60), default="")
created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now())
class User(Base):
"""用户表(用于鉴权)。"""
__tablename__ = "users"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(50), unique=True, index=True)
hashed_password: Mapped[str] = mapped_column(String(100))
is_admin: Mapped[bool] = mapped_column(default=False)
is_active: Mapped[bool] = mapped_column(default=True)
created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now())
class WatchlistGroup(Base):
"""自选股分组。"""
__tablename__ = "watchlist_groups"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(50))
description: Mapped[str] = mapped_column(String(200), default="")
color: Mapped[str] = mapped_column(String(20), default="blue") # 分组颜色标识
sort_order: Mapped[int] = mapped_column(Integer, default=0)
is_default: Mapped[bool] = mapped_column(default=False)
created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now())
class WatchlistItem(Base):
"""自选股项目。"""
__tablename__ = "watchlist_items"
__table_args__ = (UniqueConstraint("group_id", "code", name="uq_watchlist_group_code"),)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
group_id: Mapped[int] = mapped_column(Integer, index=True)
code: Mapped[str] = mapped_column(String(12), index=True)
name: Mapped[str] = mapped_column(String(40), default="")
sort_order: Mapped[int] = mapped_column(Integer, default=0)
note: Mapped[str] = mapped_column(String(200), default="") # 个股备注
added_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now())
class ScheduledTask(Base):
"""定时任务配置。"""
__tablename__ = "scheduled_tasks"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
task_id: Mapped[str] = mapped_column(String(40), unique=True, index=True) # 任务标识
name: Mapped[str] = mapped_column(String(80)) # 任务名称
description: Mapped[str] = mapped_column(String(200), default="") # 描述
enabled: Mapped[bool] = mapped_column(default=True) # 是否启用
schedule_type: Mapped[str] = mapped_column(String(20), default="cron") # cron/interval
cron_expression: Mapped[str] = mapped_column(String(50), default="") # cron表达式
interval_seconds: Mapped[int] = mapped_column(Integer, default=0) # 间隔秒数
category: Mapped[str] = mapped_column(String(20), default="其他") # 分类
last_run: Mapped[dt.datetime | None] = mapped_column(DateTime, nullable=True) # 上次运行
run_count: Mapped[int] = mapped_column(Integer, default=0) # 运行次数
last_status: Mapped[str] = mapped_column(String(20), default="") # 上次状态
last_message: Mapped[str] = mapped_column(String(500), default="") # 上次消息
created_at: Mapped[dt.datetime] = mapped_column(DateTime, server_default=func.now())