수정본
This commit is contained in:
80
backend/app/services/mdd_manager.py
Normal file
80
backend/app/services/mdd_manager.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
MDD(Maximum Drawdown) 관리 - CLAUDE.md 섹션 6.3
|
||||
|
||||
- 포트폴리오 최고점 대비 하락률 추적
|
||||
- -10% 돌파 시: 신규 진입 중단 + 기존 포지션 30% 축소
|
||||
- -20% 돌파 시: 전체 포지션 50% 강제 축소 (레버리지/인버스 우선)
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MDDManager:
|
||||
"""포트폴리오 MDD 관리자 (싱글톤으로 사용)"""
|
||||
|
||||
def __init__(self):
|
||||
self.peak_balance: float = 0.0
|
||||
self.is_paused: bool = False
|
||||
self.reduction_applied: str = None # None, "10pct", "20pct"
|
||||
self._lock = asyncio.Lock() # 스케줄러-sync 동시 접근 방지
|
||||
|
||||
async def update(self, current_balance: float) -> Dict:
|
||||
"""MDD 업데이트 및 필요한 조치 반환 (thread-safe)
|
||||
|
||||
Returns:
|
||||
{"action": "NONE"/"REDUCE_AND_PAUSE"/"FORCE_REDUCE",
|
||||
"drawdown": float, ...}
|
||||
"""
|
||||
async with self._lock:
|
||||
if current_balance > self.peak_balance:
|
||||
self.peak_balance = current_balance
|
||||
self.is_paused = False
|
||||
self.reduction_applied = None
|
||||
|
||||
if self.peak_balance <= 0:
|
||||
return {"action": "NONE", "drawdown": 0.0}
|
||||
|
||||
drawdown_pct = ((current_balance - self.peak_balance) / self.peak_balance) * 100
|
||||
|
||||
# -20% 돌파: 50% 강제 축소
|
||||
if drawdown_pct <= -20 and self.reduction_applied != "20pct":
|
||||
self.is_paused = True
|
||||
self.reduction_applied = "20pct"
|
||||
logger.warning(f"MDD {drawdown_pct:.1f}% - 50% 강제 축소 + 신규진입 중단")
|
||||
return {
|
||||
"action": "FORCE_REDUCE",
|
||||
"drawdown": drawdown_pct,
|
||||
"reduce_pct": 0.50,
|
||||
"prioritize_leverage": True,
|
||||
}
|
||||
|
||||
# -10% 돌파: 30% 축소 + 신규진입 중단
|
||||
if drawdown_pct <= -10 and self.reduction_applied is None:
|
||||
self.is_paused = True
|
||||
self.reduction_applied = "10pct"
|
||||
logger.warning(f"MDD {drawdown_pct:.1f}% - 30% 축소 + 신규진입 중단")
|
||||
return {
|
||||
"action": "REDUCE_AND_PAUSE",
|
||||
"drawdown": drawdown_pct,
|
||||
"reduce_pct": 0.30,
|
||||
}
|
||||
|
||||
return {"action": "NONE", "drawdown": drawdown_pct}
|
||||
|
||||
async def can_open_new_position(self) -> bool:
|
||||
"""신규 포지션 진입 가능 여부 (thread-safe)"""
|
||||
async with self._lock:
|
||||
return not self.is_paused
|
||||
|
||||
def reset(self):
|
||||
"""MDD 상태 리셋 (수동 복구 시) — 동기 메서드, 단독 호출 시에만 사용"""
|
||||
self.is_paused = False
|
||||
self.reduction_applied = None
|
||||
logger.info("MDD 관리자 리셋")
|
||||
|
||||
|
||||
# 싱글톤 인스턴스
|
||||
mdd_manager = MDDManager()
|
||||
Reference in New Issue
Block a user