# 绩效与风险指标 (/zh/features/analytics/performance-metrics)

结果究竟有多好，又是与什么相比？VBT 在 Python
中计算总收益、夏普比率、索提诺比率、卡玛比率、回撤和基准统计等回测绩效指标，既能分析单个投资组合，也能同时分析数千个组合；不仅适用于自身回测，也适用于任意收益序列。

```python title="Get a full report for one backtest"
>>> data = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2025-01-01")
>>> fast = data.close.rolling(20).mean()
>>> slow = data.close.rolling(50).mean()
>>> pf = vbt.PF.from_signals(
...     data,
...     fast.vbt.crossed_above(slow),
...     fast.vbt.crossed_below(slow),
...     fees=0.001,
... )
>>> pf.stats()
Start Index                   2020-01-01 00:00:00+00:00
End Index                     2024-12-31 00:00:00+00:00
Total Duration                       1827 days 00:00:00
Start Value                                       100.0
Min Value                                     95.605003
Max Value                                    787.725311
End Value                                    693.387328
Total Return [%]                             593.387328
Benchmark Return [%]                        1197.596406
Position Coverage [%]                         53.858785
Max Gross Exposure [%]                            100.0
Max Drawdown [%]                              58.610935
Max Drawdown Duration                1126 days 00:00:00
Total Orders                                         37
Total Fees Paid                               15.776306
Total Trades                                         19
Win Rate [%]                                  33.333333
Best Trade [%]                               381.070734
Worst Trade [%]                              -19.099978
Avg Winning Trade [%]                         83.748564
Avg Losing Trade [%]                          -8.434649
Avg Winning Trade Duration             93 days 08:00:00
Avg Losing Trade Duration              27 days 04:00:00
Profit Factor                                  1.754588
Expectancy                                    20.504842
Sharpe Ratio                                   1.083913
Calmar Ratio                                   0.805908
Omega Ratio                                    1.249536
Sortino Ratio                                  1.700246
dtype: object
```

相同调用也适用于整个网格。这里是 100 组均线参数，每组一行：

```python title="Compare selected metrics across 100 backtests"
>>> fast_w, slow_w = np.meshgrid(np.arange(5, 55, 5), np.arange(60, 260, 20))
>>> fast = vbt.MA.run(data.close, window=fast_w.ravel(), short_name="fast")
>>> slow = vbt.MA.run(data.close, window=slow_w.ravel(), short_name="slow")
>>> grid_pf = vbt.PF.from_signals(
...     data,
...     fast.ma_crossed_above(slow),
...     fast.ma_crossed_below(slow),
...     fees=0.001,
... )
>>> metrics = ["total_return", "sharpe_ratio", "max_dd", "total_trades"]
>>> grid_stats = grid_pf.stats(metrics)  # (1)
>>> grid_stats.sort_values("Sharpe Ratio", ascending=False).head()
                         Total Return [%]  Sharpe Ratio  Max Drawdown [%]  Total Trades
fast_window slow_window
5           120               1744.634182      1.510302         38.569486             9
            100               1577.778405      1.467615         41.260714            12
25          100               1315.741883      1.405711         40.937689             8
15          100               1241.937564      1.372806         38.948698             9
10          100               1244.107822      1.365325         41.252897            10
```

1.  存在多列时，`stats` 为每列返回一行。传入 `agg_func=np.mean`，则改为对各列的每项指标取平均。

表格让您轻松同时比较收益、回撤、交易次数和夏普比率。如需对排名靠前的结果继续测试，请参阅[稳健性与过拟合](/zh/features/optimization/robustness-and-overfitting/)。

## 收益率 \[#returns]

每项指标都从收益率序列开始。对于投资组合，`pf.returns` 是逐 K 线的价值变化率，会扣除入金和出金影响，避免将它们计入绩效。`pf.asset_returns`
忽略现金，仅衡量已投资部分。使用 `series.vbt.to_returns()` 转换价格或价值序列，再通过收益率序列的 `vbt.returns`
访问器，计算累计、总计和年化收益率、波动率及本页介绍的比率。

年化需要两个数：每根 K 线的时长，以及一年的长度。默认一年为 365 天，适合每天交易的市场。对于每年约交易 252 天的股票，请设置 `year_freq`，或通过 `"auto"` 让 VBT
统计每年的 K 线数：

