AlgoLab Blog · 완전 백과 · 2026 최신

자동매매 리스크 관리 완전 가이드 — 포지션 사이징·손절·드로다운·켈리 공식

완전 백과 2026-06-05 · 약 22분 읽기 · 알고랩 AlgoLab
한 줄 정리 이 글은 자동매매에서 가장 중요하지만 가장 적게 다뤄지는 영역인 리스크 관리를 처음부터 끝까지 정리한 한국어 완전 가이드입니다. 포지션 사이징(1~2% 룰·켈리 공식) → 5가지 손절 규칙 → 4가지 익절 전략 → 다종목 분산 → 드로다운 관리 → 통합 RiskManager 클래스까지, 모든 단계를 Python 풀코드로 정리했습니다. "좋은 전략 = 8할" 이지만 "리스크 관리 = 9할"입니다.

자동매매에서 사고는 대부분 "전략이 틀려서"가 아니라 "리스크 관리가 없어서" 발생합니다. 백테스트에서 좋게 나온 전략을 실전에 투입했다가 한 번의 큰 변동에 자본 절반이 사라지는 사례 — 알고랩이 의뢰자들에게서 가장 많이 듣는 이야기입니다.

이 글은 입문자도 따라할 수 있는 리스크 관리의 8부 프레임워크를 정리했습니다. 모든 단계에 Python 코드가 있고, 통합 RiskManager 클래스를 만들어 어떤 봇에든 그대로 붙여 쓸 수 있게 했습니다. 한 번 익혀두면 평생 쓸 수 있는 골격입니다.

이 글의 8부 구성

  1. Part 1 — 리스크 관리의 본질 (왜 전략보다 중요한가)
  2. Part 2 — 포지션 사이징 (1~2% 룰 · 켈리 공식 · 변동성 기반)
  3. Part 3 — 5가지 손절 규칙 (고정·ATR·기술적·시간·트레일링)
  4. Part 4 — 4가지 익절 전략 (고정·R:R·부분·트레일링)
  5. Part 5 — 다종목·다전략 분산 (상관관계 · 합산 리스크)
  6. Part 6 — 드로다운 관리 (MDD · 회복기간 · Circuit Breaker)
  7. Part 7 — 통합 RiskManager 클래스 (모든 봇에 붙이는 골격)
  8. Part 8 — 16단계 체크리스트

Part 1 — 리스크 관리의 본질

전략이 8할, 리스크 관리가 9할. 한 번의 큰 손실은 모든 좋은 거래를 무력화합니다.

왜 리스크 관리가 전략보다 중요한가

같은 50% 승률 전략이라도 손익비(평균이익 ÷ 평균손실)에 따라 결과가 정반대입니다.

승률손익비100거래 후 기대 수익률 (자본의)판정
70%1:3−40%❌ 망함
50%1:10%본전
50%2:1+50%✅ 수익
40%3:1+80%✅ 큰 수익

승률 70%가 좋아 보이지만, 손익비 1:3이면 결국 망합니다. 반대로 승률 40%여도 손익비 3:1이면 큰 수익. 승률이 아니라 손익비가 수익을 결정합니다. 그리고 손익비는 익절·손절 규칙으로 만들어집니다.

큰 손실의 비대칭성 — 회복의 어려움

한 번의 큰 손실은 회복이 매우 어렵습니다. 다음은 원금 회복에 필요한 수익률입니다.

손실원금 회복에 필요한 수익률
−10%+11%
−20%+25%
−30%+43%
−50%+100%
−80%+400%

⚠️ -50%는 사실상 끝: 자본의 절반을 잃으면 원금 회복에 +100%가 필요합니다. 1년 시장 평균 수익률이 +10%인데 +100%는 비현실적입니다. 큰 손실을 막는 것이 큰 수익을 내는 것보다 100배 중요합니다.

