Skip to content

Commit

Permalink
Merge pull request #15 from ricequant/DEV-RQSDK-589
Browse files Browse the repository at this point in the history
新增ulcer_index(累计回撤深度)和ulcer_performance_index(累计回撤夏普率)
  • Loading branch information
Cuizi7 authored Dec 30, 2022
2 parents b5c55f7 + 8ef8620 commit c58a9c6
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 2 deletions.
36 changes: 35 additions & 1 deletion rqrisk/risk.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,15 @@ def benchmark_annual_volatility(self):
return self.benchmark_volatility * (self._annual_factor ** 0.5)

@staticmethod
def _calc_max_drawdown(returns):
def _calc_cum(returns):
""" 计算累计净值 """
returns = [0] + list(returns)
df_cum = np.exp(np.log1p(returns).cumsum())
return df_cum

@classmethod
def _calc_max_drawdown(cls, returns):
df_cum = cls._calc_cum(returns)
max_return = np.maximum.accumulate(df_cum)
return abs(((df_cum - max_return) / max_return).min())

Expand Down Expand Up @@ -212,5 +218,33 @@ def correlation(self):
# 相关系数算法参考 https://numpy.org/doc/stable/reference/generated/numpy.corrcoef.html?highlight=corr#numpy.corrcoef
return np.corrcoef(self._portfolio, self._benchmark)[0][1]

@classmethod
def _calc_ulcer_index(cls, returns):
""" 累计回撤深度 相关计算公式参考:http://www.tangotools.com/ui/ui.htm """
cum = cls._calc_cum(returns)
drawdown_squared = ((cum / np.maximum.accumulate(cum) - 1) * 100) ** 2
return (np.sum(drawdown_squared) / len(cum)) ** 0.5

@indicator_property()
def ulcer_index(self):
return self._calc_ulcer_index(self._portfolio)

@indicator_property()
def excess_ulcer_index(self):
return self._calc_ulcer_index(self._active_returns)

def _calc_ulcer_performance_index(self, returns, ulcer_index):
""" 累计回撤夏普率 相关计算公式参考:http://www.tangotools.com/ui/ui.htm """
_return_rate = np.expm1(np.log1p(returns - self._risk_free_rate_per_period).sum())
return safe_div(_return_rate, ulcer_index)

@indicator_property()
def ulcer_performance_index(self):
return self._calc_ulcer_performance_index(self._portfolio, self.ulcer_index)

@indicator_property()
def excess_ulcer_performance_index(self):
return self._calc_ulcer_performance_index(self._active_returns, self.excess_ulcer_index)

def all(self):
return {k: getattr(self, k) for k, v in self.__class__.__dict__.items() if isinstance(v, IndicatorProperty)}
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[metadata]
name = rqrisk
version = 1.0.4
version = 1.0.5

[versioneer]
VCS = git
Expand Down
25 changes: 25 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,28 @@ def _assert(returns, benchmark, period):
_assert(weekly_returns, volatile_weekly_benchmark, WEEKLY) # -0.3411258215912419
_assert(monthly_returns, simple_monthly_benchamrk, MONTHLY) # np.nan
_assert(monthly_returns, volatile_monthly_benchmark, MONTHLY) # -0.3411258215912419


def test_ulcer_index():
""" 测试累计回撤深度 """

def _assert(returns, benchmark, ulcer_index):
r = _r(returns, benchmark, 0)
assert_almost_equal(r.ulcer_index, ulcer_index)

_assert(positive_returns, simple_benchmark, 0)
_assert(negative_returns, simple_benchmark, 21.319872051737)
_assert(weekly_returns, simple_weekly_benchamrk, 3.4688095940826726)


def test_ulcer_performance_index():
""" 测试累计回撤夏普率 """

def _assert(returns, benchmark, ulcer_performance_index):
r = _r(returns, benchmark, 0)
assert_almost_equal(r.ulcer_performance_index, ulcer_performance_index)

_assert(positive_returns, simple_benchmark, np.nan)
_assert(negative_returns, simple_benchmark, -0.019864148204580486)
_assert(weekly_returns, simple_weekly_benchamrk, -0.014606674809517502)

0 comments on commit c58a9c6

Please sign in to comment.