```python title="Annualize stock returns with the right year length"
>>> spy = vbt.YFData.pull("SPY", start="2020-01-01", end="2025-01-01")
>>> rets = spy.close.vbt.to_returns()

>>> print(round(rets.vbt.returns(freq="1D").sharpe_ratio(), 3))
0.892
>>> print(round(rets.vbt.returns(freq="1D", year_freq="252 days").sharpe_ratio(), 3))
0.741
>>> print(round(rets.vbt.returns(freq="1D", year_freq="auto").sharpe_ratio(), 3))
0.741
```

可通过 `vbt.settings.returns.year_freq` 为一次会话统一设置。

### 分析外部权益曲线 \[#analyze-an-external-equity-curve]

您可以对其他回测工具导出的权益曲线，或已在 Pandas 中算好的收益率使用相同指标。下面是一个没有入金或出金的小型账户价值序列：

```python title="Measure returns from an equity curve without creating a portfolio"
>>> equity = pd.Series([102.0, 101.0, 104.0, 103.0, 106.0], index=pd.date_range("2026-01-05", periods=5))
>>> external_returns = equity.vbt.to_returns(init_value=100)
>>> external_acc = external_returns.vbt.returns(freq="1D", year_freq="252 days")
>>> pd.Series({
...     "Total return [%]": external_acc.total() * 100,
...     "Max drawdown [%]": external_acc.max_drawdown() * 100,
... }).round(2)
Total return [%]    6.00
Max drawdown [%]   -0.98
dtype: float64
```

起始价值 100 包含第一天涨至 102 的收益。最终价值为 106，总收益率因此为 6%。同一访问器也适用于由多个收益率序列组成的 DataFrame，因此无需重建交易即可比较多种策略结果。

## 风险调整比率 \[#risk-adjusted-ratios]

夏普、索提诺、卡玛、欧米伽和信息比率，以及风险价值、条件风险价值、尾部比率、偏度和峰度，都既可作为具有默认值的属性使用，也可作为接受参数的方法调用，例如
`pf.get_sharpe_ratio(risk_free=...)`。

| 问题                | 查看哪些指标      |
| ----------------- | ----------- |
| 策略相对于收益波动获得了多少收益？ | 夏普比率        |
| 相对于下行波动获得了多少收益？   | 索提诺比率和下行风险  |
| 年化收益与最差回撤相比如何？    | 卡玛比率        |
| 最差的单根 K 线收益是什么样？  | 风险价值和条件风险价值 |

单次亏损的深度、持续时间和恢复情况，请参阅[回撤分析](/zh/features/analytics/drawdown-analysis/)。

夏普比率是单根 K 线超额收益的均值除以标准差，再乘以每年 K 线数的平方根。它并非年化收益率除以年化波动率；后者先复利计算，会得到不同数值。

!!! info "设置年化无风险利率"
    传入年利率，例如 `pf.get_sharpe_ratio(risk_free=0.04)` 表示 4%。VBT 根据配置的频率和年长度，将其转换为每根 K
    线的收益率。对于变化的利率，请先从策略收益率中减去对齐的逐 K 线无风险收益率序列，再以 `risk_free=0` 计算夏普比率。

没有持仓的 K 线收益率为零，这些零仍参与计算，因为持币等待也是策略的一部分。如果只想衡量在场时间，请先将这些收益设为 `NaN`，再计算比率。单笔交易的收益率不能替代夏普比率中的逐 K
线收益率，因为交易持续时间不同，无法直接年化。

## 基准比较 \[#benchmark-comparison]

