mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-30 16:30:30 +08:00
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:
@@ -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)))})
|
||||
|
||||
@@ -8,3 +8,6 @@ cc507b6fca18aabadac69657181edd4e
|
||||
5b48e6a70181d73ecb7f07df5a3092e2
|
||||
3343940379161143630503413627a53a
|
||||
820235c4157a3c55474836438019ef2e
|
||||
75c1b1441efbc2431215835a5079ccc6
|
||||
37e3ea4a1788f1aa6f3ee0986bc625ae
|
||||
651e611e723e2a58b1ded91d0cd39b66
|
||||
|
||||
@@ -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,))
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user