Skip to content

Commit

Permalink
chore: black style
Browse files Browse the repository at this point in the history
  • Loading branch information
chilango74 committed Feb 23, 2024
1 parent 3f55d03 commit c9c5c87
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 112 deletions.
31 changes: 15 additions & 16 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import matplotlib.pyplot as plt

import okama as ok

# Portfolio WithDrawls
weights = [0.32, 0.31, 0.18, .19]
portf = ok.Portfolio(['RGBITR.INDX', 'RUCBTRNS.INDX', 'MCFTR.INDX', 'GC.COMM'],
ccy="RUB",
weights=weights,
inflation=True,
symbol="retirement_portf.PF",
rebalancing_period='year',
cashflow=-200_000,
initial_amount=39_000_000,
discount_rate=None
)
weights = [0.32, 0.31, 0.18, 0.19]
portf = ok.Portfolio(
["RGBITR.INDX", "RUCBTRNS.INDX", "MCFTR.INDX", "GC.COMM"],
ccy="RUB",
weights=weights,
inflation=True,
symbol="retirement_portf.PF",
rebalancing_period="year",
cashflow=-200_000,
initial_amount=39_000_000,
discount_rate=None,
)

print(portf.discount_rate)
print(portf)
Expand All @@ -33,10 +35,7 @@

# Rolling / Expanding Risk

al = ok.AssetList(['DJI.INDX',
'BND.US'
])
al.get_rolling_risk_annual(window=12*20).plot()
al = ok.AssetList(["DJI.INDX", "BND.US"])
al.get_rolling_risk_annual(window=12 * 20).plot()

plt.show()

33 changes: 18 additions & 15 deletions main_notebook.ipynb

Large diffs are not rendered by default.

