From 2585d471406e3410944f09f65d864bf84d7b7c7f Mon Sep 17 00:00:00 2001 From: joungmin Date: Sat, 28 Feb 2026 10:39:30 +0900 Subject: [PATCH] feat: add hourly position status report via Telegram Run status reporter thread every 60 minutes, sends current price, PnL, drop from peak, and holding time for each position. Co-Authored-By: Claude Sonnet 4.6 --- core/notify.py | 31 +++++++++++++++++++++++++++++++ main.py | 25 ++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/core/notify.py b/core/notify.py index 493af0a..0226c9c 100644 --- a/core/notify.py +++ b/core/notify.py @@ -49,3 +49,34 @@ def notify_sell(ticker: str, price: float, pnl_pct: float, reason: str) -> None: def notify_error(message: str) -> None: _send(f"⚠️ [오류]\n{message}") + + +def notify_status(positions: dict) -> None: + """1시간마다 포지션 현황 요약 전송.""" + from datetime import datetime + import pyupbit + + now = datetime.now().strftime("%H:%M") + + if not positions: + _send(f"📊 [{now} 현황]\n보유 포지션 없음 — 매수 신호 대기 중") + return + + lines = [f"📊 [{now} 현황]"] + for ticker, pos in positions.items(): + current = pyupbit.get_current_price(ticker) + if not current: + continue + pnl = (current - pos["buy_price"]) / pos["buy_price"] * 100 + peak = pos["peak_price"] + drop = (peak - current) / peak * 100 + elapsed = (datetime.now() - pos["entry_time"]).total_seconds() / 3600 + emoji = "📈" if pnl >= 0 else "📉" + lines.append( + f"\n{emoji} {ticker}\n" + f" 현재가: {current:,.0f}원\n" + f" 수익률: {pnl:+.1f}%\n" + f" 최고가 대비: -{drop:.1f}%\n" + f" 보유: {elapsed:.1f}h" + ) + _send("\n".join(lines)) diff --git a/main.py b/main.py index 96c78d0..e962da0 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ import logging import threading +import time from dotenv import load_dotenv @@ -17,9 +18,25 @@ logging.basicConfig( ) from core.monitor import run_monitor -from core.trader import restore_positions +from core.notify import notify_status +from core.trader import get_positions, restore_positions from daemon.runner import run_scanner +STATUS_INTERVAL = 3600 # 1시간마다 요약 전송 + + +def run_status_reporter(interval: int = STATUS_INTERVAL) -> None: + """주기적으로 포지션 현황을 Telegram으로 전송.""" + logger = logging.getLogger("status") + logger.info(f"상태 리포터 시작 (주기={interval//60}분)") + time.sleep(interval) # 첫 전송은 1시간 후 + while True: + try: + notify_status(dict(get_positions())) + except Exception as e: + logger.error(f"상태 리포트 오류: {e}") + time.sleep(interval) + def main() -> None: # 재시작 시 기존 잔고 복원 (이중 매수 방지) @@ -31,6 +48,12 @@ def main() -> None: ) monitor_thread.start() + # 1시간 주기 상태 리포트 스레드 + status_thread = threading.Thread( + target=run_status_reporter, daemon=True, name="status" + ) + status_thread.start() + # 매수 스캔 루프 (60초 주기, 메인 스레드) run_scanner()