From 4d56f5746890f141bf5a100c952c37c662980775 Mon Sep 17 00:00:00 2001 From: Ryan Day Date: Tue, 29 Jan 2013 22:14:22 -0500 Subject: [PATCH] Add the information ratio to risk metrics. Calculates relative to the benchmark returns. --- tests/test_risk.py | 40 ++++++++++++++++++++++++++++++++++++ zipline/finance/risk.py | 45 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/tests/test_risk.py b/tests/test_risk.py index d9e25f6d..5b3e08ef 100644 --- a/tests/test_risk.py +++ b/tests/test_risk.py @@ -367,6 +367,46 @@ class Risk(unittest.TestCase): for x in self.metrics_06.year_periods], [-0.524]) + def test_algorithm_information_06(self): + self.assertEqual([round(x.information, 3) + for x in self.metrics_06.month_periods], + [0.131, + -0.11, + -0.067, + 0.144, + 0.298, + -0.391, + 0.106, + -0.034, + -0.058, + 0.068, + 0.09, + -0.125]) + self.assertEqual([round(x.information, 3) + for x in self.metrics_06.three_month_periods], + [-0.013, + -0.006, + 0.113, + -0.012, + -0.02, + -0.11, + 0.01, + -0.005, + 0.03, + 0.009]) + self.assertEqual([round(x.information, 3) + for x in self.metrics_06.six_month_periods], + [-0.013, + -0.013, + -0.003, + -0.002, + -0.013, + -0.042, + 0.009]) + self.assertEqual([round(x.information, 3) + for x in self.metrics_06.year_periods], + [-0.002]) + def dtest_algorithm_beta_06(self): self.assertEqual([round(x.beta, 3) for x in self.metrics_06.month_periods], diff --git a/zipline/finance/risk.py b/zipline/finance/risk.py index 968db9a1..ccf2481b 100644 --- a/zipline/finance/risk.py +++ b/zipline/finance/risk.py @@ -37,6 +37,9 @@ Risk Report | sharpe | The sharpe ratio based on the _algorithm_ (rather | | | than the static portfolio) returns. | +-----------------+----------------------------------------------------+ + | information | The information ratio based on the _algorithm_ | + | | (rather than the static portfolio) returns. | + +-----------------+----------------------------------------------------+ | beta | The _algorithm_ beta to the benchmark. | +-----------------+----------------------------------------------------+ | alpha | The _algorithm_ alpha to the benchmark. | @@ -55,6 +58,7 @@ Risk Report import logbook import datetime import math +import itertools from collections import OrderedDict import bisect import numpy as np @@ -145,6 +149,7 @@ class RiskMetricsBase(object): self.treasury_period_return = self.choose_treasury() self.sharpe = self.calculate_sharpe() self.sortino = self.calculate_sortino() + self.information = self.calculate_information() self.beta, self.algorithm_covariance, self.benchmark_variance, \ self.condition_number, self.eigen_values = self.calculate_beta() self.alpha = self.calculate_alpha() @@ -167,6 +172,7 @@ class RiskMetricsBase(object): 'benchmark_period_return': self.benchmark_period_returns, 'sharpe': self.sharpe, 'sortino': self.sortino, + 'information': self.information, 'beta': self.beta, 'alpha': self.alpha, 'excess_return': self.excess_return, @@ -196,6 +202,7 @@ class RiskMetricsBase(object): "algorithm_volatility", "sharpe", "sortino", + "information", "algorithm_covariance", "benchmark_variance", "beta", @@ -265,6 +272,23 @@ class RiskMetricsBase(object): return ((self.algorithm_period_returns - mar) / dr) + def calculate_information(self): + """ + http://en.wikipedia.org/wiki/Information_ratio + """ + + relative_returns = [ + r - b + for r, b + in itertools.izip(self.algorithm_returns, self.benchmark_returns)] + + relative_deviation = np.std(relative_returns, ddof=1) + + if relative_deviation < 0.000001 or np.isnan(relative_deviation): + return 0.0 + + return np.mean(relative_returns) / relative_deviation + def calculate_beta(self): """ @@ -450,6 +474,7 @@ class RiskMetricsIterative(RiskMetricsBase): self.benchmark_period_returns = [] self.sharpe = [] self.sortino = [] + self.information = [] self.beta = [] self.alpha = [] self.max_drawdown = 0 @@ -501,6 +526,7 @@ algorithm_returns ({algo_count}) in range {start} : {end}" self.alpha.append(self.calculate_alpha()) self.sharpe.append(self.calculate_sharpe()) self.sortino.append(self.calculate_sortino()) + self.information.append(self.calculate_information()) self.max_drawdown = self.calculate_max_drawdown() def to_dict(self): @@ -518,6 +544,7 @@ algorithm_returns ({algo_count}) in range {start} : {end}" 'benchmark_period_return': self.benchmark_period_returns[-1], 'sharpe': self.sharpe[-1], 'sortino': self.sortino[-1], + 'information': self.information[-1], 'beta': self.beta[-1], 'alpha': self.alpha[-1], 'excess_return': self.excess_returns[-1], @@ -548,6 +575,7 @@ algorithm_returns ({algo_count}) in range {start} : {end}" "algorithm_volatility", "sharpe", "sortino", + "information", "algorithm_covariance", "benchmark_variance", "beta", @@ -648,6 +676,23 @@ algorithm_returns ({algo_count}) in range {start} : {end}" return ((self.algorithm_period_returns[-1] - mar) / dr) + def calculate_information(self): + """ + http://en.wikipedia.org/wiki/Information_ratio + """ + + relative_returns = [ + r - b + for r, b + in itertools.izip(self.algorithm_returns, self.benchmark_returns)] + + relative_deviation = np.std(relative_returns, ddof=1) + + if relative_deviation < 0.000001 or np.isnan(relative_deviation): + return 0.0 + + return np.mean(relative_returns) / relative_deviation + def calculate_alpha(self): """ http://en.wikipedia.org/wiki/Alpha_(investment)