리스크 관리 부재의 5가지 실패 패턴

  1. 손절 규칙 없음 — "조금만 더 기다리면 돌아오겠지"로 -30% 누적
  2. 한 종목 올인 — 분산 없이 한 자산이 망하면 같이 망함
  3. 레버리지 과다 — 5배 레버리지에 -20% 움직임으로 청산
  4. 물타기 — 손실 중인 종목에 자본 추가 → 손실 확대
  5. 일일 손실 한도 없음 — 나쁜 날 봇이 계속 매매해 누적 손실

이 5가지가 어떤 심리에서 비롯되는지: → 자동매매 전략 30가지 완전 정리

Part 2 — 포지션 사이징

한 거래에 자본의 몇 %를 걸지 결정. 가장 단순한 1~2% 룰부터 켈리 공식까지.

방법 1. 1~2% 룰 (입문자 표준)

한 거래에서 잃을 수 있는 최대 금액을 자본의 1~2%로 제한. 손절가까지의 거리로 포지션 크기 역산.

포지션 크기 = (자본 × 리스크%) ÷ (진입가 − 손절가)
def position_size_fixed(capital, risk_pct, entry, stop):
    """1~2% 룰 — 자본 대비 리스크 % 기준 포지션 사이징"""
    risk_amount = capital * (risk_pct / 100)
    risk_per_share = abs(entry - stop)
    if risk_per_share == 0:
        return 0
    qty = risk_amount / risk_per_share
    return qty

# 예: 자본 1000만원, 리스크 1%, 진입 60,000원, 손절 58,500원
qty = position_size_fixed(10_000_000, 1, 60000, 58500)
print(f"매수 수량: {qty:.2f}주")
# 매수 수량: 66.67주 (손절 시 자본의 1% = 10만원 손실)

✅ 1% 룰의 강력함: 연속 20번 손절을 봐도 자본의 20%만 잃습니다 (정확히는 18.2%, 복리 영향). 연속 50번 손절은 자본의 39%. 즉 전략이 한참 안 통하는 시기를 견딜 수 있는 유일한 방법이 1~2% 룰입니다.

방법 2. 켈리 공식 (Kelly Criterion)

승률(p)과 손익비(b)를 알 때 자본 대비 최적 베팅 비율을 계산합니다. 1956년 정보이론에서 유래.

f = (b × p − q) / b   (q = 1 − p)
def kelly_fraction(win_rate, win_loss_ratio):
    """켈리 공식: 자본 대비 최적 베팅 비율"""
    p = win_rate
    b = win_loss_ratio   # 평균이익 / 평균손실
    q = 1 - p
    f = (b * p - q) / b
    return max(0, f)

# 예: 승률 55%, 손익비 1.5
f = kelly_fraction(0.55, 1.5)
print(f"켈리 비율: {f:.2%}")
# 켈리 비율: 25.00% — 매 거래에 자본의 25%

⚠️ 켈리 공식 그대로 쓰면 안 됨: 위 예시처럼 25%가 나오면 자본의 25%를 한 번에 거는 셈인데, 이는 변동성이 너무 큽니다. 실전에서는 Half-Kelly(켈리값의 1/2) 또는 Quarter-Kelly(1/4)을 씁니다. 또한 승률·손익비 추정이 약간만 틀려도 켈리값이 크게 왜곡됩니다.

def half_kelly(win_rate, win_loss_ratio):
    """안전한 Half-Kelly"""
    return kelly_fraction(win_rate, win_loss_ratio) * 0.5

# 위 예시: 25% → 12.5%
# 여전히 1~2% 룰보다 크므로, 입문자는 1~2% 룰 추천

방법 3. ATR 기반 변동성 사이징

시장 변동성(ATR)에 반비례하게 포지션 크기 조정. 변동성 큰 시기에는 작게, 작은 시기에는 크게.

import pandas as pd

def atr(df, period=14):
    """Average True Range — 변동성 지표"""
    high_low = df["high"] - df["low"]
    high_close = (df["high"] - df["close"].shift()).abs()
    low_close = (df["low"] - df["close"].shift()).abs()
    tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
    return tr.rolling(period).mean()

