Commit Graph

40 Commits

Author SHA1 Message Date
ILSEON-RYU 260f68adca 청산 권고 트리거 30m/1h 만 + BB 상/하단 차단 제거 + RSI 완화
## 청산 권고 트리거 제한
변동성 큰 날 5m/15m opposite signal 노이즈로 청산권고 폭주 방지.
30m / 1h 의 새 진입 신호만 청산 권고 트리거.

## BB 상/하단 차단 제거 + RSI 완화
거대 양봉/음봉이 BB 한 끝까지 가도 마커 발화하도록.
- long_signal: close < BB_upper 제거, RSI 60 -> 75
- short_signal: close > BB_lower 제거, RSI 35 -> 25

기존 close vs open 방향성 + bull_ma_2/bear_ma_2 만 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:59:15 +09:00
ILSEON-RYU b78176d4b8 모든 TF forming 알림 + 30s 재검증 + 반대 신호 시 청산 권고
## 변경 1 — closed-only 룰(a57941e) 전체 TF revert
이전: 15m / 30m / 1h 는 closed candle 만 신호 검사 -> forming 동안 알림
없음, 신호가 캔들 안에서 깜빡 사라져도 별도 알림 없음.
이후: 모든 TF (5m/15m/30m/1h) 가 forming candle 포함해 신호 검사. 30초
polling 으로 매 사이클마다 pending_groups 의 신호 상태 재검증
(a9ad52f 의 즉시 취소 로직). 결과적으로:
  5m  : 30초마다 검증 — 1캔들 절반(2.5m) 의 1/5
  15m : 30초마다 검증 — 1캔들 절반(7.5m) 의 1/15
  30m : 30초마다 검증 — 1캔들 절반(15m) 의 1/30
  1h  : 30초마다 검증 — 1캔들 절반(30m) 의 1/60
사용자 요건 "1캔들 시간의 절반 안에 한 번 검증" 자동 충족.

## 변경 2 — 반대 신호 시 청산 권고
이전: 롱 진입 신호와 숏 진입 신호가 시간차 있게 발화해도, 진입 추적
(long_entry / short_entry) 은 같은 TF 끼리만 덮어쓸 뿐 반대편 정리 없음.
이후: 새 진입 신호가 발사될 때 반대 방향 의 모든 활성 진입 (다른 TF 포함)
체크. 있으면 [반대 신호 감지 - 청산 권장] 알림 발송 + 해당 추적 해제.
메시지 포맷:
  [반대 신호 감지 - 숏 청산 권장]
  --- 기존 진입 ---
  🔽 일반 숏 진입 신호 ... (원래 entry_msg)
  --- 반대 신호 ---
  🔼 일반 롱 진입 신호 ... (현재 신호 entry_msg)

기존 stop loss 알림과 별개로 "반대 신호 발화 = 청산하라" 권고를 보내
사용자가 stop hit 전에 빠르게 정리할 수 있게 함.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:48:18 +09:00
ILSEON-RYU 67f46a427a 방향 전환 (reversal) 신호 추가 — 추세 꺾임 조기 감지
## 동작
직전 5봉 추세 + 현재 캔들의 반대 방향 강한 움직임 + 거래량 동반.

reversal_short_signal (상승 -> 하락 전환):
- close[t-1] > close[t-5] (직전 5봉 동안 상승했었음)
- close[t] < open[t] 이면서 |body|/open >= 0.3% (현재 캔들 강한 음봉)
- volume[t] > 직전 5봉 평균 volume * 1.3 (확신 동반)
- 5봉 쿨다운

reversal_long_signal (하락 -> 상승 전환):
- 위와 대칭 (직전 하락, 현재 강한 양봉)

## 알림
SIG_DEFS 에 등록되어 다른 진입 신호와 동일하게 텔레그램 발사.
LONG_SIGNALS / SHORT_SIGNALS 셋에도 포함되어 진입 추적 + 손절가
체크도 동일하게 동작.

라벨: "🔄 롱 전환" / "🔄 숏 전환"

## 검증 (오늘 24h 데이터)
- 5m 19:00 숏 전환 -0.78%
- 15m 19:00 숏 전환 -1.36%
- 30m 19:00 숏 전환 -1.67%
- 30m 19:30 롱 전환 +0.55%
- 30m 23:00 롱 전환 +0.58%  (현재 발화 케이스)
- 1h 10:00 롱 전환 +1.59%

기존 진입 신호 (long/short/strong/vol) 가 추세 추종형이라 반전
타이밍을 놓쳤던 문제를 보완 — reversal 신호가 추세 꺾임 시점에
독립 발사.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:33:54 +09:00
ILSEON-RYU a57941e1ef 15m/30m/1h 진입 알림을 closed candle 전용으로 변경 (forming 차단)
## 사례
2026-05-04 23:02 KST 1시간봉 일반 숏 진입 알림 발사. 그러나 23:00
캔들은 forming 중이고 그 시점 close 가 일시적으로 open 아래로
내려가 short_signal=True 깜빡 발화. 이후 close=79,203 으로 회복
(open 78,747 위) -> 마감 시점 short_signal=False. 사용자 입장에서는
"숏 마커도 없는데 1시간봉에 숏 알림 오는 지랄".

