Skip to content

Commit 1d559cf

Browse files
authored
Merge pull request #106 from pzzhang/main
Fixing rebalancing
2 parents 57cb7d2 + 73fbb2d commit 1d559cf

File tree

1 file changed

+40
-12
lines changed

1 file changed

+40
-12
lines changed

src/empyrial/main.py

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ def __init__(
6565
optimizer=None,
6666
max_vol=0.15,
6767
diversification=1,
68+
expected_returns=None,
69+
risk_model=None,
6870
# confidences=None,
6971
# view=None,
7072
min_weights=None,
@@ -84,6 +86,12 @@ def __init__(
8486
self.rebalance = rebalance
8587
self.max_vol = max_vol
8688
self.diversification = diversification
89+
self.expected_returns = expected_returns
90+
if expected_returns is not None:
91+
assert expected_returns in ["mean_historical_return", "ema_historical_return", "capm_return"], f"Expected return method: {expected_returns} not supported yet!"
92+
self.risk_model = risk_model
93+
if risk_model is not None:
94+
assert risk_model in ["sample_cov", "semicovariance", "exp_cov", "ledoit_wolf", "ledoit_wolf_constant_variance", "ledoit_wolf_single_factor", "ledoit_wolf_constant_correlation", "oracle_approximating"], f"Risk model: {risk_model} not supported yet!"
8795
self.max_weights = max_weights
8896
self.min_weights = min_weights
8997
self.risk_manager = risk_manager
@@ -115,26 +123,34 @@ def __init__(
115123
self.diversification,
116124
self.min_weights,
117125
self.max_weights,
126+
self.expected_returns,
127+
self.risk_model
118128
)
119129

120130

121131
def get_returns(stocks, wts, start_date, end_date=TODAY):
122132
if len(stocks) > 1:
123133
assets = yf.download(stocks, start=start_date, end=end_date, progress=False)["Adj Close"]
124134
assets = assets.filter(stocks)
125-
ret_data = assets.pct_change()[1:]
126-
returns = (ret_data * wts).sum(axis=1)
135+
initial_alloc = wts/assets.iloc[0]
136+
if initial_alloc.isna().any():
137+
raise ValueError("Some stock is not available at initial state!")
138+
portfolio_value = (assets * initial_alloc).sum(axis=1)
139+
returns = portfolio_value.pct_change()[1:]
127140
return returns
128141
else:
129142
df = yf.download(stocks, start=start_date, end=end_date, progress=False)["Adj Close"]
130143
df = pd.DataFrame(df)
131-
returns = df.pct_change()
144+
returns = df.pct_change()[1:]
132145
return returns
133146

134147

135148
def get_returns_from_data(data, wts):
136-
ret_data = data.pct_change()[1:]
137-
returns = (ret_data * wts).sum(axis=1)
149+
initial_alloc = wts/data.iloc[0]
150+
if initial_alloc.isna().any():
151+
raise ValueError("Some stock is not available at initial state!")
152+
portfolio_value = (data * initial_alloc).sum(axis=1)
153+
returns = portfolio_value.pct_change()[1:]
138154
return returns
139155

140156

@@ -535,12 +551,12 @@ def efficient_frontier(my_portfolio, perf=True) -> list:
535551
# sometimes we will pick a date range where company isn't public we can't set price to 0 so it has to go to 1
536552
df = df.fillna(1)
537553

538-
mu = expected_returns.mean_historical_return(df)
539-
S = risk_models.sample_cov(df)
554+
mu = expected_returns.return_model(df, method=my_portfolio.expected_returns)
555+
S = risk_models.risk_matrix(df, method=my_portfolio.risk_model)
540556

541557
# optimize for max sharpe ratio
542558
ef = EfficientFrontier(mu, S)
543-
559+
ef.add_objective(objective_functions.L2_reg, gamma=my_portfolio.diversification)
544560
if my_portfolio.min_weights is not None:
545561
ef.add_constraint(lambda x: x >= my_portfolio.min_weights)
546562
if my_portfolio.max_weights is not None:
@@ -608,8 +624,8 @@ def mean_var(my_portfolio, vol_max=0.15, perf=True) -> list:
608624
# sometimes we will pick a date range where company isn't public we can't set price to 0 so it has to go to 1
609625
prices = prices.fillna(1)
610626

611-
mu = expected_returns.capm_return(prices)
612-
S = risk_models.CovarianceShrinkage(prices).ledoit_wolf()
627+
mu = expected_returns.return_model(prices, method=my_portfolio.expected_returns)
628+
S = risk_models.risk_matrix(prices, method=my_portfolio.risk_model)
613629

614630
ef = EfficientFrontier(mu, S)
615631
ef.add_objective(objective_functions.L2_reg, gamma=my_portfolio.diversification)
@@ -643,8 +659,8 @@ def min_var(my_portfolio, perf=True) -> list:
643659
prices = ohlc["Adj Close"].dropna(how="all")
644660
prices = prices.filter(my_portfolio.portfolio)
645661

646-
mu = expected_returns.capm_return(prices)
647-
S = risk_models.CovarianceShrinkage(prices).ledoit_wolf()
662+
mu = expected_returns.return_model(prices, method=my_portfolio.expected_returns)
663+
S = risk_models.risk_matrix(prices, method=my_portfolio.risk_model)
648664

649665
ef = EfficientFrontier(mu, S)
650666
ef.add_objective(objective_functions.L2_reg, gamma=my_portfolio.diversification)
@@ -794,6 +810,8 @@ def make_rebalance(
794810
div,
795811
min,
796812
max,
813+
expected_returns,
814+
risk_model,
797815
) -> pd.DataFrame:
798816
sdate = str(start_date)[:10]
799817
if rebalance[0] != sdate:
@@ -836,6 +854,8 @@ def make_rebalance(
836854
diversification=div,
837855
min_weights=min,
838856
max_weights=max,
857+
expected_returns=expected_returns,
858+
risk_model=risk_model,
839859
)
840860

841861
except TypeError:
@@ -849,6 +869,8 @@ def make_rebalance(
849869
diversification=div,
850870
min_weights=min,
851871
max_weights=max,
872+
expected_returns=expected_returns,
873+
risk_model=risk_model,
852874
)
853875

854876
output_df["{}".format(dates[i + 1])] = portfolio.weights
@@ -864,6 +886,8 @@ def make_rebalance(
864886
diversification=div,
865887
min_weights=min,
866888
max_weights=max,
889+
expected_returns=expected_returns,
890+
risk_model=risk_model,
867891
)
868892

869893
except TypeError:
@@ -876,11 +900,15 @@ def make_rebalance(
876900
diversification=div,
877901
min_weights=min,
878902
max_weights=max,
903+
expected_returns=expected_returns,
904+
risk_model=risk_model,
879905
)
880906

881907
output_df["{}".format(TODAY)] = portfolio.weights
882908

883909
make_rebalance.output = output_df
910+
print("Rebalance schedule: ")
911+
print(output_df)
884912
return output_df
885913

886914

0 commit comments

Comments
 (0)