风险和绩效计算(Empyrical)指标库

Empyrical 库是一个由 Quantopian 开发的开源 Python 库,专门用于计算常用的金融风险和绩效归因指标。它包含了许多用于策略回测分析的工具,可以用于计算年平均回报、最大回撤、Alpha 值、Beta 值、卡玛比率、Omega 比率、夏普比率等。为了方便用户在 DolphinDB 中计算这些指标,我们使用 DolphinDB 脚本实现了 Empyrical 库中包含的指标函数,并封装在 DolphinDB Empyrical 模块中。

1. 函数的命名与入参规范

Empyrical 模块中的所有函数命名规则为函数具体功能名, 如 simpleReturnscumReturnscumMaxDrawdowncalmarRatiosharpeRatiorollSharpeRatioalpha 等。

本教程涉及到的所有字段如下:

参数名称 / 标准字段名称 参数含义
prices 收盘价
returns 日度简单回报率,非累计
factorReturns 用于比较的基准回报率/因子回报
factorLoadings 因子载荷

注意:

  • perfAttrib 函数中 factorReturns 为不同因子的因子回报,输入为多列的表格。
  • 回报率,可以仅输入基准回报率向量。
  • 由于 beta 函数与 DolphinDB 中内置关键字冲突,Empyrical 中将此函数命名为 covarBeta(因为Empyrical 计算方法为 covariance / variance,DolphinDB 的 beta 为 Y 在 X 上的回归系数的最小二乘估计beta)。
  • DolphinDB 中 maxDrawdown 函数为与峰值的最大差值,Empyrical 库中此函数的定义为累积最大相对回撤比例。由于命名冲突,将 Empyrical 中计算最大回撤的函数命名为 cumMaxDrawdown
  • valueAtRisk 与 DolphinDB 中同名函数意义相同,在 Empyrical 库中,我们采用历史模拟方法计算在险价值。由于命名冲突,将 Empyrical 中的计算在险价值的函数命名为 hisValueAtRisk

2. 使用范例

本章节将通过环境配置、数据准备、计算调用方法等方面具体介绍 Empyrical.dos 模块的具体用法。

2.1. 环境配置

把附件的 Empyrical.dos 放在 [home]/modules 目录下,[home] 目录由系统配置参数 home 决定,可以通过 getHomeDir() 函数查看。

有关模块使用的更多细节,请参见:模块

2.2. 脚本中直接使用指标函数

对一个向量直接使用 Empyrical 模块中的 sharpeRatio 函数进行计算:

use Empyrical
ret = 0.072 0.0697 0.08 0.74 1.49 0.9 0.26 0.9 0.35 0.63
x = sharpeRatio(ret);

2.3. 在 SQL 语句中分组使用

用户经常需要在数据表中对多组数据在每组内进行计算。在以下例子中,我们构造了一个包含2个股票的数据表:

close = 7.2 6.97 7.08 6.74 6.49 5.9 6.26 5.9 5.35 5.63 3.81 3.935 4.04 3.74 3.7 3.33 3.64 3.31 2.69 2.72
date = (2023.03.02 + 0..4 join 7..11).take(20)
symbol = take(`F,10) join take(`GPRO,10)
t = table(symbol, date, close)

对其中每只股票使用 Empyrical 模块中的 simpleReturns 函数进行计算收益:

update t set ret = simpleReturns(close) context by symbol

2.4. 返回多个列的结果

某些函数会返回多个列的结果,例如函数 alphaBeta

直接使用的例子:

use Empyrical

ret = 0.072 0.0697 0.08 0.74 1.49 0.9 0.26 0.9 0.35 0.63
factorret = 0.702 0.97 0.708 1.74 0.49 0.09 1.26 0.59 1.35 0.063
alpha, beta = alphaBeta(ret, factorret);

在 SQL 语句中使用的例子:

use Empyrical

