BUG: Fix numerous cumulative and period risk calculations.

The calculations that are expected to change are:
- cumulative.beta
- cumulative.alpha
- cumulative.information
- cumulative.sharpe
- period.sortino

* Explanation of how risk calculations are changing

** Risk Fixes for Both Period and Cumulative

*** Downside Risk

   Use sample instead of population for standard deviation.

   Add a rounding factor, so that if the two values are close for a given
   dt, that they do not count as a downside value, which would throw off
   the denominator of the standard deviation of the downside diffs.

*** Standard Deviation Type

    Across the board the standard deviation has been standardized to using
    a 'sample' calculation, whereas before cumulative risk was monstly using
    'population'. Using `ddof=1` with `np.std` calculates as if the values
    are a sample.

** Cumulative Risk Fixes

*** Beta

   Use the daily algorithm returns and benchmarks instead of annualized
   mean returns.

*** Volatility

   Use sample instead of population with standard deviation.

   The volatility is an input to other calculations so this change affects
   Sharpe and Information ratio calculations.

*** Information Ratio

   The benchmark returns input is changed from annualized benchmark returns
   to the annualized mean returns.

*** Alpha

   The benchmark returns input is changed from annualized benchmark returns
   to the annualized mean returns.

** Period Risk Fixes

*** Sortino

    Use the downside risk of the daily return vs. the mean algorithm returns
    for the minimum acceptable return instead of the treasury return.

    The above required adding the calculation of the mean algorithm returns
    for period risk.

    Also, use algorithm_period_returns and tresaury_period_return as the
    cumulative Sortino does, instead of using algorithm returns for both
    inputs into the Sortino calculation.

* Other Supporting Changes

** answer_key

   Add new mappings for downside risk and Sortino as well as
   re-address the index mappings because of changes to the answer key
   spread sheet.

** test_risk_cumulative

   Change the decimal precision to expect higher precision.
   The calculations are now more aligned with the answer key, so we can
   expect higher precision. In particular now that the standard deviation
   type matches everywhere in both the Python implementation and the answer
   sheet, the precision of the first value no longer has to be glossed over.

** test_events_through_risk

  Change the results which are used as a canary for risk changes,
  since we do expect Sharpe to change with this change..