## 수정
forming candle 깜빡임은 두 가지 방법으로 대응:
1. 5분봉 - forming 발사 허용 + 깜빡 시 a9ad52f 의 즉시 취소(30s)
   (반응성 우선, 사용자 5분 단타용)
2. 15m / 30m / 1h - forming 자체 제외, closed candle 만 신호 검사
   (가짜 알림 차단. 발사는 캔들 마감 후 다음 polling = ~30초 이내)

  recent = df.iloc[:-1].tail(3)  if interval in (15m, 30m, 1h)

장기 시간봉은 1캔들 늦은 알림 (15~60분 지연) 보다 가짜 알림 차단이
더 가치 있음 — 사용자 손익에 큰 영향. 5분봉은 깜빡 후 30초 취소로
충분.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:25:45 +09:00
ILSEON-RYU a9ad52f3d7 취소 알림 forming candle 마감 대기 X — 신호 사라지면 즉시 취소
## 사례
2026-05-04 22:00 15분봉 강한 숏 진입 알림 발사 후 같은 캔들 마감
(22:15) 후 [취소 알림]. 사용자: "15분 봉을 15분 후에 취소하는 거냐"

## 원인
pending_groups 검증 로직이 forming candle 동안에는 검증 skip 하고,
candle 이 closed 된 다음에만 신호 재확인. forming 중 신호가 깜빡
False 로 바뀌어도 다음 polling 에서 알 수 없었음.

15분봉 의 경우 forming 기간이 15분 -> 취소 알림이 진입 알림 후 최대
15분 늦게 도착 (사용자 입장 ROI 마이너스 누적).

## 수정
forming/closed 구분 없이 매 polling (30s) 마다 pending 항목의 신호
상태 재확인. 사라졌으면 즉시 [취소 알림] 발송 + 진입 추적 클리어.

흐름:
- forming + 신호 살아있음 -> 계속 감시 (pending 유지)
- forming + 신호 사라짐  -> 즉시 취소 (~30초 이내)
- closed + 신호 살아있음 -> 확정, pending 에서 제거 (조용히)
- closed + 신호 사라짐  -> 즉시 취소 (기존 동작 유지)

per-candle dedup 으로 같은 캔들 재발사는 차단됨 -> 깜빡임 폭주 X.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:22:35 +09:00
ILSEON-RYU 2e916d48cb 1m/3m 알림 제외 + 진입 신호 필터를 close vs open 방향성 검증으로 교체
## 변경 요약
1. ALERT_TIMEFRAMES 에서 1m / 3m 제거 -> [5m, 15m, 30m, 1h] 만 모니터링.
   1m / 3m 의 진입 알림 / 취소 알림 / 손절가 알림 모두 차단.
2. 진입 신호 (long/short/strong_long/strong_short) 의 늦은 진입 차단 필터를
   BB position + 3봉 모멘텀 -> 현재 캔들 자체의 close vs open 방향성으로 교체.
   - long_signal / strong_long_signal: close > open (이번 캔들이 양봉)
   - short_signal / strong_short_signal: close < open (이번 캔들이 음봉)
   이전 BB position 필터는 breakdown / breakup 캔들에서 추세 진입을 막아버리는
   부작용이 있었음 (예: 15m 19:00 -1.4% 거대 빨간 캔들에 short 마커 안 뜸).
   close vs open 검증은 "이번 캔들 자체가 신호 방향과 일치" 만 요구해, 진행
   중인 추세는 잡고, 반등 / 반락 캔들의 늦은 진입은 차단.

## 변경 안 한 것
- 다단계 ROI 알림 (-5/-10/-15%) 은 사용자 의도가 별개 (1m/3m 볼륨 변화를
  선행 지표로 활용) 라 이번 커밋에선 미적용. 추후 별도 작업.
- vol_long / vol_short 신호 정의는 그대로.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:03:39 +09:00
ILSEON-RYU 56159a0695 늦은 진입 차단 — BB position + 3봉 모멘텀 필터 추가 (long/short/strong 전체)
## 문제
30분봉 19:30 short 신호 발화: 가격이 80.5k -> 78.2k 까지 -2.8% 추락 한
직후 79k 부근 반등 시점에 short 진입 신호. 이미 다 떨어진 후 바닥에서
숏 거는 격 -> 즉시 반등에 stop 맞음. 사용자: "이거 청산이야 늦어".

같은 패턴 long 도 발생: 30분봉 18:30 long 신호 (close 79.8k) 후 80.5k
까지만 짧게 오른 뒤 78.2k 까지 추락. 진입 시점이 이미 충분히 오른
후라 결과적으로 손실.

## 수정
모든 진입 신호(long_signal, short_signal, strong_long_signal,
strong_short_signal) 에 두 가지 추가 필터:

1. BB position (close 가 BB 범위의 어느 위치인지, 0=하단 1=상단):
   - long: 0.5 < bb_pos < 0.7  (중간선 위, 상단 70% 미만)
   - short: 0.3 < bb_pos < 0.5  (중간선 아래, 하단 30% 위)
   * 이미 한 끝까지 가버린 후의 늦은 진입 차단

2. 3봉 모멘텀 (close vs close 3봉 전):
   - long: 최근 3봉 동안 +0.5% 미만 상승
   - short: 최근 3봉 동안 -0.5% 미만 하락
   * 이미 큰 폭으로 움직인 후의 추격 진입 차단

## 검증
30분봉 19:30 (이전 strong_short + short 동시 발화):
- bb_pos = 0.149 (< 0.3) -> short 차단
- 3봉 모멘텀 = -0.85% (< -0.5%) -> short 차단
- 결과: 모든 short 신호 False ✓

vol_long/vol_short 는 이벤트성 (특정 캔들의 매수/매도 폭증) 이므로
필터 미적용 — 그 시점에 잡는 것이 의도.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:52:34 +09:00
ILSEON-RYU 255054683a 신호 정의 재검증 (18:55+ 드롭 케이스 + 18:35 가짜 롱 케이스)
## 변경 사항

### 1. OI 필터 활성도 기반으로 변경
이전: vol_short/vol_long 이 oi_up 만 허용 -> "OI 하락 + 매도 우세 = 롱 청산"
케이스 못 잡음.
이후: oi_active 추가 (|OI pct change| > 0.1%). 방향 무관, 의미 있는
변동만 통과. 신규 진입 + 청산 모두 캡처.

### 2. strong_long/strong_short MA99 의존 제거
이전: bear_ma (close<MA7<MA25<MA99) -> 8h SMA 정렬까지 요구하니 단기
급락 못 잡음.
이후: bear_ma_2 / bull_ma_2 사용 (MA7/MA25 만). MA99 빼버림.
bull_ma / bear_ma 정의 자체에서도 MA99 조건 삭제.

### 3. long_signal / short_signal 에 BB 상/하단 차단 추가
이전: long_signal 조건이 close > BB_mid 만 -> BB 상단 위 (과매수)
에서도 발화. 18:35 5분봉 = close 79,775 > BB_upper 79,768 -> 롱 신호
떴는데 직후 -2% 폭락.
이후: long_signal 에 close < BB_upper 추가 (BB 중간선 위 + 상단 아래
중간 zone 만 OK). short_signal 에는 close > BB_lower 추가.

## 검증
- 18:35 5분봉: long_signal=False (BB 상단 위라 차단) ✓
- 19:05 5분봉: vol_short_signal=True (OI 활성 + sell spike) ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:47:28 +09:00
ILSEON-RYU b572758682 알림 스레드 multi-TF 모니터링 + per-(interval,key) state 분리
## 동작 변경
이전: 알림 스레드가 사용자 dropdown 선택 1개 시간봉만 polling.
이후: [1m, 3m, 5m, 15m, 30m, 1h] 6개 시간봉을 매 cycle 마다 순회 polling.
같은 신호가 여러 시간봉에서 발화하면 각 시간봉별로 독립 알림 (dedup 도
interval 별로 분리).

## 상태 구조 변경 (alert_state.py)
- last_alert : dict[(interval, key)] -> timestamp
- last_fired_candle : dict[(interval, key)] -> candle_time
- long_entry / short_entry : dict[interval] -> entry_record
  (TF 별로 진입 추적, 손절가 검증도 TF 별)

## 신호 정의는 변경 없음
OI 필터(oi_up / oi_up_2 / oi_down_2) 모두 원복 — 신호 정의는 의도된
대로 유지. "OI 하락 + 가격 하락 = 롱 청산" 케이스는 시스템 설계상
vol_short/strong_short 가 안 잡는 것이 정상 (별도 신호 추가 시 더
정밀한 분리 가능, 이번 변경에는 불포함).

## API 호출 부담
6 TF × ~4 endpoint per cycle = 24 calls / 30s = 48 calls/min. Binance
futures 1200/min limit 대비 4% 사용 — 안전.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:40:39 +09:00
ILSEON-RYU b27e2fcf51 Forming candle 알림 + 마감 후 재검증 + [취소 알림] 패턴
## 동작
이전 커밋 8fd47d0 에서 forming candle 을 알림 대상에서 아예 제외했었지만,
이제 사용자 요청에 따라 "실시간 알림 + 잘못되면 취소 알림" 방식으로 변경.

흐름:
1. 알림 스레드는 매 polling 마다 df.tail(3) (forming candle 포함) 으로
   신호 검사 -> 알림 발사. 빠른 반응 유지.
2. forming candle 기반으로 발사된 알림은 alert_state.pending_groups 에
   등록 (interval, candle_time, msg, sig_cols, direction 보관).
3. 다음 polling 부터, 그 candle 이 더 이상 forming 이 아니면 (=닫힘)
   동일 candle 의 신호 컬럼들을 다시 확인:
   - 하나라도 True 로 살아있음 -> 확정, pending 에서 제거 (조용히)
   - 모두 False 로 바뀜          -> [취소 알림] 발송 + 진입 추적 클리어

## 메시지 예
원래:
  🔽 일반 숏 진입 신호
  BTCUSDT 30분봉
  시간: 2026-05-04 09:30
  진입가: 78,318.10
  손절가: 78,905.49

