"""RS Rating — IBD 가중 (3m=2,6m=1,9m=1,12m=1).""" import pandas as pd from .base import ScoreNode, percentile_rank _PERIOD_TO_DAYS = {"3m": 63, "6m": 126, "9m": 189, "12m": 252} class RsRating(ScoreNode): name = "rs_rating" label = "RS Rating (시장 대비 상대강도)" default_params = {"weights": {"3m": 2, "6m": 1, "9m": 1, "12m": 1}} param_schema = { "type": "object", "properties": { "weights": {"type": "object"} }, } def compute(self, ctx, params: dict) -> pd.Series: weights: dict = params.get("weights", self.default_params["weights"]) prices = ctx.prices kospi = ctx.kospi if prices.empty or kospi.empty: return pd.Series(dtype=float) ordered = prices.sort_values("date") def _excess_for_ticker(g: pd.DataFrame) -> float: closes = g.set_index("date")["close"] total = 0.0 wsum = 0.0 for period, w in weights.items(): k = _PERIOD_TO_DAYS.get(period, 0) if len(closes) <= k or len(kospi) <= k: continue r_stock = closes.iloc[-1] / closes.iloc[-(k + 1)] - 1 r_market = kospi.iloc[-1] / kospi.iloc[-(k + 1)] - 1 total += w * (r_stock - r_market) wsum += w return total / wsum if wsum else float("nan") raw = ordered.groupby("ticker", group_keys=False).apply( _excess_for_ticker, include_groups=False ) return percentile_rank(raw).fillna(50.0)