25 changes: 11 additions & 14 deletions okama/common/helpers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ def annualize_return(

@staticmethod
def annualize_risk(
risk: Union[float, pd.Series, pd.DataFrame],
mean_return: Union[float, pd.Series, pd.DataFrame]
risk: Union[float, pd.Series, pd.DataFrame], mean_return: Union[float, pd.Series, pd.DataFrame]
) -> Union[float, pd.Series, pd.DataFrame]:
"""
Annualizes Risk.
Expand Down Expand Up @@ -168,11 +167,10 @@ def get_annual_return_ts_from_monthly(
if isinstance(ts, pd.Series):
ts.rename(ror_monthly.name, inplace=True)
return ts

@staticmethod
def get_wealth_indexes(
ror: Union[pd.Series, pd.DataFrame],
initial_amount: float = 1000.
ror: Union[pd.Series, pd.DataFrame], initial_amount: float = 1000.0
) -> Union[pd.Series, pd.DataFrame]:
"""
Returns wealth indexes for a list of assets (or for portfolio).
Expand All @@ -189,11 +187,11 @@ def get_wealth_indexes(

@staticmethod
def get_wealth_indexes_with_cashflow(
ror: Union[pd.Series, pd.DataFrame],
ror: Union[pd.Series, pd.DataFrame],
portfolio_symbol: Optional[str],
inflation_symbol: Optional[str],
discount_rate: float,
initial_amount: float = 1000.,
initial_amount: float = 1000.0,
cashflow: float = 0,
) -> Union[pd.Series, pd.DataFrame]:
"""
Expand Down Expand Up @@ -222,8 +220,7 @@ def get_wealth_indexes_with_cashflow(
s.iloc[0] = initial_amount # replaces NaN with the first period return
if inflation_symbol:
cum_inflation = Frame.get_wealth_indexes(
ror=ror.loc[:, inflation_symbol],
initial_amount=initial_amount
ror=ror.loc[:, inflation_symbol], initial_amount=initial_amount
)
wealth_index = pd.concat([s, cum_inflation], axis="columns")
else:
Expand Down Expand Up @@ -336,7 +333,7 @@ def skewness(ror: Union[pd.DataFrame, pd.Series]) -> Union[pd.Series, float]:
The shape of time series should be at least 12. In the opposite case empty time series is returned.
"""
sk = ror.expanding(min_periods=1).skew()
return sk.iloc[settings._MONTHS_PER_YEAR:]
return sk.iloc[settings._MONTHS_PER_YEAR :]

@staticmethod
def skewness_rolling(ror: Union[pd.DataFrame, pd.Series], window: int = 60) -> Union[pd.Series, float]:
Expand All @@ -356,7 +353,7 @@ def kurtosis(ror: Union[pd.Series, pd.DataFrame]):
Kurtosis should be close to zero for normal distribution.
"""
kt = ror.expanding(min_periods=1).kurt()
return kt.iloc[settings._MONTHS_PER_YEAR:]
return kt.iloc[settings._MONTHS_PER_YEAR :]

@staticmethod
def kurtosis_rolling(ror: Union[pd.Series, pd.DataFrame], window: int = 60):
Expand Down Expand Up @@ -566,7 +563,7 @@ def tracking_difference_annualized(tracking_diff: pd.DataFrame) -> pd.DataFrame:
y = abs(tracking_diff)
diff = (y + 1.0).pow(pwr, axis=0) - 1.0
diff = np.sign(tracking_diff) * diff
return diff.iloc[settings._MONTHS_PER_YEAR - 1:] # returns for the first 11 months can't be annualized
return diff.iloc[settings._MONTHS_PER_YEAR - 1 :] # returns for the first 11 months can't be annualized

@staticmethod
def tracking_error(ror: pd.DataFrame) -> pd.DataFrame:
Expand Down Expand Up @@ -597,7 +594,7 @@ def expanding_cov_cor(ror: pd.DataFrame, fn: str) -> pd.DataFrame:
cov_matrix_ts = getattr(ror.expanding(), fn)()
cov_matrix_ts = cov_matrix_ts.drop(index=ror.columns[1:], level=1).droplevel(1)
cov_matrix_ts.drop(columns=ror.columns[0], inplace=True)
return cov_matrix_ts.iloc[settings._MONTHS_PER_YEAR:]
return cov_matrix_ts.iloc[settings._MONTHS_PER_YEAR :]

@staticmethod
def rolling_cov_cor(ror: pd.DataFrame, window: int = 60, fn: str = "corr") -> pd.DataFrame:
Expand Down Expand Up @@ -629,7 +626,7 @@ def beta(ror: pd.DataFrame) -> pd.DataFrame:
raise ValueError("Beta coefficient is not defined for time periods < 1 year")
cov = Index.expanding_cov_cor(ror, fn="cov")
benchmark_var = ror.loc[:, ror.columns[0]].expanding().var()
benchmark_var = benchmark_var.iloc[settings._MONTHS_PER_YEAR:]
benchmark_var = benchmark_var.iloc[settings._MONTHS_PER_YEAR :]
return cov.divide(benchmark_var, axis=0)

@staticmethod
Expand Down
8 changes: 4 additions & 4 deletions okama/common/make_asset_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(
).values()
if first_date:
self.first_date = max(self.first_date, pd.to_datetime(first_date))
self.assets_ror = self.assets_ror[self.first_date:]
self.assets_ror = self.assets_ror[self.first_date :]
if last_date:
self.last_date = min(self.last_date, pd.to_datetime(last_date))
if inflation:
Expand All @@ -81,7 +81,7 @@ def __init__(
# Add inflation to the date range dict
self.assets_first_dates.update({self.inflation: macro.Inflation(self.inflation).first_date})
self.assets_last_dates.update({self.inflation: macro.Inflation(self.inflation).last_date})
self.assets_ror: pd.DataFrame = self.assets_ror[self.first_date: self.last_date]
self.assets_ror: pd.DataFrame = self.assets_ror[self.first_date : self.last_date]
self.period_length: float = round((self.last_date - self.first_date) / np.timedelta64(365, "D"), ndigits=1)
self.pl = settings.PeriodLength(
self.assets_ror.shape[0] // settings._MONTHS_PER_YEAR,
Expand Down Expand Up @@ -398,7 +398,7 @@ def plot_assets(
kind: str = "mean",
tickers: Union[str, list] = "tickers",
pct_values: bool = False,
xy_text: tuple = (0, 10)
xy_text: tuple = (0, 10),
) -> plt.axes:
"""
Plot the assets points on the risk-return chart with annotations.
Expand Down Expand Up @@ -428,7 +428,7 @@ def plot_assets(
Percents (True)
xy_text : tuple, default (0, 10)
The shift of the annotation text (x, y) from the point.
The shift of the annotation text (x, y) from the point.
Examples
--------
Expand Down
4 changes: 3 additions & 1 deletion okama/frontier/multi_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ def gmv_annual_values(self) -> Tuple[float, float]:
>>> frontier.gmv_annual_values
(0.03695845106087943, 0.04418318557516887)
"""
returns = helpers.Rebalance(period=self.rebalancing_period).return_ror_ts(self.gmv_annual_weights, self.assets_ror)
returns = helpers.Rebalance(period=self.rebalancing_period).return_ror_ts(
self.gmv_annual_weights, self.assets_ror
)
return (
helpers.Float.annualize_risk(returns.std(), returns.mean()),
(returns + 1.0).prod() ** (settings._MONTHS_PER_YEAR / returns.shape[0]) - 1.0,
Expand Down
41 changes: 17 additions & 24 deletions okama/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def __init__(
inflation: bool = True,
weights: Optional[List[float]] = None,
rebalancing_period: str = "month",
initial_amount: float = 1000.,
initial_amount: float = 1000.0,
cashflow: float = 0,
discount_rate: Optional[float] = None,
symbol: str = None,
Expand Down Expand Up @@ -2255,11 +2255,11 @@ def plot_forecast(
return ax

def plot_forecast_monte_carlo(
self,
distr: str = "norm",
years: int = 1,
n: int = 20,
figsize: Optional[tuple] = None,
self,
distr: str = "norm",
years: int = 1,
n: int = 20,
figsize: Optional[tuple] = None,
) -> None:
"""
Plot Monte Carlo simulation for portfolio wealth indexes together with historical wealth index.
Expand Down Expand Up @@ -2422,7 +2422,7 @@ def cashflow_pv(self) -> Optional[float]:
>>> pf.dcf.cashflow_pv
-3004
"""
return self.parent.cashflow / (1. + self.parent.discount_rate) ** self.parent.period_length
return self.parent.cashflow / (1.0 + self.parent.discount_rate) ** self.parent.period_length

@property
def initial_amount_pv(self) -> Optional[float]:
Expand All @@ -2443,13 +2443,11 @@ def initial_amount_pv(self) -> Optional[float]:
>>> pf.dcf.initial_amount_pv
73650
"""
return self.parent.initial_amount / (1. + self.parent.discount_rate) ** self.parent.period_length
return self.parent.initial_amount / (1.0 + self.parent.discount_rate) ** self.parent.period_length

def _monte_carlo_wealth(self,
first_value: float = 1000.,
distr: str = "norm",
years: int = 1,
n: int = 100) -> pd.DataFrame:
def _monte_carlo_wealth(
self, first_value: float = 1000.0, distr: str = "norm", years: int = 1, n: int = 100
) -> pd.DataFrame:
"""
Generate portfolio wealth indexes with cash flows (withdrawals/contributions) by Monte Carlo simulation.
Expand Down Expand Up @@ -2498,7 +2496,7 @@ def _monte_carlo_wealth(self,
df = return_ts.apply(
helpers.Frame.get_wealth_indexes_with_cashflow,
axis=0,
args=(None, None, self.parent.discount_rate, first_value, self.parent.cashflow)
args=(None, None, self.parent.discount_rate, first_value, self.parent.cashflow),
)

def remove_negative_values(s):
Expand Down Expand Up @@ -2581,10 +2579,10 @@ def plot_forecast_monte_carlo(
s2.plot(legend=None)

def monte_carlo_survival_period(
self,
distr: str = "norm",
years: int = 1,
n: int = 20,
self,
distr: str = "norm",
years: int = 1,
n: int = 20,
) -> pd.Series:
"""
Generate a survival period distribution for a portfolio with cash flows by Monte Carlo simulation.
Expand Down Expand Up @@ -2631,12 +2629,7 @@ def monte_carlo_survival_period(
>> s.quantile(50 / 100)
2.7
"""
s2 = self._monte_carlo_wealth(
first_value=self.parent.initial_amount,
distr=distr,
years=years,
n=n
)
s2 = self._monte_carlo_wealth(first_value=self.parent.initial_amount, distr=distr, years=years, n=n)
dates: pd.Series = helpers.Frame.get_survival_date(s2)

return dates.apply(helpers.Date.get_period_length, args=(self.parent.last_date,))
9 changes: 5 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ def portfolio_cashflows_inflation(init_portfolio_values):
@pytest.fixture(scope="package")
def portfolio_cashflows_NO_inflation(init_portfolio_values):
_portfolio_cashflows_NO_inflation = deepcopy(init_portfolio_values)
_portfolio_cashflows_NO_inflation["cashflow"] = -100.
_portfolio_cashflows_NO_inflation["initial_amount"] = 100_000.
_portfolio_cashflows_NO_inflation["cashflow"] = -100.0
_portfolio_cashflows_NO_inflation["initial_amount"] = 100_000.0
_portfolio_cashflows_NO_inflation["inflation"] = False
_portfolio_cashflows_NO_inflation["discount_rate"] = 0.09
return ok.Portfolio(**_portfolio_cashflows_NO_inflation)
Expand All @@ -177,8 +177,8 @@ def portfolio_cashflows_NO_inflation(init_portfolio_values):
@pytest.fixture(scope="package")
def portfolio_cashflows_NO_inflation_NO_discount_rate(init_portfolio_values):
_portfolio_cashflows_NO_inflation_NO_discount_rate = deepcopy(init_portfolio_values)
_portfolio_cashflows_NO_inflation_NO_discount_rate["cashflow"] = -100.
_portfolio_cashflows_NO_inflation_NO_discount_rate["initial_amount"] = 100_000.
_portfolio_cashflows_NO_inflation_NO_discount_rate["cashflow"] = -100.0
_portfolio_cashflows_NO_inflation_NO_discount_rate["initial_amount"] = 100_000.0
_portfolio_cashflows_NO_inflation_NO_discount_rate["inflation"] = False
_portfolio_cashflows_NO_inflation_NO_discount_rate["discount_rate"] = None
return ok.Portfolio(**_portfolio_cashflows_NO_inflation_NO_discount_rate)
Expand Down Expand Up @@ -211,6 +211,7 @@ def portfolio_short_history(init_portfolio_values):
_portfolio_short_history["first_date"] = "2019-02"
return ok.Portfolio(**_portfolio_short_history)


# Efficient Frontier Single Period
@pytest.fixture(scope="module")
def init_efficient_frontier_values1():
Expand Down
32 changes: 18 additions & 14 deletions tests/test_asset_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,31 +86,35 @@ def test_make_asset_list(self):
assert self.asset_list.assets_ror.columns.name == "Symbols"

def test_calculate_wealth_indexes(self):
assert self.asset_list.wealth_indexes.sum(axis=1)[-1] == approx(
3306.19, rel=1e-2
) # last month indexes sum
assert self.asset_list.wealth_indexes.sum(axis=1)[-1] == approx(3306.19, rel=1e-2) # last month indexes sum

def test_risk(self):
assert self.asset_list.risk_monthly.iloc[-1]["USDRUB.CBR"] == approx(0.0258, rel=1e-2)
assert self.asset_list.risk_monthly.iloc[-1]["MCFTR.INDX"] == approx(0.0264, rel=1e-2)
assert self.asset_list.risk_annual.iloc[-1]["USDRUB.CBR"] == approx(0.0825, rel=1e-2)
assert self.asset_list.risk_annual.iloc[-1]["MCFTR.INDX"] == approx(0.1222, rel=1e-2)

@mark.parametrize("window, expected", [
pytest.param(12, 0.1874, id="12-months"),
pytest.param(6, 0.2205, id="6-months"),
])
@mark.parametrize(
"window, expected",
[
pytest.param(12, 0.1874, id="12-months"),
pytest.param(6, 0.2205, id="6-months"),
],
)
def test_get_rolling_risk_annual(self, window, expected):
result = self.asset_list.get_rolling_risk_annual(window).iloc[-1]
assert result.sum() == approx(expected, abs=1e-2)

@pytest.mark.parametrize("window, exception", [
pytest.param(0, ValueError, id="error-0-months"),
pytest.param(-1, ValueError, id="error-negative-months"),
pytest.param(34, ValueError, id="error - window period is too long"),
pytest.param("twelve", TypeError, id="error-string-input"),
pytest.param(None, TypeError, id="error-none-input"),
])
@pytest.mark.parametrize(
"window, exception",
[
pytest.param(0, ValueError, id="error-0-months"),
pytest.param(-1, ValueError, id="error-negative-months"),
pytest.param(34, ValueError, id="error - window period is too long"),
pytest.param("twelve", TypeError, id="error-string-input"),
pytest.param(None, TypeError, id="error-none-input"),
],
)
def test_get_rolling_risk_annual_error_cases(self, window, exception):
with pytest.raises(exception):
self.spy.get_rolling_risk_annual(window)
Expand Down
Loading

0 comments on commit c9c5c87

Please sign in to comment.