This commit is contained in:
2026-09-29 22:48:41 +08:00
parent a490fdc110
commit 4a27e35ff4
13 changed files with 719 additions and 15 deletions
+147
View File
@@ -710,3 +710,150 @@ class IndexWeightsResponse(BaseModel):
total: int # 成分股总数(返回 items 为按权重降序的子集)
items: list[IndexWeightItemOut] = []
# ---------- 买卖点实验室(/signals:手动标注共同点分析 + 条件扫描) ----------
class SignalPlanCreate(BaseModel):
ts_code: str = Field(min_length=6, max_length=12)
name: str = Field(default="", max_length=64) # 空则后端默认「{code} 买点方案」
class SignalPlanUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=64)
note: str | None = None
conditions: list[IndicatorCondition] | None = None # None=不改;[] = 清空
class SignalPointIn(BaseModel):
kind: Literal["buy", "sell"]
trade_date: date
source: Literal["manual", "scan"] = "manual"
class SignalPointOut(BaseModel):
kind: str # buy | sell
trade_date: date
source: str # manual | scan
model_config = {"from_attributes": True}
class SignalPlanOut(BaseModel):
id: int
ts_code: str
name: str
note: str | None = None
conditions: list[IndicatorCondition] = Field(default_factory=list)
points: list[SignalPointOut] = Field(default_factory=list)
created_at: datetime
updated_at: datetime
class SignalPlanListResponse(BaseModel):
items: list[SignalPlanOut]
class SignalsAnalyzeRequest(BaseModel):
ts_code: str = Field(min_length=6, max_length=12)
buy_dates: list[date] = Field(min_length=1, max_length=200)
class FeatureFindingOut(BaseModel):
"""共同特征发现:特征在买点日的命中率 vs 全期基线比例(纯算法统计)。"""
key: str
label: str
samples_hit: int # 命中特征的买点数
samples: int # 有效买点数
hit_rate: float # 买点命中率 %(0-100)
base_rate: float # 全期基线比例 %(0-100)
lift: float # 命中率 / 基线
mappable: bool # 是否可转成 IndicatorCondition(进扫描/回测)
condition: IndicatorCondition | None = None
class ValueSummaryOut(BaseModel):
"""连续值摘要:买点日中位数 vs 全期中位数(直觉对照用)。"""
key: str
label: str
at_points: float | None
baseline: float | None
class SignalsAnalyzeResponse(BaseModel):
ts_code: str
samples: int # 有效买点数(落在该股交易日上)
total_bars: int
findings: list[FeatureFindingOut]
summaries: list[ValueSummaryOut]
class WindowStatOut(BaseModel):
window: int # 持有 N 交易日
samples: int
mean: float # 平均收益 %
median: float
win_rate: float # 收益>0 占比 %
p10: float
p90: float
class EvalPointOut(BaseModel):
"""单个买点的评估:未来 N 交易日收益矩阵(复权校正)。"""
date: date # 买点(信号)日
entry_date: date | None = None # 实际入场日(默认信号次日)
entry_price: float | None = None
rets: dict[str, float | None] = Field(default_factory=dict) # "5" -> 收益%(越界/缺数据 None)
max_gain: dict[str, float | None] = Field(default_factory=dict) # 窗口内最大涨幅 %
max_dd: dict[str, float | None] = Field(default_factory=dict) # 窗口内最大回撤 %
class EvalPairOut(BaseModel):
"""手动配对(买点 -> 其后最近卖点)的一笔评估。"""
buy_date: date
sell_date: date
entry_date: date | None = None
entry_price: float | None = None
exit_price: float | None = None
ret_pct: float | None = None # 复权校正收益 %
class PairStatsOut(BaseModel):
samples: int
mean: float
median: float
win_rate: float
max: float
min: float
class SignalsEvaluateResult(BaseModel):
samples: int
points: list[EvalPointOut] = Field(default_factory=list)
stats: list[WindowStatOut] = Field(default_factory=list)
pairs: list[EvalPairOut] = Field(default_factory=list)
pair_stats: PairStatsOut | None = None
class SignalsScanRequest(BaseModel):
ts_code: str = Field(min_length=6, max_length=12)
conditions: list[IndicatorCondition] = Field(min_length=1, max_length=10)
windows: list[int] = Field(default_factory=lambda: [1, 3, 5, 10, 20, 60])
start: date | None = None
end: date | None = None
class SignalsScanResponse(BaseModel):
ts_code: str
dates: list[date] # 命中信号日(升序,已丢弃指标预热区)
total: int
evaluate: SignalsEvaluateResult
class SignalsEvaluateRequest(BaseModel):
ts_code: str = Field(min_length=6, max_length=12)
buy_dates: list[date] = Field(min_length=1, max_length=500)
sell_dates: list[date] = Field(default_factory=list, max_length=500)
windows: list[int] = Field(default_factory=lambda: [1, 3, 5, 10, 20, 60])
# 进出场时机默认与 /api/backtest/event 对齐:信号次日入场、到期收盘卖出
entry_timing: Literal["next_open", "next_close"] = "next_open"
exit_timing: Literal["close", "open"] = "close"