"""HygieneGate — pre-filter for screener.""" from __future__ import annotations import pandas as pd from .base import GateNode class HygieneGate(GateNode): name = "hygiene" label = "위생 게이트" default_params = { "min_market_cap_won": 50_000_000_000, "min_avg_value_won": 500_000_000, "min_listed_days": 60, "skip_managed": True, "skip_preferred": True, "skip_spac": True, "skip_halted_days": 3, } param_schema = { "type": "object", "properties": { "min_market_cap_won": {"type": "integer", "minimum": 0}, "min_avg_value_won": {"type": "integer", "minimum": 0}, "min_listed_days": {"type": "integer", "minimum": 0}, "skip_managed": {"type": "boolean"}, "skip_preferred": {"type": "boolean"}, "skip_spac": {"type": "boolean"}, "skip_halted_days": {"type": "integer", "minimum": 0}, }, } def filter(self, ctx, params: dict) -> pd.Index: master = ctx.master.copy() prices = ctx.prices # 시총 master = master[master["market_cap"].fillna(0) >= params["min_market_cap_won"]] # 우선주·관리·스팩 if params.get("skip_preferred", True): master = master[master["is_preferred"] == 0] if params.get("skip_managed", True): master = master[master["is_managed"] == 0] if params.get("skip_spac", True): master = master[master["is_spac"] == 0] candidates = master.index # 20일 평균 거래대금 if not prices.empty: recent20 = ( prices[prices["ticker"].isin(candidates)] .sort_values("date") .groupby("ticker") .tail(20) ) avg_value = recent20.groupby("ticker")["value"].mean() ok = avg_value[avg_value >= params["min_avg_value_won"]].index candidates = candidates.intersection(ok) # 최근 N일 거래정지 (volume==0 N일 이상) halted_days = params.get("skip_halted_days", 3) if halted_days > 0 and not prices.empty: recent = ( prices[prices["ticker"].isin(candidates)] .sort_values("date") .groupby("ticker") .tail(halted_days) ) zero_count = ( recent.assign(z=lambda d: (d["volume"] == 0).astype(int)) .groupby("ticker")["z"].sum() ) healthy = zero_count[zero_count < halted_days].index candidates = candidates.intersection(healthy) # 상장 N일 — MVP에선 listed_date null 허용, null이면 통과 return pd.Index(candidates)