diff --git a/main.py b/main.py index 4f1a1ca..8cdb1ea 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ import okama as ok -print(ok.search('aeroflot', namespace='MOEX', response_format='frame')) +x = ok.Portfolio(['SPY.US', 'BND.US'], weights=[0.5, 0.5], ccy='RUB') + +print(x.get_sharpe_ratio(rf_return=0.07)) diff --git a/okama/__init__.py b/okama/__init__.py index 87f1d97..316f73c 100644 --- a/okama/__init__.py +++ b/okama/__init__.py @@ -36,7 +36,7 @@ macro_namespaces, symbols_in_namespace, ) -from okama.common.helpers import Float, Frame, Rebalance, Date +from okama.common.helpers.helpers import Float, Frame, Rebalance, Date import okama.settings __version__ = "1.0.2" diff --git a/okama/asset.py b/okama/asset.py index 94e6db7..cd9e30a 100644 --- a/okama/asset.py +++ b/okama/asset.py @@ -3,7 +3,7 @@ import pandas as pd import numpy as np -from .common.helpers import Frame +from .common.helpers.helpers import Frame from .settings import default_ticker from .api.data_queries import QueryData from .api.namespaces import get_assets_namespaces diff --git a/okama/asset_list.py b/okama/asset_list.py index 5eb9896..6caa6de 100644 --- a/okama/asset_list.py +++ b/okama/asset_list.py @@ -3,7 +3,7 @@ import numpy as np import pandas as pd -from .common.helpers import Frame, Float, Date, Index +from .common.helpers.helpers import Frame, Float, Date, Index from .common.make_asset_list import ListMaker diff --git a/okama/common/helpers/__init__.py b/okama/common/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/okama/common/helpers.py b/okama/common/helpers/helpers.py similarity index 99% rename from okama/common/helpers.py rename to okama/common/helpers/helpers.py index e60fe26..fd3e039 100644 --- a/okama/common/helpers.py +++ b/okama/common/helpers/helpers.py @@ -5,7 +5,7 @@ import numpy as np import scipy.stats -from ..settings import _MONTHS_PER_YEAR +from okama.settings import _MONTHS_PER_YEAR def check_rolling_window( @@ -580,7 +580,7 @@ def tracking_difference(accumulated_return: pd.DataFrame) -> pd.DataFrame: @staticmethod def tracking_difference_annualized(tracking_diff: pd.DataFrame) -> pd.DataFrame: """ - Annualizes the values of tracking difference time series. + Annualize the values of tracking difference time series. Annual values are available for periods of more than 12 months. Returns for less than 12 months can't be annualized. """ diff --git a/okama/common/helpers/rates.py b/okama/common/helpers/rates.py new file mode 100644 index 0000000..7b28e12 --- /dev/null +++ b/okama/common/helpers/rates.py @@ -0,0 +1,8 @@ +"""Portfolio rates""" + + +def get_sharpe_ratio(pf_return: float, rf_return: float, std_deviation: float) -> float: + """ + Calculate Sharpe ratio. + """ + return (pf_return - rf_return) / std_deviation diff --git a/okama/frontier/multi_period.py b/okama/frontier/multi_period.py index 126ffc0..a0eafcc 100644 --- a/okama/frontier/multi_period.py +++ b/okama/frontier/multi_period.py @@ -7,7 +7,7 @@ from scipy.optimize import minimize from .. import AssetList -from ..common.helpers import Float, Frame, Rebalance +from ..common.helpers.helpers import Float, Frame, Rebalance from ..settings import _MONTHS_PER_YEAR diff --git a/okama/frontier/single_period.py b/okama/frontier/single_period.py index 32a32d0..faed265 100644 --- a/okama/frontier/single_period.py +++ b/okama/frontier/single_period.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple, Dict, List, Union +from typing import Optional, Tuple, Dict, List import pandas as pd import numpy as np @@ -6,7 +6,7 @@ from scipy.optimize import minimize from .. import AssetList -from ..common.helpers import Float, Frame +from ..common.helpers.helpers import Float, Frame class EfficientFrontier(AssetList): diff --git a/okama/macro.py b/okama/macro.py index 69541e5..5877828 100644 --- a/okama/macro.py +++ b/okama/macro.py @@ -6,7 +6,7 @@ from .api.data_queries import QueryData from .api.namespaces import get_macro_namespaces -from .common.helpers import Float, Frame, Date +from .common.helpers.helpers import Float, Frame, Date from .settings import default_macro, PeriodLength, _MONTHS_PER_YEAR diff --git a/okama/plots.py b/okama/plots.py index 5866eef..3388784 100644 --- a/okama/plots.py +++ b/okama/plots.py @@ -4,7 +4,7 @@ from matplotlib import pyplot as plt from .asset_list import AssetList -from .common.helpers import Float +from .common.helpers.helpers import Float from .frontier.single_period import EfficientFrontier from .settings import default_ticker diff --git a/okama/portfolio.py b/okama/portfolio.py index cacf16d..5a571dd 100644 --- a/okama/portfolio.py +++ b/okama/portfolio.py @@ -6,7 +6,8 @@ import scipy.stats from matplotlib import pyplot as plt -from .common.helpers import Frame, Rebalance, Float, Date +from .common.helpers.helpers import Frame, Rebalance, Float, Date +from .common.helpers import rates from .common.make_asset_list import ListMaker from .common.validators import validate_real from .settings import _MONTHS_PER_YEAR @@ -1867,6 +1868,33 @@ def kstest(self, distr: str = "norm") -> Dict[str, float]: """ return Frame.kstest_series(self.ror, distr=distr) + def get_sharpe_ratio(self, rf_return: float = 0) -> float: + """ + Calculate Sharpe ratio. + + The Sharpe ratio is the average annual return in excess of the risk-free rate + per unit of risk (annualized standard deviation). + + Parameters + ---------- + rf_return : float, default 0 + Risk-free rate of return. + + Returns + ------- + float + + Examples + -------- + >>> pf = ok.Portfolio(['VOO.US', 'BND.US'], weights=[0.40, 0.60]) + >>> pf.get_sharpe_ratio(rf_return=0.04) + 0.7412193684695373 + """ + return rates.get_sharpe_ratio( + pf_return=self.mean_return_annual, + rf_return=rf_return, + std_deviation=self.risk_annual) + def plot_percentiles_fit( self, distr: str = "norm", figsize: Optional[tuple] = None ) -> None: diff --git a/tests/test_portfolio.py b/tests/test_portfolio.py index 1b01d0b..b3c8341 100644 --- a/tests/test_portfolio.py +++ b/tests/test_portfolio.py @@ -313,6 +313,10 @@ def test_jarque_bera(portfolio_rebalanced_month): ) +def test_get_sharpe_ratio(portfolio_no_inflation): + assert portfolio_no_inflation.get_sharpe_ratio(rf_return=0.05) == approx(0.631, rel=1e-2) + + # This test should be a last one, as it changes the weights def test_init_portfolio_failing(): with pytest.raises(