Streamlit rerun 시 알림 상태 초기화 막기 (같은 캔들 반복 알림 버그 수정)
## 증상
30분봉 일반 숏 신호가 한 캔들(20:30)에 발화한 뒤 30초 ~ 수 분 간격으로
계속 같은 시간(20:30) 으로 반복 알림이 텔레그램에 도착.
## 원인
Streamlit 은 사용자가 페이지를 새로고침하거나 컨트롤을 조작할 때마다
스크립트 전체를 위에서부터 재실행한다. 이 과정에서 모듈 최상단의 mutable
state 들이 매번 재할당된다:
_last_alert = {... 0 ...} # 쿨다운 타이머 0 으로 리셋
_last_fired_candle = {... None ...} # per-candle dedup 상태 None 리셋
_long_entry = None # 진입 추적 None 리셋
_alert_started = False # 스레드 가드 False 리셋
_alert_lock = threading.Lock() # 새 락 객체 생성 (기존 락 무시)
결과적으로 매 rerun 마다:
1. 새 알림 스레드가 추가로 spawn → 동일 polling 을 중복 수행
2. dedup 상태가 None 으로 리셋되어 이미 알린 캔들도 새로 알림 처리
3. 새 락 객체로 기존 스레드와 동기화 깨짐
streamlit.log 에 "[일일리포트] 스레드 기동" 메시지가 50회 가까이 찍힌 것이
1번 원인의 직접 증거.
## 수정
모듈 최상단에 globals() 검사 가드 추가:
if "_alert_state_initialized" not in globals():
_last_alert = {...}
_last_fired_candle = {...}
_long_entry = None
_short_entry = None
_alert_state_initialized = True
같은 패턴으로 _alert_thread_state_initialized 와 _last_report_date 도 보호.
첫 실행 시에만 초기화되고, 이후 rerun 에서는 globals() 에 이미 키가 존재
하므로 if 블록을 건너뛴다 -> 이전 값이 그대로 유지된다.
## 검증
페이지 5회 hit 후 streamlit.log 의 "기동" 로그 카운트:
이전: 50+ (rerun 마다 추가)
이후: 1 (단 한 번만)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-11
@@ -54,9 +54,6 @@ ALERT_COOLDOWN = 600
|
||||
BASE = "https://fapi.binance.com"
|
||||
KST = timedelta(hours=9)
|
||||
|
||||
_last_alert = {"strong_long": 0, "strong_short": 0, "long": 0, "short": 0, "vol_long": 0, "vol_short": 0, "short_caution": 0}
|
||||
_last_fired_candle = {"strong_long": None, "strong_short": None, "long": None, "short": None, "vol_long": None, "vol_short": None, "short_caution": None}
|
||||
|
||||
STOP_LOSS_PCT = 0.02
|
||||
LONG_SIGNALS = {"strong_long_signal", "long_signal", "vol_long_signal"}
|
||||
SHORT_SIGNALS = {"strong_short_signal", "short_signal", "vol_short_signal"}
|
||||
@@ -66,8 +63,16 @@ TF_LABEL_MAP = {
|
||||
"1h": "1시간봉", "4h": "4시간봉", "12h": "12시간봉",
|
||||
"1d": "1일봉", "3d": "3일봉", "1M": "1개월봉",
|
||||
}
|
||||
_long_entry = None
|
||||
_short_entry = None
|
||||
|
||||
# Streamlit 은 매 페이지 rerun 마다 모듈 최상단 코드를 재실행한다. 알림용 mutable
|
||||
# 상태 (dedup, 진입 추적, 스레드 기동 플래그 등)는 한 번만 초기화되어야 하므로
|
||||
# globals() 검사로 일회성 초기화를 보장한다.
|
||||
if "_alert_state_initialized" not in globals():
|
||||
_last_alert = {"strong_long": 0, "strong_short": 0, "long": 0, "short": 0, "vol_long": 0, "vol_short": 0, "short_caution": 0}
|
||||
_last_fired_candle = {"strong_long": None, "strong_short": None, "long": None, "short": None, "vol_long": None, "vol_short": None, "short_caution": None}
|
||||
_long_entry = None
|
||||
_short_entry = None
|
||||
_alert_state_initialized = True
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 텔레그램
|
||||
@@ -626,11 +631,13 @@ def build_chart(symbol, interval, candle_limit=200):
|
||||
# ──────────────────────────────────────────────
|
||||
# 알림 스레드
|
||||
# ──────────────────────────────────────────────
|
||||
_alert_symbol = "BTCUSDT"
|
||||
_alert_interval = "5m"
|
||||
_alert_lock = threading.Lock()
|
||||
_alert_started = False
|
||||
_daily_report_started = False
|
||||
if "_alert_thread_state_initialized" not in globals():
|
||||
_alert_symbol = "BTCUSDT"
|
||||
_alert_interval = "5m"
|
||||
_alert_lock = threading.Lock()
|
||||
_alert_started = False
|
||||
_daily_report_started = False
|
||||
_alert_thread_state_initialized = True
|
||||
|
||||
def _build_signal_df(symbol, interval, klines_limit=200):
|
||||
df = get_klines(symbol, interval, klines_limit)
|
||||
@@ -750,7 +757,8 @@ def send_daily_report(symbol="BTCUSDT"):
|
||||
msg_2x = _build_daily_report_lines(dfs, cutoff_kst, now_kst, symbol, offset=2, header_suffix="2배 시간 (2번째 봉 검증)")
|
||||
send_telegram(msg_2x)
|
||||
|
||||
_last_report_date = None
|
||||
if "_last_report_date" not in globals():
|
||||
_last_report_date = None
|
||||
|
||||
def _daily_report_loop():
|
||||
global _last_report_date
|
||||
|
||||
Reference in New Issue
Block a user