From dcae6af67bd49caaa61b74a73b992e487019498d Mon Sep 17 00:00:00 2001 From: Eddie Hebert Date: Wed, 9 Oct 2013 16:58:33 -0400 Subject: [PATCH] ENH: Annualize information ratio. Use annualized values for information, so that it is calculated using the same units as sharpe, etc. --- tests/risk/answer_key.py | 7 ++++- tests/risk/test_risk_cumulative.py | 8 ++++++ zipline/finance/risk/cumulative.py | 45 ++++++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/tests/risk/answer_key.py b/tests/risk/answer_key.py index cdd81e45..4366257a 100644 --- a/tests/risk/answer_key.py +++ b/tests/risk/answer_key.py @@ -238,6 +238,9 @@ class AnswerKey(object): 'CUMULATIVE_SORTINO': DataIndex( 'Sim Cumulative', 'V', 4, 254), + 'CUMULATIVE_INFORMATION': DataIndex( + 'Sim Cumulative', 'Y', 4, 254), + } def __init__(self): @@ -300,4 +303,6 @@ RISK_CUMULATIVE = pd.DataFrame({ 'downside_risk': pd.Series(dict(zip( DATES, ANSWER_KEY.CUMULATIVE_DOWNSIDE_RISK))), 'sortino': pd.Series(dict(zip( - DATES, ANSWER_KEY.CUMULATIVE_SORTINO)))}) + DATES, ANSWER_KEY.CUMULATIVE_SORTINO))), + 'information': pd.Series(dict(zip( + DATES, ANSWER_KEY.CUMULATIVE_INFORMATION)))}) diff --git a/tests/risk/test_risk_cumulative.py b/tests/risk/test_risk_cumulative.py index cd28218b..6fcf96ab 100644 --- a/tests/risk/test_risk_cumulative.py +++ b/tests/risk/test_risk_cumulative.py @@ -86,3 +86,11 @@ class TestRisk(unittest.TestCase): value, decimal=2, 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, + err_msg="Mismatch at %s" % (dt,)) diff --git a/zipline/finance/risk/cumulative.py b/zipline/finance/risk/cumulative.py index 3957cf10..be59898e 100644 --- a/zipline/finance/risk/cumulative.py +++ b/zipline/finance/risk/cumulative.py @@ -27,7 +27,6 @@ from pandas.tseries.tools import normalize_date from . risk import ( alpha, check_entry, - information_ratio, choose_treasury, ) @@ -86,6 +85,35 @@ def sortino_ratio(annualized_algorithm_return, treasury_return, downside_risk): return (annualized_algorithm_return - treasury_return) / downside_risk +def information_ratio(algo_volatility, algorithm_return, benchmark_return): + """ + http://en.wikipedia.org/wiki/Information_ratio + + Args: + algorithm_returns (np.array-like): + All returns during algorithm lifetime. + benchmark_returns (np.array-like): + All benchmark returns during algo lifetime. + + Returns: + float. Information ratio. + """ + if zp_math.tolerant_equals(algo_volatility, 0): + return np.nan + + return ( + (algorithm_return - benchmark_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. + / algo_volatility) + + class RiskMetricsCumulative(object): """ :Usage: @@ -159,6 +187,8 @@ class RiskMetricsCumulative(object): self.benchmark_returns = None self.mean_returns = None self.annualized_mean_returns = None + self.mean_benchmark_returns = None + self.annualized_benchmark_returns = None self.compounded_log_returns = pd.Series(index=cont_index) self.algorithm_period_returns = pd.Series(index=cont_index) @@ -215,6 +245,13 @@ class RiskMetricsCumulative(object): self.benchmark_returns_cont[dt] = benchmark_returns self.benchmark_returns = self.benchmark_returns_cont.valid() + 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( @@ -417,8 +454,10 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}" """ http://en.wikipedia.org/wiki/Information_ratio """ - return information_ratio(self.algorithm_returns, - self.benchmark_returns) + return information_ratio( + self.metrics.algorithm_volatility[self.latest_dt], + self.annualized_mean_returns[self.latest_dt], + self.annualized_benchmark_returns[self.latest_dt]) def calculate_alpha(self, dt): """