Strasmore Research
학습 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-05

최대 낙폭이란? 하락 깊이와 회복 기간

최대 낙폭은 고점에서 저점까지의 최대 하락폭입니다. 낙폭의 깊이뿐 아니라 저점에서 신고점까지 회복에 걸리는 시간이 더 길어지는 이유도 설명합니다.

최대 낙폭(maximum drawdown)은 가치 시계열이 고점에서 저점까지 하락한 최대 폭을 진행 중인 고점 대비 백분율로 측정한 것이다. 120,000달러까지 상승한 계좌가 84,000달러까지 하락한 뒤 회복했다면 최대 낙폭은 30%다. 이후 계좌가 회복하더라도 이 수치는 변하지 않는다. 현재 상태가 아니라 과거 전체 기간 중 가장 큰 하락 구간을 기록하는 통계이기 때문이다. 대부분의 투자자는 낙폭의 깊이만 언급하고 넘어간다. 그러나 같은 통계에는 두 가지 시간이 더 포함된다. 고점에서 저점까지 걸린 시간과, 저점에서 다시 신고점에 도달하기까지 걸린 훨씬 긴 시간이다.

최대 낙폭은 어떻게 계산하는가?

전체 계산은 시계열을 한 번 순회하는 방식으로 수행한다. 각 관측값에서 누적 고점을 갱신하고, 현재 값을 누적 고점과 비교해 낙폭을 계산한다. 그중 지금까지 가장 큰 낙폭을 기록으로 남긴다.

  1. 누적 고점을 첫 번째 값으로 설정한다.
  2. 새로운 값이 들어올 때마다 해당 값이 더 높으면 고점을 갱신한다.
  3. 해당 시점의 낙폭을 계산한다. 값은 고점으로 나눈 뒤 1을 뺀다.
  4. 가장 작은 값을 기록한다. 이것이 최대 낙폭이다.

수익률 시계열이나 변동성 추정치, 분포 가정은 계산에 필요하지 않다. Python 표준 라이브러리만으로 숫자 목록을 순회하면 된다. 새로 설치한 Ubuntu 환경에서는 먼저 인터프리터를 설치한다.

apt-get update && apt-get install -y python3

이 반복문을 maxdd.py로 저장한다. 아래 가격은 손으로 확인할 수 있도록 작게 구성한 열 개 값의 예시 시계열이다.

prices = [100.0, 104.0, 98.0, 92.0, 95.0, 88.0, 90.0, 101.0, 99.0, 106.0]

peak = prices[0]
max_dd = 0.0

for price in prices:
    if price > peak:
        peak = price
    drawdown = price / peak - 1.0
    if drawdown < max_dd:
        max_dd = drawdown

print(f"maximum drawdown: {max_dd * 100:.1f}%")

python3 maxdd.py를 실행하면 maximum drawdown: -15.4%이 출력된다. 손으로 확인해 보자. 저점 직전의 고점은 104이고 저점은 88이다. 88을 104로 나누면 0.846이 된다. 이후 시계열은 사상 최고치인 106에서 끝나지만, 15.4%라는 최대 낙폭 기록은 그대로 남는다. 최대 낙폭은 표본에서 발생한 최악의 순간을 영구적으로 기록하는 지표다. 현재 시계열이 어디에 위치하는지는 보여주지 않는다. 같은 반복문에 변수 두 개를 추가하면 시점을 포착할 수 있다. 하나는 고점이 설정된 인덱스이고, 다른 하나는 최악의 낙폭이 발생한 인덱스다.

낙폭 곡선의 형태

낙폭 곡선은 각 시점에서 3단계를 하나의 최악값으로 축약하지 않고 그대로 유지한다. 시계열이 새 고점을 기록할 때마다 선은 0에 위치한다. 그 사이에는 0 아래로 내려간다. 이 때문에 그 간극을 “수중(underwater)” 구간이라고 부른다. 다음은 2016년 1월 이후 월말 종가 기준으로 S&P 500을 추종하는 ETF인 SPY의 낙폭 곡선이다.

