自动刷新常态只拉 recent 尾部 K,每 1 分钟全量重算缠论;修复结构区缓存导入;默认指标/4h·1h·15m/近30天;同步 ECR-009 screener 相关改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
155 lines
3.6 KiB
Python
155 lines
3.6 KiB
Python
"""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"
|
|
combo_id: str = "d_w_m"
|
|
|
|
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)
|