@@ -36,17 +36,17 @@ def _calculate_return_metrics(self) -> Dict[str, float]:
3636 cagr = self ._calculate_cagr ()
3737
3838 return {
39- "total_return" : total_return ,
40- "cagr" : cagr ,
41- "annualized_return" : cagr ,
39+ "total_return" : float ( total_return ) ,
40+ "cagr" : float ( cagr ) ,
41+ "annualized_return" : float ( cagr ) ,
4242 }
4343
4444 def _calculate_risk_metrics (self ) -> Dict [str , float ]:
4545 """Calculate risk-related metrics."""
46- vol = self .returns .std () * np .sqrt (252 )
46+ vol = float ( self .returns .std (ddof = 1 ) * np .sqrt (252 ) )
4747 downside_returns = self .returns [self .returns < 0 ]
4848 downside_vol = (
49- downside_returns .std () * np .sqrt (252 ) if len (downside_returns ) > 0 else 0
49+ float ( downside_returns .std (ddof = 1 ) * np .sqrt (252 )) if len (downside_returns ) > 0 else 0. 0
5050 )
5151
5252 max_drawdown , drawdown_duration = self ._calculate_drawdown ()
@@ -56,23 +56,20 @@ def _calculate_risk_metrics(self) -> Dict[str, float]:
5656 "downside_volatility" : downside_vol ,
5757 "max_drawdown" : max_drawdown ,
5858 "drawdown_duration" : drawdown_duration ,
59- "var_95" : self .returns .quantile (0.05 ),
60- "cvar_95" : self .returns [self .returns <= self .returns .quantile (0.05 )].mean (),
59+ "var_95" : float ( self .returns .quantile (0.05 ) ),
60+ "cvar_95" : float ( self .returns [self .returns <= self .returns .quantile (0.05 )].mean () ),
6161 }
6262
6363 def _calculate_ratio_metrics (self ) -> Dict [str , float ]:
6464 """Calculate risk-adjusted ratio metrics."""
6565 cagr = self ._calculate_cagr ()
66- vol = self .returns .std () * np .sqrt (252 )
66+ vol = float ( self .returns .std (ddof = 1 ) * np .sqrt (252 ) )
6767 downside_vol = self ._calculate_downside_vol ()
6868
69- sharpe = cagr / vol if vol > 0 else 0
70- sortino = cagr / downside_vol if downside_vol > 0 else 0
71- calmar = (
72- cagr / abs (self ._calculate_drawdown ()[0 ])
73- if self ._calculate_drawdown ()[0 ] < 0
74- else 0
75- )
69+ sharpe = float (cagr / vol ) if vol > 0 else 0.0
70+ sortino = float (cagr / downside_vol ) if downside_vol > 0 else 0.0
71+ dd , _ = self ._calculate_drawdown ()
72+ calmar = float (cagr / abs (dd )) if dd < 0 else 0.0
7673
7774 return {
7875 "sharpe_ratio" : sharpe ,
@@ -98,15 +95,6 @@ def _calculate_benchmark_metrics(self) -> Dict[str, float]:
9895
9996 x = benchmark_returns .values .astype (float )
10097 y = strategy_returns .values .astype (float )
101-
102- # print("DEBUG_RISK: len=", len(x))
103- # print("DEBUG_RISK: x_mean, y_mean =", x.mean(), y.mean())
104- # print("DEBUG_RISK: x_var, y_var =", np.var(x, ddof=0), np.var(y, ddof=0))
105- # print("DEBUG_RISK: cov_xy =", np.mean((x - x.mean()) * (y - y.mean())))
106- # # print first 8 values to visually inspect alignment
107- # print("DEBUG_RISK: x[:8] =", x[:8])
108- # print("DEBUG_RISK: y[:8] =", y[:8])
109-
11098
11199 # If benchmark has (near) zero variance, beta is undefined; return 0.0 to keep old behavior.
112100 if np .allclose (np .var (x , ddof = 0 ), 0.0 ):
@@ -115,7 +103,6 @@ def _calculate_benchmark_metrics(self) -> Dict[str, float]:
115103 # Use stable least-squares (with intercept) to get slope (beta)
116104 # design matrix: [x, 1]
117105 A = np .vstack ([x , np .ones_like (x )]).T
118- # lstsq returns (coeffs, residuals, rank, s); coeffs = [slope, intercept]
119106 coeffs , * _ = np .linalg .lstsq (A , y , rcond = None )
120107 slope = float (coeffs [0 ])
121108 beta = slope
@@ -139,16 +126,6 @@ def _calculate_benchmark_metrics(self) -> Dict[str, float]:
139126 "information_ratio" : info_ratio ,
140127 "active_return" : float (strategy_cagr - benchmark_cagr ),
141128 }
142- # print("DEBUG_RISK: len=", len(x))
143- # print("DEBUG_RISK: x_mean, y_mean =", x.mean(), y.mean())
144- # print("DEBUG_RISK: x_var, y_var =", np.var(x, ddof=0), np.var(y, ddof=0))
145- # print("DEBUG_RISK: cov_xy =", np.mean((x - x.mean()) * (y - y.mean())))
146- # # print first 8 values to visually inspect alignment
147- # print("DEBUG_RISK: x[:8] =", x[:8])
148- # print("DEBUG_RISK: y[:8] =", y[:8])
149-
150-
151-
152129
153130 def _calculate_cagr (self ) -> float :
154131 """Calculate Compound Annual Growth Rate."""
@@ -162,20 +139,20 @@ def _calculate_cagr_from_returns(self, returns: pd.Series) -> float:
162139 total_return = (1 + returns ).prod () - 1
163140 years = (returns .index [- 1 ] - returns .index [0 ]).days / 365.25
164141
165- return ( 1 + total_return ) ** (1 / years ) - 1 if years > 0 else 0
142+ return float (( 1 + total_return ) ** (1 / years ) - 1 ) if years > 0 else 0. 0
166143
167144 def _calculate_downside_vol (self ) -> float :
168145 """Calculate downside volatility (for Sortino ratio)."""
169146 downside_returns = self .returns [self .returns < 0 ]
170- return downside_returns .std () * np .sqrt (252 ) if len (downside_returns ) > 0 else 0
147+ return float ( downside_returns .std (ddof = 1 ) * np .sqrt (252 )) if len (downside_returns ) > 0 else 0. 0
171148
172149 def _calculate_drawdown (self ) -> Tuple [float , int ]:
173150 """Calculate maximum drawdown and duration."""
174151 cumulative = (1 + self .returns ).cumprod ()
175152 running_max = cumulative .expanding ().max ()
176153 drawdown = (cumulative / running_max ) - 1
177154
178- max_drawdown = drawdown .min ()
155+ max_drawdown = float ( drawdown .min ()) if not drawdown . isna (). all () else 0.0
179156
180157 # Handle edge cases safely
181158 if drawdown .isna ().all ():
@@ -191,16 +168,10 @@ def _calculate_drawdown(self) -> Tuple[float, int]:
191168 try :
192169 prior_max_mask = running_max [running_max .index <= max_dd_period ]
193170 drawdown_start_val = prior_max_mask .max ()
194- start_candidates = prior_max_mask [
195- prior_max_mask == drawdown_start_val
196- ].index
197- drawdown_start = (
198- start_candidates [- 1 ]
199- if len (start_candidates ) > 0
200- else running_max .index [0 ]
201- )
202- drawdown_duration = (max_dd_period - drawdown_start ).days
171+ start_candidates = prior_max_mask [prior_max_mask == drawdown_start_val ].index
172+ drawdown_start = start_candidates [- 1 ] if len (start_candidates ) > 0 else running_max .index [0 ]
173+ drawdown_duration = int ((max_dd_period - drawdown_start ).days )
203174 except Exception :
204175 drawdown_duration = 0
205176
206- return float ( max_drawdown ), int ( drawdown_duration )
177+ return max_drawdown , drawdown_duration
0 commit comments