You can either construct the quadratic term independently:
using JuMP, Ipopt
model = Model(Ipopt.Optimizer)
@variable(model, x[1:2])
A = [0.00076204 0.00051972; 0.00051972 0.00546173]
@expression(model, quad_expr, x' * A * x)
@NLobjective(model, Min, sqrt(quad_expr))
Or write out the scalarized summation:
using JuMP, Ipopt
model = Model(Ipopt.Optimizer)
A = [0.00076204 0.00051972; 0.00051972 0.00546173]
N = size(A, 1)
@variable(model, x[1:N])
@NLobjective(model, Min, sqrt(sum(x[i] * A[i, j] * x[j] for i in 1:N, j in 1:N)))
@edow
Thank you so much for your very prompt answer. That works perfectly - I was trying to solve it for the last 5 hours :). Just have one more question, my apologies for writing in installments. I want to add a constraint that the sum of elements of x will equal 1. Individual elements will be greater than 0 and less than 1
@NLconstraint(model, sum(x) = 1)
@NLconstraint(model, 0 <= x <= 1)
I hope it makes sense. Is there any way to implement this too?
Thank you so much for your attention and participation.
Best regards
Mehmet
@odow,
Thanks for helping me with the optimization problem earlier (Feb 3). Indeed thank you for JuMP in the very first place. It’s a great package.
JuMP.jl is a dependency for PortoflioAnalytics.jl which I am actively developing. I will soon make a second release and want to improve the portfolio optimization function in it and wondering if you could make a sanity check for the below function.
Function either minimizes the portfolio variance or maximizes the Sharpe ratio. I want users to define (if they want) a target portfolio mean return so that they can move across the efficient frontier.
The output looks reasonable, but I am unsure if it is doing what I think it is.
I am placing the code here - I hope it will be easy to reproduce.
using JuMP
using Ipopt
using Statistics
using TSFrames
using NamedArrays
bond = [0.06276629, 0.03958098, 0.08456482,0.02759821,0.09584956,0.06363253,0.02874502,0.02707264,0.08776449,0.02950032]
stock = [0.1759782,0.20386651,0.21993588,0.3090001,0.17365969,0.10465274,0.07888138,0.13220847,0.28409742,0.14343067]
R = TSFrame([bond stock], colnames = [:bond, :stock])
function PortfolioOptimize(R::TSFrame, objective::String = "minumum variance"; target = Nothing, Rf::Number = 0)
means = mean.(eachcol(Matrix(R)))
covMatrix = cov(Matrix(R))
model = Model(Ipopt.Optimizer)
# set_silent(model)
@variable(model, 0 <= w[1:size(R)[2]] <= 1)
@constraint(model, sum(w) == 1)
if objective == "minumum variance"
@expression(model, quad_expr, w' * covMatrix * w)
@NLobjective(model, Min, sqrt(quad_expr))
if target != Nothing
@NLconstraint(model, sum(w[i] * means[i] for i in 1:size(R)[2]) >= target)
end
elseif objective == "maximum sharpe"
# Equation should be transformed to either scalar values or expressed in the form of expressions.
# N = size(covMatrix, 1) # either
# @NLobjective(model, Max, (sum(w[i] * means[i] for i in 1:N) - Rf) / sqrt(sum(w[i] * covMatrix[i, j] * w[j] for i in 1:N, j in 1:N))) # either
@expression(model, quad_expr1, w' * means) # or
@expression(model, quad_expr2, w' * covMatrix * w) # or
@NLobjective(model, Max, (quad_expr1 - Rf)/sqrt(quad_expr2))
if target != Nothing
# @NLconstraint(model, sum(w[i] * means[i] for i in 1:size(R)[2]) >= target) # either
@NLconstraint(model, quad_expr1 >= target) # or
end
else
throw(ArgumentError("one of the available objective must be chosen"))
end
optimize!(model)
portobj = objective_value(model)
weights = value.(w)
pmreturn = weights' * means
colnames = names(R) # used only for naming array
weights = NamedArray(weights, colnames, "Optimal Weights")
return (preturn = pmreturn, obj = portobj, pweights = weights)
end
opt1 = PortfolioOptimize(R, "minumum variance", target = 0.1)
opt2 = PortfolioOptimize(R, "maximum sharpe", target = 0.15)
Thanks very much for the answer. Could you please let me know which version of Gurobi you use? It cannot load for some reason. I’m using Julia version 1.8.5
I’m very excited. I am testing it now, and hopefully, I will complete it tomorrow and share the result here. Will definitely add the plotting functionality to the function definition so that users can display it. The function will become way more advanced than I was ever imagining.
it gives the maximum expected_return for each level of variance, which is essentially the efficient frontier.
Before releasing the next version of PortfolioAnalytics.jl I want to ask one more question.
If I would like to add a non-linear expression, in case I want to add new functionalities to the function, is it possible? I want to do the same but with different objectives. If I want to obtain the maximum expected_return per given Sharpe ratio defined as expected_return / sqrt(variance) assuming the risk-free rate is zero.
For example, it fails when I want to add the below expression and objective. It is because of the square root, I believe. I think there is a specific syntax for MultiObjectiveAlgorithms, but I could not find it in the documentation.
Or should I use the output of this optimization, which will automatically give the expected_return per level of Sharpe something like the one below? But I am still curious whether there is any possible way of defining non-linear expressions and objectives.
[value(expected_return; result = i) for i in 1:result_count(model)] ./ sqrt([value(variance; result = i) for i in 1:result_count(model)])
Everything is way beyond what I imagined. The below is just a demonstration of my excitement. Hopefully, starting from the next week, the wider Julia community will enjoy this and other financial functions to perform quantitative portfolio analytics.
Thanks very much again. I wouldn’t be able to do it without your and JuMP.jl’s help.