조회SPY 수중 곡선: 2016~2026년 월말 종가와 직전 고점 비교
모든 수치 뒤에 숨겨진 정확한 SQL
WITH monthly AS (
    SELECT toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2016-01-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY month_start
),
runs AS (
    SELECT month_start,
           close_px,
           max(close_px) OVER (ORDER BY month_start
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_peak
    FROM monthly
)
SELECT formatDateTime(month_start, '%Y-%m') AS month,
       round(100 * (close_px / running_peak - 1), 2) AS drawdown_pct
FROM runs
ORDER BY month_start
Run this yourself

127개의 월말 관측치에서 이 선은 긴 기간 동안 0 부근에 머물다가 몇 차례 깊게 패인 구간으로 내려간다. 기록상 마지막 월의 수치는 누적 고점 대비 -1.27%이다. 이 곡선의 형태는 두 가지 특징을 보여준다. 회복은 급격한 반등보다 완만한 상승으로 나타난다. 또한 새로운 고점을 기록할 때마다 곡선이 0으로 재설정되면서 이전 상황의 시각적 흔적이 사라진다.

하락폭에서 회복하는 데 얼마나 걸리는가?

하나의 하락폭에는 서로 다른 세 가지 수치가 포함된다. 이를 하나로 취급하는 것이 성과표를 가장 흔히 잘못 해석하는 방식이다.

  1. 하락폭: 고점 대비 가치가 얼마나 하락했는지를 나타내는 비율이다.
  2. 저점까지 걸린 시간: 고점에서 저점까지의 달력 일수다.
  3. 회복 기간: 저점에서 다시 신고점을 기록할 때까지의 달력 일수다.

세 번째 수치가 대체로 가장 길다. 그러나 헤드라인 수치에는 이 기간이 나타나지 않는다. 아래 패널은 2016년 이후 SPY의 일일 종가를 기준으로, 종료된 모든 하락 구간을 재구성한 것이다. 하락 구간은 고점이 형성된 날 시작하며, 펀드가 해당 고점을 처음으로 웃도는 종가를 기록한 날 종료된다.

조회2016년 이후 완료된 SPY 낙폭: 깊이, 하락 일수, 회복 일수
모든 수치 뒤에 숨겨진 정확한 SQL
WITH daily AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2016-01-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY session_date
),
runs AS (
    SELECT session_date,
           close_px,
           max(close_px) OVER (ORDER BY session_date
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_peak
    FROM daily
),
episodes AS (
    SELECT running_peak AS peak_px,
           min(session_date) AS peak_date,
           argMin(session_date, close_px) AS trough_date,
           min(close_px) AS trough_px
    FROM runs
    GROUP BY running_peak
),
sequenced AS (
    SELECT peak_px,
           peak_date,
           trough_date,
           trough_px,
           leadInFrame(peak_date, 1) OVER (ORDER BY peak_date
                                           ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS recovery_date
    FROM episodes
)
SELECT formatDateTime(peak_date, '%b %Y') AS episode_label,
       round(100 * (1 - trough_px / peak_px), 1) AS fall_from_peak_pct,
       dateDiff('day', peak_date, trough_date) AS days_to_trough,
       dateDiff('day', trough_date, recovery_date) AS days_to_new_high,
       dateDiff('day', peak_date, recovery_date) AS days_underwater
FROM sequenced
WHERE recovery_date > trough_date
  AND round(100 * (1 - trough_px / peak_px), 1) >= 5
ORDER BY fall_from_peak_pct DESC
LIMIT 8
Run this yourself

목록에서 가장 깊었던 종료 하락 구간은 Feb 2020에 시작해 이전 고점보다 34.2% 낮은 수준까지 하락했다. 하락하는 데 33일이 걸렸고 회복하는 데 148일이 걸렸다. 총 181일 동안 고점 아래에 머물렀다. 다음 항목은 Jan 2022에 발생했으며, 하락폭은 25.4%로 더 작았다. 그러나 펀드는 746일 동안 이전 고점을 회복하지 못했다.

하락폭과 기간은 전혀 별개의 개념이다. 급락 후 빠르게 원래 수준으로 돌아오는 경우와, 완만하게 하락하며 수년간 이어지는 경우가 모두 하나의 비율로 압축된다. 2020년 3월 폭락은 전자의 전형적인 사례다. 시장이 폭락에서 회복하는 과정은 그 여정의 두 번째 단계에 해당한다.

측정 기간이 길어질수록 최대 낙폭이 커지는 이유

최대 낙폭은 최댓값이므로 과거 데이터를 추가하면 기존 수준에 머물거나 더 깊어질 수밖에 없다. 더 긴 기간에서는 각 시점의 누적 고점이 더 짧은 기간의 고점보다 적어도 같거나 높다. 따라서 산술적으로 최대 낙폭은 줄어들 수 없다. 2026년 7월 31일에 모두 종료되는 다섯 개 기간으로 동일한 펀드를 측정하면 이 효과를 확인할 수 있다.

조회동일 펀드, 5개 룩백 기간: 2026년 7월 31일까지 표본 기간별 SPY 최대 낙폭
모든 수치 뒤에 숨겨진 정확한 SQL
WITH daily AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2016-07-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY session_date
),
scoped AS (
    SELECT arrayJoin([1, 2, 3, 5, 10]) AS lookback_years,
           session_date,
           close_px
    FROM daily
),
windowed AS (
    SELECT lookback_years, session_date, close_px
    FROM scoped
    WHERE session_date >= subtractYears(toDate('2026-07-31'), lookback_years)
),
runs AS (
    SELECT lookback_years,
           session_date,
           close_px,
           max(close_px) OVER (PARTITION BY lookback_years ORDER BY session_date
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_peak
    FROM windowed
)
SELECT concat(toString(lookback_years), if(lookback_years = 1, ' year', ' years')) AS lookback,
       round(100 * max(1 - close_px / running_peak), 1) AS max_drawdown_pct,
       formatDateTime(argMax(session_date, 1 - close_px / running_peak), '%b %Y') AS worst_point
FROM runs
GROUP BY lookback_years
ORDER BY lookback_years
Run this yourself

최근 1 year 기간에서 측정한 최악의 하락폭은 9.1%였다. 동일한 시계열을 10 years까지 확장하면 수치는 34.2%이 되며, 최악의 시점은 Mar 2020에 나타난다. 펀드의 위험이 커진 것은 아니다. 표본 기간이 길어진 결과다.

같은 산술적 특성은 전략 비교도 왜곡한다. 3년 백테스트와 20년 백테스트가 동일한 움직임을 설명하면서도 매우 다른 낙폭을 보고할 수 있다. 따라서 측정 기간을 함께 제시하지 않으면 이 수치는 의미가 제한적이다. 백테스트의 미래정보 편향에서는 겉보기에는 깔끔한 백테스트가 스스로를 과대평가하는 또 다른 방식을 다룬다.

최대 낙폭과 변동성 비교

변동성은 하루 동안 어느 방향으로든 발생하는 일반적인 가격 변동 폭을 측정한다. 최대 낙폭은 특정한 하락 경로 하나를 측정한다. 둘 다 위험을 나타내는 수치지만, 서로 다른 질문에 답한다. 이 표는 2026년 7월 31일까지 5년간 주요 대형주 8개의 변동성과 최대 낙폭을 나란히 제시한다. 변동성은 일별 종가를 기준으로 연율화했다.

조회연환산 변동성 대비 최대 낙폭: 2026년 7월 31일까지 5년간 대형주 8종
모든 수치 뒤에 숨겨진 정확한 SQL
WITH daily AS (
    SELECT ticker,
           toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('SPY', 'AAPL', 'MSFT', 'KO', 'JNJ', 'CVX', 'VZ', 'PG')
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2021-08-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY ticker, session_date
),
runs AS (
    SELECT ticker,
           session_date,
           close_px,
           max(close_px) OVER (PARTITION BY ticker ORDER BY session_date
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_peak,
           lagInFrame(close_px, 1) OVER (PARTITION BY ticker ORDER BY session_date
                                         ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
    FROM daily
)
SELECT ticker,
       round(100 * max(1 - close_px / running_peak), 1) AS max_drawdown_pct,
       round(100 * sqrt(252) * stddevSampIf(close_px / prev_close - 1, prev_close > 0), 1) AS annualized_volatility_pct
FROM runs
GROUP BY ticker
HAVING countIf(prev_close > 0) > 20
ORDER BY max_drawdown_pct DESC
Run this yourself

두 열을 함께 살펴봐야 한다. VZ는 연율화 변동성 22.6%를 기록하면서 그룹 내 최대 낙폭이 45.4%로 가장 컸다. 반대로 하락 폭이 가장 작았던 KO20.9% 하락했고 변동성은 16.7%였다. 변동성은 해당 기간의 모든 일별 변동을 평균한 값이다. 따라서 장기간에 걸친 완만한 하락은 평균값에 희석될 수 있다. 낙폭은 실제 가격이 지나온 경로 중 최악의 단일 경로를 나타낸다. 2년 동안 조용히 하락한 종목은 변동성이 두드러지지 않아도 큰 낙폭을 기록할 수 있다.

저변동성 이상현상은 장기간에 걸쳐 변동성이 낮은 종목이 어떤 성과를 보이는지 살펴본다. 켈리 기준 포지션 규모 산정은 허용 가능한 낙폭을 포지션 규모로 전환하는 방법을 보여준다.

숫자가 말해주지 못하는 것

다음 네 가지 한계를 기억할 필요가 있다.

  1. 이는 실현된 하나의 경로에 불과하다. 동일한 프로세스라도 다른 무작위 표본에서는 훨씬 더 나쁜 수치가 나올 수 있다.
  2. 표본 길이에 좌우된다. 따라서 두 낙폭은 동일한 기간을 기준으로 비교해야 한다.
  3. 표본 추출 빈도에 좌우된다. 월말 종가는 장중 저점을 반영하지 않으므로, 동일한 기간의 일별 종가보다 작은 수치를 산출한다.
  4. 미래에 관한 정보는 전혀 제공하지 않는다. 고점보다 30% 낮은 상태라는 사실만으로 31번째 지점이 내일 도달할지는 알 수 없다.

자동화 시스템에 낙폭 임계값이 아니라 낙폭 정책이 필요한 이유도 마지막 한계에 있다. “20%에서 중단”은 한 가지 질문에 답할 뿐이다. 중단 시점에 열린 포지션을 어떻게 처리할지, 그리고 어떤 근거가 충족되면 시스템을 재가동할지는 답하지 않는다. 트레이딩 봇의 서킷 브레이커는 이러한 정책의 구체적인 구조를 다룬다. 임의의 고정 규칙을 적용했을 때의 비용은 회복 구간에서 발생한다. 시스템이 저점 부근에서 중단된 뒤 늦게 재가동되면 상승 구간을 놓치기 때문이다. 최고의 상승일을 놓치는 문제에서는 이러한 산술적 결과를 수치로 제시한다.

두 수치를 비교하기 전에 한 가지 측정상의 세부 사항도 확인해야 한다. 현금이 유입되거나 유출되고 배당금이 지급될 때마다 펀드 가격 시계열의 낙폭과 실제 계좌의 낙폭은 달라진다. 월간 수익률을 측정하는 방법에서 이 차이를 자세히 설명한다.

최대 낙폭 FAQ

적정한 최대 낙폭은 어느 정도인가?

보편적으로 적용되는 수치는 없다. 비교 기준과 측정 기간을 함께 정해야 한다. 2026년 7월 31일까지 10년 동안 SPY가 직전 고점 대비 기록한 최대 하락폭은 일일 종가 기준으로 34.2%였다. 더 짧은 기간을 대상으로 더 작은 수치를 제시하는 전략이 반드시 더 안전한 것은 아니다.

최대 낙폭은 변동성과 같은가?

그렇지 않다. 변동성은 일반적인 일일 가격 움직임을 나타낸다. 최대 낙폭은 고점에서 저점까지 이어진 경로 중 가장 큰 하락을 나타낸다. 변동성이 낮은 자산이라도 2년 동안 꾸준히 하락하면, 등락은 크지만 계속 신고점을 경신하는 자산보다 낙폭이 더 클 수 있다.

최대 낙폭에서 회복하는 데 얼마나 걸리는가?

상황에 따라 다르다. 회복 과정이 전체 기간의 더 긴 부분을 차지하는 경우도 많다. 2016년 이후 SPY가 이전 고점 아래에 머문 기간 중 완료된 최장 구간은 고점에서 신고점 회복까지 181일이었으며, 이 중 33일은 하락 구간이었다.

최대 낙폭이 미래의 손실을 예측하는가?

그렇지 않다. 최대 낙폭은 이미 발생한 한 가지 경로를 설명할 뿐이다. 측정 기간이 길어질수록 일반적으로 더 큰 수치가 나온다. 이는 해당 전략이 지금까지 견뎌낸 손실을 기록한 것이며, 앞으로 발생할 손실의 상한을 의미하지 않는다.


위의 모든 패널에는 이를 산출한 SQL이 저장되어 있다. 패널을 열고 ticker 또는 측정 기간을 바꾼 뒤, Strasmore terminal에서 동일한 낙폭 산식을 실행할 수 있다.

#risk#drawdown#backtesting#performance metrics#python