"""研究领域对象:Research Specification、标准化研究结果。 原则(AGENT.md §16/§21/§24、ARCHITECTURE §14): - 前端 / Agent / 后端统一经 Research Specification 描述任务,禁止直接拼引擎配置 - 回测结果一律标准化为 BacktestResult;未建模的成本/市场约束显式列在 unimplemented,禁止默认「无成本 / 永远可成交」假设 """ from __future__ import annotations from datetime import date, datetime from pydantic import BaseModel, Field, field_validator, model_validator # ---------- Research Specification ---------- class UniverseSpec(BaseModel): """股票池口径。MVP:市场 + 过滤条件;指数成分等 Phase 3 扩展。""" market: str = Field(default="CN_A", description="CN_A / CN_B / ...") exclude_st: bool = True exclude_suspended: bool = True min_listing_days: int = Field(default=250, ge=0, description="上市至少 N 个自然日") class FactorSpec(BaseModel): """引用一个已注册因子并给定权重。""" name: str weight: float = Field(default=1.0, gt=0) class SelectionSpec(BaseModel): """选股方式。MVP:按加权因子得分取 Top N 等权。""" top_n: int = Field(default=30, ge=1, le=1000) class CostSpec(BaseModel): """交易成本模型(单边比例)。 buy = commission + slippage;sell = commission + stamp_tax + slippage。 """ commission_rate: float = Field(default=0.0003, ge=0, le=0.01) stamp_tax_rate: float = Field(default=0.0005, ge=0, le=0.01) slippage_rate: float = Field(default=0.001, ge=0, le=0.05) benchmark: str = Field(default="000300.SH", description="对照基准指数代码") class ResearchSpec(BaseModel): """一次研究的完整描述。type 决定执行路径。""" type: str = Field(default="backtest", pattern="^(factor_test|backtest)$") universe: UniverseSpec = UniverseSpec() factors: list[FactorSpec] = Field(min_length=1) selection: SelectionSpec = SelectionSpec() rebalance: str = Field(default="monthly", pattern="^(weekly|monthly)$") period: tuple[date, date] costs: CostSpec = CostSpec() initial_capital: float = Field(default=1_000_000.0, gt=0) @field_validator("period") @classmethod def _period_ordered(cls, period: tuple[date, date]) -> tuple[date, date]: if period[0] >= period[1]: raise ValueError("period 必须满足 start < end") return period @model_validator(mode="after") def _no_duplicate_factors(self) -> ResearchSpec: names = [f.name for f in self.factors] if len(set(names)) != len(names): raise ValueError("factors 存在重复因子名") return self # ---------- 回测结果 ---------- class CurvePoint(BaseModel): date: date value: float class MonthlyReturn(BaseModel): year: int month: int return_pct: float # 百分数,如 3.2 表示 +3.2% class YearlyReturn(BaseModel): year: int return_pct: float class BacktestSummary(BaseModel): start: date end: date initial_capital: float final_equity: float total_return_pct: float annual_return_pct: float sharpe: float max_drawdown_pct: float volatility_pct: float win_rate_pct: float total_trades: int avg_turnover_pct: float benchmark_return_pct: float | None = None class Trade(BaseModel): entry_date: date exit_date: date symbol: str entry_price: float exit_price: float return_pct: float class Position(BaseModel): date: date symbol: str weight: float class BacktestResult(BaseModel): """标准化回测结果(ARCHITECTURE §14)。前端只依赖该结构。""" summary: BacktestSummary equity_curve: list[CurvePoint] drawdown: list[CurvePoint] monthly_returns: list[MonthlyReturn] yearly_returns: list[YearlyReturn] positions: list[Position] trades: list[Trade] turnover_pct: float unimplemented: list[str] = Field( default_factory=list, description="本结果中未建模的约束(AGENT §24:必须显式标注,禁止假装支持)", ) config_snapshot: dict = Field(default_factory=dict, description="复现用完整配置快照") # ---------- 因子测试结果 ---------- class QuantileReturn(BaseModel): """分层收益:按因子值升序分 N 层后各层等权组合的区间收益。""" quantile: int return_pct: float class FactorTestReport(BaseModel): factor_name: str ic_mean: float icir: float rank_ic_mean: float positive_ratio_pct: float quantile_returns: list[QuantileReturn] spread_quantile: int | None = Field( default=None, description="分层价差 = 最高层收益 - 最低层收益(若多头/空头语义适用)" ) sample_days: int unimplemented: list[str] = Field(default_factory=list) config_snapshot: dict = Field(default_factory=dict) # ---------- 异步 Job 与 Experiment(Phase 4) ---------- class JobStatus(str): """统一状态机(AGENT.md §20):queued→running→(success|failed|cancelled)。""" QUEUED = "queued" RUNNING = "running" SUCCESS = "success" FAILED = "failed" CANCELLED = "cancelled" class JobRecord(BaseModel): """一次异步研究任务。spec/result 以 JSON 文本存储(保持 Schema 演进自由)。""" id: str kind: str # factor_test | backtest status: str = JobStatus.QUEUED stage: str | None = None spec_json: str error: str | None = None result_json: str | None = None experiment_id: str | None = None created_at: datetime | None = None started_at: datetime | None = None finished_at: datetime | None = None class ExperimentRecord(BaseModel): """一次研究的可复现存档(AGENT.md §21)。""" id: str kind: str # factor_test | backtest spec_json: str result_json: str summary_text: str | None = None # 便于列表展示的摘要(如 total_return_pct) code_version: str | None = None # git commit / 代码指纹 data_version: str | None = None job_id: str | None = None created_at: datetime | None = None