Files
momentum devandClaude Opus 4.8 a16a8cd29d WS3: surface evaluation internals (gap/rvol/per-criterion reasons)
- SetupEvaluationResult.criteria_detail: per-criterion {name,passed,reason}.
- watcher CriteriaEvaluatedEvent now carries gap_pct, relative_volume, status,
  reason, and the per-criterion detail (was only passed/failed names).
- CLI: 'inspect criteria SYMBOL' shows status + gap/rvol + each criterion reason;
  new 'inspect signals' = ready board + per-symbol gap/rvol/status.

Verified on real 2026-06-15 bars: RGNT shows 9/9 with reasons (rvol 26.2x),
'late' explained by the 10:30 ET cutoff. 67/67 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 07:22:55 -04:00

78 lines
2.5 KiB
Python

"""Strategy data models.
Pydantic models for configuration and results.
"""
from pydantic import BaseModel, Field
class SetupCriteria(BaseModel):
"""Configuration for setup evaluation criteria."""
gap_pct_min: float = Field(default=0.05, description="Minimum gap percentage")
relative_volume_min: float = Field(
default=2.5, description="Minimum relative volume"
)
impulse_size_min: float = Field(default=0.05, description="Minimum impulse size")
max_pullback_depth_pct: float = Field(
default=0.40, description="Max pullback depth percent"
)
pullback_volume_ratio_max: float = Field(
default=0.6, description="Max pullback volume ratio"
)
breakout_volume_ratio_min: float = Field(
default=1.5, description="Min breakout volume ratio"
)
min_quality_score: float = Field(
default=0.60, description="Minimum data quality score (default: 0.60)"
)
class CriteriaWeights(BaseModel):
"""Weights for scoring criteria."""
sufficient_data: int = 10
gap: int = 15
relative_volume: int = 15
impulse: int = 15
pullback: int = 10
pullback_volume: int = 10
vwap: int = 10
candle_quality: int = 5
breakout: int = 10
class CriteriaResult(BaseModel):
"""Result of criteria evaluation."""
name: str
passed: bool
reason: str | None = None
class SetupEvaluationResult(BaseModel):
"""Result of setup evaluation."""
status: str = Field(description="Status: ready, blocked, or late")
reason: str | None = Field(
default=None, description="Blocking reason if status is blocked/late"
)
evaluated_at: str | None = Field(default=None, description="Evaluation timestamp")
price: float = Field(description="Current price")
gap_pct: float = Field(description="Gap percentage")
relative_volume: float = Field(description="Relative volume")
criteria_passed: int = Field(description="Number of criteria passed")
criteria_total: int = Field(description="Total number of criteria")
success_score_pct: float = Field(description="Success score percentage")
criteria_names_passed: list[str] = Field(
default_factory=list, description="Names of passed criteria"
)
criteria_names_failed: list[str] = Field(
default_factory=list, description="Names of failed criteria"
)
setups: list[dict] = Field(default_factory=list, description="Detected setups")
criteria_detail: list[dict] = Field(
default_factory=list,
description="Per-criterion breakdown: {name, passed, reason}",
)