最大回撤:从数字到体感
两份报告摆在你面前。年化收益都是 20%,其中一份写着「最大回撤 12%」,另一份写着「最大回撤 45%」。几乎所有人都会脱口而出选前面那个。
问题是,你真的知道那个 45% 描述的是什么吗?
它不是「这套东西亏了 45%」。它也不是「平均每年会跌 45%」。它甚至跟「风险有多大」不是同一个意思。它是一句非常具体、非常窄的陈述:在过去这段历史里,有那么一段日子,账户从某个历史最高点一路往下掉,最深的时候比那个高点低了 45%。 只有这一句,多一个字都没有。
这个数字最麻烦的地方在于,它是量化报告里少数几个能直接翻译成体感的指标——可也正因为它太好懂了,几乎没人去追究它是怎么算出来的、以及它悄悄漏掉了什么。
先说人话:它记的是「离你最风光的时候差多远」
想象你在爬一座山,一边爬一边记录自己到过的最高海拔。这个记录只涨不跌——你爬到 800 米,它就是 800;你后来下到 600 米,它还是 800,因为你确实到过 800。
现在随时问自己一个问题:我现在比我到过的最高点低了多少?在 600 米的时候,答案是 200 米。这个「比自己历史最高点低多少」的差值,就是回撤。整段路走完,这个差值最大的那一刻是多少,就是最大回撤。
注意这里的两个关键词。第一,参照物不是起点,是你自己到过的最高点。你从山脚爬到 600 米,虽然一直在往上,回撤是零——因为你没有比自己更高过。第二,这个最高点会被不断刷新,但从不后退。它像一条只往上走的棘轮,卡住了就再也不松开。
换成账户的说法:假设你的账户从 100 万涨到 150 万,然后跌到 105 万,再涨回 160 万。你亏钱了吗?从头到尾看,你赚了 60 万。可中间那一段,你眼睁睁看着 150 万变成 105 万,账面上少了 45 万——这 30% 的下滑,就是那段时间里你真正经历的东西。最大回撤记录的正是这种「你曾经拥有过、又眼看着消失」的感觉,而不是最终的盈亏。
为什么这个视角重要?因为人对钱的痛感,参照物从来不是买入成本,而是账户到过的最高数字。150 万在你脑子里已经落地生根了,跌回 105 万时你不会想「我还赚 5 万」,你只会想「我亏了 45 万」。最大回撤之所以比别的风险指标更贴近体感,就是因为它用的参照系跟你脑子里的参照系是同一个。
投资层:它比波动率更能说明什么问题
量化报告里还有另一个常用的风险数字,叫波动率——大致就是「收益上下摆动的剧烈程度」,用统计上的标准差来量。这两个数经常被摆在一起,但它们回答的根本不是同一个问题。
波动率不区分方向。 它把上涨的剧烈和下跌的剧烈一视同仁地算进去。一套连续几个月疯狂上涨的东西,波动率会很高,可你一点都不难受。反过来,一套慢慢阴跌、每天只跌一点点的东西,波动率可能不高,但一年下来能让你怀疑人生。用一个不分方向的数去描述只在下跌时才出现的痛感,本身就有点错位。
波动率不记路径。 它是把所有收益打散、算离散程度,跟这些收益出现的先后顺序无关。你把同一串收益随机打乱顺序,波动率一分不变,可最大回撤能天差地别——因为把所有坏日子挪到一起,坑就深了。而现实中的资金曲线,恰恰是有顺序的。你会不会在半路被迫离场、会不会在最深的位置割肉,取决于坏日子怎么排队,而不取决于它们的离散程度。
波动率背后有个分布假设,最大回撤没有。 波动率这个数要好用,暗含着「收益大致围绕一个中心上下摆动、极端值不算太离谱」这类前提。而最大回撤不假设任何东西——它就是从这段历史里直接挑出来的一个真实事件,是曾经发生过的事,不是推算出来的概率。
所以两者的分工大致是:波动率描述「平常的颠簸程度」,最大回撤描述「最惨的那一次有多惨」。对你的决策来说,后者往往更硬——因为让人放弃一套方法的,从来不是平常的颠簸,而是最惨的那一段。想把这两类数字放到一条曲线上一起读,可以看 怎么看一条收益曲线;如果你在意的是「只惩罚往下的那部分波动」,那是 下行风险 这一支在处理的事。
顺带说一句,回撤的深度和「爬回来要多难」不是同一件事,且后者非线性地更狠——亏 50% 要涨 100% 才回本这条数学,亏损的不对称数学 那篇讲得很透,这里就不重复了。本篇只管「这个数字在系统里怎么算出来、怎么用」。
系统层:三行代码和它们背后的分歧
到了真实系统里,最大回撤的算法核心短得让人意外。翻遍几个成熟框架,骨架都是同一个:
peak = equity.cummax() # 滚动峰值:到当前为止的历史最高
dd = equity / peak - 1.0 # 每一时刻离历史最高有多远(负数)
max_dd = dd.min() # 整段里最深的那个
cummax 就是前面说的那根棘轮——它逐点扫过去,永远记着「到目前为止见过的最大值」。第二行算出的是一条全程为负、只在创新高时短暂归零的曲线,业内管它叫水下曲线(underwater curve):曲线贴着零线走,说明你正在或刚刚创新高;曲线沉在下面,说明你还没爬回上一个高点。最大回撤,就是这条水下曲线最深的那一点。
三行代码看着没什么可争的,但真正的分歧全在「equity 这一列到底是什么」上。
分歧一:算术累计还是复利累计
qlib 的风险分析函数里,回撤有两种模式,写得很直白。一种把每期收益直接相加:
max_drawdown = (r.cumsum() - r.cumsum().cummax()).min()
另一种先把收益连乘成一条复利曲线,再取比例回撤:
cumulative_curve = (1 + r).cumprod()
max_drawdown = (cumulative_curve / cumulative_curve.cummax() - 1).min()
前者算出来的是「累计收益率的绝对下滑」,后者算出来的是「账户市值的百分比下滑」。同一串收益,这两个数不一样,而且历史越长、复利效应越明显,差得越远。报告上都印着「最大回撤」四个字,你不看代码根本不知道拿到的是哪一个。
分歧二:参照物是账户还是收益率
freqtrade 把这件事拆得更细。它的回撤序列同时算两列:一列是纯粹的收益累计差值,另一列在给定账户起始资金的情况下,用「峰值时的账户余额」当分母:
cumulative_balance = starting_balance + max_drawdown_df["cumulative"]
max_balance = starting_balance + max_drawdown_df["high_value"]
max_drawdown_df["drawdown_relative"] = (max_balance - cumulative_balance) / max_balance
这一列被命名为「相对回撤」,也是账户层面真正有意义的那个口径——它回答的是「你的钱缩水了百分之几」。而当起始资金缺失时,代码里退回到用累计收益本身当分母,注释很坦白地写着这不完全准确,只是「够用」。这种坦白值得留意:一个指标在数据不全时会悄悄换算法,而报告上的名字不会变。
它的回撤结果对象里除了最大回撤,还单独存了一组「当前回撤」——你此刻离历史最高点有多远。这个数在实盘里比最大回撤有用得多:最大回撤是已经发生完的历史,当前回撤是你正在承受的现实。
分歧三:它还能不是一个统计量,而是一个开关
前面讲的都是「事后算个数」。freqtrade 里最大回撤还有另一重身份——一个真的会拦住你下单的保护机制。它的逻辑是:往回看一段时间窗口内的交易,算出这段的回撤,一旦超过设定的上限,就直接锁住,停止开新仓,等一段时间再说。
有意思的是这个插件提供了两种计算模式,而当前的默认值是「ratios」这个旧口径(可配置)。上游文档对这两种模式的说明大意是:ratios 模式基于累计的交易收益率近似,保留是为了向后兼容,当仓位大小随时间变化时,它可能和账户层面的真实回撤对不上;equity 模式才是标准的账户权益峰谷回撤,新配置推荐用它。
这是本节最值得记住的一处。同一个框架、同一个词、同一个配置项,底下坐着两套算法,默认那套还是为了兼容历史而保留的近似版。「最大回撤 20%」这句话在不同口径下不是同一件事,而口径信息通常不会出现在报告首页。
分歧四:什么时候它压根算不出来
Vibe-Trading 的期权组合引擎里有一段很有教养的处理:算回撤前先检查峰值序列是不是全为正,如果不是,它不给数,而是往结果里塞一条警告,说明「峰值权益非正,最大回撤无定义」。同一处还有一条:卡玛比率需要一个非零的回撤才有意义,否则也拒绝给数。
为什么峰值可能不是正数?因为在带杠杆、带衍生品的组合里,权益理论上可以归零甚至为负,此时「跌了百分之几」这个除法本身就没有意义了。它的风险体检模块另有一份实现,除了最大回撤还顺手记下回撤开始的日期和最深处的日期——有了这两个时间点,你才能问出「这个坑持续了多久」这种真正有价值的问题。
这四处分歧合起来说明一件事:最大回撤是个概念,不是一段代码。你在报告上看到的那个百分数,是「用哪条曲线、按什么口径、在什么保护下」共同决定的产物。
它的致命缺陷:只记得最坏的一次
现在说这篇最想让你带走的东西。最大回撤有一个几乎无解的结构性缺陷——它是一个极值统计量,整个数字由一个时刻决定,其余所有历史信息全部被丢弃。
展开来说,至少有五层麻烦。
它不含频率。 一套东西在五年里只跌过一次 30%,另一套在五年里跌了八次 15%。前者报告上写 30%,后者写 15%,后者数字漂亮得多。可真拿钱去坐,八次 15% 的折磨感未必比一次 30% 轻——你会在第三次、第四次的时候开始怀疑,然后在某一次谷底放弃。最大回撤对「你被折磨了几回」这件事完全失明。
它不含时间。 跌 30% 用三个月爬回来,和跌 30% 趴了三年,最大回撤是同一个数。可这两种体验隔着一整个世界。回撤有多深是一个维度,趴了多久是另一个完全独立的维度,后者往往才是真正让人离场的原因——这正是 回撤恢复期 要单独立一个词条的理由。把这个 30% 和你自己那套东西的年化速度一起丢进 回撤恢复期测算器,就能看出报告上这个数换算成年数是多久。
它随观察期单调不减。 这条特别隐蔽。因为最大回撤取的是历史上的最深点,所以你看的历史越长,这个数只可能变大或不变,绝不可能变小。一套跑三年的回测报回撤 15%,跑十年的报 35%,这未必说明后者更危险,可能只是它多经历了几轮行情。不同回测长度的最大回撤,横向比较是没有意义的,可报告并排放着的时候,几乎没人注意到时间跨度不一样。
它对采样口径极其敏感。 同一套持仓,用日频权益算、用逐笔平仓的收益算、用分钟级权益算,得到的最大回撤能差出一截——频率越细,越容易抓到日内那些一闪而过的深坑。而收盘价口径会把盘中那次插针完全抹平。所以看到一个回撤数字,「它是按什么频率的曲线算的」和数字本身一样重要。
它是单个样本点,几乎没有统计稳健性。 波动率是所有数据点共同贡献出来的,去掉一天几乎不动;最大回撤只由「谷底那一刻」决定,把那一天从数据里抠掉,整个数字可能立刻从 45% 掉到 20%。用一个由单点决定的量去做重大判断,本身就很脆。也正因如此,把它当成「未来最坏情况」的上限特别危险——历史上没发生过的更深回撤,不代表它不会发生。
把历史最大回撤当成未来的心理准备上限。 「它历史最大才跌 20%,我扛得住 20%」是一句听起来极其合理、实际上极其危险的话。历史最大回撤是已实现的极值,不是可能的极值——它只说明这段样本里没出现过更糟的,从来没说过更糟的不会来。样本越短,这句安慰的含金量越低。真要给自己留余量,至少该按「比历史最大回撤更深一截」去准备,并且认清那也只是拍脑袋,不是保证。
那怎么补救
既然它漏掉了频率和时间,补救的方向自然就是把漏掉的补回来。三条常见路子:
看整条水下曲线,不只看最深那一点。 前面那三行代码算出来的中间产物 dd,本身就是完整信息——它能告诉你一共下过几次水、每次多深、每次多久才浮上来。qlib 的报告模块也是这个思路,它把「累计收益减滚动峰值」直接画成一条图上的填充带;而且它算了好几条:不含成本的、含成本的、超额收益的,各自一条水下曲线。把成本算进去之后水下曲线会更深更长,这件事本身就很有信息量。
把回撤和收益放在一起看。 一个只报回撤不报收益的数字没法用——承担 30% 回撤换来 40% 年化,和承担 30% 回撤换来 8% 年化,是两回事。把年化收益除以最大回撤,就是 卡玛比率 在干的事,前面几处代码里那句「卡玛需要一个非零回撤」说的就是它。
记下回撤的起止时间。 只有存下了开始日和谷底日,才能进一步算出「这段坑持续了多久、什么时候才真正爬回上一个高点」。这也是为什么成熟的风险模块普遍不只返回一个数,而是返回一组带日期的结构。
失效边界与常见误用
拿两份不同时间跨度的报告比最大回撤。 前面说了,这个数随观察期只涨不跌。比之前先对齐时间窗口,否则你比的是「谁经历得多」,不是「谁风险大」。
以为最大回撤小就是稳。 它只说明这段历史里最深的坑不深,可能是运气,可能是样本短,也可能是这套东西根本没怎么动过——一套长期空仓的方法,最大回撤接近零,这个数字漂亮得毫无意义。
忽略口径就直接引用数字。 算术累计还是复利累计、相对账户还是相对收益率、日频还是逐笔、含不含交易成本——任意一个换掉,数字都会变。引用别人的回撤数字时,把口径一起引用,否则这个数只是个孤零零的百分号。
把它当成止损参数直接用。 「历史最大回撤 20%,那我就设 20% 熔断」这个推论有个隐藏毛病:历史上那次 20% 之后市场涨回来了,所以它才只有 20%;你在实盘里跌到 20% 就停手,恰恰可能停在最不该停的位置。回撤保护是一套需要单独想清楚的规则,不是把历史极值抄过来当阈值。真要设,要想清楚的是「触发之后干什么、什么时候恢复」,而不只是那个数。
用它推断「我扛得住」。 你在报告上看到 −35% 是四个字符,实盘里连着七个月每天打开账户看到红色是另一回事。数字和体感之间隔着时间维度,而最大回撤这个数恰好把时间维度扔了。
小结
- 最大回撤 = 账户从自己到过的历史最高点往下掉,最深的那一次掉了多少。参照物是自己的峰值,不是成本,也不是起点。
- 它比波动率更贴近体感的原因有三条:只算下跌、记得顺序、不依赖任何分布假设——它是发生过的事,不是推算出来的数。
- 系统里的算法骨架就三行:滚动峰值、离峰值多远、取最深。但口径分歧全在细节里——算术累计还是复利累计、相对账户还是相对收益率、按什么频率采样、含不含成本,换一个数字就变一个。
- 它还能从「事后统计量」变成「事中开关」,此时不同计算模式之间的差别会直接影响你有没有被拦住下单。
- 它的致命缺陷是只记得最坏的一次:不含频率、不含时间、随观察期只增不减、对采样口径敏感、由单个数据点决定。历史最大回撤是已实现的极值,不是可能的极值。
- 补救靠三件事:看整条水下曲线而不是一个点、把回撤和收益放一起看、记下回撤的起止日期。
一句话记住这篇:这个数只告诉你「你最惨的那一天有多惨」,没告诉你惨了几回、惨了多久,更没保证以后不会更惨。
本文所引源码与文档均来自上述快照版本,是阅读代码得出的机制说明,不是运行结果;具体行为请以你手上那个版本为准。回 量化与 AI 投研概念库 看其它概念各自在说什么;本站内容为投资教育,不构成任何投资建议,边界见 免责声明。
本文为投资教育整理,关键数据与结论请结合下列权威来源验证。
- freqtrade 源码快照 b3404c9(2026-08-18):freqtrade/data/metrics.py、freqtrade/plugins/protections/max_drawdown_protection.py、docs/includes/protections.md ↗
- qlib 源码快照 79633dd(2026-07-23):qlib/contrib/evaluate.py、qlib/contrib/report/analysis_position/report.py ↗
- Vibe-Trading 源码快照 3a752d5(2026-08-04):agent/backtest/risk_xray.py、agent/backtest/metrics.py、agent/backtest/engines/options_portfolio.py ↗