如何用 Python 计算溃疡指数(Ulcer Index)?比最大回撤更懂「痛苦」的风险指标
如何用 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 | 越大越好,可为负 |
关键坑与注意事项
- 区间要统一:UI 不是年化、无量纲标准化,比较不同股票时必须用同一时间区间与同样天数,否则不可比。
- 前复权:除权缺口会被
cummax误判成创新高/深回撤。 - 百分比 vs 对数:本文用百分比回撤(经典定义)。若用对数回撤数值会略有差异,但结论一致。
- UI ≤ |最大回撤|:这是数学必然(平均 ≤ 最大),可当作实现自检。
- 历史不代表未来:UI 描述的是过去的煎熬程度,不预测未来回撤。
常见问题(FAQ)
Q:溃疡指数和卡玛比率(Calmar)有啥区别? A:卡玛 = 年化收益 ÷ |最大回撤|,分母只用单点最深;马丁比率 = 年化收益 ÷ UI,分母是整段煎熬。回撤又久又反复时,马丁比率比卡玛更严格。
Q:UI 多大算高? A:没有绝对阈值,它是相对比较工具。同区间下谁的 UI 更小,谁的持有体验更平稳。
Q:需要付费吗?
A:单只/几只股票日线在免费额度内即可;若要对全市场批量算 UI 排序选「最稳」的票,需要 universes 标的池能力(Starter 及以上),详见官网定价页。
小结
溃疡指数把「回撤有多深」和「水下有多久」一起装进一个数字,比最大回撤更懂持有者的痛苦;配上马丁比率就能做风险调整后的收益比较。有了 AlphaFeed 的前复权净值,cummax + 平方平均开方几行就落地。
相关链接:
- AlphaFeed 官网:https://alphafeed.org/
- Python SDK 快速开始:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart
免责声明:本文仅为技术与数据接口使用示例,代码与策略仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。