feat: 리스크 매니저 구현 (일일 손실 한도, 포지션 수 제한)
Made-with: Cursor
This commit is contained in:
36
src/risk_manager.py
Normal file
36
src/risk_manager.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from loguru import logger
|
||||
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.daily_pnl: float = 0.0
|
||||
self.initial_balance: float = 0.0
|
||||
self.open_positions: list = []
|
||||
|
||||
def is_trading_allowed(self) -> bool:
|
||||
"""일일 최대 손실 초과 시 거래 중단"""
|
||||
if self.initial_balance <= 0:
|
||||
return True
|
||||
loss_pct = abs(self.daily_pnl) / self.initial_balance
|
||||
if self.daily_pnl < 0 and loss_pct >= self.max_daily_loss_pct:
|
||||
logger.warning(
|
||||
f"일일 손실 한도 초과: {loss_pct:.2%} >= {self.max_daily_loss_pct:.2%}"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
def can_open_new_position(self) -> bool:
|
||||
"""최대 동시 포지션 수 체크"""
|
||||
return len(self.open_positions) < self.config.max_positions
|
||||
|
||||
def record_pnl(self, pnl: float):
|
||||
self.daily_pnl += pnl
|
||||
logger.info(f"오늘 누적 PnL: {self.daily_pnl:.4f} USDT")
|
||||
|
||||
def reset_daily(self):
|
||||
"""매일 자정 초기화"""
|
||||
self.daily_pnl = 0.0
|
||||
logger.info("일일 PnL 초기화")
|
||||
36
tests/test_risk_manager.py
Normal file
36
tests/test_risk_manager.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import pytest
|
||||
import os
|
||||
from src.risk_manager import RiskManager
|
||||
from src.config import Config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config():
|
||||
os.environ.update({
|
||||
"BINANCE_API_KEY": "k",
|
||||
"BINANCE_API_SECRET": "s",
|
||||
"SYMBOL": "XRPUSDT",
|
||||
"LEVERAGE": "10",
|
||||
"RISK_PER_TRADE": "0.02",
|
||||
})
|
||||
return Config()
|
||||
|
||||
|
||||
def test_max_drawdown_check(config):
|
||||
rm = RiskManager(config, max_daily_loss_pct=0.05)
|
||||
rm.daily_pnl = -60.0
|
||||
rm.initial_balance = 1000.0
|
||||
assert rm.is_trading_allowed() is False
|
||||
|
||||
|
||||
def test_trading_allowed_normal(config):
|
||||
rm = RiskManager(config, max_daily_loss_pct=0.05)
|
||||
rm.daily_pnl = -10.0
|
||||
rm.initial_balance = 1000.0
|
||||
assert rm.is_trading_allowed() is True
|
||||
|
||||
|
||||
def test_position_size_capped(config):
|
||||
rm = RiskManager(config, max_daily_loss_pct=0.05)
|
||||
rm.open_positions = ["pos1", "pos2", "pos3"]
|
||||
assert rm.can_open_new_position() is False
|
||||
Reference in New Issue
Block a user