캔들 마감 후 신호 사라진 경우:
  [취소 알림]
  🔽 일반 숏 진입 신호
  BTCUSDT 30분봉
  시간: 2026-05-04 09:30
  진입가: 78,318.10
  손절가: 78,905.49

## 부가
- pending entry 중 long_entry/short_entry 와 open_time 이 일치하면
  같이 None 으로 클리어 -> 잘못된 손절가 알림 방지.
- 다른 시간봉으로 polling 가는 동안에는 pending 항목 그대로 보존
  (interval 매칭 시점까지 대기). 시간봉 다시 돌아오면 그때 검증 시도.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:14:11 +09:00
ILSEON-RYU 8fd47d0926 Forming candle 의 일시적 신호 깜빡임으로 인한 false alert 차단
## 사례
2026-05-04 09:38 KST 에 30분봉 [09:30, 10:00) 형성 중 캔들 기준으로
'일반 숏 진입 신호' 알림이 발사되었으나, 09:30 캔들이 close=78,504.90
으로 마감된 후 동일 캔들에 대해 모든 진입 신호가 False 로 확인됨. 즉,
캔들 형성 중 일시적으로 close 가 MA / BB 기준선 아래로 내려간 순간
short_signal 이 잠깐 True 로 떴다가 close 가 위로 회복되며 False 로
전환된 것을 알림 스레드가 그 순간 잡아 발사함. 이후 손절가 알림
([손절가알림])까지 trail 되어 잘못된 시그널이 두 번 텔레그램에 도착.

## 수정
check_and_alert 의 검사 윈도우를 df.tail(3) 에서
df.iloc[:-1].tail(3) 로 변경. 마지막 행 (= 현재 형성 중 캔들) 을
제외하고 최근 3 개 닫힌 캔들만 검사. close 가 확정된 데이터만
보므로 forming 깜빡임에 속지 않음.

부작용: 알림이 캔들 마감 후 ~30s (다음 polling 주기) 이내로 자연
미뤄짐. accuracy / latency 트레이드오프에서 accuracy 우선이라고 판단.
손절가 도달 체크는 여전히 df.iloc[-1]['close'] (현재가) 사용해서
real-time 반응성 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:08:05 +09:00
ILSEON-RYU 632fd551ae 손절가 비율 1.5% -> 0.75% (10x 레버리지 기준 ROI -7.5%)
10x x ±0.75% = ±7.5% ROI. STOP_LOSS_PCT = 0.0075.
손절 알림 도달 빈도 ~2배 증가 예상.

리포트 메시지의 ROI 라벨도 STOP_LOSS_PCT 변수에 연동되도록 변경
(이제 상수 한 줄만 바꾸면 메시지도 자동 갱신).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:35:16 +09:00
ILSEON-RYU 9c72141b3f 일일 리포트 3번째 메시지: 손절가 터치 횟수 (시간봉 *3배 기준)
기존 1배/2배 검증 메시지 다음으로 한 건 더 발송.

## 동작
- 각 진입 신호 캔들을 1번째 캔들로 보고, 1번째 ~ 4번째 캔들 시작가까지의
  구간 (= 3 개 캔들 시간 범위) 동안 손절가를 터치했는지 카운트.
- 롱: window 내 최저가(low) <= 진입가 * (1 - STOP_LOSS_PCT) 이면 터치.
- 숏: window 내 최고가(high) >= 진입가 * (1 + STOP_LOSS_PCT) 이면 터치.
- short_caution_signal 은 진입 신호 아니므로 추적 X.

## 메시지 형식
[손절가 터치 횟수 알림(시간봉 *3배기준)] (BTCUSDT)
기준: 2026-05-03 00:00 KST
손절 비율: ±1.5% (10x 레버리지 기준 ROI ±15%)

[5분봉]
강한 롱: 0/3
강한 숏: 0/1
일반 롱: 0/11
일반 숏: 0/8
볼륨 롱: 0/5
볼륨 숏: 0/8
합계: 0/36 (터치율 0.00%)
[15분봉] ...

(touch / total — total 은 24h 내 해당 시간봉의 진입 신호 발화 수)

## 구현
- _count_stop_touches_per_type(df, cutoff_kst, lookahead=3): signal 별
  [touch, total] 카운트 반환.
- _build_stop_touch_lines: 시간봉 블록 + 합계 메시지 본문.
- send_daily_report: 기존 1x/2x 발송 후 msg_touch 추가 발송.
- dfs 는 1x/2x 빌드 시 이미 fetch 되어있어 재사용 (API 호출 추가 없음).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:11:49 +09:00
ILSEON-RYU 949d876887 손절가 비율 2% -> 1.5% (10x 레버리지 기준 ROI -15%)
손익은 가격 변동 × 레버리지 이므로:
  10x 에서 ROI = -15% <=> 가격 = ±1.5%

