@@ -65,6 +65,8 @@ def __init__(
65
65
optimizer = None ,
66
66
max_vol = 0.15 ,
67
67
diversification = 1 ,
68
+ expected_returns = None ,
69
+ risk_model = None ,
68
70
# confidences=None,
69
71
# view=None,
70
72
min_weights = None ,
@@ -84,6 +86,12 @@ def __init__(
84
86
self .rebalance = rebalance
85
87
self .max_vol = max_vol
86
88
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!"
87
95
self .max_weights = max_weights
88
96
self .min_weights = min_weights
89
97
self .risk_manager = risk_manager
@@ -115,26 +123,34 @@ def __init__(
115
123
self .diversification ,
116
124
self .min_weights ,
117
125
self .max_weights ,
126
+ self .expected_returns ,
127
+ self .risk_model
118
128
)
119
129
120
130
121
131
def get_returns (stocks , wts , start_date , end_date = TODAY ):
122
132
if len (stocks ) > 1 :
123
133
assets = yf .download (stocks , start = start_date , end = end_date , progress = False )["Adj Close" ]
124
134
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 :]
127
140
return returns
128
141
else :
129
142
df = yf .download (stocks , start = start_date , end = end_date , progress = False )["Adj Close" ]
130
143
df = pd .DataFrame (df )
131
- returns = df .pct_change ()
144
+ returns = df .pct_change ()[ 1 :]
132
145
return returns
133
146
134
147
135
148
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 :]
138
154
return returns
139
155
140
156
@@ -535,12 +551,12 @@ def efficient_frontier(my_portfolio, perf=True) -> list:
535
551
# 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
536
552
df = df .fillna (1 )
537
553
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 )
540
556
541
557
# optimize for max sharpe ratio
542
558
ef = EfficientFrontier (mu , S )
543
-
559
+ ef . add_objective ( objective_functions . L2_reg , gamma = my_portfolio . diversification )
544
560
if my_portfolio .min_weights is not None :
545
561
ef .add_constraint (lambda x : x >= my_portfolio .min_weights )
546
562
if my_portfolio .max_weights is not None :
@@ -608,8 +624,8 @@ def mean_var(my_portfolio, vol_max=0.15, perf=True) -> list:
608
624
# 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
609
625
prices = prices .fillna (1 )
610
626
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 )
613
629
614
630
ef = EfficientFrontier (mu , S )
615
631
ef .add_objective (objective_functions .L2_reg , gamma = my_portfolio .diversification )
@@ -643,8 +659,8 @@ def min_var(my_portfolio, perf=True) -> list:
643
659
prices = ohlc ["Adj Close" ].dropna (how = "all" )
644
660
prices = prices .filter (my_portfolio .portfolio )
645
661
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 )
648
664
649
665
ef = EfficientFrontier (mu , S )
650
666
ef .add_objective (objective_functions .L2_reg , gamma = my_portfolio .diversification )
@@ -794,6 +810,8 @@ def make_rebalance(
794
810
div ,
795
811
min ,
796
812
max ,
813
+ expected_returns ,
814
+ risk_model ,
797
815
) -> pd .DataFrame :
798
816
sdate = str (start_date )[:10 ]
799
817
if rebalance [0 ] != sdate :
@@ -836,6 +854,8 @@ def make_rebalance(
836
854
diversification = div ,
837
855
min_weights = min ,
838
856
max_weights = max ,
857
+ expected_returns = expected_returns ,
858
+ risk_model = risk_model ,
839
859
)
840
860
841
861
except TypeError :
@@ -849,6 +869,8 @@ def make_rebalance(
849
869
diversification = div ,
850
870
min_weights = min ,
851
871
max_weights = max ,
872
+ expected_returns = expected_returns ,
873
+ risk_model = risk_model ,
852
874
)
853
875
854
876
output_df ["{}" .format (dates [i + 1 ])] = portfolio .weights
@@ -864,6 +886,8 @@ def make_rebalance(
864
886
diversification = div ,
865
887
min_weights = min ,
866
888
max_weights = max ,
889
+ expected_returns = expected_returns ,
890
+ risk_model = risk_model ,
867
891
)
868
892
869
893
except TypeError :
@@ -876,11 +900,15 @@ def make_rebalance(
876
900
diversification = div ,
877
901
min_weights = min ,
878
902
max_weights = max ,
903
+ expected_returns = expected_returns ,
904
+ risk_model = risk_model ,
879
905
)
880
906
881
907
output_df ["{}" .format (TODAY )] = portfolio .weights
882
908
883
909
make_rebalance .output = output_df
910
+ print ("Rebalance schedule: " )
911
+ print (output_df )
884
912
return output_df
885
913
886
914
0 commit comments