ENH: Annualize sortino ratio.

Use annualized values for sortino, so that it is calculated using the
same units as sharpe, etc.
This commit is contained in:
Eddie Hebert
2013-10-09 16:11:50 -04:00
parent 1afc9069b1
commit 0ebdb2fe77
4 changed files with 65 additions and 10 deletions
+13 -2
View File
@@ -230,7 +230,14 @@ class AnswerKey(object):
'Sim Cumulative', 'P', 4, 254),
'ALGORITHM_CUMULATIVE_SHARPE': DataIndex(
'Sim Cumulative', 'R', 4, 254)
'Sim Cumulative', 'R', 4, 254),
'CUMULATIVE_DOWNSIDE_RISK': DataIndex(
'Sim Cumulative', 'U', 4, 254),
'CUMULATIVE_SORTINO': DataIndex(
'Sim Cumulative', 'V', 4, 254),
}
def __init__(self):
@@ -289,4 +296,8 @@ RISK_CUMULATIVE = pd.DataFrame({
'volatility': pd.Series(dict(zip(
DATES, ANSWER_KEY.ALGORITHM_CUMULATIVE_VOLATILITY))),
'sharpe': pd.Series(dict(zip(
DATES, ANSWER_KEY.ALGORITHM_CUMULATIVE_SHARPE)))})
DATES, ANSWER_KEY.ALGORITHM_CUMULATIVE_SHARPE))),
'downside_risk': pd.Series(dict(zip(
DATES, ANSWER_KEY.CUMULATIVE_DOWNSIDE_RISK))),
'sortino': pd.Series(dict(zip(
DATES, ANSWER_KEY.CUMULATIVE_SORTINO)))})
+3
View File
@@ -8,3 +8,6 @@ cc507b6fca18aabadac69657181edd4e
5b48e6a70181d73ecb7f07df5a3092e2
3343940379161143630503413627a53a
820235c4157a3c55474836438019ef2e
75c1b1441efbc2431215835a5079ccc6
37e3ea4a1788f1aa6f3ee0986bc625ae
651e611e723e2a58b1ded91d0cd39b66
+16
View File
@@ -70,3 +70,19 @@ class TestRisk(unittest.TestCase):
self.cumulative_metrics_06.metrics.sharpe[dt],
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,
err_msg="Mismatch at %s" % (dt,))
def test_sortino_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.sortino.iterkv():
np.testing.assert_almost_equal(
self.cumulative_metrics_06.metrics.sortino[dt],
value,
decimal=2,
err_msg="Mismatch at %s" % (dt,))
+33 -8
View File
@@ -29,7 +29,6 @@ from . risk import (
check_entry,
information_ratio,
choose_treasury,
sortino_ratio,
)
log = logbook.Logger('Risk Cumulative')
@@ -67,6 +66,26 @@ def sharpe_ratio(algorithm_volatility, annualized_return, treasury_return):
/ 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
class RiskMetricsCumulative(object):
"""
:Usage:
@@ -80,6 +99,7 @@ class RiskMetricsCumulative(object):
'sharpe',
'algorithm_volatility',
'benchmark_volatility',
'downside_risk',
'sortino',
'information',
)
@@ -137,6 +157,7 @@ class RiskMetricsCumulative(object):
# returns container.
self.algorithm_returns = None
self.benchmark_returns = None
self.mean_returns = None
self.annualized_mean_returns = None
self.compounded_log_returns = pd.Series(index=cont_index)
@@ -258,6 +279,7 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
self.metrics.beta[dt] = self.calculate_beta()
self.metrics.alpha[dt] = self.calculate_alpha(dt)
self.metrics.sharpe[dt] = self.calculate_sharpe()
self.metrics.downside_risk[dt] = self.calculate_downside_risk()
self.metrics.sortino[dt] = self.calculate_sortino()
self.metrics.information[dt] = self.calculate_information()
self.max_drawdown = self.calculate_max_drawdown()
@@ -383,16 +405,13 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
self.annualized_mean_returns[self.latest_dt],
self.daily_treasury[self.latest_dt.date()])
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[self.latest_dt],
mar)
return sortino_ratio(self.annualized_mean_returns[self.latest_dt],
self.daily_treasury[self.latest_dt.date()],
self.metrics.downside_risk[self.latest_dt])
def calculate_information(self):
"""
@@ -413,6 +432,12 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
def calculate_volatility(self, daily_returns):
return np.std(daily_returns) * math.sqrt(252)
def calculate_downside_risk(self):
rets = self.algorithm_returns
mar = self.mean_returns
downside_diff = (rets[rets < mar] - mar).valid()
return np.std(downside_diff) * math.sqrt(252)
def calculate_beta(self):
"""