def position_size_atr(capital, risk_pct, atr_value, k=2):
    """ATR 기반 사이징: 손절폭 = k × ATR"""
    risk_amount = capital * (risk_pct / 100)
    stop_distance = k * atr_value
    if stop_distance == 0:
        return 0
    return risk_amount / stop_distance

# 예: ATR이 2,000원이면 손절폭 = 2 × 2,000 = 4,000원
qty = position_size_atr(10_000_000, 1, 2000, k=2)
print(f"매수 수량: {qty:.2f}주 (ATR 변동성 기반)")

방법별 비교

방법장점단점추천 대상
1~2% 룰단순, 안전최적 아님입문자 무조건
Half-Kelly이론적으로 최적승률·손익비 추정 오차에 민감검증된 전략 운영자
ATR 기반시장 변동성 자동 반영ATR 계산 필요변동성 큰 자산(코인)

Part 3 — 5가지 손절 규칙

손절은 의지의 문제가 아니라 코드의 문제. 봇이 무조건 실행하게.

1. 고정 % 손절

진입가 대비 N% 하락 시 즉시 청산. 가장 단순.

def fixed_pct_stop(entry, stop_pct=3):
    """진입가 -3% 손절"""
    return entry * (1 - stop_pct / 100)

장점: 구현 단순. 단점: 시장 변동성 무시 (변동성 큰 자산은 너무 자주 손절).

2. ATR 기반 손절 (권장)

손절폭 = N × ATR. 시장 변동성에 자동 적응.

def atr_stop(entry, atr_value, k=2):
    """진입가 - 2×ATR 손절"""
    return entry - k * atr_value

장점: 변동성 자동 반영. 단점: ATR 계산 필요.

3. 기술적 손절 (지지선 깨짐)

최근 N봉의 최저가 또는 이동평균선 아래로 깨지면 청산.

def technical_stop(df, lookback=20):
    """최근 20봉의 최저가를 손절선으로"""
    return df["low"].tail(lookback).min()

장점: 시장 구조 반영. 단점: 손절폭이 매번 다름 → 포지션 사이징 어려움.

4. 시간 손절

진입 후 N봉(또는 N일) 동안 수익 안 나면 청산. "시간 가치" 개념.

def time_stop(entry_bar, current_bar, max_bars=20):
    """진입 후 20봉 지나면 청산 (수익 무관)"""
    return current_bar - entry_bar >= max_bars

장점: 죽은 포지션 정리, 자본 효율. 단점: 큰 추세 놓칠 수 있음.

5. 트레일링 스탑 (Trailing Stop)

가격이 오를 때마다 손절선도 따라 올라감. 이익 보호 + 추세 따라가기.

class TrailingStop:
    def __init__(self, entry, trail_pct=5):
        self.entry = entry
        self.trail_pct = trail_pct
        self.highest = entry
        self.stop = entry * (1 - trail_pct / 100)

    def update(self, current_price):
        if current_price > self.highest:
            self.highest = current_price
            self.stop = self.highest * (1 - self.trail_pct / 100)
        return current_price <= self.stop   # True면 손절

# 예: 60,000원 진입, 5% 트레일링
ts = TrailingStop(60000, trail_pct=5)
ts.update(63000)   # 최고가 63,000 → 손절선 59,850으로 상향
ts.update(64500)   # 최고가 64,500 → 손절선 61,275로 상향
ts.update(61000)   # 손절선 아래 → True 반환 (청산)

장점: 이익 보호 + 추세 추종. 단점: 잦은 휩쏘에 손절 자주.

손절 규칙 선택 매트릭스

시장 특성추천 손절
저변동성 주식 (일봉)고정 % 또는 ATR
고변동성 코인 (분봉)ATR (k=2~3)
박스권 횡보기술적 (박스권 하단)
강추세 종목트레일링 스탑
분봉 스캘핑시간 손절 + 고정 %

Part 4 — 4가지 익절 전략

