I am building a gradient descent model based on portfolio optimization. Currently, I have finished the model and am able to run it smoothly without any problem. However, there's one issue that I couldn't missfix: the optimal vector components have negative values. As my portfolio does not support short-selling stocks, this optimal solution may not be a convincing solution to my portfolio.
Model Description
I will brief through my model as below: MyMy cost function for the portfolio problem is defined as
$$F(x)=\frac{\beta_1}{2}(x^T\boldsymbol{\Sigma}x)-\mu^Tx+\frac{\gamma}{2}(e^Tx-1)^2+\lambda(e^Tx-1)+\frac{\beta_2}{2}\Vert{x}\Vert^2_2+\rho\sum_{i=1}^n\max(0,-x_i)^2$$
where x$x$ and $\boldsymbol{\Sigma}$ are my minimizer vector and positive definite covariance matrix.
$\frac{\gamma}{2}(e^Tx-1)^2+\lambda(e^Tx-1)$ is the augmented lagrangian method for the constraint
$e^Tx=1$ and $\rho\sum_{i=1}^n\max(0,-x_i)^2$ is the penalty methodimposing $x_i>0]$ for all , the component i incomponents of x$x$.
As it can be seen here, my objective is to find the optimum solution for x$x$ using the Steepest Descent method. Hence, I start to build my Python code as below:
import numpy as np
from numpy.linalg import norm
def penalty_f(v):
return np.sum([np.power(max(0., -xi),2) for xi in v])
def penalty_df(v):
return -2.*np.array([max(0, -xi) for xi in v])
def aug_lag(lam, v):
new_lam = lam + gam*(np.sum(v)-1.)
return new_lam
def lipschitz(v):
lips = beta_1*np.sqrt(np.trace(cov@cov)) + beta_2*np.sqrt(len(v)) + gam*len(v)
return min(1,1/lips)
def project_f(v, lam_k):
func = 0.5*beta_1*v.T@cov@v - b*mean.T@v + gam/2*(np.sum(v)-1.)**2 + lam_k*(np.sum(v)-1.) + beta_2/2*v@v + rho*penalty_f(v)
return func
def project_df(v, lam_k):
par_func = beta_1*cov@v - b*mean + gam*np.ones_like(v)*(np.sum(v)-1.) + lam_k*np.ones_like(v) + beta_2*v + rho*penalty_df(v)
return par_func
def gradient_descent(f, df, ini_v, tolerance, lam, MAX_ITER=10000, output_fname='output.txt'):
vec = ini_v
negative = 0
aug_lam = lam
with open(output_fname, 'w') as out:
for i in range(MAX_ITER):
alpha = lipschitz(vec)
f_value = f(vec, aug_lam)
gradient = df(vec, aug_lam)
direction = np.negative(gradient)
vec += alpha * direction
aug_lam = aug_lag(aug_lam, vec)
print("No. of zeros: ", len(vec)-np.count_nonzero(vec))
print(norm(gradient,2), "," , f_value)
msg = f'{f_value},{norm(gradient,2)}, {str(list(vec))}\n'
out.write(msg)
# stopping criteria
if norm(gradient,2) < tolerance:
print ("The optimum vector for", {df}, " is at ", vec,"at iteration ", i+1)
for i in vec:
if i < 0:
negative += 1
print("No of negative: ", negative)
print("Vector sum: ",np.sum(vec))
print("Gradient: ", norm(gradient,2))
break
if i == MAX_ITER:
print ('Higher no. of iterations is needed for', {df})
print ("Vector: ", vec)
print("Vector sum: ",np.sum(vec))
print("Gradient: ", norm(gradient,2))
return vec
When my assessed stocks areI assess 30 and aboveor more stocks, the negative components start to appear. Hope to know if there'sIs there anything I can improve or take note of when performing portfolio optimization?