as-event/backend/app/db.py

138 lines
6.2 KiB
Python
Raw Permalink Normal View History

2026-07-27 09:05:52 +08:00
"""自有库PostgresORM 模型与会话。
看板只读自有库ETL 把内网库数据同步进这里
建表正式部署用 ddl/own_db_init.sql本模块也提供 init_db() 供开发期 create_all
"""
from __future__ import annotations
from datetime import date, datetime
from typing import Iterator
from sqlalchemy import (
BigInteger,
Date,
DateTime,
Integer,
Numeric,
String,
Text,
UniqueConstraint,
create_engine,
func,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, sessionmaker
from .config import get_settings
settings = get_settings()
engine = create_engine(settings.own_db_dsn, pool_pre_ping=True, future=True)
SessionLocal = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False, future=True)
class Base(DeclarativeBase):
pass
class IndexDaily(Base):
"""指数日线同步落地。OHLC 缺源时 open=high=low=closeohlc_source='close_only'"""
__tablename__ = "index_daily"
__table_args__ = (UniqueConstraint("index_code", "trade_date", name="uq_index_daily"),)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
index_code: Mapped[str] = mapped_column(String(16), index=True)
trade_date: Mapped[date] = mapped_column(Date, index=True)
open: Mapped[float | None] = mapped_column(Numeric(16, 4))
high: Mapped[float | None] = mapped_column(Numeric(16, 4))
low: Mapped[float | None] = mapped_column(Numeric(16, 4))
close: Mapped[float | None] = mapped_column(Numeric(16, 4))
volume: Mapped[int | None] = mapped_column(BigInteger) # 成交量(手),如源有
amount: Mapped[float | None] = mapped_column(Numeric(24, 4)) # 成交额(元)
pct_chg: Mapped[float | None] = mapped_column(Numeric(10, 4)) # 涨跌幅 %
turnover_rate: Mapped[float | None] = mapped_column(Numeric(10, 4))
ohlc_source: Mapped[str] = mapped_column(String(24), default="close_only")
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
class SentimentDaily(Base):
"""情绪日度(源自 gp_market_sentiment可扩展两融/开户等)。"""
__tablename__ = "sentiment_daily"
trade_date: Mapped[date] = mapped_column(Date, primary_key=True)
up_down_ratio: Mapped[float | None] = mapped_column(Numeric(10, 4)) # 涨跌家数比
median_pct_chg: Mapped[float | None] = mapped_column(Numeric(10, 4)) # 全市场涨跌幅中位数
pct_chg_gt_5_count: Mapped[int | None] = mapped_column(Integer) # 涨幅>5%家数(涨停潮代理)
limit_up_count: Mapped[int | None] = mapped_column(Integer)
limit_down_count: Mapped[int | None] = mapped_column(Integer)
margin_balance: Mapped[float | None] = mapped_column(Numeric(24, 4)) # 两融余额(元)
new_accounts: Mapped[int | None] = mapped_column(Integer) # 新增开户数
sentiment_score: Mapped[float | None] = mapped_column(Numeric(10, 4)) # 归一情绪分 0-100
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
class EtfFlow(Base):
"""ETF 流入(人工/导入/自动)。可按单只 ETF 或按类别聚合存。"""
__tablename__ = "etf_flow"
__table_args__ = (
UniqueConstraint("trade_date", "etf_code", name="uq_etf_flow"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
trade_date: Mapped[date] = mapped_column(Date, index=True)
etf_code: Mapped[str] = mapped_column(String(24), default="") # 空串=类别聚合行
etf_name: Mapped[str | None] = mapped_column(String(64))
category: Mapped[str | None] = mapped_column(String(32)) # broad/chinext/star/...
related_index: Mapped[str | None] = mapped_column(String(16)) # 关联指数 dot 式
net_inflow: Mapped[float | None] = mapped_column(Numeric(20, 4)) # 净流入(亿元)
shares_change: Mapped[float | None] = mapped_column(Numeric(20, 4)) # 份额变化(亿份)
source: Mapped[str] = mapped_column(String(16), default="manual") # manual/import/auto
note: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
class MarketEvent(Base):
"""重大事件证监会抓人、巨无霸IPO、政策等"""
__tablename__ = "market_event"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
event_date: Mapped[date] = mapped_column(Date, index=True)
title: Mapped[str] = mapped_column(String(255))
category: Mapped[str] = mapped_column(String(32), default="其他") # 监管/IPO/政策/资金/外部/其他
impact_direction: Mapped[str] = mapped_column(String(16), default="neutral") # bullish/bearish/neutral
severity: Mapped[int] = mapped_column(Integer, default=3) # 1-5
related_indices: Mapped[str | None] = mapped_column(String(128)) # CSV空=全市场
cycle_tag: Mapped[str] = mapped_column(String(16), default="none") # top/bottom/none 经典顶底标记
description: Mapped[str | None] = mapped_column(Text)
source_url: Mapped[str | None] = mapped_column(String(512))
source: Mapped[str] = mapped_column(String(16), default="manual") # manual/import/auto
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
class CycleAnnotation(Base):
"""人工顶底标注(复盘用,看板上手动打点)。"""
__tablename__ = "cycle_annotation"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
index_code: Mapped[str] = mapped_column(String(16), index=True)
anno_date: Mapped[date] = mapped_column(Date, index=True)
kind: Mapped[str] = mapped_column(String(16), default="watch") # top/bottom/watch
note: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
def init_db() -> None:
"""开发期便捷建表;正式部署建议用 ddl/own_db_init.sql。"""
Base.metadata.create_all(bind=engine)
def get_session() -> Iterator[Session]:
"""FastAPI 依赖注入用。"""
with SessionLocal() as s:
yield s