diff --git a/stock-lab/app/screener/nodes/high52w.py b/stock-lab/app/screener/nodes/high52w.py new file mode 100644 index 0000000..9b6f86d --- /dev/null +++ b/stock-lab/app/screener/nodes/high52w.py @@ -0,0 +1,30 @@ +"""52주 신고가 근접도 (룰 기반: 70% 미만 0점, 100% 도달 100점, 선형).""" + +import pandas as pd + +from .base import ScoreNode + + +class High52WProximity(ScoreNode): + name = "high52w" + label = "52주 신고가 근접도" + default_params = {"window_days": 252} + param_schema = { + "type": "object", + "properties": { + "window_days": {"type": "integer", "minimum": 60, "maximum": 504, "default": 252} + }, + } + + def compute(self, ctx, params: dict) -> pd.Series: + window = int(params.get("window_days", 252)) + prices = ctx.prices + if prices.empty: + return pd.Series(dtype=float) + + ordered = prices.sort_values("date") + last = ordered.groupby("ticker").tail(window) + agg = last.groupby("ticker").agg(close=("close", "last"), high=("high", "max")) + proximity = (agg["close"] / agg["high"]).clip(upper=1.0) + score = ((proximity - 0.7) / 0.3).clip(lower=0.0, upper=1.0) * 100.0 + return score.fillna(0.0) diff --git a/stock-lab/app/test_screener_nodes_high52w.py b/stock-lab/app/test_screener_nodes_high52w.py new file mode 100644 index 0000000..70f0ff1 --- /dev/null +++ b/stock-lab/app/test_screener_nodes_high52w.py @@ -0,0 +1,32 @@ +import datetime as dt +import pandas as pd + +from app.screener.engine import ScreenContext +from app.screener.nodes.high52w import High52WProximity +from app.screener._test_fixtures import make_master, make_prices, make_flow + + +def _ctx(master, prices, flow): + return ScreenContext(master=master, prices=prices, flow=flow, + kospi=pd.Series(dtype=float, name="kospi"), + asof=dt.date(2026, 5, 12)) + + +def test_proximity_at_high_returns_100(): + asof = dt.date(2026, 5, 12) + master = make_master(["A"]) + prices = make_prices(["A"], days=260, asof=asof, trend_pct=0.05) + flow = make_flow(["A"], days=260, asof=asof) + + out = High52WProximity().compute(_ctx(master, prices, flow), {"window_days": 252}) + assert out["A"] >= 95 + + +def test_proximity_below_70pct_returns_0(): + asof = dt.date(2026, 5, 12) + master = make_master(["A"]) + prices = make_prices(["A"], days=260, asof=asof, start_close=100000, trend_pct=-0.5) + flow = make_flow(["A"], days=260, asof=asof) + + out = High52WProximity().compute(_ctx(master, prices, flow), {"window_days": 252}) + assert out["A"] == 0