"""统一量化可视化 DTO(v3 §20 Chart Service)。 原则:前端只展示本结构,不得自行重算选股/信号/成交(v3 §20.1)。 价格口径:bars 已按请求 adjust 折算(显示层);成交/信号标记与 K 线同坐标系; metadata 记录 adjust_mode 与回测执行价 basis,杜绝图表与回测口径混用(v3 §20.5)。 """ from __future__ import annotations from datetime import date from pydantic import BaseModel, Field class OHLC(BaseModel): time: date open: float | None = None high: float | None = None low: float | None = None close: float | None = None class VolumePoint(BaseModel): time: date value: float | None = None class SeriesPoint(BaseModel): """指标/分数等 (时间, 值) 序列点。""" time: date value: float | None = None class EventMarker(BaseModel): """K 线上可点击的事件标记(选股/信号/实际成交)。""" time: date kind: str = Field( description="selection | signal_buy | signal_sell | signal_watch | fill_buy | fill_sell" ) symbol: str = "" price: float | None = None score: float | None = None text: list[str] = Field(default_factory=list, description="原因/说明(tooltip)") ref_id: str | None = Field(default=None, description="关联 selection/signal 记录 id") class ChartMetadata(BaseModel): symbol: str name: str = "" adjust_mode: str = Field(default="none", description="显示口径:none | qfq | hfq") price_basis: str = Field(default="chart_display", description="显示价基准(显示层折算)") execution_price_basis: str | None = Field( default=None, description="回测执行价口径(如 none),与显示口径不同时用于解释" ) start: date | None = None end: date | None = None bar_count: int = 0 indicator_windows: list[int] = Field(default_factory=list) class ChartResult(BaseModel): """单只股票 / 回测个股的统一图表数据(v3 §20.2)。""" metadata: ChartMetadata bars: list[OHLC] = Field(default_factory=list) volume: list[VolumePoint] = Field(default_factory=list) indicators: dict[str, list[SeriesPoint]] = Field(default_factory=dict) selections: list[EventMarker] = Field(default_factory=list) signals: list[EventMarker] = Field(default_factory=list) fills: list[EventMarker] = Field(default_factory=list) holding_periods: list[dict] = Field(default_factory=list) factor_values: dict[str, list[SeriesPoint]] = Field(default_factory=dict) strategy_scores: dict[str, list[SeriesPoint]] = Field(default_factory=dict) unimplemented: list[str] = Field(default_factory=list) class SelectionHit(BaseModel): """个股在选股历史中的命中(by-symbol 查询)。""" selection_id: str as_of: date method: str symbol: str rank: int score: float selection_reason: list[str] = Field(default_factory=list)