"""사용자 스코프 리스크 매니저 팩토리 (설계 문서 §5.6). `UserContextRegistry`(dev-team/trading-agent 조립 담당)가 사용자 컨텍스트를 만들 때 이 팩토리로 `MDDManager`/`ConsecutiveLossTracker` 인스턴스를 새로 생성하고 영속화 상태(Redis `mdd:state:{user_id}`, `stock_bans_v2`)를 복원한다. 기존 전역 싱글톤(`app.services.mdd_manager.mdd_manager`, `app.services.loss_tracker.loss_tracker`)은 legacy 컨텍스트 전용으로 그대로 남겨두고 이 모듈에서 재사용하지 않는다 — 여러 사용자가 동일 인스턴스를 공유하면 peak_balance/연속손실 카운트가 뒤섞이기 때문이다. """ import logging from app.services.mdd_manager import MDDManager from app.services.loss_tracker import ConsecutiveLossTracker logger = logging.getLogger(__name__) def create_mdd_manager_for_user(user_id: int) -> MDDManager: """사용자 전용 `MDDManager` 신규 인스턴스 생성. peak_balance/is_paused 등은 기본값(0/False)에서 시작 — 실제 복원은 `load_user_risk_state()`가 `load_from_redis_v2(user_id)`로 수행한다. """ return MDDManager() def create_loss_tracker_for_user(user_id: int) -> ConsecutiveLossTracker: """사용자 전용 `ConsecutiveLossTracker` 신규 인스턴스 생성. `ban_count`/`ban_calendar_days`는 전역 설정(§CLAUDE.md 5.1: 3연패/42일)을 그대로 따른다 — 사용자별 오버라이드는 이번 범위 밖. 인스턴스의 `user_id` 속성을 세팅해 두면 호출 측(스케줄러/엔드포인트)이 `ctx.loss_tracker.save_to_db_v2(ctx.loss_tracker.user_id, stock_code)`처럼 컨텍스트에서 user_id를 다시 꺼낼 필요 없이 인스턴스에서 바로 참조할 수 있다. """ try: from app.core.config import settings tracker = ConsecutiveLossTracker( ban_count=settings.CONSECUTIVE_LOSS_BAN_COUNT, ban_calendar_days=42, ) except Exception: tracker = ConsecutiveLossTracker() tracker.user_id = user_id return tracker async def load_user_risk_state( ctx_user_id: int, mdd: MDDManager, tracker: ConsecutiveLossTracker, db, ) -> None: """사용자 컨텍스트 생성 직후 1회 호출 — Redis(MDD) + DB(ban) 상태 복원. 두 복원 모두 실패해도 예외를 전파하지 않는다(각 `_v2` 메서드 내부에서 이미 try/except 처리됨) — 신규 사용자는 그냥 초기 상태로 시작한다. """ try: restored = await mdd.load_from_redis_v2(ctx_user_id) if not restored: logger.debug(f"[risk_factory] user={ctx_user_id} MDD 상태 없음 — 초기값으로 시작") except Exception as e: logger.warning(f"[risk_factory] user={ctx_user_id} MDD 복원 실패: {e}") try: await tracker.load_from_db_v2(db, ctx_user_id) except Exception as e: logger.warning(f"[risk_factory] user={ctx_user_id} loss_tracker 복원 실패: {e}")