@@ -938,7 +938,10 @@ def sortino(returns, rf=0, periods=252, annualize=True, smart=False):
938938 downside = downside * autocorr_penalty (returns )
939939
940940 # Calculate base Sortino ratio
941- res = returns .mean () / downside
941+ if downside == 0 :
942+ res = _np .nan
943+ else :
944+ res = returns .mean () / downside
942945
943946 # Annualize if requested
944947 if annualize :
@@ -1579,7 +1582,10 @@ def ulcer_performance_index(returns, rf=0):
15791582 >>> print(f"Ulcer Performance Index: {upi_value:.4f}")
15801583 """
15811584 # Calculate excess return divided by Ulcer Index
1582- return (comp (returns ) - rf ) / ulcer_index (returns )
1585+ ulcer = ulcer_index (returns )
1586+ if ulcer == 0 :
1587+ return _np .nan
1588+ return (comp (returns ) - rf ) / ulcer
15831589
15841590
15851591def upi (returns , rf = 0 ):
@@ -1627,10 +1633,17 @@ def serenity_index(returns, rf=0):
16271633 dd = to_drawdown_series (returns )
16281634
16291635 # Calculate pitfall measure using conditional value at risk of drawdowns
1630- pitfall = - cvar (dd ) / returns .std ()
1631-
1636+ std_returns = returns .std ()
1637+ if std_returns == 0 :
1638+ return _np .nan
1639+
1640+ pitfall = - cvar (dd ) / std_returns
1641+ denominator = ulcer_index (returns ) * pitfall
1642+
16321643 # Calculate serenity index incorporating both ulcer index and pitfall
1633- return (returns .sum () - rf ) / (ulcer_index (returns ) * pitfall )
1644+ if denominator == 0 :
1645+ return _np .nan
1646+ return (returns .sum () - rf ) / denominator
16341647
16351648
16361649def risk_of_ruin (returns , prepare_returns = True ):
@@ -1876,7 +1889,10 @@ def payoff_ratio(returns, prepare_returns=True):
18761889 returns = _utils ._prepare_returns (returns )
18771890
18781891 # Calculate ratio of average win to absolute average loss
1879- return avg_win (returns ) / abs (avg_loss (returns ))
1892+ avg_loss_val = avg_loss (returns )
1893+ if avg_loss_val == 0 :
1894+ return _np .nan
1895+ return avg_win (returns ) / abs (avg_loss_val )
18801896
18811897
18821898def win_loss_ratio (returns , prepare_returns = True ):
@@ -1931,8 +1947,8 @@ def profit_ratio(returns, prepare_returns=True):
19311947 return float ('inf' )
19321948
19331949 # Calculate win and loss ratios
1934- win_ratio = abs (wins .mean () / wins .count ())
1935- loss_ratio = abs (loss .mean () / loss .count ())
1950+ win_ratio = abs (wins .mean () / wins .count ()) if wins . count () > 0 else 0
1951+ loss_ratio = abs (loss .mean () / loss .count ()) if loss . count () > 0 else 0
19361952
19371953 try :
19381954 if loss_ratio == 0 :
@@ -2062,7 +2078,10 @@ def outlier_win_ratio(returns, quantile=0.99, prepare_returns=True):
20622078 returns = _utils ._prepare_returns (returns )
20632079
20642080 # Calculate ratio of high quantile to mean positive return
2065- return returns .quantile (quantile ).mean () / returns [returns >= 0 ].mean ()
2081+ positive_mean = returns [returns >= 0 ].mean ()
2082+ if _pd .isna (positive_mean ) or positive_mean == 0 :
2083+ return _np .nan
2084+ return returns .quantile (quantile ).mean () / positive_mean
20662085
20672086
20682087def outlier_loss_ratio (returns , quantile = 0.01 , prepare_returns = True ):
@@ -2090,7 +2109,10 @@ def outlier_loss_ratio(returns, quantile=0.01, prepare_returns=True):
20902109 returns = _utils ._prepare_returns (returns )
20912110
20922111 # Calculate ratio of low quantile to mean negative return
2093- return returns .quantile (quantile ).mean () / returns [returns < 0 ].mean ()
2112+ negative_mean = returns [returns < 0 ].mean ()
2113+ if _pd .isna (negative_mean ) or negative_mean == 0 :
2114+ return _np .nan
2115+ return returns .quantile (quantile ).mean () / negative_mean
20942116
20952117
20962118def recovery_factor (returns , rf = 0.0 , prepare_returns = True ):
@@ -2154,7 +2176,10 @@ def risk_return_ratio(returns, prepare_returns=True):
21542176 returns = _utils ._prepare_returns (returns )
21552177
21562178 # Calculate mean return divided by standard deviation
2157- return returns .mean () / returns .std ()
2179+ std = returns .std ()
2180+ if std == 0 :
2181+ return _np .nan
2182+ return returns .mean () / std
21582183
21592184
21602185def _get_baseline_value (prices ):
@@ -2313,6 +2338,8 @@ def kelly_criterion(returns, prepare_returns=True):
23132338 win_prob = win_rate (returns )
23142339 lose_prob = 1 - win_prob
23152340
2341+ if win_loss_ratio == 0 or _pd .isna (win_loss_ratio ):
2342+ return _np .nan
23162343 return ((win_loss_ratio * win_prob ) - lose_prob ) / win_loss_ratio
23172344
23182345
@@ -2448,7 +2475,10 @@ def greeks(returns, benchmark, periods=252.0, prepare_returns=True):
24482475 matrix = _np .cov (returns , benchmark )
24492476
24502477 # Calculate beta (sensitivity to benchmark movements)
2451- beta = matrix [0 , 1 ] / matrix [1 , 1 ]
2478+ if matrix [1 , 1 ] == 0 :
2479+ beta = _np .nan
2480+ else :
2481+ beta = matrix [0 , 1 ] / matrix [1 , 1 ]
24522482
24532483 # Calculate alpha (excess return after adjusting for beta)
24542484 alpha = returns .mean () - beta * benchmark .mean ()
@@ -2507,8 +2537,8 @@ def rolling_greeks(returns, benchmark, periods=252, prepare_returns=True):
25072537 corr = df .rolling (int (periods )).corr ().unstack ()["returns" ]["benchmark" ]
25082538 std = df .rolling (int (periods )).std ()
25092539
2510- # Calculate rolling beta
2511- beta = corr * std ["returns" ] / std ["benchmark" ]
2540+ # Calculate rolling beta (protect against division by zero)
2541+ beta = corr * std ["returns" ] / std ["benchmark" ]. replace ( 0 , _np . nan )
25122542
25132543 # Calculate rolling alpha (not annualized for rolling version)
25142544 alpha = df ["returns" ].mean () - beta * df ["benchmark" ].mean ()
@@ -2587,7 +2617,8 @@ def compare(
25872617 )
25882618
25892619 # Calculate performance multiplier and win/loss indicator
2590- data ["Multiplier" ] = data ["Returns" ] / data ["Benchmark" ]
2620+ # Protect against division by zero in benchmark
2621+ data ["Multiplier" ] = data ["Returns" ] / data ["Benchmark" ].replace (0 , _np .nan )
25912622 data ["Won" ] = _np .where (data ["Returns" ] >= data ["Benchmark" ], "+" , "-" )
25922623
25932624 # Handle DataFrame input (multiple strategies)
0 commit comments