따라서 STOP_LOSS_PCT = 0.015. 롱은 진입가 * 0.985, 숏은 진입가 * 1.015
에 도달 시 손절 알림.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:04:12 +09:00
ILSEON-RYU 7dcd9591dc 알림 상태를 별도 모듈로 분리해 Streamlit rerun 영향 차단
## 배경
이전 커밋 fedbf1c 에서 globals() 검사 가드로 rerun 시 상태 초기화를 막으려
했으나 동작하지 않음. streamlit.log 의 "[일일리포트] 스레드 기동" 메시지가
restart 후에도 12 회 이상 누적된 것이 증거.

## 원인
Streamlit 은 rerun 시 메인 스크립트를 새 namespace 에서 exec() 한다.
이 namespace 는 매번 새로 만들어지므로 globals() 안에는 이전 run 의
변수가 존재하지 않음 -> 가드가 항상 True 분기를 타고 mutable state 가
매번 초기화됨. 그 결과:
- 알림 dedup 상태 (last_fired_candle) 가 None 으로 리셋
- 진입 추적 (long_entry / short_entry) 가 None 으로 리셋
- 스레드 기동 가드 (alert_started) 가 False 로 리셋 -> 새 스레드 spawn
- threading.Lock() 도 매번 새로 생성되어 동기화 깨짐

이로 인해 같은 캔들 (예: 30분봉 20:30 일반 숏) 알림이 텔레그램으로 30초
간격 반복 발사되는 증상 발생.

## 수정
mutable state 를 별도 alert_state.py 모듈로 분리. 메인 스크립트는
"import alert_state" 만 실행하는데, 이 import 는 sys.modules 캐싱
덕분에 첫 실행 후 노옵 -> alert_state 모듈은 process lifetime 동안
같은 객체이며 그 attribute 들은 보존된다. 메인 스크립트 namespace 가
매번 새로 만들어져도 alert_state 의 state 는 영향받지 않음.

상태 항목:
- last_alert        (signal type 별 마지막 발사 시각, 쿨다운용)
- last_fired_candle (signal type 별 마지막 발사 캔들 open_time, dedup)
- long_entry / short_entry (진입 추적)
- alert_lock, alert_symbol, alert_interval (스레드 동기화 + UI -> 스레드)
- alert_started, daily_report_started (스레드 1회 기동 가드)
- last_report_date (자정 통과 감지용)

## 검증
import 동작 확인 (별도 process 에서):
- 같은 모듈 객체 (s1 is s2)
- 같은 dict 객체 (s1.last_fired_candle is s2.last_fired_candle)
- attribute set/get 양쪽에 반영

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:54:01 +09:00
ILSEON-RYU fedbf1c81e 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>
2026-05-02 21:44:41 +09:00
ILSEON-RYU 4cbd62e7e1 일일 리포트에 2배 시간 검증 두 번째 메시지 추가
## 동작
자정 KST 발송 시 텔레그램 알림 2개를 연속 발송:
1. 1배 시간 (다음 봉 검증) — 기존 로직, 신호 발화 캔들의 다음 캔들에서
   반대 신호가 뜨면 실패.
2. 2배 시간 (2번째 봉 검증) — 신규, 신호 발화 캔들의 2개 뒤 캔들에서
   반대 신호가 뜨면 실패.

예: 5분봉 14:00 숏 진입 신호
   1배 검증: 14:05 캔들에 반대(롱)신호 -> F
   2배 검증: 14:10 캔들에 반대(롱)신호 -> F

## 구현
- _count_daily_signals_per_type 에 offset 파라미터 추가 (기본 1).
- _build_daily_report_lines 헬퍼 추출 — 동일 dfs 와 cutoff 로 offset 만
  바꿔서 두 메시지 본문 생성.
- send_daily_report 에서 시간봉별 df 한 번만 빌드 후 1x / 2x 두 번 포맷
  -> 두 메시지 발송 (API 비용 중복 제거).

## 메시지 헤더 변화
이전: "📊 24시간 신호 통계 (BTCUSDT)"
이후: "📊 24시간 신호 통계 (BTCUSDT) - 1배 시간 (다음 봉 검증)"
       "📊 24시간 신호 통계 (BTCUSDT) - 2배 시간 (2번째 봉 검증)"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:14:26 +09:00
ILSEON-RYU 577fb44914 stdout/stderr UTF-8 강제 + print 메시지에서 비-ASCII 기호 제거
## 배경
이전 자정에 일일 리포트가 발송되지 않은 1차 원인은 긴 sleep 의존(별도 커밋
057335a 에서 폴링 방식으로 전환). 그러나 폴링 적용 후 다시 살펴보니, 새
print 문에서 cp949 (Windows 콘솔 기본 인코딩) 환경에서 em-dash(—)와
right arrow(→) 인코딩 실패로 print 가 예외를 던져 try/except 가
"[일일리포트 스레드 오류]" 로 매번 잡히고 있었음. 자정 발송 로직 자체는
실행되어도 로그가 silent fail 가능성. 향후 동일 문제 차단을 위해 처리.

## 변경
- sys.stdout.reconfigure(encoding="utf-8") + sys.stderr 동일 처리.
- PYTHONIOENCODING=utf-8 환경변수도 설정 (subprocess 가 상속받도록).
- 로그 메시지의 em-dash -> "--", right arrow -> "->" 로 ASCII 화 (이중
  안전장치).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 00:08:53 +09:00
