1616import os
1717
1818
19- def load_portfolio_data (cached_dir = '/Users/johngeorgealexander/qc/quantum-portfolio-opt/data/cached' ):
19+ def load_portfolio_data (
20+ cached_dir = "/Users/johngeorgealexander/qc/quantum-portfolio-opt/data/cached" ,
21+ ):
2022 """
2123 Load portfolio data from Day 2 cached results.
2224
@@ -26,11 +28,13 @@ def load_portfolio_data(cached_dir='/Users/johngeorgealexander/qc/quantum-portfo
2628 stock_names (list): List of stock names
2729 """
2830 # Load mean returns (μ) and covariance (Σ)
29- mu = np .load (os .path .join (cached_dir , ' mu.npy' ))
30- sigma = np .load (os .path .join (cached_dir , ' sigma.npy' ))
31+ mu = np .load (os .path .join (cached_dir , " mu.npy" ))
32+ sigma = np .load (os .path .join (cached_dir , " sigma.npy" ))
3133
3234 # Load stock names from CSV
33- expected_returns_df = pd .read_csv (os .path .join (cached_dir , 'expected_returns.csv' ), index_col = 0 )
35+ expected_returns_df = pd .read_csv (
36+ os .path .join (cached_dir , "expected_returns.csv" ), index_col = 0
37+ )
3438 stock_names = expected_returns_df .index .tolist ()
3539
3640 return mu , sigma , stock_names
@@ -66,8 +70,11 @@ def optimize_portfolio(mu, sigma, target_return):
6670
6771 # Constraints
6872 constraints = [
69- {'type' : 'eq' , 'fun' : lambda w : w .sum () - 1 }, # weights sum to 1
70- {'type' : 'eq' , 'fun' : lambda w : portfolio_return (w , mu ) - target_return } # target return
73+ {"type" : "eq" , "fun" : lambda w : w .sum () - 1 }, # weights sum to 1
74+ {
75+ "type" : "eq" ,
76+ "fun" : lambda w : portfolio_return (w , mu ) - target_return ,
77+ }, # target return
7178 ]
7279
7380 # Bounds: no short selling (w_i >= 0)
@@ -78,10 +85,10 @@ def optimize_portfolio(mu, sigma, target_return):
7885 portfolio_variance ,
7986 w0 ,
8087 args = (sigma ,),
81- method = ' SLSQP' ,
88+ method = " SLSQP" ,
8289 bounds = bounds ,
8390 constraints = constraints ,
84- options = {' disp' : False }
91+ options = {" disp" : False },
8592 )
8693
8794 if result .success :
@@ -118,12 +125,14 @@ def generate_efficient_frontier(mu, sigma, n_points=50):
118125 for target_ret in target_returns :
119126 w_opt , ret_opt , var_opt = optimize_portfolio (mu , sigma , target_ret )
120127 if w_opt is not None :
121- portfolios .append ({
122- 'return' : ret_opt ,
123- 'variance' : var_opt ,
124- 'volatility' : np .sqrt (var_opt ),
125- 'weights' : w_opt
126- })
128+ portfolios .append (
129+ {
130+ "return" : ret_opt ,
131+ "variance" : var_opt ,
132+ "volatility" : np .sqrt (var_opt ),
133+ "weights" : w_opt ,
134+ }
135+ )
127136
128137 return portfolios
129138
@@ -136,8 +145,8 @@ def get_efficient_frontier_data(mu, sigma, n_points=30):
136145 tuple: (returns_list, volatilities_list, portfolios_list)
137146 """
138147 portfolios = generate_efficient_frontier (mu , sigma , n_points )
139- returns = [p [' return' ] for p in portfolios ]
140- volatilities = [p [' volatility' ] for p in portfolios ]
148+ returns = [p [" return" ] for p in portfolios ]
149+ volatilities = [p [" volatility" ] for p in portfolios ]
141150 return returns , volatilities , portfolios
142151
143152
@@ -168,22 +177,28 @@ def get_efficient_frontier_data(mu, sigma, n_points=30):
168177 portfolios = generate_efficient_frontier (mu , sigma , n_points = 20 )
169178 if portfolios :
170179 print (f"\n Generated { len (portfolios )} portfolios on efficient frontier" )
171- print (f"Return range: { min (p ['return' ] for p in portfolios ):.4f} to { max (p ['return' ] for p in portfolios ):.4f} " )
172- print (f"Volatility range: { min (p ['volatility' ] for p in portfolios ):.4f} to { max (p ['volatility' ] for p in portfolios ):.4f} " )
180+ min_ret = min (p ["return" ] for p in portfolios )
181+ max_ret = max (p ["return" ] for p in portfolios )
182+ print (f"Return range: { min_ret :.4f} to { max_ret :.4f} " )
183+
184+ min_vol = min (p ["volatility" ] for p in portfolios )
185+ max_vol = max (p ["volatility" ] for p in portfolios )
186+ print (f"Volatility range: { min_vol :.4f} to { max_vol :.4f} " )
173187
174188 print ("=" * 70 )
175189import time
176190
191+
177192def greedy_qubo_search (Q , k ):
178193 """
179194 Greedy heuristic to find an approximate solution for a QUBO.
180195 Iteratively adds the single asset that minimizes the objective
181196 until k assets are selected.
182-
197+
183198 Args:
184199 Q: QUBO matrix (numpy array)
185200 k: Int, number of stocks to pick
186-
201+
187202 Returns:
188203 best_x: numpy array (binary vector)
189204 best_obj: float
@@ -192,28 +207,28 @@ def greedy_qubo_search(Q, k):
192207 start_time = time .time ()
193208 n = len (Q )
194209 x = np .zeros (n , dtype = int )
195-
210+
196211 # We want exactly k stocks
197212 for _ in range (k ):
198213 best_gain = float ("inf" )
199214 best_idx = - 1
200-
215+
201216 # Test flipping each 0 to 1
202217 for i in range (n ):
203218 if x [i ] == 0 :
204219 x_test = x .copy ()
205220 x_test [i ] = 1
206221 obj = float (x_test @ Q @ x_test )
207-
222+
208223 if obj < best_gain :
209224 best_gain = obj
210225 best_idx = i
211-
226+
212227 # Permanently flip the best one
213228 if best_idx != - 1 :
214229 x [best_idx ] = 1
215-
230+
216231 final_obj = float (x @ Q @ x )
217232 exec_time = time .time () - start_time
218-
233+
219234 return x , final_obj , exec_time
0 commit comments