"""VCP-lite — 단기/장기 일중 변동성 비율 기반 수축률.""" import pandas as pd from .base import ScoreNode, percentile_rank class VcpLite(ScoreNode): name = "vcp_lite" label = "VCP-lite (변동성 수축)" default_params = {"short_window": 40, "long_window": 252} param_schema = { "type": "object", "properties": { "short_window": {"type": "integer", "minimum": 10, "maximum": 120, "default": 40}, "long_window": {"type": "integer", "minimum": 60, "maximum": 504, "default": 252}, }, } def compute(self, ctx, params: dict) -> pd.Series: short_w = int(params.get("short_window", 40)) long_w = int(params.get("long_window", 252)) prices = ctx.prices if prices.empty: return pd.Series(dtype=float) ordered = prices.sort_values("date").copy() ordered["range_pct"] = (ordered["high"] - ordered["low"]) / ordered["close"] def _ratio(s: pd.Series) -> float: if len(s) < long_w: return float("nan") short_vol = s.tail(short_w).mean() long_vol = s.tail(long_w).mean() if long_vol == 0 or pd.isna(long_vol): return float("nan") return 1 - (short_vol / long_vol) raw = ordered.groupby("ticker", group_keys=False)["range_pct"].apply(_ratio) return percentile_rank(raw).fillna(50.0)