ILSEON-RYU 057335a173 일일 리포트 스레드를 긴 sleep -> 60s 폴링 방식으로 변경 + 로깅
## 문제
2026-05-02 00:00 KST 자정에 일일 신호 통계 알림이 발송되지 않았음.
원인 추정: time.sleep(약 2.77h) 같은 긴 sleep 이 Streamlit 의 메인 루프와
함께 돌면서 데몬 스레드가 깨어나지 않았거나, 깨어났더라도 silent 하게
중단됨. send_daily_report 함수 자체를 직접 호출하면 정상 동작 확인.

## 변경
- 짧은 sleep(60s) 폴링 루프로 변경.
- 매 폴링마다 KST 날짜를 확인 → 마지막 발송 날짜와 다르면(=자정 통과) 발송.
- 자정 통과 후 최대 60초 이내 발송 보장.
- 첫 폴링에서는 _last_report_date 를 오늘 날짜로 초기화 (재기동 직후 즉시
  발송되어 사용자가 혼란해지는 것 방지).
- 발송 / 기동 / 오류 시 print 로그 남김.

기존 send_daily_report 함수 자체는 변경 없음.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 00:06:57 +09:00
ILSEON-RYU 060e592869 손절가 비율 3% -> 2%
3% 도 단타에서는 다소 헐거워, 2% 로 좁혀 손절 알림 반응성 향상.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:13:24 +09:00
ILSEON-RYU d2f08ff4e9 같은 캔들 같은 방향 신호 그룹핑 + 손절 3% + 천단위 포맷
## 1. 같은 캔들 같은 방향 신호를 1개 알림으로 그룹핑
이전: 한 캔들에서 강한 롱 / 일반 롱 / 볼륨 롱 신호가 동시에 떴을 때
3개의 별도 텔레그램이 같은 분 안에 연속 도착 → 스팸 체감.
이후: 발화한 신호들의 라벨을 " + " 로 결합해 1개 메시지로 발송.

  🟢 강한 롱 + 🔼 일반 롱 + 🔼 볼륨 롱 진입 신호
  BTCUSDT 5분봉
  시간: 2026-05-01 21:00
  진입가: 76,200.00
  손절가: 73,914.00

per-candle dedup 과 ALERT_COOLDOWN 가드는 그대로 유지. 그룹 안 모든
신호의 _last_alert / _last_fired_candle 한꺼번에 갱신.

## 2. 손절가 비율 10% -> 3%
STOP_LOSS_PCT = 0.03 으로 조정. 5m/15m 단타 기준에 10% 는 너무 헐거워
손절 알림이 사실상 작동 안 함. 3% 면 보통 1~2시간 내 결과 결판.

