"""하드닝 R1: realtime 규칙 디스패치 — SELL 규칙·차단 신호 종목이 매수 후보가 되지 않는다.""" from types import SimpleNamespace as NS from app.api.endpoints.recommendations import _generic_rule_matches_v2 def _rule(t): return NS(rule_type=t) def test_sell_rule_never_buy_candidate(): s = NS(rsi=10, signals=[]) assert _generic_rule_matches_v2(_rule("SELL"), s, {}) is False def test_blocked_signal_excluded(): s = NS(rsi=10, signals=["RSI 과매도 (거래량부족-차단)"]) assert _generic_rule_matches_v2(_rule("BUY"), s, {}) is False def test_buy_rule_keeps_existing_threshold(): ok = NS(rsi=40, signals=[]) no = NS(rsi=41, signals=[]) assert _generic_rule_matches_v2(_rule("BUY"), ok, {}) is True assert _generic_rule_matches_v2(_rule("BUY"), no, {}) is False assert _generic_rule_matches_v2(_rule("BUY"), no, {"rsi_threshold": 45}) is True def test_rsi_none_false(): assert _generic_rule_matches_v2(_rule("BUY"), NS(rsi=None, signals=[]), {}) is False def test_rsi_below_key_is_honored_strictly(): r = _rule("BUY") cond = {"rsi_below": 30} assert _generic_rule_matches_v2(r, NS(rsi=29.9, signals=[]), cond) is True assert _generic_rule_matches_v2(r, NS(rsi=30, signals=[]), cond) is False assert _generic_rule_matches_v2(r, NS(rsi=35, signals=[]), cond) is False def test_volume_condition_enforced_and_fail_closed(): r = _rule("BUY") cond = {"rsi_below": 30, "volume_above_average": 1.5} assert _generic_rule_matches_v2(r, NS(rsi=20, signals=[], volume_ratio=1.6), cond) is True assert _generic_rule_matches_v2(r, NS(rsi=20, signals=[], volume_ratio=1.2), cond) is False # 거래량 배수 미노출 -> 통과 금지 assert _generic_rule_matches_v2(r, NS(rsi=20, signals=[]), cond) is False def test_unknown_condition_keys_fail_closed(): assert _generic_rule_matches_v2(_rule("BUY"), NS(rsi=10, signals=[]), {"foo": 1}) is False