7
7
import numpy as np
8
8
import pandas as pd
9
9
import warnings
10
- from typing import Union
10
+ from typing import Union , Literal
11
11
12
12
from ..log import get_module_logger
13
13
from ..utils import get_date_range
24
24
logger = get_module_logger ("Evaluate" )
25
25
26
26
27
- def risk_analysis (r , N : int = None , freq : str = "day" ):
27
+ def risk_analysis (r , N : int = None , freq : str = "day" , mode : Literal [ "sum" , "product" ] = "sum" ):
28
28
"""Risk Analysis
29
29
NOTE:
30
- The calculation of annulaized return is different from the definition of annualized return.
30
+ The calculation of annualized return is different from the definition of annualized return.
31
31
It is implemented by design.
32
- Qlib tries to cumulated returns by summation instead of production to avoid the cumulated curve being skewed exponentially.
32
+ Qlib tries to cumulate returns by summation instead of production to avoid the cumulated curve being skewed exponentially.
33
33
All the calculation of annualized returns follows this principle in Qlib.
34
34
35
- TODO: add a parameter to enable calculating metrics with production accumulation of return.
36
-
37
35
Parameters
38
36
----------
39
37
r : pandas.Series
@@ -42,11 +40,14 @@ def risk_analysis(r, N: int = None, freq: str = "day"):
42
40
scaler for annualizing information_ratio (day: 252, week: 50, month: 12), at least one of `N` and `freq` should exist
43
41
freq: str
44
42
analysis frequency used for calculating the scaler, at least one of `N` and `freq` should exist
43
+ mode: Literal["sum", "product"]
44
+ the method by which returns are accumulated:
45
+ - "sum": Arithmetic accumulation (linear returns).
46
+ - "product": Geometric accumulation (compounded returns).
45
47
"""
46
48
47
49
def cal_risk_analysis_scaler (freq ):
48
50
_count , _freq = Freq .parse (freq )
49
- # len(D.calendar(start_time='2010-01-01', end_time='2019-12-31', freq='day')) = 2384
50
51
_freq_scaler = {
51
52
Freq .NORM_FREQ_MINUTE : 240 * 238 ,
52
53
Freq .NORM_FREQ_DAY : 238 ,
@@ -62,11 +63,26 @@ def cal_risk_analysis_scaler(freq):
62
63
if N is None :
63
64
N = cal_risk_analysis_scaler (freq )
64
65
65
- mean = r .mean ()
66
- std = r .std (ddof = 1 )
67
- annualized_return = mean * N
66
+ if mode == "sum" :
67
+ mean = r .mean ()
68
+ std = r .std (ddof = 1 )
69
+ annualized_return = mean * N
70
+ max_drawdown = (r .cumsum () - r .cumsum ().cummax ()).min ()
71
+ elif mode == "product" :
72
+ cumulative_curve = (1 + r ).cumprod ()
73
+ # geometric mean (compound annual growth rate)
74
+ mean = cumulative_curve .iloc [- 1 ] ** (1 / len (r )) - 1
75
+ # volatility of log returns
76
+ std = np .log (1 + r ).std (ddof = 1 )
77
+
78
+ cumulative_return = cumulative_curve .iloc [- 1 ] - 1
79
+ annualized_return = (1 + cumulative_return ) ** (N / len (r )) - 1
80
+ # max percentage drawdown from peak cumulative product
81
+ max_drawdown = (cumulative_curve / cumulative_curve .cummax () - 1 ).min ()
82
+ else :
83
+ raise ValueError (f"risk_analysis accumulation mode { mode } is not supported. Expected `sum` or `product`." )
84
+
68
85
information_ratio = mean / std * np .sqrt (N )
69
- max_drawdown = (r .cumsum () - r .cumsum ().cummax ()).min ()
70
86
data = {
71
87
"mean" : mean ,
72
88
"std" : std ,
0 commit comments