Maksymalne obsunięcie: głębokość a czas odbudowy
Maksymalne obsunięcie pokazuje największy spadek od szczytu do dołka. Sprawdź, co oznacza głębokie obsunięcie i dlaczego powrót do nowego maksimum trwa dłużej.
Maksymalne obsunięcie to największy spadek wartości od szczytu do dołka, wyrażony jako odsetek dotychczasowego szczytu. Rachunek, którego wartość rośnie do 120 000 USD, następnie spada do 84 000 USD, a później się odbudowuje, odnotowuje maksymalne obsunięcie na poziomie 30%. Wartość ta pozostaje niezmienna, ponieważ statystyka rejestruje najgorszy okres w całej historii, a nie bieżący stan rachunku. Większość osób podaje głębokość obsunięcia i na tym poprzestaje. W tej samej statystyce zawarte są jednak jeszcze dwie wartości: czas od szczytu do dołka oraz znacznie dłuższy czas od dołka do ustanowienia nowego maksimum.
Jak oblicza się maksymalne obsunięcie?
Całe obliczenie przebiega w jednym przejściu po szeregu danych. Przy każdej obserwacji aktualizowane jest bieżące maksimum, następnie porównywana jest z nim aktualna wartość, a na końcu zapamiętywany jest najgorszy odczyt.
- Ustaw bieżący szczyt na pierwszą wartość.
- Przy każdej nowej wartości podnieś szczyt, jeśli wartość jest wyższa.
- Oblicz obsunięcie w danym punkcie: wartość podzielona przez szczyt minus jeden.
- Zachowaj najbardziej ujemny odczyt. Jest to maksymalne obsunięcie.
Nie są potrzebne szeregi stóp zwrotu, oszacowanie zmienności ani założenia dotyczące rozkładu. Pętla działa na zwykłej liście liczb z wykorzystaniem biblioteki standardowej Python. Na świeżo zainstalowanym systemie Ubuntu należy najpierw zainstalować interpreter:
apt-get update && apt-get install -y python3
Zapisz pętlę jako maxdd.py. Poniższe ceny tworzą przykładowy dziesięciopunktowy szereg. Został on celowo ograniczony, aby można było zweryfikować wynik ręcznie:
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}%")
Uruchom python3 maxdd.py. Wynik to maximum drawdown: -15.4%. Można sprawdzić go ręcznie: szczyt poprzedzający minimum wynosi 104, minimum wynosi 88, a 88 podzielone przez 104 daje 0,846. Następnie szereg kończy się na poziomie 106, czyli na historycznym maksimum, ale obsunięcie w wysokości 15,4% pozostaje bez zmian. Maksymalne obsunięcie jest trwałym zapisem najgorszego momentu w próbie i nie informuje, gdzie szereg znajduje się obecnie. Dwie dodatkowe zmienne w tej samej pętli pozwalają ustalić moment wystąpienia tych zdarzeń: indeks, przy którym ustanowiono szczyt, oraz indeks, przy którym odnotowano najgorszy odczyt.
Jak wygląda krzywa obsunięcia
Krzywa obsunięcia zachowuje krok 3 dla każdego punktu, zamiast sprowadzać go do jednej najniższej wartości. Linia znajduje się na poziomie zera za każdym razem, gdy seria wyznacza nowy szczyt, a pomiędzy nimi spada poniżej zera. Dlatego ten obszar określa się jako „underwater”. Poniżej przedstawiono tę krzywą dla SPY, funduszu ETF odwzorowującego indeks S&P 500, na podstawie cen zamknięcia z końca miesiąca od stycznia 2016 roku.
Dokładny kod SQL dla każdej liczby
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_startW całym szeregu obejmującym 127 odczytów z końca miesiąca linia przez długie okresy utrzymuje się w pobliżu zera, po czym spada do kilku głębokich dołków. W ostatnim analizowanym miesiącu znajdowała się -1.27% poniżej bieżącego szczytu. Dwa elementy tej struktury dobrze pokazują mechanizm. Odbudowa przebiega stopniowo, a nie w formie gwałtownych odbić. Każdy nowy szczyt sprowadza krzywą z powrotem do zera i usuwa z wykresu wizualny ślad wcześniejszego obsunięcia.
Ile trwa odrobienie straty po obsunięciu?
W jednym obsunięciu zawarte są trzy odrębne liczby. Traktowanie ich jako jednej wartości jest najczęstszą błędną interpretacją tabeli wyników:
- Głębokość: procentowy spadek wartości poniżej szczytu.
- Czas do minimum: liczba dni kalendarzowych od szczytu do dołka.
- Czas odrobienia straty: liczba dni kalendarzowych od dołka do ustanowienia nowego maksimum.
Trzeci parametr jest zwykle najdłuższy. To właśnie jego nie pokazuje pojedyncza wartość nagłówkowa. Poniższe zestawienie odtwarza każde zakończone obsunięcie SPY od 2016 r. na podstawie dziennych cen zamknięcia. Obsunięcie zaczyna się w dniu ustanowienia szczytu i kończy w dniu, w którym fundusz po raz pierwszy zamyka notowania powyżej tego poziomu.
Dokładny kod SQL dla każdej liczby
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 8Najgłębsze zakończone obsunięcie na liście rozpoczęło się w Feb 2020 i sięgnęło 34.2% poniżej poprzedniego szczytu. Spadek trwał 33 dni kalendarzowych, a powrót do wcześniejszego poziomu 148 dni. Łącznie fundusz pozostawał poniżej szczytu przez 181 dni. Kolejne obsunięcie, z Jan 2022, było płytsze i wyniosło 25.4%. Fundusz pozostawał jednak poniżej wcześniejszego maksimum przez 746 dni.
Głębokość i czas trwania mogą całkowicie się rozbiegać. Gwałtowny spadek z szybkim powrotem oraz płytki, wieloletni trend spadkowy zostają sprowadzone do jednej wartości procentowej. Krach z marca 2020 r. jest podręcznikowym przykładem pierwszego scenariusza, natomiast sposób odrabiania strat po krachach pokazuje drugi etap tego procesu.
Dlaczego maksymalne obsunięcie pogłębia się wraz z wydłużeniem okresu pomiaru
Maksymalne obsunięcie jest wartością maksymalną, dlatego dodanie danych historycznych może jedynie pozostawić je bez zmian albo zwiększyć jego głębokość. Bieżące maksimum w dłuższym okresie jest w każdym punkcie co najmniej tak wysokie jak maksimum w okresie krótszym, co determinuje wynik obliczeń. Pomiar tego samego funduszu w pięciu okresach kończących się 31 lipca 2026 r. pokazuje ten efekt.
Dokładny kod SQL dla każdej liczby
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_yearsW kroczącym okresie 1 year najgłębszy spadek wyniósł 9.1%. Po wydłużeniu identycznego szeregu danych do 10 years wynik wynosi 34.2%, a najgłębszy punkt przypadł na Mar 2020. Fundusz nie stał się bardziej ryzykowny. Wydłużono jedynie próbę.
Ta sama arytmetyka zniekształca porównania strategii. Backtest obejmujący trzy lata i backtest obejmujący dwadzieścia lat mogą opisywać identyczne zachowanie, a mimo to wykazywać bardzo różne maksymalne obsunięcia. Dlatego sama wartość niewiele znaczy bez wskazania okresu pomiaru. Błąd wyprzedzenia w backtestingu opisuje inny sposób, w jaki pozornie przejrzysty backtest może zawyżać własną atrakcyjność.
Maksymalne obsunięcie a zmienność
Zmienność mierzy typową skalę dziennego ruchu w dowolnym kierunku. Maksymalne obsunięcie mierzy konkretną ścieżkę spadkową. Oba wskaźniki opisują ryzyko, ale odpowiadają na różne pytania. Zestawienie pokazuje je obok siebie dla ośmiu powszechnie znanych spółek za pięć lat do 31 lipca 2026 roku; zmienność annualizowano na podstawie dziennych kursów zamknięcia.
Dokładny kod SQL dla każdej liczby
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 DESCObie kolumny należy analizować łącznie. VZ odnotował w tej grupie najgłębszy spadek, wynoszący 45.4%, przy annualizowanej zmienności na poziomie 22.6%. Na drugim krańcu KO spadł o 20.9%, przy zmienności wynoszącej 16.7%. Zmienność jest średnią dla każdego dnia w analizowanym okresie, a średnie mogą nie uwzględniać długotrwałego, powolnego spadku. Obsunięcie pokazuje najgorszą ścieżkę, którą faktycznie przebył kurs. Spółka o niskiej zmienności, której kurs stopniowo spada przez dwa lata, może odnotować głębokie obsunięcie przy pozornie zwyczajnym odczycie zmienności.
Anomalia niskiej zmienności analizuje zachowanie spokojnych spółek w długich horyzontach, a Dobór wielkości pozycji według kryterium Kelly’ego pokazuje, jak tolerancja obsunięcia przekłada się na wielkość pozycji.
Czego ta liczba nie pokazuje
Warto pamiętać o czterech ograniczeniach:
- Jest to jedna zrealizowana ścieżka. Ten sam proces mógłby przy innym przebiegu doprowadzić do znacznie gorszego wyniku.
- Zależy od długości próby. Dlatego dwa obsunięcia można porównywać wyłącznie w odpowiadających sobie oknach czasowych.
- Zależy od częstotliwości próbkowania. Kursy zamknięcia na koniec miesiąca pomijają minima śróddzienne i dają niższą wartość niż kursy zamknięcia z poszczególnych dni w tym samym okresie.
- Nie zawiera żadnej informacji o przyszłości. Spadek o 30% od szczytu nie mówi, czy punkt odpowiadający 31% zostanie osiągnięty jutro.
To ostatnie ograniczenie sprawia, że system automatyczny potrzebuje zasad postępowania przy obsunięciu, a nie tylko progu obsunięcia. „Zatrzymaj przy 20%” odpowiada na jedno pytanie, ale pozostawia dwa bez odpowiedzi: co dzieje się z otwartymi pozycjami po zatrzymaniu oraz jakie przesłanki umożliwiają ponowne uruchomienie systemu. Wyłączniki bezpieczeństwa dla botów tradingowych omawia strukturę takich zasad. Koszt arbitralnej, sztywnej reguły pojawia się na etapie odrabiania strat. System, który zatrzymuje się w pobliżu minimum i uruchamia z opóźnieniem, nie uczestniczy w odbiciu. Utracone najlepsze sesje przedstawia skalę tego efektu.
Przed porównaniem dwóch wartości należy uwzględnić jeden szczegół pomiaru. Obsunięcie obliczone na podstawie notowań funduszu i obsunięcie na rzeczywistym rachunku będą się różnić, gdy na rachunek wpływa lub z niego wypływa gotówka oraz gdy księgowane są dywidendy. Zagadnienie to szczegółowo opisano w sposobie pomiaru miesięcznych stóp zwrotu.
FAQ: maksymalne obsunięcie
Jakie maksymalne obsunięcie można uznać za akceptowalne?
Nie istnieje uniwersalna wartość. Potrzebne są punkt odniesienia i okres pomiaru. W ciągu dziesięciu lat do 31 lipca 2026 r. największy spadek SPY od lokalnego szczytu, mierzony na podstawie dziennych kursów zamknięcia, wyniósł 34.2%. Strategia, dla której w krótszej próbie odnotowano mniejsze obsunięcie, nie musi być bezpieczniejsza.
Czy maksymalne obsunięcie jest tym samym co zmienność?
Nie. Zmienność opisuje typową dzienną zmianę kursu. Maksymalne obsunięcie opisuje najgorszą pojedynczą ścieżkę od szczytu do dołka. Aktywo o niskiej zmienności, które przez dwa lata systematycznie traci na wartości, może odnotować większe obsunięcie niż aktywo o gwałtownych wahaniach, które nadal ustanawia nowe maksima.
Ile czasu zajmuje odrobienie maksymalnego obsunięcia?
To zależy od przypadku. Powrót do wcześniejszego szczytu często trwa dłużej niż sam spadek. W przypadku SPY od 2016 r. najdłuższy zakończony okres poniżej wcześniejszego maksimum trwał od szczytu do ustanowienia nowego maksimum przez 181 dni kalendarzowych, z czego 33 dni przypadło na spadki.
Czy maksymalne obsunięcie pozwala prognozować przyszłe straty?
Nie. Opisuje ono jedną ścieżkę, która już się wydarzyła. Dłuższy okres pomiaru niemal zawsze prowadzi do większej wartości. Wskaźnik pokazuje, co strategia przetrwała do tej pory, ale nie wyznacza górnej granicy przyszłych strat.
Każdy z powyższych paneli przechowuje zapytanie SQL, na którym się opiera. Należy otworzyć wybrany panel, zmienić ticker lub okres i uruchomić te same obliczenia obsunięcia w terminalu Strasmore.