每个投资组合都有基准，默认是买入并持有交易资产，也可通过 `bm_close` 指定其他价格序列，如下方[基准](#benchmark)功能介绍所示。随后收益率访问器即可比较两者：

```python title="Compare the crossover strategy with holding Bitcoin"
>>> ret_acc = pf.returns_acc
>>> pd.Series({
...     "alpha": ret_acc.alpha(),
...     "beta": ret_acc.beta(),
...     "information ratio": ret_acc.information_ratio(),
...     "up capture": ret_acc.up_capture_ratio(),
...     "down capture": ret_acc.down_capture_ratio(),
... }).round(3)
alpha                0.142
beta                 0.491
information ratio   -0.027
up capture           0.127
down capture         0.883
dtype: float64
```

策略的比特币市场敞口约为持有的一半。相对直接持有，它捕获的上涨很少，却承受了大部分下跌，因此收益只有一半。

## 滚动指标 \[#rolling-metrics]

单个数值会掩盖绩效随时间的变化。每种比率都有基于 K 线窗口的滚动版本：

```python title="Plot a rolling one-year Sharpe ratio against the benchmark"
>>> rolling = pd.DataFrame({
...     "Strategy": pf.returns_acc.rolling_sharpe_ratio(365),
...     "Benchmark": pf.bm_returns.vbt.returns(freq="1D").rolling_sharpe_ratio(365),
... })
>>> rolling.vbt.plot().show()
```

2020 至 2024 年比特币均线策略和持有比特币的滚动一年夏普比率. [Figure data (JSON)](/assets/figures/features/analytics/performance-metrics-rolling-sharpe.856e51fa45cc.json)

## 归因与市场状态 \[#attribution-and-regimes]

分组收益率是其合计价值的收益率，而非成员收益率之和。如需按资产拆分，请用前一根 K 线上每列价值占分组价值的比例作为权重，现金也包含在内。若要比较市场状态，可为每根 K
线标注状态，按标签对收益率分组，再通过收益率访问器为各组计算指标：

```python title="Compare Sharpe ratios in uptrends and downtrends"
>>> sma200 = data.close.rolling(200).mean()
>>> regime = pd.Series(
...     np.where(data.close > sma200, "uptrend", "downtrend"),
...     index=data.index,
...     name="regime",
... )[sma200.notna()]  # (1)

>>> def sharpe(returns):
...     return returns.vbt.returns(freq="1D").sharpe_ratio()

>>> pd.DataFrame({
...     "bars": regime.value_counts(),
...     "strategy": pf.returns.groupby(regime).apply(sharpe),
...     "buy and hold": pf.bm_returns.groupby(regime).apply(sharpe),
... }).round(2)
           bars  strategy  buy and hold
regime
downtrend   617      -1.3         -1.14
uptrend    1011       2.0          2.67
```

1.  跳过前 199 根 K 线，因为此时尚不存在 200 天均线。

该策略在上涨趋势中盈利，在下跌趋势中亏损，而直接持有在两种状态下的夏普比率都更高。这样的拆分显示规则是否在市场本身的趋势之外增添了价值。

## 统计报告 \[#statistics-reports]

`stats` 根据指标列表构建报告。选择所需指标，也能加快大网格的处理；还可在内置指标旁添加自己的指标：

```python title="Add a custom metric to the report"
>>> pf.stats([
...     "total_return",
...     "sharpe_ratio",
...     ("trades_per_year", dict(
...         title="Trades per Year",
...         calc_func=lambda self: self.trades.count() / (len(self.wrapper.index) / 365),
...     )),
... ])
Total Return [%]    593.387328
Sharpe Ratio          1.083913
Trades per Year        3.79584
dtype: object
```

报告中的持续时间按 K 线数乘以频率计算，而非日历时间。分布在九个日历日中的四根日 K 线，报告总持续时间为 4 天，因此数据中不存在的周末和节假日不会计入。

设置会传递给每项指标，例如 `pf.stats(settings=dict(use_asset_returns=True))`
在资产收益率上计算收益指标。收益率（`returns_stats`）、交易、回撤和订单也都有相同类型的报告；分组投资组合的 `stats` 按组报告。对于极大的网格，可使用 `vbt.chunked`
按列分块并行计算 `stats`。

## QuantStats \[#quantstats]

安装 QuantStats 后，`pf.qs` 公开其函数、图表和报告，并已填入投资组合的收益率、基准和频率。例如，`pf.qs.sharpe()` 返回 QuantStats
版本的夏普比率，`pf.qs.html_report()` 构建完整绩效报告。适配器也会转换适用于 QuantStats 的无风险利率和年化设置。

## 基准 \[#benchmark]

新增于 1.0.4

✅ 现在可以轻松为整个投资组合设置基准。

```python title="Compare Microsoft to S&P 500"
>>> data = vbt.YFData.pull(["SPY", "MSFT"], start="2010", missing_columns="drop")

>>> pf = vbt.PF.from_holding(
...     close=data.data["MSFT"]["Close"],
...     bm_close=data.data["SPY"]["Close"]
... )
>>> pf.plot_cumulative_returns().show()
```

微软股票与标准普尔 500 基准的累计收益比较. [Figure data (JSON)](/assets/figures/features/analysis/benchmark.c1d30a1b0d0b.json)