ret = 0.072 0.0697 0.08 0.74 1.49 0.9 0.26 0.9 0.35 0.63 0.702 0.97 0.708 1.74 0.49 0.09 1.26 0.59 1.35 0.063 
factorret = 0.702 0.97 0.708 1.74 0.49 0.09 1.26 0.59 1.35 0.063 0.072 0.0697 0.08 0.74 1.49 0.9 0.26 0.9 0.35 0.63
symbol = take(`F,10) join take(`GPRO,10)
date = (2022.03.02 + 0..4 join 7..11).take(20)
t = table(symbol as sym, date as dt, ret, factorret) 
select sym,alphaBeta(ret, factorret) as `alpha`beta from t context by sym

/*
sym  alpha                beta              
---- -------------------- ------------------
F    2.936093190579921E62 -0.276883617899559
F    2.936093190579921E62 -0.276883617899559
...   
*/

3. 函数计算性能

本节将以 rollSharpeRatio 函数为例做直接使用的性能对比,同时使用真实股票日频数据对所有函数进行分组使用性能对比。

3.1. 直接使用性能对比

在 DolphinDB 中:

use Empyrical

ret= 0.072 0.0697 0.08 0.74 1.49 0.9 0.26 0.9 0.35 0.63
ret = take(ret, 10000000)
timer x = rollSharpeRatio(ret, windows =10)

对一个长度为 10000000 的向量直接使用 Empyrical 模块中的 rollSharpeRatio 函数,耗时为 300.135ms。

与之对应的 Python 代码如下:

import numpy as np
import empyrical as em
import time
ret = np.array([0.072, 0.0697, 0.08, 0.74, 1.49, 0.9, 0.26, 0.9, 0.35, 0.63])
ret = np.tile(ret,10000000)
start_time = time.time()
x = em.roll_sharpe_ratio(ret, 10)
print("--- %s seconds ---" % (time.time() - start_time))

Python Empyrical 库中的 roll_sharpe_ratio 函数耗时为 25965.46ms,是 DolphinDB Empyrical module 中的 rollSharpeRatio 函数的 86 倍。

3.2. 分组使用性能对比

  • 性能测试数据为2019年1月3日至2022年7月1日全部A股的日频收益数据以及模拟因子数据及持有份额比例,总记录数为3,452,106 条。附录提供了测试文件的部分样例数据。解压后放置于[home]下。
  • 计算逻辑为按照股票代码进行分组计算各指标。
  • 为了测试函数计算性能,DolphinDB 和 Python 测试代码都是单线程运行。
  • 如需测试 computeExposureperfAttrib 函数,需额外提供风险因子及投资组合仓位数据,本教程提供模拟数据 factors,可以依据需要进行下载。

测试结果如下表所示:

序号 函数 DolphinDB耗时 Python耗时 Python/DolphinDB
1 simpleReturns 190.7 ms 5,871.1ms 30.78
2 aggregateReturns 238.5 ms 11,167.8 ms 46.83
3 annualReturns 66.8 ms 889.1ms 13.30
4 cumReturns 84.7 ms 2,758.7ms 32.56
5 cumReturnsFinal 82.4 ms 835.4ms 10.13
6 alpha 131.2 ms 6,910.7 ms 52.67
7 rollAlpha 548.1 ms 5,143.4 ms 9.38
8 covarBeta 84.3ms 5,422.9 ms 64.32
9 rollCovarBeta 195.9 ms 4,306.8 ms 21.98
10 alphaBeta 112.5 ms 6,929.8 ms 61.59
11 rollAlphaBeta 547.7 ms 8,676.7 ms 15.84
12 upAlphaBeta 156.1 ms 5,653.1 ms 36.21
13 downAlphaBeta 148.6 ms 5,722.9 ms 38.52
14 betaFragilityHeuristic 262.7 ms 12,906.5 ms 49.14
15 annualVolatility 64.5 ms 776.5 ms 12.03
16 rollAnnualVolatility 136.9 ms 2,869.9 ms 20.96
17 downsideRisk 137.3 ms 679.0 ms 4.94
18 hisValueAtRisk 152.9 ms 710.3 ms 4.64
19 conditionalValueAtRisk 155.8 ms 306.2 ms 1.97
20 gpdRiskEstimates 7.3s 26.4s 3.61
21 tailRatio 235.4 ms 1,224.2 ms 5.20
22 capture 103.7 ms 2,343.0 ms 22.59
23 upCapture 165.7 ms 4,284.7 ms 25.69
24 downCapture 165.6 ms 4,254.3 ms 25.85
25 upDownCapture 232.8 ms 7,380.0 ms 31.70
26 rollUpCapture 30.1 s 2,204.8 s 73.33
27 rollDownCapture 30.4 s 2,195.3 s 72.32
28 rollUpDownCapture 53.7 s 4,146.4 s 77.20
29 omegaRatio 111.6 ms 3,946.0 ms 35.37
30 cumMaxDrawdown 99.4 ms 500.3ms 5.03
31 rollCumMaxDrawdown 1.4s 3.1s 2.11
32 calmarRatio 118.3 ms 1,338.4 ms 11.32
33 sharpeRatio 100.6 ms 974.7 ms 9.68
34 rollSharpeRatio 160.7 ms 3,277.3 ms 20.39
35 excessSharpe 103.6 ms 2,828.9 ms 27.31
36 sortinoRatio 180.5 ms 988.8 ms 5.47
37 rollSortinoRatio 287.0 ms 3,160.3 ms 11.01
38 stabilityOfTimeseries 365.5 ms 1,707.9 ms 4.67
39 computeExposure 183.6 ms 256.5 ms 1.39
40 perfAttrib 251.9 ms 3,484.8 ms 13.83

