"""이평선 정배열 점수 — 5개 조건 충족 개수 / 5 × 100.""" import pandas as pd from .base import ScoreNode class MaAlignment(ScoreNode): name = "ma_alignment" label = "이평선 정배열" default_params = {"ma_periods": [50, 150, 200]} param_schema = { "type": "object", "properties": { "ma_periods": {"type": "array", "items": {"type": "integer"}} }, } def compute(self, ctx, params: dict) -> pd.Series: ma_periods = params.get("ma_periods", self.default_params["ma_periods"]) if len(ma_periods) != 3: raise ValueError("ma_periods must have 3 entries (short, medium, long)") ma_s, ma_m, ma_l = (int(x) for x in ma_periods) prices = ctx.prices if prices.empty: return pd.Series(dtype=float) ordered = prices.sort_values("date") min_history = max(252, ma_l) def _score(s: pd.Series) -> float: closes = s.astype(float).reset_index(drop=True) if len(closes) < min_history: return float("nan") close = closes.iloc[-1] ma_short = closes.rolling(ma_s).mean().iloc[-1] ma_medium = closes.rolling(ma_m).mean().iloc[-1] ma_long = closes.rolling(ma_l).mean().iloc[-1] low52 = closes.iloc[-252:].min() conds = [ close > ma_short, ma_short > ma_medium, ma_medium > ma_long, close > ma_long, close >= low52 * 1.25, ] return sum(conds) / 5 * 100.0 raw = ordered.groupby("ticker", group_keys=False)["close"].apply(_score) return raw.fillna(0.0)