"언제 팔지"는 "언제 살지"만큼 중요. 손익비를 만드는 핵심.

1. 고정 익절 (Target Price)

진입가 +N% 도달 시 청산.

def fixed_target(entry, target_pct=6):
    return entry * (1 + target_pct / 100)

2. R:R 기반 익절 (Risk:Reward Ratio)

손절 거리의 N배에서 청산. 손익비 명시.

def rr_target(entry, stop, rr=2):
    """손절 거리의 2배에서 익절 (R:R = 1:2)"""
    risk = entry - stop
    return entry + risk * rr

R:R 2 이상 권장. 승률 40%여도 R:R 2면 장기 수익.

3. 부분 익절 (Scale-out)

1차 목표 도달 시 절반 청산 + 손절선을 본전으로 이동 (위험 0). 나머지는 추세 따라.

class ScaleOutPosition:
    def __init__(self, entry, stop, target1_rr=1, target2_rr=3):
        self.entry = entry
        self.stop = stop
        risk = entry - stop
        self.target1 = entry + risk * target1_rr
        self.target2 = entry + risk * target2_rr
        self.scaled_out = False

    def check(self, current):
        if not self.scaled_out and current >= self.target1:
            self.stop = self.entry   # 손절선 본전 이동
            self.scaled_out = True
            return "PARTIAL_EXIT"   # 절반 청산
        if current >= self.target2:
            return "FULL_EXIT"
        if current <= self.stop:
            return "STOP_OUT"
        return None

장점: 일부 이익 확정 + 나머지로 큰 추세 따라가기. 알고랩이 의뢰 봇에 가장 많이 쓰는 패턴.

4. 트레일링 익절

트레일링 스탑과 동일하지만 익절 영역에서 작동. 큰 추세를 끝까지 따라감.

Part 5 — 다종목·다전략 분산

봇을 여러 개 돌리는 것은 분산이 아니라 곱셈일 수 있습니다.

상관관계 매트릭스 — 가짜 분산 피하기

"5종목 분산했어요" 했는데 모두 반도체 종목이면 분산이 아닙니다. 상관관계 0.7 이상이면 사실상 같은 자산.

import pandas as pd

def correlation_matrix(prices_df):
    """일일 수익률 기준 상관관계"""
    returns = prices_df.pct_change().dropna()
    return returns.corr()

# 예: 5종목 일봉 데이터
corr = correlation_matrix(df_5stocks)
print(corr)
# 0.7 이상은 사실상 동일 자산으로 취급

합산 리스크 한도

봇을 N개 운영해도 모든 봇의 한 거래 리스크 합이 자본의 5% 이내여야 합니다.

class PortfolioRiskCheck:
    def __init__(self, total_capital, max_portfolio_risk=5):
        self.total = total_capital
        self.max_risk = max_portfolio_risk
        self.open_positions = []   # 각 포지션의 risk_pct

    def can_open(self, new_risk_pct):
        current = sum(p["risk_pct"] for p in self.open_positions)
        return current + new_risk_pct <= self.max_risk

    def open(self, symbol, risk_pct):
        if self.can_open(risk_pct):
            self.open_positions.append({"symbol": symbol, "risk_pct": risk_pct})
            return True
        return False   # 합산 한도 초과

자산군 분산

분산 단계예시
1. 종목 분산한 섹터 안에 5종목 (약한 분산)
2. 섹터 분산반도체·금융·바이오 등 (중간 분산)
3. 자산군 분산주식·코인·선물·채권 (강한 분산)
4. 전략 분산추세 + 평균회귀 + 차익 (가장 강력)

Part 6 — 드로다운 관리

MDD가 한도를 넘으면 봇 자체를 멈춰야 합니다. Circuit Breaker.

드로다운 지표 3가지

지표의미경계값
MDD (Maximum Drawdown)역대 최대 낙폭-25% 이상 = 위험
Recovery Time고점 회복 기간3개월 이상 = 점검
Current Drawdown현재 진행 중인 낙폭-15% 이상 = 알림

