일일 리포트에 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>
This commit is contained in:
ILSEON-RYU
2026-05-02 21:14:26 +09:00
parent 577fb44914
commit 4cbd62e7e1
+29 -17
View File
@@ -689,40 +689,37 @@ DAILY_REPORT_SIGNAL_LABELS = [
("vol_short_signal", "볼륨 숏"),
]
def _count_daily_signals_per_type(df, cutoff_kst):
def _count_daily_signals_per_type(df, cutoff_kst, offset=1):
result = {sig: [0, 0] for sig, _ in DAILY_REPORT_SIGNAL_LABELS}
if df is None or df.empty or "open_time" not in df.columns:
return result
recent = df[df["open_time"] >= cutoff_kst].reset_index(drop=True)
if len(recent) < 2:
if len(recent) <= offset:
return result
for long_sig, short_sig in DAILY_REPORT_PAIRS:
if long_sig not in recent.columns or short_sig not in recent.columns:
continue
for i in range(len(recent) - 1):
for i in range(len(recent) - offset):
row = recent.iloc[i]
nxt = recent.iloc[i + 1]
future = recent.iloc[i + offset]
if bool(row.get(long_sig, False)):
result[long_sig][0] += 1
if bool(nxt.get(short_sig, False)):
if bool(future.get(short_sig, False)):
result[long_sig][1] += 1
if bool(row.get(short_sig, False)):
result[short_sig][0] += 1
if bool(nxt.get(long_sig, False)):
if bool(future.get(long_sig, False)):
result[short_sig][1] += 1
return result
def send_daily_report(symbol="BTCUSDT"):
now_kst = (datetime.now(timezone.utc) + KST).replace(tzinfo=None)
cutoff_kst = now_kst - timedelta(hours=24)
lines = [f"📊 24시간 신호 통계 ({symbol})", f"기준: {now_kst.strftime('%Y-%m-%d %H:%M')} KST"]
def _build_daily_report_lines(dfs, cutoff_kst, now_kst, symbol, offset, header_suffix):
lines = [
f"📊 24시간 신호 통계 ({symbol}) - {header_suffix}",
f"기준: {now_kst.strftime('%Y-%m-%d %H:%M')} KST",
]
for tf in DAILY_REPORT_TIMEFRAMES:
try:
df = _build_signal_df(symbol, tf, DAILY_REPORT_KLINES_LIMIT[tf])
counts = _count_daily_signals_per_type(df, cutoff_kst)
except Exception as e:
print(f"[일일리포트 {tf} 오류] {e}")
counts = {sig: [0, 0] for sig, _ in DAILY_REPORT_SIGNAL_LABELS}
df = dfs.get(tf)
counts = _count_daily_signals_per_type(df, cutoff_kst, offset=offset)
lines.append("")
lines.append(f"[{TF_LABEL_MAP.get(tf, tf)}]")
total_all = 0
@@ -736,7 +733,22 @@ def send_daily_report(symbol="BTCUSDT"):
passed_all = total_all - failed_all
rate = (passed_all / total_all * 100) if total_all > 0 else 0.0
lines.append(f"합계: {passed_all}T {failed_all}F (승률 {rate:.2f}%)")
send_telegram("\n".join(lines))
return "\n".join(lines)
def send_daily_report(symbol="BTCUSDT"):
now_kst = (datetime.now(timezone.utc) + KST).replace(tzinfo=None)
cutoff_kst = now_kst - timedelta(hours=24)
dfs = {}
for tf in DAILY_REPORT_TIMEFRAMES:
try:
dfs[tf] = _build_signal_df(symbol, tf, DAILY_REPORT_KLINES_LIMIT[tf])
except Exception as e:
print(f"[일일리포트 {tf} 데이터 오류] {e}")
dfs[tf] = None
msg_1x = _build_daily_report_lines(dfs, cutoff_kst, now_kst, symbol, offset=1, header_suffix="1배 시간 (다음 봉 검증)")
send_telegram(msg_1x)
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