""" app/ai/status.py 현재 수익 현황 조회 — 실현손익 + 미실현손익 합산 """ import asyncio import os import sys from datetime import datetime from pathlib import Path ROOT = Path(__file__).parent.parent.parent sys.path.insert(0, str(ROOT)) def load_env(): env_path = ROOT / ".env" if not env_path.exists(): return for line in env_path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue k, _, v = line.partition("=") k, v = k.strip(), v.strip() if " #" in v: v = v[:v.index(" #")] v = v.strip().strip('"').strip("'") if k and v and k not in os.environ: os.environ[k] = v load_env() from app.db.models import get_conn from app.execution.kis_client import KISClient async def get_status(): today = datetime.now().strftime("%Y-%m-%d") now_str = datetime.now().strftime("%H:%M:%S") # ── 실현손익 (오늘 종료된 거래) ── with get_conn() as conn: rows = conn.execute(""" SELECT ticker, name, entry_price, exit_price, quantity, pnl, exit_reason FROM trades WHERE date=? AND exit_time IS NOT NULL ORDER BY exit_time DESC """, (today,)).fetchall() open_rows = conn.execute(""" SELECT ticker, name, entry_price, quantity FROM positions """).fetchall() realized_trades = rows realized_pnl = sum(r[5] for r in rows if r[5] is not None) wins = sum(1 for r in rows if r[5] and r[5] > 0) losses = sum(1 for r in rows if r[5] and r[5] <= 0) # ── 미실현손익 (현재 포지션) ── unrealized_pnl = 0.0 position_details = [] if open_rows: kis = KISClient() await kis.ensure_token() for ticker, name, entry_price, qty in open_rows: try: info = await kis.get_price(ticker) current = info.get("current", 0) if entry_price and current: unreal = (current - entry_price) * qty pct = (current - entry_price) / entry_price * 100 unrealized_pnl += unreal position_details.append({ "ticker": ticker, "name": name, "entry": entry_price, "current": current, "qty": qty, "pnl": unreal, "pct": pct, }) except Exception as e: position_details.append({ "ticker": ticker, "name": name, "entry": entry_price, "current": 0, "qty": qty, "pnl": 0.0, "pct": 0.0, "error": str(e), }) await asyncio.sleep(0.5) total_pnl = realized_pnl + unrealized_pnl # ── 출력 ── print(f"\n{'='*50}") print(f" StockBot 현황 [{today} {now_str}]") print(f"{'='*50}") # 총 손익 sign = "+" if total_pnl >= 0 else "" print(f"\n 총 손익: {sign}{total_pnl:,.0f}원") print(f" 실현손익: {'+' if realized_pnl >= 0 else ''}{realized_pnl:,.0f}원 ({wins}승 {losses}패)") print(f" 미실현손익: {'+' if unrealized_pnl >= 0 else ''}{unrealized_pnl:,.0f}원 (보유 {len(position_details)}종목)") # 보유 포지션 if position_details: print(f"\n [보유 포지션]") for p in position_details: if "error" in p: print(f" {p['name']}({p['ticker']}) {p['qty']}주 — 현재가 조회 실패") else: sign = "+" if p['pnl'] >= 0 else "" print(f" {p['name']}({p['ticker']}) {p['qty']}주 | " f"매수 {p['entry']:,.0f} → 현재 {p['current']:,.0f} | " f"{sign}{p['pnl']:,.0f}원 ({sign}{p['pct']:.2f}%)") # 오늘 거래 내역 if realized_trades: print(f"\n [오늘 체결]") for r in realized_trades[:10]: ticker, name, ep, xp, qty, pnl, reason = r pnl_str = f"{'+' if pnl and pnl >= 0 else ''}{pnl:,.0f}원" if pnl else "0원" print(f" {name}({ticker}) {qty}주 | {ep:,.0f}→{xp:,.0f} | {pnl_str} [{reason}]") if len(realized_trades) > 10: print(f" ... 외 {len(realized_trades)-10}건") else: print(f"\n 오늘 체결 내역 없음") print(f"\n{'='*50}\n") return {"total_pnl": total_pnl, "realized": realized_pnl, "unrealized": unrealized_pnl} if __name__ == "__main__": asyncio.run(get_status())