From 0d0b2121a31b63dd9ce5bd07bd0c1ae809ecfdeb Mon Sep 17 00:00:00 2001 From: Gourav Kumar Date: Sat, 7 May 2022 14:09:21 +0530 Subject: [PATCH] Sharpe ratio is working --- fincal/fincal.py | 35 +++++++++++++++++++++++++++-------- fincal/statistics.py | 17 ++++++----------- tests/test_fincal.py | 2 ++ 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/fincal/fincal.py b/fincal/fincal.py index 7524bbb..b911a9e 100644 --- a/fincal/fincal.py +++ b/fincal/fincal.py @@ -449,7 +449,7 @@ class TimeSeries(TimeSeriesCore): prior_match: str = "closest", closest: Literal["previous", "next", "exact"] = "previous", if_not_found: Literal["fail", "nan"] = "fail", - annual_compounded_returns: bool = False, + annual_compounded_returns: bool = None, date_format: str = None, ) -> float: """Calculates the volatility of the time series.add() @@ -503,6 +503,12 @@ class TimeSeries(TimeSeriesCore): from_date = self.start_date + relativedelta(**{return_period_unit: return_period_value}) if to_date is None: to_date = self.end_date + years = _interval_to_years(return_period_unit, return_period_value) + if annual_compounded_returns is None: + if years > 1: + annual_compounded_returns = True + else: + annual_compounded_returns = False rolling_returns = self.calculate_rolling_returns( from_date=from_date, @@ -524,7 +530,7 @@ class TimeSeries(TimeSeriesCore): if return_period_unit == "months": sd *= math.sqrt(12) elif return_period_unit == "days": - sd *= math.sqrt(traded_days) + sd *= math.sqrt(traded_days / return_period_value) return sd @@ -544,19 +550,32 @@ class TimeSeries(TimeSeriesCore): --------- TimeSeries.calculate_rolling_returns() """ - kwargs["return_period_unit"] = kwargs.get("return_period_unit", self.frequency.freq_type) kwargs["return_period_value"] = kwargs.get("return_period_value", 1) - kwargs["to_date"] = kwargs.get("to_date", self.end_date) - if kwargs.get("from_date", None) is None: - start_date = self.start_date + relativedelta( + years = _interval_to_years(kwargs["return_period_unit"], kwargs["return_period_value"]) + if kwargs.get("annual_compounded_returns", True): + if years >= 1: + kwargs["annual_compounded_returns"] = True + annualise_returns = False + else: + kwargs["annual_compounded_returns"] = False + annualise_returns = True + elif not kwargs["annual_compounded_returns"]: + annualise_returns = False + + if kwargs.get("from_date") is None: + kwargs["from_date"] = self.start_date + relativedelta( **{kwargs["return_period_unit"]: kwargs["return_period_value"]} ) - kwargs["from_date"] = start_date + kwargs["to_date"] = kwargs.get("to_date", self.end_date) rr = self.calculate_rolling_returns(**kwargs) - return statistics.mean(rr.values) + mean_rr = statistics.mean(rr.values) + if annualise_returns: + mean_rr = (1 + mean_rr) ** (1 / years) - 1 + + return mean_rr def max_drawdown(self) -> MaxDrawdown: """Calculates the maximum fall the stock has taken between any two points. diff --git a/fincal/statistics.py b/fincal/statistics.py index 08c0abb..ca85989 100644 --- a/fincal/statistics.py +++ b/fincal/statistics.py @@ -21,10 +21,10 @@ def sharpe_ratio( closest: Literal["previous", "next"] = "previous", date_format: str = None, ): - pass - if risk_free_data is None and risk_free_rate is None: raise ValueError("At least one of risk_free_data or risk_free rate is required") + elif risk_free_data is not None: + risk_free_rate = risk_free_data.mean() common_params = { "from_date": from_date, @@ -37,18 +37,13 @@ def sharpe_ratio( "closest": closest, "date_format": date_format, } - returns_ts = time_series_data.calculate_rolling_returns(**common_params, annual_compounded_returns=True) + average_rr = time_series_data.average_rolling_return(**common_params, annual_compounded_returns=True) - if risk_free_data is not None: - risk_free_data = returns_ts.sync(risk_free_data) - else: - risk_free_data = risk_free_rate - - excess_returns = returns_ts - risk_free_data + excess_returns = average_rr - risk_free_rate sd = time_series_data.volatility( **common_params, annualize_volatility=True, ) - sharpe_ratio = excess_returns.mean() / sd - return sharpe_ratio + sharpe_ratio_value = excess_returns / sd + return sharpe_ratio_value diff --git a/tests/test_fincal.py b/tests/test_fincal.py index e49716f..baa5169 100644 --- a/tests/test_fincal.py +++ b/tests/test_fincal.py @@ -206,6 +206,8 @@ class TestTimeSeriesCreation: class TestTimeSeriesBasics: + FincalOptions.get_closest = "exact" + def test_fill(self): ts_data = create_test_data(frequency=AllFrequencies.D, num=50, skip_weekends=True) ts = TimeSeries(ts_data, frequency="D")