This commit is contained in:
Eddie Hebert
2014-04-14 16:44:28 -04:00
parent 12f6b95982
commit 7cc24cec1f
9 changed files with 187 additions and 183 deletions
+50 -36
View File
@@ -163,66 +163,80 @@ class AnswerKey(object):
# Below matches the inconsistent capitalization in spreadsheet
'BENCHMARK_PERIOD_RETURNS': {
'Monthly': DataIndex('s_p', 'P', 8, 19),
'3-Month': DataIndex('s_p', 'Q', 10, 19),
'6-month': DataIndex('s_p', 'R', 13, 19),
'year': DataIndex('s_p', 'S', 19, 19),
'Monthly': DataIndex('s_p', 'R', 8, 19),
'3-Month': DataIndex('s_p', 'S', 10, 19),
'6-month': DataIndex('s_p', 'T', 13, 19),
'year': DataIndex('s_p', 'U', 19, 19),
},
'BENCHMARK_PERIOD_VOLATILITY': {
'Monthly': DataIndex('s_p', 'T', 8, 19),
'3-Month': DataIndex('s_p', 'U', 10, 19),
'6-month': DataIndex('s_p', 'V', 13, 19),
'year': DataIndex('s_p', 'W', 19, 19),
'Monthly': DataIndex('s_p', 'V', 8, 19),
'3-Month': DataIndex('s_p', 'W', 10, 19),
'6-month': DataIndex('s_p', 'X', 13, 19),
'year': DataIndex('s_p', 'Y', 19, 19),
},
'ALGORITHM_PERIOD_RETURNS': {
'Monthly': DataIndex('Sim Period', 'V', 23, 34),
'3-Month': DataIndex('Sim Period', 'W', 25, 34),
'6-month': DataIndex('Sim Period', 'X', 28, 34),
'year': DataIndex('Sim Period', 'Y', 34, 34),
},
'ALGORITHM_PERIOD_VOLATILITY': {
'Monthly': DataIndex('Sim Period', 'Z', 23, 34),
'3-Month': DataIndex('Sim Period', 'AA', 25, 34),
'6-month': DataIndex('Sim Period', 'AB', 28, 34),
'year': DataIndex('Sim Period', 'AC', 34, 34),
},
'ALGORITHM_PERIOD_SHARPE': {
'Monthly': DataIndex('Sim Period', 'AD', 23, 34),
'3-Month': DataIndex('Sim Period', 'AE', 25, 34),
'6-month': DataIndex('Sim Period', 'AF', 28, 34),
'year': DataIndex('Sim Period', 'AG', 34, 34),
},
'ALGORITHM_PERIOD_BETA': {
'ALGORITHM_PERIOD_VOLATILITY': {
'Monthly': DataIndex('Sim Period', 'AH', 23, 34),
'3-Month': DataIndex('Sim Period', 'AI', 25, 34),
'6-month': DataIndex('Sim Period', 'AJ', 28, 34),
'year': DataIndex('Sim Period', 'AK', 34, 34),
},
'ALGORITHM_PERIOD_ALPHA': {
'ALGORITHM_PERIOD_SHARPE': {
'Monthly': DataIndex('Sim Period', 'AL', 23, 34),
'3-Month': DataIndex('Sim Period', 'AM', 25, 34),
'6-month': DataIndex('Sim Period', 'AN', 28, 34),
'year': DataIndex('Sim Period', 'AO', 34, 34),
},
'ALGORITHM_PERIOD_BETA': {
'Monthly': DataIndex('Sim Period', 'AP', 23, 34),
'3-Month': DataIndex('Sim Period', 'AQ', 25, 34),
'6-month': DataIndex('Sim Period', 'AR', 28, 34),
'year': DataIndex('Sim Period', 'AS', 34, 34),
},
'ALGORITHM_PERIOD_ALPHA': {
'Monthly': DataIndex('Sim Period', 'AT', 23, 34),
'3-Month': DataIndex('Sim Period', 'AU', 25, 34),
'6-month': DataIndex('Sim Period', 'AV', 28, 34),
'year': DataIndex('Sim Period', 'AW', 34, 34),
},
'ALGORITHM_PERIOD_BENCHMARK_VARIANCE': {
'Monthly': DataIndex('Sim Period', 'BB', 23, 34),
'3-Month': DataIndex('Sim Period', 'BC', 25, 34),
'6-month': DataIndex('Sim Period', 'BD', 28, 34),
'year': DataIndex('Sim Period', 'BE', 34, 34),
'Monthly': DataIndex('Sim Period', 'BJ', 23, 34),
'3-Month': DataIndex('Sim Period', 'BK', 25, 34),
'6-month': DataIndex('Sim Period', 'BL', 28, 34),
'year': DataIndex('Sim Period', 'BM', 34, 34),
},
'ALGORITHM_PERIOD_COVARIANCE': {
'Monthly': DataIndex('Sim Period', 'AX', 23, 34),
'3-Month': DataIndex('Sim Period', 'AY', 25, 34),
'6-month': DataIndex('Sim Period', 'AZ', 28, 34),
'year': DataIndex('Sim Period', 'BA', 34, 34),
'Monthly': DataIndex('Sim Period', 'BF', 23, 34),
'3-Month': DataIndex('Sim Period', 'BG', 25, 34),
'6-month': DataIndex('Sim Period', 'BH', 28, 34),
'year': DataIndex('Sim Period', 'BI', 34, 34),
},
'ALGORITHM_PERIOD_DOWNSIDE_RISK': {
'Monthly': DataIndex('Sim Period', 'BN', 23, 34),
'3-Month': DataIndex('Sim Period', 'BO', 25, 34),
'6-month': DataIndex('Sim Period', 'BP', 28, 34),
'year': DataIndex('Sim Period', 'BQ', 34, 34),
},
'ALGORITHM_PERIOD_SORTINO': {
'Monthly': DataIndex('Sim Period', 'BR', 23, 34),
'3-Month': DataIndex('Sim Period', 'BS', 25, 34),
'6-month': DataIndex('Sim Period', 'BT', 28, 34),
'year': DataIndex('Sim Period', 'BU', 34, 34),
},
'ALGORITHM_RETURN_VALUES': DataIndex(
@@ -241,16 +255,16 @@ class AnswerKey(object):
'Sim Cumulative', 'V', 4, 254),
'CUMULATIVE_INFORMATION': DataIndex(
'Sim Cumulative', 'Y', 4, 254),
'Sim Cumulative', 'AA', 4, 254),
'CUMULATIVE_BETA': DataIndex(
'Sim Cumulative', 'AB', 4, 254),
'Sim Cumulative', 'AD', 4, 254),
'CUMULATIVE_ALPHA': DataIndex(
'Sim Cumulative', 'AC', 4, 254),
'Sim Cumulative', 'AE', 4, 254),
'CUMULATIVE_MAX_DRAWDOWN': DataIndex(
'Sim Cumulative', 'AF', 4, 254),
'Sim Cumulative', 'AH', 4, 254),
}
+1
View File
@@ -13,3 +13,4 @@ cc507b6fca18aabadac69657181edd4e
651e611e723e2a58b1ded91d0cd39b66
d62fce39ec78f032165d8f356bba5c2c
97632f6f64dfc4a2de09882419a79421
79d117cd4849745bf72ee1fd7442ef89
+4 -10
View File
@@ -71,15 +71,13 @@ class TestRisk(unittest.TestCase):
np.testing.assert_almost_equal(
self.cumulative_metrics_06.metrics.sharpe[dt],
value,
decimal=2,
err_msg="Mismatch at %s" % (dt,))
def test_downside_risk_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.downside_risk.iterkv():
np.testing.assert_almost_equal(
self.cumulative_metrics_06.metrics.downside_risk[dt],
value,
decimal=2,
self.cumulative_metrics_06.metrics.downside_risk[dt],
err_msg="Mismatch at %s" % (dt,))
def test_sortino_06(self):
@@ -87,15 +85,14 @@ class TestRisk(unittest.TestCase):
np.testing.assert_almost_equal(
self.cumulative_metrics_06.metrics.sortino[dt],
value,
decimal=2,
decimal=4,
err_msg="Mismatch at %s" % (dt,))
def test_information_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.information.iterkv():
np.testing.assert_almost_equal(
self.cumulative_metrics_06.metrics.information[dt],
value,
decimal=2,
self.cumulative_metrics_06.metrics.information[dt],
err_msg="Mismatch at %s" % (dt,))
def test_alpha_06(self):
@@ -103,15 +100,13 @@ class TestRisk(unittest.TestCase):
np.testing.assert_almost_equal(
self.cumulative_metrics_06.metrics.alpha[dt],
value,
decimal=2,
err_msg="Mismatch at %s" % (dt,))
def test_beta_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.beta.iterkv():
np.testing.assert_almost_equal(
self.cumulative_metrics_06.metrics.beta[dt],
value,
decimal=2,
self.cumulative_metrics_06.metrics.beta[dt],
err_msg="Mismatch at %s" % (dt,))
def test_max_drawdown_06(self):
@@ -119,5 +114,4 @@ class TestRisk(unittest.TestCase):
np.testing.assert_almost_equal(
self.cumulative_metrics_06.max_drawdowns[dt],
value,
decimal=2,
err_msg="Mismatch at %s" % (dt,))
+37 -38
View File
@@ -198,45 +198,44 @@ class TestRisk(unittest.TestCase):
[x.sharpe for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SHARPE['year'])
def test_algorithm_downside_risk_06(self):
np.testing.assert_almost_equal(
[x.downside_risk for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_DOWNSIDE_RISK['Monthly'],
decimal=4)
np.testing.assert_almost_equal(
[x.downside_risk for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_DOWNSIDE_RISK['3-Month'],
decimal=4)
np.testing.assert_almost_equal(
[x.downside_risk for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_DOWNSIDE_RISK['6-month'],
decimal=4)
np.testing.assert_almost_equal(
[x.downside_risk for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_DOWNSIDE_RISK['year'],
decimal=4)
def test_algorithm_sortino_06(self):
self.assertEqual([round(x.sortino, 3)
for x in self.metrics_06.month_periods],
[4.491,
-2.842,
-2.052,
3.898,
7.023,
-8.532,
3.079,
-0.354,
-1.125,
3.009,
3.277,
-3.122])
self.assertEqual([round(x.sortino, 3)
for x in self.metrics_06.three_month_periods],
[-0.769,
-1.043,
6.677,
-2.77,
-3.209,
-6.769,
1.253,
1.085,
3.659,
1.674])
self.assertEqual([round(x.sortino, 3)
for x in self.metrics_06.six_month_periods],
[-2.728,
-3.258,
-1.84,
-1.366,
-1.845,
-3.415,
2.238])
self.assertEqual([round(x.sortino, 3)
for x in self.metrics_06.year_periods],
[-0.524])
np.testing.assert_almost_equal(
[x.sortino for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SORTINO['Monthly'],
decimal=3)
np.testing.assert_almost_equal(
[x.sortino for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SORTINO['3-Month'],
decimal=3)
np.testing.assert_almost_equal(
[x.sortino for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SORTINO['6-month'],
decimal=3)
np.testing.assert_almost_equal(
[x.sortino for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SORTINO['year'],
decimal=3)
def test_algorithm_information_06(self):
self.assertEqual([round(x.information, 3)
+2 -2
View File
@@ -145,8 +145,8 @@ class TestEventsThroughRisk(unittest.TestCase):
# at least be an early warning against changes.
expected_sharpe = {
first_date: np.nan,
second_date: -31.56903265,
third_date: -11.459888981,
second_date: -22.322677,
third_date: -9.353741
}
for bar in gen:
+1
View File
@@ -22,6 +22,7 @@ class TestTradeSimulation(TestCase):
def test_minutely_emissions_generate_performance_stats_for_last_day(self):
params = factory.create_simulation_parameters(num_days=1)
params.data_frequency = 'minute'
params.emission_rate = 'minute'
algo = NoopAlgorithm()
algo.run(source=[], sim_params=params)
+62 -76
View File
@@ -31,6 +31,8 @@ from . risk import (
check_entry,
choose_treasury,
downside_risk,
sharpe_ratio,
sortino_ratio,
)
log = logbook.Logger('Risk Cumulative')
@@ -40,54 +42,6 @@ choose_treasury = functools.partial(choose_treasury, lambda *args: '10year',
compound=False)
def sharpe_ratio(algorithm_volatility, annualized_return, treasury_return):
"""
http://en.wikipedia.org/wiki/Sharpe_ratio
Args:
algorithm_volatility (float): Algorithm volatility.
algorithm_return (float): Algorithm return percentage.
treasury_return (float): Treasury return percentage.
Returns:
float. The Sharpe ratio.
"""
if zp_math.tolerant_equals(algorithm_volatility, 0):
return np.nan
return (
(annualized_return - treasury_return)
# The square of the annualization factor is in the volatility,
# because the volatility is also annualized,
# i.e. the sqrt(annual factor) is in the volatility's numerator.
# So to have the the correct annualization factor for the
# Sharpe value's numerator, which should be the sqrt(annual factor).
# The square of the sqrt of the annual factor, i.e. the annual factor
# itself, is needed in the numerator to factor out the division by
# its square root.
/ algorithm_volatility)
def sortino_ratio(annualized_algorithm_return, treasury_return, downside_risk):
"""
http://en.wikipedia.org/wiki/Sortino_ratio
Args:
algorithm_returns (np.array-like):
Returns from algorithm lifetime.
algorithm_period_return (float):
Algorithm return percentage from latest period.
mar (float): Minimum acceptable return.
Returns:
float. The Sortino ratio.
"""
if np.isnan(downside_risk) or zp_math.tolerant_equals(downside_risk, 0):
return 0.0
return (annualized_algorithm_return - treasury_return) / downside_risk
def information_ratio(algo_volatility, algorithm_return, benchmark_return):
"""
http://en.wikipedia.org/wiki/Information_ratio
@@ -181,6 +135,11 @@ class RiskMetricsCumulative(object):
self.algorithm_returns_cont = pd.Series(index=cont_index)
self.benchmark_returns_cont = pd.Series(index=cont_index)
self.mean_returns_cont = pd.Series(index=cont_index)
self.annualized_mean_returns_cont = pd.Series(index=cont_index)
self.mean_benchmark_returns_cont = pd.Series(index=cont_index)
self.annualized_mean_benchmark_returns_cont = pd.Series(
index=cont_index)
# The returns at a given time are read and reset from the respective
# returns container.
@@ -189,7 +148,7 @@ class RiskMetricsCumulative(object):
self.mean_returns = None
self.annualized_mean_returns = None
self.mean_benchmark_returns = None
self.annualized_benchmark_returns = None
self.annualized_mean_benchmark_returns = None
self.algorithm_cumulative_returns = pd.Series(index=cont_index)
self.benchmark_cumulative_returns = pd.Series(index=cont_index)
@@ -235,46 +194,61 @@ class RiskMetricsCumulative(object):
self.algorithm_returns_cont[dt] = algorithm_returns
self.algorithm_returns = self.algorithm_returns_cont[:dt]
self.num_trading_days = len(self.algorithm_returns)
if self.create_first_day_stats:
if len(self.algorithm_returns) == 1:
self.algorithm_returns = pd.Series(
{'null return': 0.0}).append(self.algorithm_returns)
self.mean_returns = pd.rolling_mean(self.algorithm_returns,
window=len(self.algorithm_returns),
min_periods=1)
self.algorithm_cumulative_returns[dt] = \
self.calculate_cumulative_returns(self.algorithm_returns)
self.annualized_mean_returns = self.mean_returns * 252
algo_cumulative_returns_to_date = \
self.algorithm_cumulative_returns[:dt]
self.mean_returns_cont[dt] = \
algo_cumulative_returns_to_date[dt] / self.num_trading_days
self.mean_returns = self.mean_returns_cont[:dt]
self.annualized_mean_returns_cont[dt] = \
self.mean_returns_cont[dt] * 252
self.annualized_mean_returns = self.annualized_mean_returns_cont[:dt]
if self.create_first_day_stats:
if len(self.mean_returns) == 1:
self.mean_returns = pd.Series(
{'null return': 0.0}).append(self.mean_returns)
self.annualized_mean_returns = pd.Series(
{'null return': 0.0}).append(self.annualized_mean_returns)
self.benchmark_returns_cont[dt] = benchmark_returns
self.benchmark_returns = self.benchmark_returns_cont[:dt]
self.mean_benchmark_returns = pd.rolling_mean(
self.benchmark_returns,
window=len(self.benchmark_returns),
min_periods=1)
self.annualized_benchmark_returns = self.mean_benchmark_returns * 252
if self.create_first_day_stats:
if len(self.benchmark_returns) == 1:
self.benchmark_returns = pd.Series(
{'null return': 0.0}).append(self.benchmark_returns)
self.mean_benchmark_returns = pd.rolling_mean(
self.benchmark_returns,
window=len(self.benchmark_returns),
min_periods=1)
self.annualized_benchmark_returns = self.mean_benchmark_returns * 252
self.num_trading_days = len(self.algorithm_returns)
self.algorithm_cumulative_returns[dt] = \
self.calculate_cumulative_returns(self.algorithm_returns)
self.benchmark_cumulative_returns[dt] = \
self.calculate_cumulative_returns(self.benchmark_returns)
benchmark_cumulative_returns_to_date = \
self.benchmark_cumulative_returns[:dt]
self.mean_benchmark_returns_cont[dt] = \
benchmark_cumulative_returns_to_date[dt] / self.num_trading_days
self.mean_benchmark_returns = self.mean_benchmark_returns_cont[:dt]
self.annualized_mean_benchmark_returns_cont[dt] = \
self.mean_benchmark_returns_cont[dt] * 252
self.annualized_mean_benchmark_returns = \
self.annualized_mean_benchmark_returns_cont[:dt]
if not self.algorithm_returns.index.equals(
self.benchmark_returns.index
):
@@ -333,6 +307,16 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
'null return')
self.benchmark_returns.index = pd.to_datetime(
self.benchmark_returns.index)
if 'null return' in self.mean_returns:
self.mean_returns = self.mean_returns.drop(
'null return')
self.mean_returns.index = pd.to_datetime(
self.mean_returns.index)
if 'null return' in self.annualized_mean_returns:
self.annualized_mean_returns = \
self.annualized_mean_returns.drop('null return')
self.annualized_mean_returns.index = pd.to_datetime(
self.mean_returns.index)
def to_dict(self):
"""
@@ -435,7 +419,7 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
return information_ratio(
self.metrics.algorithm_volatility[self.latest_dt],
self.annualized_mean_returns[self.latest_dt],
self.annualized_benchmark_returns[self.latest_dt])
self.annualized_mean_benchmark_returns[self.latest_dt])
def calculate_alpha(self, dt):
"""
@@ -443,11 +427,13 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
"""
return alpha(self.annualized_mean_returns[self.latest_dt],
self.treasury_period_return,
self.annualized_benchmark_returns[self.latest_dt],
self.annualized_mean_benchmark_returns[self.latest_dt],
self.metrics.beta[dt])
def calculate_volatility(self, daily_returns):
return np.std(daily_returns) * math.sqrt(252)
if len(daily_returns) <= 1:
return 0.0
return np.std(daily_returns, ddof=1) * math.sqrt(252)
def calculate_downside_risk(self):
return downside_risk(self.algorithm_returns,
@@ -468,8 +454,8 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
if len(self.annualized_mean_returns) < 2:
return 0.0
returns_matrix = np.vstack([self.annualized_mean_returns,
self.annualized_benchmark_returns])
returns_matrix = np.vstack([self.algorithm_returns,
self.benchmark_returns])
C = np.cov(returns_matrix, ddof=1)
algorithm_covariance = C[0][1]
benchmark_variance = C[1][1]
+20 -6
View File
@@ -30,6 +30,7 @@ from . import risk
from . risk import (
alpha,
check_entry,
downside_risk,
information_ratio,
sharpe_ratio,
sortino_ratio,
@@ -90,6 +91,17 @@ class RiskMetricsPeriod(object):
raise Exception(message)
self.num_trading_days = len(self.benchmark_returns)
self.trading_day_counts = pd.stats.moments.rolling_count(
self.algorithm_returns, self.num_trading_days)
self.mean_algorithm_returns = pd.Series(
index=self.algorithm_returns.index)
for dt, ret in self.algorithm_returns.iterkv():
self.mean_algorithm_returns[dt] = (
self.algorithm_returns[:dt].sum()
/
self.trading_day_counts[dt]
)
self.benchmark_volatility = self.calculate_volatility(
self.benchmark_returns)
self.algorithm_volatility = self.calculate_volatility(
@@ -195,15 +207,17 @@ class RiskMetricsPeriod(object):
self.algorithm_period_returns,
self.treasury_period_return)
def calculate_sortino(self, mar=None):
def calculate_sortino(self):
"""
http://en.wikipedia.org/wiki/Sortino_ratio
"""
if mar is None:
mar = self.treasury_period_return
return sortino_ratio(self.algorithm_returns,
self.algorithm_period_returns,
mar = downside_risk(self.algorithm_returns,
self.mean_algorithm_returns,
self.num_trading_days)
# Hold on to downside risk for debugging purposes.
self.downside_risk = mar
return sortino_ratio(self.algorithm_period_returns,
self.treasury_period_return,
mar)
def calculate_information(self):
+10 -15
View File
@@ -1,5 +1,5 @@
#
# Copyright 2013 Quantopian, Inc.
# Copyright 2014 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -99,19 +99,21 @@ def sharpe_ratio(algorithm_volatility, algorithm_return, treasury_return):
float. The Sharpe ratio.
"""
if zp_math.tolerant_equals(algorithm_volatility, 0):
return 0.0
return np.nan
return (algorithm_return - treasury_return) / algorithm_volatility
def downside_risk(algorithm_returns, mean_returns, normalization_factor):
rets = algorithm_returns
mar = mean_returns
rets = algorithm_returns.round(8)
mar = mean_returns.round(8)
downside_diff = (rets[rets < mar] - mar[rets < mar])
return np.std(downside_diff) * math.sqrt(normalization_factor)
if len(downside_diff) <= 1:
return 0.0
return np.std(downside_diff, ddof=1) * math.sqrt(normalization_factor)
def sortino_ratio(algorithm_returns, algorithm_period_return, mar):
def sortino_ratio(algorithm_period_return, treasury_period_return, mar):
"""
http://en.wikipedia.org/wiki/Sortino_ratio
@@ -125,17 +127,10 @@ def sortino_ratio(algorithm_returns, algorithm_period_return, mar):
Returns:
float. The Sortino ratio.
"""
if len(algorithm_returns) == 0:
if zp_math.tolerant_equals(mar, 0):
return 0.0
rets = algorithm_returns
downside = (rets[rets < mar] - mar) ** 2
dr = np.sqrt(downside.sum() / len(rets))
if zp_math.tolerant_equals(dr, 0):
return 0.0
return (algorithm_period_return - mar) / dr
return (algorithm_period_return - treasury_period_return) / mar
def information_ratio(algorithm_returns, benchmark_returns):