- Published on
Max Drawdown Calculating Functions (Optimized)
- Authors

- Name
- CoresightQuant
NOTE
本文为公开交易策略的技战术复盘,聚焦方法逻辑、交易场景与风险边界。
在量化交易中,最大回撤是衡量资金曲线风险的核心指标之一。它告诉投资者在历史过程中,账户净值从最高点回落到最低谷地的最大幅度。许多交易者希望在自己的策略逻辑中动态计算这一数值,但现成的工具往往不够灵活。近期在公开交易社区中,一位昵称为“sickojacko”的作者分享了一组计算最大回撤的函数,并提供两种算法:一种直白但低效,一种经过优化后可以应对海量数据。本文将以该分享为引子,系统讲解最大回撤的算法原理、优点缺点、发展脉络与改进方向。
策略解读
原作者在开发策略时,需要从一组账户余额序列中计算最大回撤百分比。他尝试寻找现成的方法,但没有找到直接可用的函数,于是决定自己实现。在他的描述中,有两个算法用于计算“最大回撤”和“最大相对回撤百分比”:一个未优化版本,对于一个包含 n 个数据的集合,需要执行 n*(n-1)/2 次比较;另一个优化版本只需要 n-1 次比较。作者还在示例中展示了如何计算最近 10 个数据的最大回撤。
这里我们可以扩展解释什么叫“回撤”。假设我们有一条资金曲线,从某一时刻开始上涨到一个高点,随后又出现下跌,那个高点与之后某个低点之间的跌幅,就是一次回撤。最大回撤则是在整条曲线上寻找所有可能的“峰-谷”配对中,最大的一次跌幅。它是投资者可能承受的最大账面损失的近似度量。更进一步的“最大相对回撤百分比”,是用回撤幅度除以前期高点,得到百分比形式,这样不同资金规模的账户之间便可以直接对比。
很多人初次接触回撤时,容易把它误解为“最高点减最低点”。实际上,由于时间顺序的存在,最大回撤必须满足“高点在前、低点在后”的序列关系。若某次低点出现在高点之前,那么它就不能构成这次高点的回撤。理解这一点,是理解后续两种算法的关键。
作者之所以要提供两个版本,正是为了让使用者根据数据量的大小在“简单可靠”和“高性能”之间做出选择。优化算法虽然逻辑上稍显抽象,却具有很强的工程价值,尤其在实时交易中,数据点不断产生,每一次新数据到来都可能触发重新计算,此时线性复杂度显得极其重要。下面,我们先用名词解释梳理基本概念,再详细讲述算法思路。
名词解释
-
最大回撤:在一段观察期内,从账户净值的某个历史峰值开始,到其后出现的某个低点为止,期间净值下跌的最大幅度。通常以绝对数值或百分比表示,是衡量策略风险水平和波动特征的重要指标。
-
相对回撤:将回撤的绝对数值除以峰值,得到一个无量纲的百分比值。例如净值为 100 万元,回撤 20 万元,则相对回撤为 20%。它便于不同规模资金之间的横向比较,也是许多资金管理规则中触发减仓或止损的重要参考。
-
时间复杂度:计算机科学中描述算法效率的概念,表示当输入数据规模 n 增加时,基本计算操作次数的增长趋势。暴力算法的时间复杂度为 O(n²),而优化的线性扫描算法为 O(n),这两者在 n 较大时运算代价差别巨大。
-
暴力列举法:将数组中所有可能出现回撤的起点和终点两两组合,逐一计算跌幅,最后取最大值。这种办法逻辑直观、不易出错,但会重复比较大量无需关注的组合,导致高度冗余的计算量。
-
线性扫描:从数据序列的第一个元素开始,只遍历一次,同时维护“当前历史最高值”和“当前最大回撤”两个变量,每读入一个新数据立即更新结果。最终得到的最大回撤与暴力枚举完全一致,但计算次数大幅减少。
-
历史峰值:在扫描过程中,截至当前时刻所出现过的最高净值数据。它是计算潜在回撤的基准点,也是最大回撤定义中“高点”的一种自然推断。
策略思路讲解
最大回撤的数学定义并不复杂。假设一条长度为 n 的序列 a[0], a[1], ..., a[n-1],任意取两个下标 i 和 j,且 i < j。若 a[i] 是 i 时刻之前(包括 i 时刻)的一个高点,而 a[j] 是其后某时刻的值,那么从 a[i] 到 a[j] 的跌幅就可以表示为 (a[i] - a[j]) / a[i](相对回撤)或 a[i] - a[j](绝对回撤)。最大回撤就是这些所有可能组合中的最大值。
先看暴力列举法。为了不遗漏任何一次历史回撤,我们可以把 i 从 0 一直取到 n-2,把 j 从 i+1 一直取到 n-1,并针对每一对 (i, j) 计算一次跌幅。由于所有 i < j 的有序点对数量是 n(n-1)/2,因此需要比较这么多次。这也是原作者所说的 n*(n-1)/2 次比较。这种方法很容易实现:只要写两层循环,将所有计算结果与当前最大值比较,最终留下最大者即可。缺点是当 n 增大时,计算量近似按平方级增长。若一天有几千个数据点,那么一次完整扫描就需要数百万次运算,这在实时系统中是不可接受的。
优化算法利用了最大回撤结构上的一个朴素事实:对于被充当“谷底”的那个点,它对应的“峰顶”必然是该点之前所有数据中的最高点。我们不需要枚举每个可能的峰,只需要一路上记住当前遇到过的最高点。具体处理逻辑是:从头开始遍历,用一个变量保存到目前为止的最高价或最高净值,另一个变量保存当前最大回撤。当遇到一个新数值时,先与历史峰值比较:若大于峰值,则更新峰值;若小于峰值,则用 (峰值 - 当前值) / 峰值 计算一个回撤值,并检查它是否大于当前最大回撤。如果大于,则更新最大回撤。整个过程中,每个数据点只需要与历史峰值做一次比较,所以总比较次数为 n-1 次。无论数据规模多大,内存里只需要两个变量,空间复杂度是常数。
为什么这个线性算法是正确的?因为任何一次回撤路径都可以简化为“一个峰值后的下跌”。假设最大回撤最终发生在某个高点 H 与某个低点 L 之间,其中 L 位于 H 之后。那么在扫描到 L 时,变量“历史峰值”一定不会小于 H,因为 H 在该点之前已经出现过;甚至可能已经被更高的峰值取代。无论峰值变量是什么,它代表的都是 L 之前最高的净值,因此以 L 为谷底计算出的回撤一定大于或等于 H 到 L 的回撤。所以说,扫描到 L 时更新过的最大回撤不会漏掉这一事件。推广到所有点,经过一次遍历后,得到的就是全期间最大回撤。
这个优化算法还可以计算“最大相对回撤百分比”,也就是以百分比形式表示回撤与峰值的关系。在账户余额不出现负值的前提下,公式为 (峰值 - 当前值) / 峰值 × 100%。这项指标往往比绝对回撤更有参考意义,因为不同账户初始资金不同,只有相对比例才能反映真实的亏损比例。
在作者分享的例子中,他计算的是“最近 10 个数据”的最大回撤。这暗示了实际应用中我们会刻意设置一个回看窗口。为什么要限制窗口?因为过于久远的峰值可能和当前行情已无关联,全历史最大回撤一旦形成便永久不变,这会使风控信号反应迟缓。滚动窗口的方式让回撤指标能够适应市场环境的变化,这也是模板本身之外值得借鉴的思路。
优点
-
优化算法的性能优势突出:线性时间复杂度 O(n) 和常数空间意味着即使面对实时行情中的高频净值更新,也能以极低开销不断刷新最大回撤,不像暴力算法那样随着数据积累而越来越慢。
-
算法简洁,易于理解和验证:暴力算法两重循环,优化算法只有两个变量更新规则,逻辑非常清晰。即便没有深厚的编程基础,交易者也能手工验证其正确性,容易建立对结果的信任。
-
适用于多种数据形态:只要输入是一个有序数值序列,无论是账户余额、策略净值、股票价格、收益曲线,甚至是自定义指标输出,都可以套用该函数来计算最大回撤,通用性较强。
-
相对回撤让不同策略可以横向比较:优化算法输出的是百分比形式的相对回撤,与账户规模无关。这样,不同资金量、不同品种的交易策略之间,可以统一用“最大相对回撤”衡量风险,有利于资金管理评分和策略筛选。
-
为后续扩展提供基础模块:由于作者将回撤计算抽象为独立函数,用户可以直接复用,并与止损、加减仓、仓位再平衡等规则相结合,形成更复杂的风险管理逻辑。
缺点
-
暴力算法性能瓶颈明显:如果 n 达到 5000,比较次数已经超过 1200 万次;若达到 10000,将接近 5000 万次。在需要频繁调用的策略回测中,这会造成明显的延迟,不适合长期运行。
-
最大回撤只刻画极端单点损失,信息量有限:它没有描述回撤持续了多久、恢复花了多久、期间是否反复波动,也没有解释回撤发生的背景。单独依赖这个指标做风控,容易忽略策略可能长期低迷但回撤数值却平稳的情形。
-
异常值敏感:如果某个数据点因为输入错误、极端滑点或瞬间数据跳变而出现一个极低值,这个异常点很可能被当作“最大回撤”的谷底,导致回撤数值过于悲观,进而错误触发减仓或停止交易。
-
基于“历史峰值”的假设并不适用于所有场景:在某些资金管理体系中,回撤基准是初始本金或移动平均线,而不是历史最高点。例如,一位基金经理希望监控“净值相比月初的新高跌幅”而不是全历史跌幅,这种算法需要调整后才能满足需求。
-
峰值接近于零时会失效:如果账户净值出现负值或零值,以峰值作为分母的比例计算会变成负值或无穷大。在杠杆类策略、期货账户或极端亏损情况下,这一问题不可忽略。
起源年份考证
最大回撤作为一种风险度量方法,并非来自某一个具体发明日期,而是在现代投资理论发展过程中逐步形成的。以下时间线是针对“最大回撤指标”这一类方法的行业考证,并非针对原脚本作者的发布年份。
- 20世纪50年代:马科维茨提出现代投资组合理论,首次将风险量化并纳入资产配置框架,但当时的风险概念主要是标准差,尚未使用回撤指标。
- 20世纪60至70年代:基金业绩评价开始成熟,资产管理人逐渐意识到单纯依赖收益率无法描述投资者的实际痛苦程度,于是“从峰值到谷底的跌幅”开始被用作基金说明中的风险指标。
- 20世纪80年代:个人电脑和电子表格软件的出现让回撤计算可以快速完成,早期的程序实现多采用直观的两层循环,也就是暴力算法。这个时代虽然已经有更高效的思路流传,但普通投资者仍以简单计算为主。
- 20世纪90年代:对冲基金和期货管理基金规模激增,最大回撤被广泛用于季度报告和风险管理体系,市面上逐渐出现“最大回撤要越小越好”的评价习惯。与此同时,算法领域开始讨论线性扫描这类优化方式。
- 21世纪初:在线交易社区蓬勃发展,大量策略作者开始在公开社区分享回撤计算函数,线性扫描算法逐渐成为相关代码库的常用模板。本文所说的“优化算法”,本质上便是这类公开实践中的经典版本。
至于该脚本具体由作者发布于哪一年,我们无法考证。从代码风格和社区流行度判断,很可能出现在2010年代之后,但这仅是基于属类的推测,没有确切依据。
改进建议
-
引入滚动窗口:不要只计算全历史最大回撤,而是通过循环数组保存最近 N 个数据点,在窗口内动态计算回撤。这样既能避免远古峰值的长期压制,也能让风险指标更快反映新行情。
-
结合波动率等指标:最大回撤虽然重要,但单独使用有些单薄。可将最大回撤与日均波动率、最大单日跌幅等指标结合,构建一个综合风险评分。例如当回撤扩大且波动率同步上升时,给出更高的风险警示。
-
允许自定义回撤基准:将“历史峰值”改为可配置项,例如支持“初始净值”“移动平均线”“指定时间点”等不同基准。这样算法可以兼容更丰富的策略需求,例如关注净值是否跌破某一均线或成本线。
-
记录回撤的持续时间与恢复速度:在线性扫描时同时记录峰值出现的具体时间、最大回撤发生谷底的时间,以及回撤结束恢复至峰值的时间。有了这些信息,投资者就能分析回撤的深度与长度,从而估计策略的“痛点区间”。
-
增加异常值过滤:在数据进入回撤计算前,使用中位数滤波、MAD(绝对中位差)等稳健统计方法识别极端异常点。默认可以忽略那些与前后数据偏离过大的点,避免数据错误导致回撤指标失真。
-
输出更多回撤分位数:除了最大回撤,还可以按同样思路计算 5%、10%、50% 回撤分位数。这能帮助交易者理解风险分布,而不仅仅是“最坏的那一次”。
-
增加可视化接口:让函数除了返回数值,也返回回撤起止位置。这样在绘制净值曲线时,可以直接标注最大回撤区间,让交易者直观看到最痛苦的阶段发生在哪些时间点,为策略优化提供依据。
以上改进建议并非都聚焦性能,因为性能优化已经足够优秀;更多是希望将最大回撤这一指标从“单一数字”扩展为“完整的风险管理工具箱”,使其在实际决策中发挥更大价值。
总结
最大回撤是一个看似简单却能反映策略风险本质的指标。本文以公开交易社区中的一段分享为引子,梳理了计算最大回撤的两种方法:暴力列举法与线性扫描法。前者易懂但复杂度高,后者以 n-1 次比较完成同样的任务,巧妙利用了“回撤谷底前必有一个历史峰值”这一性质。我们还分析了该方法的优点与不足,并指出最大回撤的使用方式需要结合具体交易场景。回撤计算不是目的,而是手段;优化算法让我们能以极低的成本持续监控风险,从而在真实交易中更从容地应对极端行情。希望读者通过这篇文章,不仅理解函数背后的逻辑,更能将这种计算思路迁移到自己的风险管理实践中。