feat(signal): M8.1 交易信号引擎(规则 + signal_event 落库 + /api/signals)

- SignalRules(买入 rank 阈值/趋势 MA/动量 + 卖出区间/破位警示)+ SignalEvent
  (BUY/WATCH/SELL,score/price/trigger_reason 可解释)+ SignalResult/Meta
- quant/signal.generate_signals:与选股同一评分引擎取全市场 rank,按规则分类输出
- signal_snapshot/signal_event 表(migration d8e0b2f3c4d5,MySQL 已应用)+ Repo
- SignalService + POST /api/signals(同步+落库)、GET 详情/列表
- tests/test_signals.py(引擎分类/排序/破位不 BUY、service、API 提交读回);全量 pytest 通过
This commit is contained in:
Simon
2026-09-09 00:35:37 +08:00
parent ef09d5b419
commit ba52edc2d6
12 changed files with 715 additions and 0 deletions
@@ -26,3 +26,7 @@ from app.infrastructure.persistence.sqlalchemy.models.selection import ( # noqa
SelectionResultModel,
SelectionSnapshotModel,
)
from app.infrastructure.persistence.sqlalchemy.models.signal import ( # noqa: F401
SignalEventModel,
SignalSnapshotModel,
)
@@ -0,0 +1,41 @@
"""交易信号表(M8.1)。
signal_snapshot:一次信号运行的查询/规则/统计快照
signal_event:逐信号(signal_type + score/price + trigger_reason JSON)
"""
from __future__ import annotations
from datetime import date, datetime
from sqlalchemy import BigInteger, Date, DateTime, Integer, Numeric, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.infrastructure.persistence.sqlalchemy.base import Base
PK_INT = BigInteger().with_variant(Integer, "sqlite")
class SignalSnapshotModel(Base):
__tablename__ = "signal_snapshot"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
as_of: Mapped[date] = mapped_column(Date, index=True)
query_json: Mapped[str] = mapped_column(Text)
rules_json: Mapped[str] = mapped_column(Text)
statistics_json: Mapped[str] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(DateTime, index=True)
class SignalEventModel(Base):
__tablename__ = "signal_event"
id: Mapped[int] = mapped_column(PK_INT, primary_key=True, autoincrement=True)
signal_id: Mapped[str] = mapped_column(String(32), index=True)
symbol: Mapped[str] = mapped_column(String(12))
signal_date: Mapped[date] = mapped_column(Date)
signal_type: Mapped[str] = mapped_column(String(8))
score: Mapped[float | None] = mapped_column(Numeric(14, 6), nullable=True)
price: Mapped[float | None] = mapped_column(Numeric(14, 4), nullable=True)
reason_json: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime)