AlphaFeed Blog

如何用 Python 计算索提诺比率和卡玛比率?(比夏普更懂下行风险的收益评估)

分析与指标 · 代码验证于 2026-09-27

如何用 Python 计算索提诺比率和卡玛比率?

一句话回答: 夏普比率把「上涨的波动」也算成风险,而索提诺比率(Sortino)只惩罚下行波动、卡玛比率(Calmar)用最大回撤当风险分母——更贴合投资者「怕跌不怕涨」的真实感受。用 AlphaFeed 取回前复权日线算日收益后,几行 pandas 就能得到年化收益、下行波动、最大回撤,进而算出 Sortino 与 Calmar,不需要专门的库。

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

只看夏普比率有个尴尬:一只票暴涨也会推高波动率、反而拉低夏普,可上涨明明是好事。索提诺只把「跌」当风险,卡玛直接盯「最大回撤」,二者更能反映实际持有体验。计算的坑在于:要用前复权价算真实收益,注意年化口径(A 股一年约 252 个交易日),下行波动只统计负收益。

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

第 1 步:取前复权日线并算日收益

from alphafeed import AlphaFeed
import numpy as np
import pandas as pd

af = AlphaFeed()  # 读取 ALPHAFEED_API_KEY

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)
ret = df["close"].pct_change().dropna()

用前复权价(adjust="forward")算收益,避免除权日的伪涨跌污染指标。

第 2 步:夏普 vs 索提诺(下行波动的差别)

TRADING_DAYS = 252
rf_daily = 0.0            # 简化:无风险利率按 0

def sharpe(ret, rf=0.0):
    excess = ret - rf
    return np.sqrt(TRADING_DAYS) * excess.mean() / excess.std(ddof=1)

def sortino(ret, rf=0.0):
    excess = ret - rf
    downside = excess[excess < 0]                  # 只统计下行
    dd_std = np.sqrt((downside ** 2).mean())       # 下行标准差
    return np.sqrt(TRADING_DAYS) * excess.mean() / dd_std

print(f"夏普 {sharpe(ret):.2f} | 索提诺 {sortino(ret):.2f}")
指标风险分母特点
夏普 Sharpe全部收益的标准差上涨波动也算“风险”
索提诺 Sortino仅下行收益的标准差只罚下跌,更符合直觉

第 3 步:卡玛比率(年化收益 / 最大回撤)

def cagr(close):
    total = close.iloc[-1] / close.iloc[0]
    years = len(close) / TRADING_DAYS
    return total ** (1 / years) - 1

def max_drawdown(close):
    equity = close / close.iloc[0]
    return (equity / equity.cummax() - 1).min()   # 负值

c = cagr(df["close"])
mdd = max_drawdown(df["close"])
calmar = c / abs(mdd) if mdd != 0 else np.nan
print(f"年化收益 {c:.2%} | 最大回撤 {mdd:.2%} | 卡玛 {calmar:.2f}")
指标公式解读
卡玛 Calmar年化收益 ÷ |最大回撤|每承受 1 单位回撤换来的年化收益
越大越好—通常看近 3 年;>1 较优(因品种而异)

第 4 步:批量对比多只/多策略

codes = ["600519.SH", "000001.SZ", "601318.SH", "000858.SZ"]
data = af.klines.batch(codes, period="1d", count=500,
                       adjust="forward", to_dataframe=True)
rows = []
for s, k in data.items():
    k = k.sort_values("trade_date"); r = k["close"].pct_change().dropna()
    rows.append({"symbol": s, "Sharpe": round(sharpe(r), 2),
                 "Sortino": round(sortino(r), 2),
                 "Calmar": round(cagr(k["close"]) / abs(max_drawdown(k["close"])), 2)})
print(pd.DataFrame(rows).to_string(index=False))

关键坑与注意事项

常见问题(FAQ)

Q:索提诺一定比夏普高吗? A:通常是,因为它剔除了上行波动这个「假风险」。若下跌集中且剧烈,索提诺也可能不高。

Q:卡玛比率多少算好? A:没有绝对标准,通常 >1 较优、越大越好,但严重依赖回测区间与品种,跨样本对比才有意义。

Q:为什么用 252? A:A 股一年约 252 个交易日,是日频年化的常用因子;用月频则年化因子是 12。

Q:需要付费吗? A:单只历史日线有免费额度;批量对比多只/组合(klines.batch + universes)需 Starter 及以上。详见定价页。

小结

评估收益别只看夏普:索提诺只罚下行波动、卡玛用最大回撤当分母,更贴合真实风险偏好。用 AlphaFeed 取回前复权日线算日收益,几行 pandas 就能得到 Sharpe/Sortino/Calmar 三件套。关键是前复权、统一年化口径、下行波动只算负收益,并结合多指标综合判断。

相关链接:


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