Circuit Breaker — 자동 정지 패턴

일일·주간·월간 손실 한도를 미리 정해 자동으로 멈추게 합니다.

class CircuitBreaker:
    def __init__(self, daily=2, weekly=5, monthly=10):
        """단위는 % (자본 대비)"""
        self.daily = daily
        self.weekly = weekly
        self.monthly = monthly
        self.daily_pnl = 0
        self.weekly_pnl = 0
        self.monthly_pnl = 0

    def record_trade(self, pnl_pct):
        self.daily_pnl += pnl_pct
        self.weekly_pnl += pnl_pct
        self.monthly_pnl += pnl_pct

    def should_halt(self):
        if self.daily_pnl <= -self.daily:
            return "DAILY_LIMIT"
        if self.weekly_pnl <= -self.weekly:
            return "WEEKLY_LIMIT"
        if self.monthly_pnl <= -self.monthly:
            return "MONTHLY_LIMIT"
        return None

    def reset_daily(self):
        self.daily_pnl = 0
    # reset_weekly, reset_monthly 도 스케줄러로 호출

💡 Circuit Breaker 발동 시 행동: 단순히 신규 매매만 막는 것이 아니라, 관리자(사장님)에게 텔레그램 알림 + 1시간 자동 일시정지가 표준입니다. 1시간 후 자동 재개 또는 수동 점검 후 재개를 선택할 수 있게.

Part 7 — 통합 RiskManager 클래스

모든 봇에 그대로 붙여 쓰는 통합 골격. 매매 전 모든 체크를 한 줄로.

import time
from dataclasses import dataclass, field

@dataclass
class Position:
    symbol: str
    entry: float
    qty: float
    stop: float
    target: float
    entry_time: float

class RiskManager:
    """모든 봇에 그대로 붙이는 통합 리스크 관리 골격"""
    def __init__(self, capital, risk_per_trade=1, max_portfolio_risk=5,
                 daily_limit=2, weekly_limit=5):
        self.capital = capital
        self.risk_per_trade = risk_per_trade   # %
        self.max_portfolio_risk = max_portfolio_risk   # %
        self.positions = []
        self.cb = CircuitBreaker(daily_limit, weekly_limit, weekly_limit * 2)
        self.halted = False

    # ─── 진입 전 체크 ────────────────────────────────
    def can_enter(self, symbol, entry, stop):
        if self.halted:
            return False, "HALTED"
        # 1. 같은 종목 중복 진입 금지
        if any(p.symbol == symbol for p in self.positions):
            return False, "ALREADY_OPEN"
        # 2. 합산 리스크 한도
        current_risk = sum(self.risk_per_trade for _ in self.positions)
        if current_risk + self.risk_per_trade > self.max_portfolio_risk:
            return False, "PORTFOLIO_LIMIT"
        # 3. 손절폭이 0이면 진입 금지
        if abs(entry - stop) < 0.0001:
            return False, "INVALID_STOP"
        return True, "OK"

    # ─── 포지션 사이징 ──────────────────────────────
    def calc_qty(self, entry, stop):
        risk_amount = self.capital * (self.risk_per_trade / 100)
        return risk_amount / abs(entry - stop)

    # ─── 진입 ──────────────────────────────────────
    def enter(self, symbol, entry, stop, target):
        ok, reason = self.can_enter(symbol, entry, stop)
        if not ok:
            return None, reason
        qty = self.calc_qty(entry, stop)
        pos = Position(symbol, entry, qty, stop, target, time.time())
        self.positions.append(pos)
        return pos, "OK"

    # ─── 매 봉 체크 (손절·익절) ──────────────────────
    def check_exits(self, symbol, current_price):
        for pos in self.positions[:]:
            if pos.symbol != symbol:
                continue
            if current_price <= pos.stop:
                self._close(pos, current_price, "STOP")
                return "STOP"
            if current_price >= pos.target:
                self._close(pos, current_price, "TARGET")
                return "TARGET"
        return None

    def _close(self, pos, exit_price, reason):
        pnl = (exit_price - pos.entry) * pos.qty
        pnl_pct = pnl / self.capital * 100
        self.cb.record_trade(pnl_pct)
        self.positions.remove(pos)
        # 일일 한도 체크
        halt_reason = self.cb.should_halt()
        if halt_reason:
            self.halted = True
            print(f"[CIRCUIT BREAKER] {halt_reason} 발동 — 봇 일시정지")