从测试结果分析可知:

  • DolphinDB Empyrical module 中的函数计算性能大部分都超过了 Python Empyrical 库,最大的性能差距达到 70 倍,普遍性能差距在 9 倍左右。

Python pandas 测试核心代码

grouped = returns.groupby('SecurityID')
cum_returns = grouped['ret'].apply(lambda x: em.cum_returns(x))

DolphinDB 测试核心代码

cumReturns = 
    select SecurityID, DateTime, cumReturns(ret) as cumReturns
    from returns
    context by SecurityID

4. 正确性验证

基于分组使用性能对比中的测试数据和代码,验证 DolphinDB Empyrical module 中函数的计算结果是否和 Python Empyrical 库一致。

4.1. NULL 值的处理

若 Python Empyrical 的输入向量开始包含空值,会将空值带入函数中进行计算。Empyrical 模块采用了相同的策略。

对一个滚动/累积窗口长度为 k 的函数,每组最初的 (k-1) 个位置的结果均为空。这一点 Empyrical 模块与 Python 中 Empyrical 库的结果一致。

DolphinDB 代码与结果:

ret = NULL NULL 0.08 0.74 1.49 0.9 0.26 0.9 0.35 0.63 0.702 0.97 0.708 1.74
0.49 0.09 1.26 0.59 1.35 0.063 
rollAnnualVolatility(ret, windows =10)

/*
[,,,,,,,,,7.107626186006128,6.650903096572676,6.445987651244765,5.4412961691126505,
7.3080574710383885,6.603612950499143,7.349681897878303,7.448075187590415,
7.475411961892134,7.649430305584855,8.584637604465316]
*/

Python 代码与结果:

ret = np.array([np.nan, np.nan, 0.08, 0.74, 1.49, 0.9, 0.26,
0.9, 0.35, 0.63, 0.702, 0.97, 0.708, 1.74, 0.49, 0.09, 1.26, 0.59, 1.35, 0.063])
print(em.roll_annual_volatility(ret,10))

/*
[7.10762619 6.6509031  6.44598765 5.44129617 7.30805747 6.60361295
 7.3496819  7.44807519 7.47541196 7.64943031 8.5846376 ]
 */

4.2. 函数输出形式优化

结果有差异的函数:

  • alphaBeta, upAlphaBeta, downAlphaBeta, gpdRiskEstimates

原因:

Python Empyrical 中,alphaBeta, upAlphaBeta, downAlphaBeta 结果将 alpha、beta 组合为一个二维数组,后续还需要进一步的处理。如:

而在 DolphinDB 的 Empyrical 模块中,我们利用SQL查询语句可以直接将 alpha、beta 拆分为两列:

