如何用 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 三件套。关键是前复权、统一年化口径、下行波动只算负收益,并结合多指标综合判断。

参考

相关推荐
小小张说故事1 小时前
Python 的描述符到底是个啥?property 和 classmethod 底层都靠它,3 个例子讲透
python
郑州光合科技余经理1 小时前
出海外卖平台:支付对接为什么要和下单收款分开
android·java·开发语言·前端·后端·php·ai编程
kimnoic1 小时前
Python操作MySQL数据库的两种方式实例分析【pymysql和pandas】
开发语言·python
外收内放1 小时前
Python基础语法练习题(64)
python·学习
苏辰澈1 小时前
C++独属于自己的变身:多态
c语言·开发语言·c++·visualstudio·编辑器
谢亮_vipxieliang1 小时前
PHP 异常处理全解析:try-catch、自定义异常与全局异常处理器
开发语言·php
fanjiu20201 小时前
flask接收消息
python·flask
外收内放1 小时前
Python基础语法练习题(65)
开发语言·python
浅尝辄止;1 小时前
设计模式在HIS/LIS医疗系统中的完整实践指南(C#版)
开发语言·设计模式·c#