"""Pydantic 数据模型""" from pydantic import BaseModel from datetime import datetime class StockQuote(BaseModel): ts_code: str name: str price: float pct_chg: float volume: float # 成交量(手) amount: float # 成交额(万元) turnover_rate: float # 换手率 % pe: float | None = None pb: float | None = None circ_mv: float | None = None # 流通市值(亿) total_mv: float | None = None # 总市值(亿) volume_ratio: float | None = None # 量比 high: float | None = None low: float | None = None open: float | None = None pre_close: float | None = None limit_up: float | None = None limit_down: float | None = None amplitude: float | None = None # 振幅 % class CapitalFlow(BaseModel): ts_code: str trade_date: str buy_elg_amount: float = 0 # 特大单买入(万) sell_elg_amount: float = 0 buy_lg_amount: float = 0 # 大单买入(万) sell_lg_amount: float = 0 buy_md_amount: float = 0 # 中单买入(万) sell_md_amount: float = 0 buy_sm_amount: float = 0 # 小单买入(万) sell_sm_amount: float = 0 net_mf_amount: float = 0 # 主力净流入(万) @property def main_net_inflow(self) -> float: """主力净流入 = 特大单净买 + 大单净买""" return (self.buy_elg_amount - self.sell_elg_amount + self.buy_lg_amount - self.sell_lg_amount) @property def total_amount(self) -> float: return (self.buy_elg_amount + self.sell_elg_amount + self.buy_lg_amount + self.sell_lg_amount + self.buy_md_amount + self.sell_md_amount + self.buy_sm_amount + self.sell_sm_amount) class SectorInfo(BaseModel): sector_code: str sector_name: str board_type: str = "snapshot" # industry / concept / snapshot theme_id: str = "" theme_name: str = "" theme_aliases: list[str] = [] trade_date: str = "" pct_change: float = 0 # 涨跌幅 % capital_inflow: float = 0 # 主力净流入(万元,原始数据来自Tushare亿元×10000) limit_up_count: int = 0 # 涨停数 days_continuous: int = 0 # 连续资金流入天数 heat_score: float = 0 # 热度综合评分 stage: str = "mid" # 板块阶段: early/mid/late/end # ── 板块分析增强字段 ── member_count: int = 0 # 成分股数量 leading_stocks: list[dict] = [] # 领涨股 [{ts_code, name, pct_chg, amount}] capital_trend: list[float] = [] # 近5日资金净流入趋势(万) pct_trend: list[float] = [] # 近5日涨跌幅趋势 turnover_avg: float = 0 # 板块平均换手率 main_force_ratio: float = 0 # 主力资金占比(主力净流入/总成交额) realtime_pct_change: float | None = None realtime_limit_up_count: int | None = None realtime_amount: float | None = None realtime_turnover_rate: float | None = None realtime_up_count: int | None = None realtime_down_count: int | None = None leading_stocks_realtime: list[dict] = [] is_realtime: bool = False data_mode: str = "daily_snapshot" source: str = "snapshot" data_status: str = "fresh" # fresh / stale / fallback / snapshot source_detail: str = "" catalyst_score: float = 0 catalyst_count: int = 0 catalyst_reasons: list[str] = [] class MarketTemperature(BaseModel): trade_date: str up_count: int = 0 # 上涨家数 down_count: int = 0 # 下跌家数 limit_up_count: int = 0 # 涨停数(自然涨停) limit_down_count: int = 0 # 跌停数 max_streak: int = 0 # 最高连板 broken_rate: float = 0 # 炸板率 % index_above_ma20: bool = False # 上证在 MA20 上方 temperature: float = 0 # 综合温度 0-100 source: str = "snapshot" data_status: str = "fresh" # fresh / estimated / degraded / snapshot source_detail: str = "" limit_counts_reliable: bool = False class MarketBreadth(BaseModel): trade_date: str up_count: int = 0 down_count: int = 0 flat_count: int = 0 limit_up_count: int = 0 limit_down_count: int = 0 total_count: int = 0 sample_count: int = 0 source: str = "snapshot" reliable: bool = False limit_counts_reliable: bool = False class TechnicalSignal(BaseModel): ts_code: str name: str = "" ma_bullish: bool = False # 均线多头排列 volume_breakout: bool = False # 放量突破 macd_golden: bool = False # MACD金叉 rsi_healthy: bool = False # RSI节奏区间(辅助参考) pullback_support: bool = False # 缩量回踩支撑 big_yang: bool = False # 底部放量长阳 boll_support: bool = False # 布林带下轨支撑 score: float = 0 # 信号触发计数分 trend_score: float = 0 # 趋势评分(推荐体系用的技术面分数) signal_count: int = 0 # 满足的信号数量 # 位置安全评估(防追高) rally_pct_5d: float = 0 # 近5日累计涨幅 rally_pct_10d: float = 0 # 近10日累计涨幅 distance_from_high: float = 0 # 距离60日高点 %(负=已回调) position_score: float = 50 # 位置安全得分 0-100 # 价格参考 support_price: float | None = None # 支撑位 resist_price: float | None = None # 压力位 stop_loss_price: float | None = None # 止损价 class Recommendation(BaseModel): ts_code: str name: str sector: str score: float # 综合评分 market_temp_score: float sector_score: float capital_score: float technical_score: float supply_demand_score: float = 0 # 供需评分 price_action_score: float = 0 # 价格行为评分 position_score: float = 50 # 位置安全得分 valuation_score: float = 50 # 估值安全得分 signal: str # BUY / SELL / HOLD entry_price: float | None = None target_price: float | None = None stop_loss: float | None = None reasons: list[str] = [] risk_note: str = "" level: str = "" # 强烈推荐/推荐/观望/回避 strategy: str = "trend_breakout" # trend_breakout / momentum(旧) / potential(旧) entry_signal_type: str = "none" # breakout / pullback / launch / none entry_timing: str = "" # 进场时机建议(盘中适用) action_plan: str = "观察" # 可操作 / 重点关注 / 观察 trigger_condition: str = "" # 触发条件 invalidation_condition: str = "" # 失效条件 suggested_position_pct: float = 0 # 建议仓位 % review_after_days: int = 3 # 建议复盘天数 lifecycle_status: str = "candidate" # candidate / actionable / tracking / closed data_freshness: str = "" # 数据新鲜度说明 llm_analysis: str = "" # LLM 深度分析 llm_score: float | None = None # AI 评分 1-10 recall_tags: list[str] = [] prefilter_decision: str = "" prefilter_reason: str = "" focus_points: list[str] = [] decision_trace: dict = {} scan_session: str = "" created_at: datetime | None = None class StrategyFocus(BaseModel): label: str description: str class StrategySectorFocus(BaseModel): sector_name: str stage: str = "mid" heat_score: float = 0 pct_change: float = 0 limit_up_count: int = 0 turnover_rate: float = 0 up_count: int = 0 down_count: int = 0 data_mode: str = "daily_snapshot" view: str = "" class StrategyBoard(BaseModel): trade_date: str data_mode: str = "daily_snapshot" market_regime: str risk_level: str action_bias: str position_suggestion: str summary: str recommended_mode: str strategy_focus: list[StrategyFocus] = [] watch_sectors: list[StrategySectorFocus] = [] avoid_rules: list[str] = [] iteration_notes: list[str] = [] iteration_report: dict = {} metrics: dict = {} ai_review: str = "" generated_by: str = "rules"