"""Wyckoff Screener domain models — Architecture v1.0 frozen contracts.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import date, datetime from enum import Enum from typing import Any, Optional class WyckoffCycle(str, Enum): ACCUMULATION = "Accumulation" RE_ACCUMULATION = "ReAccumulation" MARKUP = "Markup" DISTRIBUTION = "Distribution" RE_DISTRIBUTION = "ReDistribution" MARKDOWN = "Markdown" UNKNOWN = "Unknown" class WyckoffPhase(str, Enum): A = "A" B = "B" C = "C" D = "D" E = "E" NONE = "None" class WyckoffEvent(str, Enum): PS = "PS" SC = "SC" AR = "AR" ST = "ST" SPRING = "Spring" TEST = "Test" SOS = "SOS" LPS = "LPS" JUMP = "Jump" BACKUP = "Backup" BC = "BC" UTAD = "UTAD" SOW = "SOW" LPSY = "LPSY" NONE = "None" class DecisionSignal(str, Enum): STRONG_BUY = "StrongBuy" BUY = "Buy" WATCH = "Watch" AVOID = "Avoid" SELL = "Sell" class RiskLevel(str, Enum): LOW = "Low" MEDIUM = "Medium" HIGH = "High" @dataclass class EngineResult: """Unified result envelope for every Wyckoff engine (v1.0 contract).""" name: str version: str = "1.0.0" confidence: float = 0.0 score: float = 0.0 reasons: list[str] = field(default_factory=list) warnings: list[str] = field(default_factory=list) metrics: dict[str, Any] = field(default_factory=dict) payload: dict[str, Any] = field(default_factory=dict) def to_dict(self) -> dict[str, Any]: return { "name": self.name, "version": self.version, "confidence": self.confidence, "score": self.score, "reasons": self.reasons, "warnings": self.warnings, "metrics": self.metrics, "payload": self.payload, } @dataclass class OHLCVFrame: """In-memory OHLCV for one symbol one timeframe. Engines never touch DB.""" ts_code: str timeframe: str # "1d" | "1w" | "1M" trade_dates: list[date] open: list[float] high: list[float] low: list[float] close: list[float] volume: list[float] amount: list[float] = field(default_factory=list) def __len__(self) -> int: return len(self.close) @property def empty(self) -> bool: return len(self.close) == 0 @dataclass class WyckoffScanRow: """Persisted scan row for wyckoff_scan table.""" trade_date: date ts_code: str name: str = "" industry: str = "" engine_version: str = "v1.0.0" m_cycle: str = WyckoffCycle.UNKNOWN.value cycle_confidence: float = 0.0 trend_score: float = 0.0 w_cycle: str = WyckoffCycle.UNKNOWN.value w_phase: str = WyckoffPhase.NONE.value w_current_event: str = WyckoffEvent.NONE.value w_recent_events_json: str = "[]" phase_confidence: float = 0.0 structure_score: float = 0.0 d_current_event: str = WyckoffEvent.NONE.value d_recent_events_json: str = "[]" event_confidence: float = 0.0 entry_score: float = 0.0 entry: Optional[float] = None stop: Optional[float] = None target1: Optional[float] = None target2: Optional[float] = None rr: Optional[float] = None alignment: float = 0.0 stars: int = 1 decision_signal: str = DecisionSignal.WATCH.value signal_confidence: float = 0.0 overall_confidence: float = 0.0 overall_score: float = 0.0 risk: str = RiskLevel.MEDIUM.value reasons_json: str = "[]" feature_snapshot_json: str = "{}" markers_json: str = "[]" scanned_at: datetime = field(default_factory=datetime.now)