# ─── 봇에서 사용 예 ────────────────────────────────
rm = RiskManager(capital=10_000_000, risk_per_trade=1,
                 max_portfolio_risk=5, daily_limit=2, weekly_limit=5)

# 진입 시
pos, reason = rm.enter("BTCUSDT", entry=60000, stop=58500, target=63000)
if pos:
    print(f"매수: {pos.qty:.4f} @ {pos.entry}")
else:
    print(f"진입 거부: {reason}")

# 매 봉 체크
rm.check_exits("BTCUSDT", current_price=63100)   # TARGET 청산

✅ 이 골격의 강점: 어떤 봇이든 이 클래스만 붙이면 (1) 1% 룰 자동 적용 (2) 같은 종목 중복 금지 (3) 합산 리스크 한도 (4) 손절·익절 자동 (5) Circuit Breaker — 5가지가 즉시 작동합니다. 알고랩에서 제작하는 모든 봇에 이 골격이 기본 포함됩니다.

Part 8 — 16단계 리스크 관리 체크리스트

새 봇 만들 때마다 따라가는 체크리스트.

포지션 사이징 (4단계)

손절·익절 (4단계)

다종목·분산 (4단계)

드로다운·Circuit Breaker (4단계)

자주 묻는 질문

Q. 리스크 관리는 왜 전략보다 중요한가요?

승률이 50%여도 손익비 2:1이면 장기 수익, 70%여도 1:3이면 망함. 또한 한 번의 -50% 손실은 +100% 수익이 필요한 비대칭. 큰 손실을 막는 게 큰 수익보다 100배 중요.

Q. 한 거래당 자본의 몇 %?

표준 1~2%. 1% 룰이면 연속 20번 손절 봐도 자본의 20%만 잃음.

Q. 켈리 공식 그대로 써도 되나요?

안 됩니다. 변동성이 너무 큼. Half-Kelly(1/2) 또는 Quarter-Kelly(1/4) 사용.

Q. 손절 규칙은 어떻게?

입문은 ATR 기반(k=2). 변동성 자동 반영. 강추세 종목은 트레일링 스탑.

Q. MDD -30%면 봇을 멈춰야 하나요?

회복 기간을 봐야 함. 1개월 안 회복은 정상, 3개월 이상이면 점검. Circuit Breaker로 자동 멈춤 설정 권장.

Q. 여러 봇 동시 운영 시 리스크는?

모든 봇 합산 리스크 ≤ 자본의 5%. 봇 개수 늘리는 게 분산이 아니라 곱셈일 수 있음. 상관관계 매트릭스 확인.

마무리

리스크 관리는 "잃지 않는 기술"입니다. 자동매매의 진짜 가치는 감정 없이 이 규칙들을 일관되게 실행하는 것입니다. 위 RiskManager 클래스는 알고랩이 모든 의뢰 봇에 기본 포함하는 골격이고, 한 번 익혀두면 어떤 전략에도 그대로 적용됩니다.

전략을 100개 시도하기보다, 이 8부 프레임워크를 1개 전략에 깊이 적용하는 것이 장기 수익으로 가는 정도입니다.

리스크 관리가 박혀 있는 봇으로

전략은 본인이 만들었거나 정해졌는데, 위 8부 리스크 관리를 직접 구현하기 부담스럽다면 알고랩이 RiskManager + Circuit Breaker + 텔레그램 알림이 통합된 봇을 1~3주 안에 제작해드립니다.
24시간 빠른 답변 가능합니다.

무료 상담 시작하기