@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.
Sorry for the ugly pictures!
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)
