diff --git a/alert_state.py b/alert_state.py index 37e33b5..b11ce47 100644 --- a/alert_state.py +++ b/alert_state.py @@ -33,6 +33,11 @@ pending_groups = [] # skip. interval 별로 각각 한 번씩 sync. synced_intervals = set() +# (interval, sig) 별 forming candle 의 연속 True polling 카운트. +# 진입 신호가 forming 중 1회 깜빡으로 발사되는 false alert 차단용. +# 항목 형식: {(interval, sig): {"candle_time": ts, "count": int}} +signal_seen_count = {} + alert_symbol = "BTCUSDT" alert_interval = "5m" # UI 표시용; 알림 스레드는 multi-TF 모니터링이라 무시 alert_lock = threading.Lock() diff --git a/app_streamlit.py b/app_streamlit.py index 1286558..3fc79a3 100644 --- a/app_streamlit.py +++ b/app_streamlit.py @@ -149,7 +149,12 @@ def check_and_alert(df, symbol, interval): if sig not in recent.columns: continue triggered = recent[recent[sig].fillna(False)] + seen_key = (interval, sig) + prev_seen = alert_state.signal_seen_count.get(seen_key) if triggered.empty: + # 신호 사라짐 → 카운터 리셋 (다음 True 시점부터 다시 1회 카운트) + if prev_seen: + alert_state.signal_seen_count[seen_key] = {"candle_time": prev_seen["candle_time"], "count": 0} continue candle_time = triggered.iloc[-1]["open_time"] state_key = (interval, key) @@ -157,6 +162,15 @@ def check_and_alert(df, symbol, interval): continue if now - alert_state.last_alert.get(state_key, 0) <= ALERT_COOLDOWN: continue + # 연속 True polling 카운트 갱신 + if prev_seen is None or prev_seen["candle_time"] != candle_time: + alert_state.signal_seen_count[seen_key] = {"candle_time": candle_time, "count": 1} + else: + alert_state.signal_seen_count[seen_key] = {"candle_time": candle_time, "count": prev_seen["count"] + 1} + count = alert_state.signal_seen_count[seen_key]["count"] + # forming candle 만 안정성 (2 polls) 요구. 닫힌 캔들은 즉시 발사 (data 확정). + if candle_time == forming_ct and count < 2: + continue eligible.append({ "sig": sig, "key": key, "sub_label": sub_label, "direction": direction, "candle_time": candle_time, "row": triggered.iloc[-1],