66
77
88
9- def _buy_and_hold_port (port , j :int , q :float , y , s :dict , cov , port_kwargs ):
9+ def index_of_closest (w , ws ):
10+ l1s = [ sum (abs (np .array (w )- np .array (wsi ))) for wsi in ws ]
11+ return l1s .index (min (l1s ))
12+
13+
14+ def _buy_and_hold_and_choose_port (port , j :int , q :float , l :int , y , s :dict , cov , port_kwargs ):
1015 """
1116
1217 Sporadically calls port() but uses buy and hold in between.
@@ -30,10 +35,19 @@ def _buy_and_hold_port(port, j:int, q:float, y, s:dict, cov, port_kwargs):
3035 else :
3136 s ['count' ] = s ['count' ]+ 1
3237 if s ['count' ] % j == 0 :
33- # Create a compromise between the roll forward portfolio and what the optimizer wants
38+ # Compute roll forward weights
3439 s ['multiplier' ] = [mi * math .exp (yi ) for mi , yi in zip (s ['multiplier' ], y )]
3540 w_roll = normalize ([wi * mi for wi , mi in zip (s ['w' ], s ['multiplier' ])])
36- w_port = port (cov = cov , ** port_kwargs )
41+
42+ # Run the portfolio optimizer a few times and pick portfolio closest to prior
43+ if l is None :
44+ w_port = port (cov = cov , ** port_kwargs )
45+ else :
46+ w_ports = [ port (cov = cov , ** port_kwargs ) for _ in range (l ) ]
47+ choice = index_of_closest (w_roll , w_ports )
48+ w_port = list (w_ports [choice ])
49+
50+ # Create a compromise between the roll forward portfolio and what the optimizer wants
3751 w = [q * wi + (1 - q ) * wpi for wi , wpi in zip (w_port , w_roll )]
3852 s ['w' ] = [wi for wi in w ]
3953 s ['multiplier' ] = [1 for _ in range (n_dim )]
@@ -45,7 +59,7 @@ def _buy_and_hold_port(port, j:int, q:float, y, s:dict, cov, port_kwargs):
4559 return w , s
4660
4761
48- def static_cov_manager_factory_d0 (y , s , f , port , e = 1 , f_kwargs :dict = None , port_kwargs :dict = None , n_cold = 5 , zeta = 0.0 , j = 1 ,q = 1.0 ):
62+ def static_cov_manager_factory_d0 (y , s , f , port , e = 1 , f_kwargs :dict = None , port_kwargs :dict = None , n_cold = 5 , zeta = 0.0 , j = 1 ,q = 1.0 , l = None ):
4963 """
5064 Basic manager pattern ignoring mean.
5165 Expects to receive changes in log(price).
@@ -57,6 +71,7 @@ def static_cov_manager_factory_d0(y, s, f, port, e=1, f_kwargs:dict=None, port_k
5771 :param zeta Compromise between cov and corr portfolio (zeta=0, use cov only)
5872 :param j How often to run the static portfolio construction.
5973 (Warning: if j>1 this will be nonsense unless 'y' represents changes in log prices)
74+ :param l How many times to run the static portfolio construction
6075
6176 :returns w, s
6277 """
@@ -76,11 +91,11 @@ def static_cov_manager_factory_d0(y, s, f, port, e=1, f_kwargs:dict=None, port_k
7691 s ['count' ]+= 1
7792 if s ['count' ]>= n_cold and (e > 0 ):
7893 s_account = s ['account_state' ]
79- w , s_account = _buy_and_hold_port (port = port , y = y , j = j , q = q , s = s_account , cov = x_cov , port_kwargs = port_kwargs )
94+ w , s_account = _buy_and_hold_and_choose_port (port = port , y = y , j = j , q = q , l = l , s = s_account , cov = x_cov , port_kwargs = port_kwargs )
8095 s ['account_state' ] = s_account
8196 if zeta is not None and (zeta > 0 ):
8297 # Drags towards the portfolio determined by cov
83- # FIXME: This should be moved inside _buy_and_hold_port
98+ # FIXME: This should be moved inside _buy_and_hold_and_choose_port
8499 x_diag = np .diag (x_cov )
85100 x_corr = cov_to_corrcoef (x_cov )
86101 w_corr_raw = port (cov = x_corr , ** port_kwargs )
0 commit comments