"""멀티테넌트 사용자 실행 컨텍스트 (설계 문서 §2, §5.6). `UserContext`는 사용자 1명의 매매 실행에 필요한 모든 상태(KIS 클라이언트, 리스크 매니저, 데이트레이딩 상태 등)를 한데 묶는다. 실제 조립(레지스트리)은 dev-team/risk-monitor-agent가 담당하는 `registry.py`에서 이루어지며, 이 파일은 데이터 구조만 정의한다 — import 순환을 피하기 위해 mdd/loss_tracker/dt_state는 구체 타입을 import하지 않고 `Any`로 둔다. 이 모듈은 DB 세션을 열지 않는다(자격증명 복호화는 호출 측이 수행해 넘겨준다). """ from dataclasses import dataclass from typing import Any, Optional from app.services.korea_investment_api import KoreaInvestmentAPI @dataclass class UserContext: """사용자 1명의 매매 실행 컨텍스트. Attributes: user_id: `users.id`. legacy(단일 계좌) 컨텍스트는 legacy owner id 또는 None. username: 표시/로그용. mode: "virtual" | "real" — `kis.base_url`과 동일해야 함. kis: 이 사용자의 주문·잔고 전용 `KoreaInvestmentAPI` 인스턴스 (`KoreaInvestmentAPI.for_user(...)` 또는 legacy 전역 `korea_investment_api`). mdd: 사용자별 `MDDManager` 인스턴스 (risk-monitor-agent 담당, 타입은 순환 방지 위해 Any). loss_tracker: 사용자별 `ConsecutiveLossTracker` 인스턴스 (Any, 위와 동일 이유). dt_state: 사용자별 `DayTradingState` 인스턴스 (2단계에서 사용, 1단계는 None 허용). portfolio_cache: 사용자별 `PortfolioCache` 인스턴스 (2단계, 1단계는 None 허용). auto_trade_enabled: 사용자 단위 자동매매 on/off. is_legacy: True면 기존 전역 싱글톤(korea_investment_api/mdd_manager/loss_tracker)을 그대로 감싼 컨텍스트 — Redis 락/키 네임스페이스가 하위 호환 경로(user_id 미포함)를 쓴다. """ user_id: Optional[int] username: str mode: str kis: KoreaInvestmentAPI mdd: Any loss_tracker: Any dt_state: Any = None portfolio_cache: Any = None auto_trade_enabled: bool = False is_legacy: bool = False @property def effective_auto_trade_enabled(self) -> bool: """`auto_trade_enabled`의 실시간 버전. legacy 컨텍스트는 `registry.legacy()`가 조립 시점에 `settings.AUTO_TRADE_ENABLED`를 스냅샷해 최대 60초(TTL) 동안 고정한다(L4) — 런타임에 `.env`/설정을 토글해도 캐시가 갱신되기 전까지 반영되지 않는 문제. legacy 컨텍스트에서는 필드 대신 이 프로퍼티로 `settings.AUTO_TRADE_ENABLED`를 매번 다시 읽어 즉시 반영한다. 사용자 컨텍스트(is_legacy=False)는 DB에 저장된 사용자별 토글이 필드값이므로 그대로 반환한다. portfolio_v2 buy 가드는 `ctx.auto_trade_enabled` 대신 이 프로퍼티 사용을 권장. """ if self.is_legacy: from app.core.config import settings return settings.AUTO_TRADE_ENABLED return self.auto_trade_enabled