select SecurityID, alphaBeta(ret, factorRet) as `alpha`beta
from return1
group by SecurityID

对于gpdRiskEstimates,Python 中输出所有估计系数处于一列,如下图所示:

DolphinDB 的 Empyrical 模块中将不同估计系数分为单独的列,更方便结果展示。

5. 实时流计算

在DolphinDB V1.30.3 中发布的响应式状态引擎(createReactiveStateEngine)是许多金融场景流批统一计算中的重要构件,DolphinDB Empyrical module 在开发时就对其做了适配,使得 Empyrical 模块中的大部分函数可以在响应式状态引擎中实现增量计算。

示例代码如下:

//clean environment
def cleanEnvironment(){
    try{ unsubscribeTable(tableName="inputTable",actionName="calculateEmpyrical") } 
    catch(ex){ print(ex) }
    try{ dropStreamEngine("EmpyricalReactiveSateEngine") } catch(ex){ print(ex) }
    try{ dropStreamTable(`inputTable) } catch(ex){ print(ex) }
    try{ dropStreamTable(`outputTable) } catch(ex){ print(ex) }
    undef all
}
cleanEnvironment()
go

//load module
use Empyrical

//load data
schema = table(`DateTime`SecurityID`ret`factor2`factor1`factor3`position as name, 
`DATE`SYMBOL`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE as type)
data=loadText(<YOUR_DIR>+"factors_data.csv" ,schema=schema)
//define stream table
share streamTable(1:0, `DateTime`SecurityID`ret`factor2`factor1`factor3`position, 
`DATE`SYMBOL`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE) as inputTable
share streamTable(1:0,`SecurityID`DateTime`sortinoRatio`annualVolatility`sharpeRatio,
`SYMBOL`DATE`DOUBLE`DOUBLE`DOUBLE) as outputTable

//register stream computing engine
reactiveStateMetrics=<[
    DateTime,
    Empyrical::rollAnnualVolatility(ret) as `sortinoRatio, 
    Empyrical::rollSharpeRatio(ret) as `annualVolatility, 
    Empyrical::rollSortinoRatio(ret) as `sharpeRatio
]>
createReactiveStateEngine(name="EmpyricalReactiveSateEngine", 
metrics=reactiveStateMetrics, dummyTable=inputTable, outputTable=outputTable,
keyColumn=`SecurityID, keepOrder=true)
subscribeTable(tableName="inputTable", actionName="calculateEmpyrical", 
offset=-1, handler=getStreamEngine("EmpyricalReactiveSateEngine"), 
msgAsTable=true, reconnect=true)
//replay data
submitJob("replay","replay",replay{data,inputTable,`tradedate,`tradedate,1000,true})

6. DolphinDB Empyrical 函数列表

6.1 收益处理类函数

函数 语法 解释
simpleReturns simpleReturns(prices) 简单收益
aggregateReturns aggregateReturns(returns,date, convertTo='yearly') 按周、月或年聚合收益
annualReturns annualReturn(returns, period="daily", annualization=NULL) 复合年增长率(CAGR)
cumReturns cumReturns(returns, startingValue=0) 简单收益的累计收益
cumReturnsFinal cumReturnsFinal(returns, startingValue=0) 简单收益的总收益

6.2 alpha&beta类函数

函数 语法 解释
alpha alpha(returns, factorReturns, riskFree=0.0, period="daily", annualization=NULL, beta=NULL) 年化alpha值
rollAlpha rollAlpha(returns, factorReturns, riskFree=0.0, period="daily", annualization=NULL, windows = 10) 滚动窗口alpha
covarBeta covarBeta(returns, factorReturns, riskFree=0.0) beta值
rollCovarBeta rollCovarBeta(returns, factorReturns, riskFree=0.0, windows = 10) 滚动窗口beta
alphaBeta alphaBeta(returns, factorReturns, riskFree=0.0, period="daily", annualization=NULL) 年化alpha和beta值
rollAlphaBeta rollAlphaBeta(returns, factorReturns, riskFree=0.0, period="daily", annualization=NULL, windows=10) 滚动窗口alpha和beta
upAlphaBeta upAlphaBeta(returns, factorReturns, riskFree=0.0, period="daily", annualization=NULL) 基准收益为正时期的alpha,beta
downAlphaBeta downAlphaBeta(returns, factorReturns, riskFree=0.0, period="daily", annualization=NULL) 基准收益为负时期的alpha,beta
betaFragilityHeuristic betaFragilityHeuristic(returns, factorReturns) 估计beta下降时的脆弱性

