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

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

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

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

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

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

python 复制代码
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 索提诺(下行波动的差别)

python 复制代码
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 步:卡玛比率(年化收益 / 最大回撤)

python 复制代码
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 步:批量对比多只/多策略

python 复制代码
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))

关键坑与注意事项

  • 前复权价:未复权价在除权日的伪跳变会污染收益与所有比率。
  • 年化口径统一:A 股按 252 交易日年化;日/周/月频率的年化因子不同,别混用。
  • 下行波动只统计负收益:Sortino 的分母是负收益的均方根,别用全样本 std。
  • 卡玛对区间长度敏感:最大回撤随样本变化大,通常固定看近 3 年、且需足够样本。
  • 无风险利率简化为 0:更严谨可减去货币基金/国债日收益,但对横向对比影响小。
  • 单指标不足以定优劣:三个比率各有侧重,应结合收益、回撤、稳定性综合看,且历史不代表未来。

常见问题(FAQ)

Q:索提诺一定比夏普高吗?

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

Q:卡玛比率多少算好?

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

Q:为什么用 252?

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

Q:需要付费吗?

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

小结

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

参考

相关推荐
超级架构师38 分钟前
PRAXO-CORE 2.5.0:用 conformance 和显式边界构建 Go Agent 框架
开发语言·人工智能·后端·golang·ai编程·智能体框架·praxo-core
叼烟扛炮1 小时前
Linux 学习笔记(四十三):网络编程进阶——自定义协议解决 TCP 粘包与守护进程
linux·开发语言·网络·笔记·tcp/ip
LMT流氓兔2 小时前
第五单元——第十六课:给书店助手加上网页界面
java·开发语言·jvm·人工智能·oracle·langchain·1024程序员节
专注仿真2 小时前
梯度提升类(XFBoost)算法详解与代码实例
开发语言·人工智能·python·算法·大模型训练·控制算法
外收内放2 小时前
Python基础语法练习题(75)
开发语言·python·学习
starzy19903 小时前
Scala循环与判断:一切皆表达式的控制流
开发语言·人工智能·scala
starzy19903 小时前
Scala类和对象:从构造器到样例类的完整指南
开发语言·后端·scala
niucloud-admin4 小时前
NIUCLOUD PHP SAAS上门家政使用文档--服务订单:订单列表
开发语言·php
黑十4 小时前
PyTorch 报 “no CUDA GPUs are available“ 怎么办?3 步排查
服务器·网络·人工智能·pytorch·python
怪我冷i4 小时前
Rust 的impl语法详解
开发语言·后端·rust