## 3. 가격 표기 천단위 반점
진입가/손절가/현재가 등 모든 텔레그램 가격 출력에 ',' 천단위 구분자 적용.
(차트 hover 는 이미 적용돼있어 변경 없음.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:57:57 +09:00
ILSEON-RYU 29a36a1bc5 진입가 open 변경 + 알림 포맷 개선 + 일일 리포트 신호별 테이블화
## 변경 사항

### 진입가 open 가격 사용
- 신호 캔들의 close 가 아닌 open 을 진입가로 표기. "그 캔들이 알려준
  시점의 시작가" 를 진입가로 보고 싶다는 사용자 요청.
- 텔레그램 진입 알림, 손절가 알림 reference, 차트 hover 모두 일괄 변경.

### 진입 알림 포맷 확장
이전:
  🔼 롱 진입 신호
  BTCUSDT 5m
  진입가: ...
  손절가: ...

이후:
  🔼 롱 진입 신호
  BTCUSDT 5분봉
  시간: 2026-05-01 15:00
  진입가: ...
  손절가: ...

- 시간봉 코드(5m) 대신 한글 라벨(5분봉) 사용. TF_LABEL_MAP 으로 매핑.
- 신호 캔들 open_time 을 시간 행으로 추가.

### 손절가 알림 [손절가알림] 프리픽스
이전 손절가 알림은 별도 포맷이었음.
이후 진입 알림 메시지를 그대로 보존(_long_entry/_short_entry 에 entry_msg
저장)하고, 손절 도달 시 [손절가알림] 헤더와 현재가 한 줄만 추가:

  [손절가알림]
  🔼 롱 진입 신호
  BTCUSDT 5분봉
  시간: 2026-05-01 15:00
  진입가: ...
  손절가: ...
  현재가: ...

### 일일 리포트 신호별 테이블화
이전: 시간봉당 1줄 합계만 표기.
이후: 시간봉별 블록 안에 6개 신호(강한/일반/볼륨 × 롱/숏) 라인 + 합계.

  [5분봉]
  강한 롱: 1T 0F
  강한 숏: 1T 0F
  일반 롱: 7T 0F
  일반 숏: 8T 0F
  볼륨 롱: 9T 1F
  볼륨 숏: 9T 1F
  합계: 35T 2F (승률 94.59%)
  [15분봉] ...

_count_daily_signals 를 _count_daily_signals_per_type 으로 교체.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:46:18 +09:00
ILSEON-RYU a0cb5b466a 매일 자정(KST) 5개 시간봉 24시간 신호 통계 텔레그램 리포트 추가
## 동작
- 매일 00:00 KST 에 BTCUSDT 기준으로 5m/15m/30m/1h/4h 봉의 지난 24시간
  진입 신호를 backfill 분석해 텔레그램으로 발송.
- 실패 정의: 신호가 발화한 캔들의 다음 캔들에서 반대 방향 신호가 발화하면
  해당 신호는 실패.
- 대상 신호 6종(롱/숏 × 강한/일반/볼륨급등). short_caution 은 진입 신호가
  아니므로 통계에서 제외.

## 메시지 예시
📊 24시간 신호 통계 (BTCUSDT)
기준: 2026-05-02 00:00 KST
5분봉 45번 T, 5번 F (승률 90.00%)
15분봉 14번 T, 0번 F (승률 100.00%)
30분봉 7번 T, 0번 F (승률 100.00%)
1시간봉 2번 T, 0번 F (승률 100.00%)
4시간봉 0번 T, 0번 F (승률 0.00%)

## 구현
- 알림 스레드의 fetch+merge+compute 로직을 _build_signal_df 헬퍼로 분리해
  일일 리포트와 공유.
- _daily_report_loop 스레드가 다음 자정 KST 까지 대기 → send_daily_report
  호출 → 다시 다음 자정까지 sleep.
- main() 에서 _daily_report_started 가드로 1회만 기동.
- 시간봉별 lookback 캔들 수: 5m=500, 15m=250, 30m=200, 1h=200, 4h=200
  (24h 데이터 + MA99 워밍업 여유분).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:44:18 +09:00
ILSEON-RYU 279d549555 차트 hover 가격을 close 로 통일 (텔레그램 진입가와 일치)
마커 위치는 시각적 충돌 방지를 위해 기존 보정값(low*0.9998 / high*1.0002)
유지하되, hover 텍스트의 "가격" 표기는 customdata 로 close 를 전달해 표시.
이제 차트 hover 와 텔레그램 알림 모두 동일한 close 가격으로 통일됨.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:35:46 +09:00
ILSEON-RYU c0dbc046b6 진입가를 close 가격으로 변경 (트레이더 표준 컨벤션)
이전: 롱 = low * 0.9998, 숏 = high * 1.0002 (차트 마커 시각 보정값)
이후: 롱/숏 모두 신호 캔들의 close

지표(MA, RSI, MACD, BB)가 close 기준으로 계산되고 백테스팅 / TradingView
디폴트도 close 기준이라, 진입가 표기를 close 로 통일해 트레이더가 보는
관행과 일치시킴. 손절가 비율은 STOP_LOSS_PCT 그대로 ±10%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:32:41 +09:00
ILSEON-RYU ffbc0da011 진입 신호 알림에 진입가/손절가 포함 + 차트 마커 표기와 일치
## 변경 사항
- 진입가를 차트 hover 마커가 보여주는 값과 동일하게 통일.
  - 롱: low * 0.9998
  - 숏: high * 1.0002
- 손절가는 진입가 * (1 ± STOP_LOSS_PCT) 로 계산되므로 reference 가격이
  10bps 차이나도 손절가 비율은 정확히 ±10% 로 유지됨.
- 텔레그램 진입 신호 메시지에 진입가/손절가 두 줄 추가.

## 메시지 예시
🔼 롱 진입 신호
BTCUSDT 5m
진입가: 76245.84
손절가: 68621.26

🛑 롱 손절가 도달 (-10%)
BTCUSDT 5m
진입가: 76245.84
손절가: 68621.26
현재가: 68500.00

## 영향 없는 시그널
short_caution_signal 은 진입 신호가 아니므로 가격 정보 없이 기존 형식 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:29:19 +09:00
ILSEON-RYU 43a5c52ec5 10% 손절가 도달 알림 추가
## 동작
- 진입 신호(strong/일반/볼륨급등 × long/short)가 발화한 캔들의 close 가격을
  진입가로 기록.
- 롱 진입 → 손절가 = 진입가 * 0.90, 현재가가 손절가 이하로 내려가면 알림.
- 숏 진입 → 손절가 = 진입가 * 1.10, 현재가가 손절가 이상으로 올라가면 알림.
- 발화 시 해당 방향의 진입 상태를 클리어 → 새 진입 신호 전까지 같은 손절가
  알림은 재발송되지 않음.
- 기본 비율은 STOP_LOSS_PCT 상수(0.10)로 분리. 추후 조정 용이.

## 메시지 예시
🛑 롱 손절가 도달 (-10%)
BTCUSDT 5m
진입가: 76200.00
손절가: 68580.00
현재가: 68500.00

## 주의
- short_caution_signal 은 진입 신호가 아니므로 손절가 추적 대상에서 제외.
- 롱/숏 진입 상태는 독립 추적 — 한쪽 손절 도달이 다른 쪽 상태에 영향 없음.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:10:14 +09:00
ILSEON-RYU 2449c7766d .env 시크릿을 추적에서 제외하고 .env.example 패턴 도입
## 변경 사항
- git rm --cached .env  : 토큰/채팅ID 가 평문으로 git push 되지 않도록
  추적 해제. 로컬 파일은 그대로 유지.
- .env.example 추가     : 클론하는 사람이 어떤 환경변수가 필요한지 알 수
  있게 placeholder 만 담은 템플릿 커밋.
- .gitignore 에 .env    : 향후 실수로 추가되는 것 방지.

## 주의
- 이미 git history 에 들어간 옛날 토큰은 그대로 남아있음. 해당 토큰은
  이미 revoke 되어 무효화되었으므로 별도 history 재작성은 진행하지 않음.
- 새 환경(서버 등)에 배포할 때는 .env.example 을 .env 로 복사한 뒤
  실제 토큰/ID 를 채워 넣어야 함.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:09:59 +09:00
ILSEON-RYU b9bba95b55 같은 캔들에 대한 중복 알림 방지 (per-candle dedup)
## 문제
30s 폴링 × 3캔들 윈도우 × 600s 쿨다운이 맞물려, 동일 캔들에 대한 알림이
한 번 발송된 뒤 쿨다운(10분)이 풀리는 시점에 같은 캔들이 여전히 tail(3)
윈도우 안에 남아있으면(5m 기준 최대 15분 머무름) 두 번째 알림이 다시 발송
되는 현상.

## 변경 사항
- 시그널별로 마지막 발화 캔들의 open_time 을 추적하는 _last_fired_candle
  dict 추가.
- check_and_alert 에서 tail(3) 중 신호가 True 인 가장 최신 캔들의 open_time
  을 키로 잡고, 직전 발화와 동일하면 스킵.
- 기존 ALERT_COOLDOWN(시간 기반) 가드는 그대로 유지 — 다른 캔들로 신호가
  연속 발생할 때의 과다 알림은 여전히 차단.

결과적으로 한 캔들당 시그널 종류별로 1회만 알림이 발송됨.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:01:32 +09:00
ILSEON-RYU 6cd263f1f4 requirements.txt 실제 의존성과 일치하도록 정리
- streamlit / urllib3 추가: 코드에서 import 하지만 누락돼있어 새 환경에서
  서버 기동 자체가 실패하던 원인.
- ccxt / dash 제거: 어디에서도 import 되지 않음 (잔재).
- python-dotenv 버전 핀 추가, 파일 끝 개행 정리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:01:15 +09:00
ILSEON-RYU 10944ae907 알림 신호 누락 버그 수정 (3건 묶음)
## 문제
대시보드 차트에는 진입 신호가 정상 표기되지만 텔레그램 알림이 전혀 발송되지
않거나 일부 신호 종류가 영구적으로 누락되는 현상.

## 변경 사항

1. 알림 스레드 캔들 50 -> 200
   - MA99(99 SMA) 가 50 캔들에서는 항상 NaN 이라 bull_ma / bear_ma 가
     False 가 되고, strong_long_signal / strong_short_signal 이 영원히
     발화하지 않던 문제. UI 와 동일한 200 캔들로 맞춰 신호 일관성 확보.
   - OI 도 50 -> 200 으로 정렬해 lookback 보강.

2. 알림 스레드에 fundingRate fetch + merge 추가
   - 기존 스레드 df 에 fundingRate 컬럼이 없어 short_caution_signal
     ('fundingRate' in df.columns 분기) 가 항상 False 였음.
   - UI 와 동일한 패턴(get_funding_rate -> floor('1h') merge -> ffill)
     으로 fundingRate 합류, short_caution_signal 정상 발화.

3. check_and_alert 1캔들 -> 3캔들 체크
   - df.iloc[-1] (현재 형성 중 캔들)만 보던 로직을 df.tail(3) 으로 확장.
   - 30s 폴링 사이 닫혀버린 캔들의 신호 누락 방지. cooldown 은 그대로
     유지되므로 중복 알림은 발생하지 않음.

## 부가
- streamlit.log / streamlit.err.log 를 .gitignore 에 추가
  (런타임 산출물 — 6.9MB까지 커지는 상황 발생).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:55:22 +09:00
ILSEON-RYU 790d4f1c83 서버 최신버전 동기화 2026-04-30 00:27:27 +09:00
ILSEON-RYU 28097da970 알림 쿨다운 600초로 통일 2026-04-29 23:21:07 +09:00
ILSEON-RYU 42b9199ed3 갱신시간 KST 우측상단 고정 + StochRSI K 빨간실선 2026-04-29 18:39:00 +09:00
ILSEON-RYU 1f551519fd StochRSI K선 빨간 실선으로 변경 2026-04-29 18:33:03 +09:00
ILSEON-RYU dfe3a2c19a 알림 3캔들 체크 + 시간/가격 추가 2026-04-29 18:00:20 +09:00
ILSEON-RYU 38db7cac09 갱신시간 위치 KST 수정 2026-04-29 17:37:36 +09:00
ILSEON-RYU 369aa4de00 first commit 2026-04-27 12:34:08 +09:00
ILSEON-RYU bd133ad2ae remove env 2026-04-25 21:20:08 +09:00
ILSEON-RYU 453b288a95 init 2026-04-25 21:19:25 +09:00