feat: shared RiskManager with async lock, same-direction limit, per-symbol tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
21in7
2026-03-05 23:06:41 +09:00
parent 7aef391b69
commit 9318fb887e
2 changed files with 89 additions and 9 deletions

View File

@@ -1,3 +1,4 @@
import asyncio
from loguru import logger
from src.config import Config
@@ -5,10 +6,11 @@ from src.config import Config
class RiskManager:
def __init__(self, config: Config, max_daily_loss_pct: float = 0.05):
self.config = config
self.max_daily_loss_pct = max_daily_loss_pct # 일일 최대 손실 5%
self.max_daily_loss_pct = max_daily_loss_pct
self.daily_pnl: float = 0.0
self.initial_balance: float = 0.0
self.open_positions: list = []
self.open_positions: dict[str, str] = {} # {symbol: side}
self._lock = asyncio.Lock()
def is_trading_allowed(self) -> bool:
"""일일 최대 손실 초과 시 거래 중단"""
@@ -22,9 +24,33 @@ class RiskManager:
return False
return True
def can_open_new_position(self) -> bool:
"""최대 동시 포지션 수 체크"""
return len(self.open_positions) < self.config.max_positions
async def can_open_new_position(self, symbol: str, side: str) -> bool:
"""포지션 오픈 가능 여부 (전체 한도 + 중복 진입 + 동일 방향 제한)"""
async with self._lock:
if len(self.open_positions) >= self.config.max_positions:
logger.info(f"최대 포지션 수 도달: {len(self.open_positions)}/{self.config.max_positions}")
return False
if symbol in self.open_positions:
logger.info(f"{symbol} 이미 포지션 보유 중")
return False
same_dir = sum(1 for s in self.open_positions.values() if s == side)
if same_dir >= self.config.max_same_direction:
logger.info(f"동일 방향({side}) 한도 도달: {same_dir}/{self.config.max_same_direction}")
return False
return True
async def register_position(self, symbol: str, side: str):
"""포지션 등록"""
async with self._lock:
self.open_positions[symbol] = side
logger.info(f"포지션 등록: {symbol} {side} (현재 {len(self.open_positions)}개)")
async def close_position(self, symbol: str, pnl: float):
"""포지션 닫기 + PnL 기록"""
async with self._lock:
self.open_positions.pop(symbol, None)
self.daily_pnl += pnl
logger.info(f"포지션 종료: {symbol}, PnL={pnl:+.4f}, 누적={self.daily_pnl:+.4f}")
def record_pnl(self, pnl: float):
self.daily_pnl += pnl
@@ -36,7 +62,7 @@ class RiskManager:
logger.info("일일 PnL 초기화")
def set_base_balance(self, balance: float) -> None:
"""봇 시작 시 기준 잔고 설정 (동적 비율 계산 기준점)"""
"""봇 시작 시 기준 잔고 설정"""
self.initial_balance = balance
def get_dynamic_margin_ratio(self, balance: float) -> float: