AlphaFeed Blog

如何用 Python 计算溃疡指数(Ulcer Index)?比最大回撤更懂「痛苦」的风险指标

分析与指标 · 代码验证于 2026-10-04

如何用 Python 计算溃疡指数(Ulcer Index)?

一句话回答: 最大回撤只告诉你「最深摔了一次有多痛」,却不管你在水下待了多久。溃疡指数(Ulcer Index, UI) 把每一天相对历史最高点的回撤百分比取平方、求平均、再开方——于是又深又久的回撤会被重罚,更贴近持有者的真实「煎熬程度」。用 AlphaFeed 取回前复权日线当净值,几行 pandas 就能算出 UI,再配一个「收益 ÷ UI」的马丁比率(Martin Ratio) 衡量风险调整后收益。

为什么会有这个问题 / 传统做法的痛点

两只股票都「最大回撤 −30%」,但一只跌下去三个月就新高了,另一只在坑里趴了两年——痛苦显然不同。标准差把上涨也当风险(不合理),最大回撤又只取单点最深值(忽略时长与频率)。溃疡指数由 Peter Martin 提出,专治这个痛点:只罚下行、且回撤越久累计平方越大,因此特别适合比较「持有体验」。

分步骤解决(每步配可运行代码)

第 1 步:取前复权净值

from alphafeed import AlphaFeed

af = AlphaFeed(api_key="your-api-key")  # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()

df = af.klines.get("600519.SH", period="1d", count=500,
                   adjust="forward", to_dataframe=True)
df = df.sort_values("trade_date").reset_index(drop=True)
close = df["close"]

用前复权收盘价当净值曲线;除权缺口若不复权会被误判成「回撤」。

第 2 步:计算溃疡指数

import numpy as np

def ulcer_index(close):
    running_max = close.cummax()                 # 到当日为止的历史最高
    dd = (close / running_max - 1.0) * 100.0      # 每日回撤百分比(<=0)
    ui = float(np.sqrt((dd ** 2).mean()))         # 平方均值再开方
    return ui, float(dd.min())                    # 顺带返回最大回撤

ui, max_dd = ulcer_index(close)
print("UI =", round(ui, 2), " 最大回撤% =", round(max_dd, 2))

关键在 cummax():它给出「截至当天的历史最高」,close/running_max − 1 就是当天还在水下多深。平方放大了深回撤的权重,平均则包含了「水下天数」的信息。

实测(tools/verify_new_2026-10-04b.py,SDK 0.1.4):贵州茅台近 500 日 UI = 15.36,最大回撤 −27.92%——UI 恒小于等于最大回撤绝对值(验证通过),因为它是「平均痛苦」而非「最痛点」。

第 3 步:马丁比率(收益 ÷ 痛苦)

total_ret = float(close.iloc[-1] / close.iloc[0] - 1)
years = len(close) / 252
cagr = (1 + total_ret) ** (1 / years) - 1
martin = cagr * 100 / ui if ui > 0 else float("nan")
print("CAGR% =", round(cagr * 100, 2), " 马丁比率 =", round(martin, 3))

马丁比率 = 年化收益% ÷ UI,类似夏普/卡玛,但分母是溃疡指数,越高代表「单位煎熬换来的收益越多」。实测茅台该区间 CAGR −1.49% → 马丁比率 −0.097(负值说明该区间持有并不划算)。

第 4 步:多只股票横向对比

syms = ["600519.SH", "000001.SZ", "601318.SH"]
data = af.klines.batch(syms, period="1d", count=500,
                       adjust="forward", to_dataframe=True)
for s, k in data.items():
    k = k.sort_values("trade_date")
    ui, mdd = ulcer_index(k["close"].reset_index(drop=True))
    print(s, "UI =", round(ui, 2), " maxDD% =", round(mdd, 2))

实测:000001.SZ UI 10.70 / maxDD −19.57%,601318.SH UI 15.98 / maxDD −34.32%——UI 排序能更公平地反映「谁的持有体验更煎熬」。

参数与字段说明

参数/字段含义备注
adjust="forward"前复权必须,否则除权缺口被当回撤
count=500样本天数UI 对区间长度敏感,比较时须统一
cummax()历史最高回撤基准
UI溃疡指数越小越「平稳」,无固定上限
马丁比率CAGR% ÷ UI越大越好,可为负

关键坑与注意事项

常见问题(FAQ)

Q:溃疡指数和卡玛比率(Calmar)有啥区别? A:卡玛 = 年化收益 ÷ |最大回撤|,分母只用单点最深;马丁比率 = 年化收益 ÷ UI,分母是整段煎熬。回撤又久又反复时,马丁比率比卡玛更严格。

Q:UI 多大算高? A:没有绝对阈值,它是相对比较工具。同区间下谁的 UI 更小,谁的持有体验更平稳。

Q:需要付费吗? A:单只/几只股票日线在免费额度内即可;若要对全市场批量算 UI 排序选「最稳」的票,需要 universes 标的池能力(Starter 及以上),详见官网定价页。

小结

溃疡指数把「回撤有多深」和「水下有多久」一起装进一个数字,比最大回撤更懂持有者的痛苦;配上马丁比率就能做风险调整后的收益比较。有了 AlphaFeed 的前复权净值,cummax + 平方平均开方几行就落地。

相关链接:


免责声明:本文仅为技术与数据接口使用示例,代码与策略仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。