"""데이트레이딩 전용 API v2 엔드포인트 prefix: /api/v1/daytrading-v2 기존 day_trading.py는 유지하며, 새 아키텍처 기반의 엔드포인트를 추가. 엔드포인트: GET /watchlist — 감시 종목 조회 POST /watchlist — 감시 종목 설정 GET /positions — 활성 포지션 조회 GET /pnl — 당일 실현 손익 POST /close-all — 전체 강제 청산 GET /state — 전체 상태 스냅샷 (디버그용) """ import logging from typing import List, Dict from fastapi import APIRouter, HTTPException from pydantic import BaseModel from app.services.daytrading.position_manager import day_trading_state from app.services.daytrading.entry_service import set_watchlist, get_watchlist from app.services.daytrading.exit_service import force_close_all router = APIRouter() logger = logging.getLogger(__name__) class WatchlistRequest(BaseModel): stocks: List[Dict[str, str]] # [{"code": "005930", "name": "삼성전자"}, ...] @router.get("/watchlist") async def get_watchlist_v2(): """감시 종목 목록 조회.""" wl = get_watchlist() return { "count": len(wl), "stocks": [{"code": k, "name": v} for k, v in wl.items()], } @router.post("/watchlist") async def set_watchlist_v2(req: WatchlistRequest): """감시 종목 설정 (기존 목록 교체).""" set_watchlist(req.stocks) return {"message": f"{len(req.stocks)}개 감시 종목 설정 완료"} @router.get("/positions") async def get_positions_v2(): """활성 데이트레이딩 포지션 조회 (인메모리).""" day_trading_state.reset_if_new_day() positions = [] for code, pos in day_trading_state.positions.items(): entry_price = pos.get("entry_price", 0) highest = pos.get("highest_price", entry_price) positions.append({ "stock_code": code, "stock_name": pos.get("stock_name", code), "quantity": pos.get("quantity", 0), "entry_price": entry_price, "highest_price": highest, "strategy": pos.get("strategy", ""), "reason": pos.get("reason", ""), "entry_time": str(pos.get("entry_time", "")), }) return {"count": len(positions), "positions": positions} @router.get("/pnl") async def get_daily_pnl_v2(): """당일 실현 손익 조회.""" day_trading_state.reset_if_new_day() return { "daily_pnl": round(day_trading_state.daily_pnl), "closed_today_count": len(day_trading_state.closed_today), "active_count": day_trading_state.position_count(), } @router.post("/close-all") async def close_all_positions_v2(reason: str = "수동 강제 청산"): """전체 데이트레이딩 포지션 강제 청산.""" if not day_trading_state.positions: return {"message": "활성 포지션 없음", "exits": []} results = await force_close_all(reason=reason) logger.info(f"[DayTradingAPI] 강제 청산: {len(results)}건") return {"exits": results, "count": len(results)} @router.get("/state") async def get_state_snapshot(): """전체 상태 스냅샷 (디버그용).""" day_trading_state.reset_if_new_day() return { "position_count": day_trading_state.position_count(), "orb_cache_count": len(day_trading_state.orb_cache), "daily_pnl": round(day_trading_state.daily_pnl), "closed_today": list(day_trading_state.closed_today), "last_reset_date": str(day_trading_state.last_reset_date), "watchlist_count": len(get_watchlist()), }