什么是最大回撤?跌幅深度与恢复时间
了解最大回撤如何计算,区分下跌深度与恢复周期,并用账户从十二万美元跌至八万四千美元的例子判断30%回撤为何可能长期影响投资。
最大回撤是价值序列从峰值跌至谷值的最大跌幅,以当时滚动峰值的百分比表示。一个账户升至12万美元,随后跌至8.4万美元,之后又恢复,其最大回撤为30%。这一数字会永久保留,因为该指标记录的是历史上最差的一段时期,而不是账户当前的状态。多数人只引用回撤幅度,便不再深入。实际上,同一指标还包含另外两个时间维度:从峰值跌至谷值所用的时间,以及从谷值回升至创出新高所需的、通常长得多的时间。
如何计算最大回撤?
整个计算只需遍历一次序列。在每个观测点更新滚动峰值,将当前值与峰值进行比较,并保留截至当时出现的最差读数。
- 将滚动峰值设为第一个数值。
- 读取每个新数值时,如果该数值更高,就更新峰值。
- 计算该点的回撤:数值除以峰值,再减去一。
- 保留最负的读数。这就是最大回撤。
计算不需要收益率序列、波动率估计或任何分布假设。该循环只需使用 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步的结果,而不是将其压缩为一个最差值。序列创出新高时,曲线保持在零位;在两个新高之间,曲线则位于零线下方,因此这一差距被称为“水下”。下面是SPY的回撤曲线。SPY是一只跟踪标普500指数的交易所交易基金,数据为自2016年1月以来的月末收盘价。
每个数字背后的完整 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在127个月末数据中,曲线长时间贴近零位,并数次跌入深幅凹口。最新一个月的数据相对历史峰值为-1.27%。这一形态有两点值得注意。回升通常是缓慢爬升,而不是快速反弹;每次创出新高都会将曲线重置为零,从视觉上抹去此前的回撤记录。
需要多长时间才能从回撤中恢复?
一次回撤包含三个不同的数字。将它们混为一谈,是误读绩效表最常见的原因:
- 深度:价值较峰值下跌了多少,以百分比表示。
- 触底时间:从峰值到谷底的日历天数。
- 恢复时间:从谷底回升至创出新高所需的日历天数。
第三项通常最长,也是标题数字永远不会展示的部分。下方表格根据每日收盘价,重建了SPY自2016年以来每一段已经结束的水下区间。一段区间从创出峰值当天开始,到基金首次收盘突破该峰值当天结束。
每个数字背后的完整 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列表中最深的一段完整区间始于Feb 2020,较前期峰值低34.2%。该基金用了33个日历日下跌,随后用了148个日历日收复失地,总计181天处于水下。下一段始于Jan 2022,回撤较浅,为25.4%,但基金用了746天才回到此前高点之上。
深度和持续时间完全是两回事。急剧下跌后迅速收复失地,与持续数年的浅幅缓慢下跌,都会被压缩成一个百分比。2020年3月的暴跌是第一种形态的典型案例,而市场如何从暴跌中复苏则讲述了这段走势的第二阶段。
为什么测量区间越长,最大回撤越严重
最大回撤取的是最大值,因此增加历史数据只会让它保持不变或进一步加深。在更长区间内,任一时点的滚动峰值至少与较短区间内的峰值一样高,这决定了计算结果。以截至2026年7月31日的五个区间测量同一只基金,可以看到这一影响。
每个数字背后的完整 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在过去1 year区间内,测得的最大跌幅为9.1%。将同一组数据延长至10 years后,结果变为34.2%,最差点出现在Mar 2020。基金的风险并没有增加,只是样本区间变长了。
同样的计算方式也会扭曲策略比较。三年回测和二十年回测可能描述完全相同的行为,却仍会报告截然不同的回撤。因此,如果不注明测量区间,这一指标的意义就十分有限。回测中的前视偏差介绍了另一种让回测结果看起来更好的方式。
最大回撤与波动率
波动率衡量每日上涨或下跌的典型幅度。最大回撤衡量一段特定的下行路径。两者都是风险指标,但回答的是不同问题。该图将八只家喻户晓的股票并列比较,统计区间截至2026年7月31日的五年,并根据每日收盘价计算年化波动率。
每个数字背后的完整 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请结合两列数据进行比较。VZ的跌幅最深,最大回撤为45.4%,年化波动率为22.6%。跌幅最浅的是KO,最大回撤为20.9%,波动率为16.7%。波动率是统计区间内每个交易日的平均值,而平均值可能掩盖长期缓慢下跌。回撤则是价格实际经历的最差下行路径。一项价格走势平静但持续两年走低的资产,可能出现较深的最大回撤,但波动率读数并不突出。
低波动率异象考察低波动率股票在长期持有期间的表现,凯利准则仓位管理则说明如何将可承受的回撤幅度转化为仓位大小。
数字无法告诉您的内容
有四项局限值得牢记:
- 这是一次已经实现的路径。相同的过程在另一种走势下,可能产生糟糕得多的结果。
- 它取决于样本长度。因此,只有在观察窗口相同的情况下,两个回撤数据才具有可比性。
- 它取决于采样频率。在相同期间内,月末收盘价会掩盖盘中低点,因此计算出的回撤小于使用日收盘价计算的结果。
- 它不包含任何前瞻性信息。价格较峰值低30%,并不能说明第31个点是否会在明天出现。
正是最后一项局限,决定了自动化系统需要回撤政策,而不是单一的回撤阈值。“回撤达到20%时暂停”只回答了一个问题,却留下两个问题未解:暂停时如何处理未平仓头寸,以及什么证据可以让系统重新启动。交易机器人的熔断机制详细说明了这类政策应如何设计。任意设定的固定规则,其代价会出现在恢复阶段:如果系统在接近低点时停止、又在上涨开始后才重新启动,就会错过反弹行情。错过最佳交易日用数字说明了这种计算结果。
在比较两个回撤数据前,还需注意一个测量细节:只要有现金流入或流出,或者收到股息,基金价格序列的回撤就可能不同于实际账户的回撤;月度回报如何衡量对此有完整说明。
最大回撤常见问题
什么是合理的最大回撤?
不存在适用于所有情况的统一数值,只有可供比较的基准和衡量窗口。截至2026年7月31日的十年期间,SPY从运行高点回落的最大幅度,按日收盘价计算为 34.2%。如果一项策略在更短样本期内给出更小的回撤,并不一定意味着它更安全。
最大回撤与波动率相同吗?
不相同。波动率描述通常的日内变动幅度;最大回撤描述从峰值到谷值的单次最差路径。一项波动率较低的资产如果持续下跌两年,其回撤可能深于一项波动较大但不断创出新高的资产。
从最大回撤中恢复需要多长时间?
这取决于具体情况,而且回升过程往往占用更长时间。自2016年以来,SPY低于此前高点的最长完整阶段,从峰值到创出新高持续了 181 个日历日,其中 33 天处于下跌阶段。
最大回撤能预测未来损失吗?
不能。它描述的是已经发生的一条路径,而更长的样本期几乎总会产生更大的数值。它只能记录一项策略迄今为止经受过的回撤,无法限定未来可能出现的损失。
上方每个图表都保存了背后的 SQL。打开其中一个,替换 ticker 或时间窗口,然后在 Strasmore terminal 上运行相同的回撤计算。