2022-04-30 07:18:31 +00:00
|
|
|
import datetime
|
2022-05-29 12:26:00 +00:00
|
|
|
import statistics
|
2022-04-30 07:18:31 +00:00
|
|
|
from typing import Literal
|
|
|
|
|
|
|
|
from fincal.core import date_parser
|
|
|
|
|
2022-04-29 02:13:06 +00:00
|
|
|
from .fincal import TimeSeries
|
2022-05-29 12:26:00 +00:00
|
|
|
from .utils import _interval_to_years
|
2022-04-29 02:13:06 +00:00
|
|
|
|
|
|
|
|
2022-04-30 07:18:31 +00:00
|
|
|
@date_parser(3, 4)
|
2022-04-29 02:13:06 +00:00
|
|
|
def sharpe_ratio(
|
2022-04-30 07:18:31 +00:00
|
|
|
time_series_data: TimeSeries,
|
|
|
|
risk_free_data: TimeSeries = None,
|
|
|
|
risk_free_rate: float = None,
|
|
|
|
from_date: str | datetime.datetime = None,
|
|
|
|
to_date: str | datetime.datetime = None,
|
|
|
|
frequency: Literal["D", "W", "M", "Q", "H", "Y"] = None,
|
|
|
|
return_period_unit: Literal["years", "months", "days"] = "years",
|
|
|
|
return_period_value: int = 1,
|
|
|
|
as_on_match: str = "closest",
|
|
|
|
prior_match: str = "closest",
|
|
|
|
closest: Literal["previous", "next"] = "previous",
|
|
|
|
date_format: str = None,
|
2022-04-29 02:13:06 +00:00
|
|
|
):
|
2022-05-31 15:48:55 +00:00
|
|
|
"""Calculate the Sharpe ratio of any time series
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
time_series_data :
|
|
|
|
|
|
|
|
risk_free_data :
|
|
|
|
|
|
|
|
risk_free_rate :
|
|
|
|
|
|
|
|
from_date :
|
|
|
|
|
|
|
|
to_date :
|
|
|
|
|
|
|
|
frequency :
|
|
|
|
|
|
|
|
return_period_unit :
|
|
|
|
|
|
|
|
return_period_value :
|
|
|
|
|
|
|
|
as_on_match :
|
|
|
|
|
|
|
|
prior_match :
|
|
|
|
|
|
|
|
closest :
|
|
|
|
|
|
|
|
date_format :
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
_description_
|
|
|
|
|
|
|
|
Raises
|
|
|
|
------
|
|
|
|
ValueError
|
|
|
|
_description_
|
|
|
|
"""
|
2022-05-29 12:26:00 +00:00
|
|
|
interval_days = int(_interval_to_years(return_period_unit, return_period_value) * 365 + 1)
|
|
|
|
|
|
|
|
if from_date is None:
|
|
|
|
from_date = time_series_data.start_date + datetime.timedelta(days=interval_days)
|
|
|
|
if to_date is None:
|
|
|
|
to_date = time_series_data.end_date
|
|
|
|
|
2022-04-29 02:13:06 +00:00
|
|
|
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")
|
2022-05-07 08:39:21 +00:00
|
|
|
elif risk_free_data is not None:
|
|
|
|
risk_free_rate = risk_free_data.mean()
|
2022-04-29 02:13:06 +00:00
|
|
|
|
2022-04-30 07:18:31 +00:00
|
|
|
common_params = {
|
|
|
|
"from_date": from_date,
|
|
|
|
"to_date": to_date,
|
|
|
|
"frequency": frequency,
|
|
|
|
"return_period_unit": return_period_unit,
|
|
|
|
"return_period_value": return_period_value,
|
|
|
|
"as_on_match": as_on_match,
|
|
|
|
"prior_match": prior_match,
|
|
|
|
"closest": closest,
|
|
|
|
"date_format": date_format,
|
|
|
|
}
|
2022-05-07 08:39:21 +00:00
|
|
|
average_rr = time_series_data.average_rolling_return(**common_params, annual_compounded_returns=True)
|
2022-04-29 02:13:06 +00:00
|
|
|
|
2022-05-07 08:39:21 +00:00
|
|
|
excess_returns = average_rr - risk_free_rate
|
2022-04-30 07:18:31 +00:00
|
|
|
sd = time_series_data.volatility(
|
|
|
|
**common_params,
|
|
|
|
annualize_volatility=True,
|
|
|
|
)
|
|
|
|
|
2022-05-07 08:39:21 +00:00
|
|
|
sharpe_ratio_value = excess_returns / sd
|
|
|
|
return sharpe_ratio_value
|
2022-05-29 12:26:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
@date_parser(2, 3)
|
|
|
|
def beta(
|
|
|
|
asset_data: TimeSeries,
|
|
|
|
market_data: TimeSeries,
|
|
|
|
from_date: str | datetime.datetime = None,
|
|
|
|
to_date: str | datetime.datetime = None,
|
|
|
|
frequency: Literal["D", "W", "M", "Q", "H", "Y"] = None,
|
|
|
|
return_period_unit: Literal["years", "months", "days"] = "years",
|
|
|
|
return_period_value: int = 1,
|
|
|
|
as_on_match: str = "closest",
|
|
|
|
prior_match: str = "closest",
|
|
|
|
closest: Literal["previous", "next"] = "previous",
|
|
|
|
date_format: str = None,
|
|
|
|
):
|
2022-05-31 15:48:55 +00:00
|
|
|
interval_years = _interval_to_years(return_period_unit, return_period_value)
|
|
|
|
interval_days = int(interval_years * 365 + 1)
|
2022-05-29 12:26:00 +00:00
|
|
|
|
2022-05-31 15:48:55 +00:00
|
|
|
annual_compounded_returns = True if interval_years > 1 else False
|
2022-05-29 12:26:00 +00:00
|
|
|
|
|
|
|
if from_date is None:
|
|
|
|
from_date = asset_data.start_date + datetime.timedelta(days=interval_days)
|
|
|
|
if to_date is None:
|
|
|
|
to_date = asset_data.end_date
|
|
|
|
|
|
|
|
common_params = {
|
|
|
|
"from_date": from_date,
|
|
|
|
"to_date": to_date,
|
|
|
|
"frequency": frequency,
|
|
|
|
"return_period_unit": return_period_unit,
|
|
|
|
"return_period_value": return_period_value,
|
|
|
|
"as_on_match": as_on_match,
|
|
|
|
"prior_match": prior_match,
|
|
|
|
"closest": closest,
|
|
|
|
"date_format": date_format,
|
2022-05-31 15:48:55 +00:00
|
|
|
"annual_compounded_returns": annual_compounded_returns,
|
2022-05-29 12:26:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
asset_rr = asset_data.calculate_rolling_returns(**common_params)
|
|
|
|
market_rr = market_data.calculate_rolling_returns(**common_params)
|
|
|
|
|
|
|
|
cov = statistics.covariance(asset_rr.values, market_rr.values)
|
|
|
|
market_var = statistics.variance(market_rr.values)
|
|
|
|
|
|
|
|
beta = cov / market_var
|
|
|
|
return beta
|