6.3 风险管理类函数

函数 语法 解释
annualVolatility annualVolatility(returns, period="daily", annualization=NULL, alpha=2.0) 年度波动率
rollAnnualVolatility rollAnnualVolatility(returns, period="daily", annualization=NULL, windows=10, alpha=2.0) 滚动窗口年度波动率
downsideRisk downsideRisk(returns, requiredReturn=0.0, period="daily", annualization=NULL) 确定一个阈值以下的下行偏差
hisValueAtRisk valueAtRisk(returns, cutoff=0.05) 在险价值
conditionalValueAtRisk conditionalValueAtRisk(returns, cutoff=0.05) 条件在险价值
gpdRiskEstimates gpdRiskEstimates(returns, varP=0.01) 使用广义Pareto分布(GPD)来估计VaR和ES
tailRatio tailRatio(returns) 右尾部(95%)和左尾部(5%)之间的比率

6.4 捕获比率类函数

函数 语法 解释
capture capture(returns, factorReturns, period="daily", annualization=NULL) capture比率
upCapture upCapture(returns, factorReturns, period="daily", annualization=NULL) 在基准收益为正的时期内的上行capture比率
downCapture downCapture(returns, factorReturns, period="daily", annualization=NULL) 在基准收益为负的时期内的下行capture比率
upDownCapture upDownCapture(returns, factorReturns, period="daily", annualization=NULL) 上行capture比率和下行capture比率的比值
rollUpCapture rollUpCapture(returns, factorReturns, windows=10, period="daily", annualization=NULL) 滚动上行capture比率
rollDownCapture rollDownCapture(returns, factorReturns, windows=10, period="daily", annualization=NULL) 滚动下行capture比率
rollUpDownCapture rollUpDownCapture(returns, factorReturns, windows=10, period="daily", annualization=NULL) 滚动上行capture比率和下行capture比率的比值

6.5 风险调整收益类函数

函数 语法 解释
omegaRatio omegaRatio(returns, period="daily", riskFree=0.0, requiredReturn=0.0, annualization=NULL) omega比率
cumMaxDrawdown maxdrawdown(returns) 最大回撤
rollCumMaxDrawdown rollCumMaxDrawdown(returns, windows = 10) 滚动窗口最大回撤
calmarRatio calmarRatio(returns, period="daily", annualization=NULL) 策略的Calmar比率,即回撤比率
sharpeRatio sharpeRatio(returns, riskFree=0.0, period="daily", annualization=NULL) 夏普比率
rollSharpeRatio rollSharpeRatio(returns, riskFree=0.0, period="daily", annualization=NULL, windows=10) 滚动夏普比率
excessSharpe excessSharpe(returns, factorReturns) 超额夏普比率
sortinoRatio sortinoRatio(returns, requiredReturn=0.0, period="daily", annualization=NULL) 索提诺比率
rollSortinoRatio rollSortinoRatio(returns, requiredReturn=0.0, period="daily", annualization=NULL, windows=10) 滚动索提诺比率

6.6 收益稳定及绩效归因类函数

函数 语法 解释
stabilityOfTimeseries stabilityOfTimeseries(returns) 积累对数收益的线性拟合的R^2
computeExposure computeExposures( factorLoadings, factorColumns=`factor1`factor2`factor3, positionName=`position`position`position, dtName=`DateTime) 每日风险因子敞口
perfAttrib perfAttrib(returns, factorReturns, factorLoadings, factorColumns=`factor1`factor2`factor3, positionName=`position`position`position, dtName=`DateTime) 绩效归因(将投资回报归因于一组风险因素)