138 lines
6.2 KiB
Python
138 lines
6.2 KiB
Python
"""自有库(Postgres)ORM 模型与会话。
|
||
|
||
看板只读自有库;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=close,ohlc_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
|