"""交易信号领域实体(M8.1,v2 §15 Signal Engine)。 Signal 输入 = Selection 排序(score)+ 价格/技术条件 + 规则;输出事件可解释: BUY / WATCH / SELL(破位警示),每条带 trigger_reason —— 回答 「某日为什么对该股票给 BUY/SELL」(v2 §8)。 """ from __future__ import annotations from datetime import date, datetime from pydantic import BaseModel, Field class SignalRules(BaseModel): """规则(结构化,MVP):买入区间 + 趋势/动量条件 + 卖出/警示区间。""" buy_rank_threshold: int = Field(default=20, ge=1, le=500, description="rank<=此值进入买入候选") buy_require_trend: bool = Field(default=True, description="买入需 close > MA(trend_ma)") buy_require_momentum: bool = Field(default=False, description="买入需 close > 20 日前 close") trend_ma: int = Field(default=60, ge=10, le=250) sell_rank_threshold: int = Field(default=50, ge=1, le=1000, description="rank>此值或破位 → SELL 警示") sell_on_trend_break: bool = Field(default=True, description="买入区间内 close < MA(trend_ma) → SELL") max_output_rank: int = Field(default=80, ge=1, le=2000, description="仅输出排名前 N 的信号") class SignalEvent(BaseModel): symbol: str signal_date: date signal_type: str = Field(pattern="^(BUY|WATCH|SELL)$") score: float | None = None price: float | None = None trigger_reason: list[str] = Field(default_factory=list) class SignalStatistics(BaseModel): universe_size: int = 0 buy: int = 0 watch: int = 0 sell: int = 0 class SignalResult(BaseModel): as_of_date: date rules: SignalRules statistics: SignalStatistics events: list[SignalEvent] = Field(default_factory=list) config_snapshot: dict = Field(default_factory=dict) class SignalMeta(BaseModel): id: str as_of: date buy: int = 0 watch: int = 0 sell: int = 0 created_at: datetime | None = None class SignalHit(BaseModel): """个股在信号历史中的命中(by-symbol 查询,供 Chart 标记)。""" signal_id: str signal_date: date signal_type: str score: float | None = None price: float | None = None trigger_reason: list[str] = Field(default_factory=list)