Skip to content

Commit 0398e7c

Browse files
committed
implement portfolio optimization
1 parent 62479a1 commit 0398e7c

8 files changed

Lines changed: 174 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- Update docs example in travelingsalesman.
44
- Update the documentation so that the examples are runnable by copy-pasting.
55
- Add example in the docs of game solver.
6+
- Add Porfolio Optimization
67

78
### 0.2.9
89

Project.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
name = "OperationsResearchModels"
22
uuid = "8042aa49-e599-49ec-a8f7-b5b80cc82a88"
3-
authors = ["Mehmet Hakan Satman <mhsatman@gmail.com>"]
43
version = "0.2.9"
4+
authors = ["Mehmet Hakan Satman <mhsatman@gmail.com>"]
55

66
[deps]
77
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
8+
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
89
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
10+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
11+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
912

1013
[compat]
1114
HiGHS = "1"
15+
Ipopt = "1.13.0"
1216
JuMP = "1"
17+
LinearAlgebra = "1.12.0"
18+
Statistics = "1.11.1"
1319
julia = "1.6"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ makedocs(
1919
"Scheduling" => "scheduling.md",
2020
"The Simplex Method" => "simplex.md",
2121
"Zero-sum Games" => "game.md",
22+
"Portfolio Optimization" => "portfolio.md",
2223
],
2324
)
2425

docs/src/portfolio.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Portfolio Optimization
2+
3+
## PortfolioProblem
4+
5+
```@docs
6+
OperationsResearchModels.PortfolioProblem
7+
```
8+
9+
## PortfolioResult
10+
11+
```@docs
12+
OperationsResearchModels.PortfolioResult
13+
```
14+
15+
## solve
16+
17+
```@docs
18+
OperationsResearchModels.solve(t::PortfolioProblem)
19+
```

src/OperationsResearchModels.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ using JuMP, HiGHS
1414
# solve(m::MstProblem)::MstResult
1515
# solve(t::TravelingSalesmanProblem)::TravelingSalesmanResult
1616
# solve(p::JohnsonProblem)::JohnsonResult
17+
# solve(p::PortfolioProblem)::PortfolioResult
1718
solve() = nothing
1819

1920
# solve!(s::SimplexProblem)::SimplexProblem
@@ -51,6 +52,7 @@ include("latex.jl")
5152
include("randomkeyga.jl")
5253
include("johnsons.jl")
5354
include("travelingsalesman.jl")
55+
include("portfolio.jl")
5456

5557
import .Network
5658
import .Transportation
@@ -69,6 +71,7 @@ import .Utility
6971
import .RandomKeyGA
7072
import .Johnsons
7173
import .TravelingSalesman
74+
import .Portfolio
7275

7376

7477
import .Transportation:
@@ -97,6 +100,8 @@ import .RandomKeyGA: Chromosome, run_ga
97100
import .TravelingSalesman: TravelingSalesmanResult, TravelingSalesmanProblem
98101
import .Simplex:
99102
SimplexProblem, simplexiterations, createsimplexproblem, gaussjordan, OptimizationType
103+
import .Portfolio: PortfolioProblem, PortfolioResult
104+
100105

101106
export TransportationProblem,
102107
TransportationResult, balance, isbalanced, northwestcorner, leastcost
@@ -115,6 +120,7 @@ export latex
115120
export Chromosome, run_ga
116121
export JohnsonProblem, JohnsonResult, JohnsonException, makespan, johnsons_ga
117122
export TravelingSalesmanResult, TravelingSalesmanProblem
123+
export PortfolioProblem, PortfolioResult
118124
export simplexiterations,
119125
SimplexProblem, createsimplexproblem, gaussjordan, OptimizationType
120126

src/portfolio.jl

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
module Portfolio
2+
3+
using JuMP, Ipopt, Statistics, LinearAlgebra
4+
5+
import ..OperationsResearchModels: solve
6+
7+
export PortfolioProblem, PortfolioResult
8+
9+
10+
11+
12+
"""
13+
PortfolioProblem
14+
15+
# Description
16+
17+
Defines the portfolio optimization problem.
18+
19+
# Fields
20+
21+
- `returns::Matrix{<:Real}`: A matrix of historical returns for the assets.
22+
- `thresholdreturn::Real`: The minimum expected return required for the portfolio.
23+
24+
"""
25+
struct PortfolioProblem
26+
returns::Matrix{<:Real}
27+
thresholdreturn::Real
28+
end
29+
30+
31+
32+
"""
33+
PortfolioResult
34+
35+
# Description
36+
37+
A structure to hold the result of the portfolio optimization problem.
38+
39+
# Fields
40+
41+
- `weights::Vector{Float64}`: The optimal weights for the assets in the portfolio.
42+
- `expectedreturn::Float64`: The expected return of the optimal portfolio.
43+
- `model::JuMP.Model`: The JuMP model used to solve the problem.
44+
"""
45+
struct PortfolioResult
46+
weights::Vector{Float64}
47+
expectedreturn::Float64
48+
model::JuMP.Model
49+
end
50+
51+
52+
53+
"""
54+
solve(problem)
55+
56+
# Description
57+
58+
Solves a portfolio optimization problem given by an object of in type `PortfolioProblem`.
59+
The optimization problem is formulated as a quadratic programming problem where the objective
60+
is to minimize the portfolio variance (risk) subject to constraints on the expected return and the weights.
61+
62+
Mathematically, the problem can be stated as:
63+
64+
Minimize: w' * Σ * w
65+
Subject to:
66+
- sum(w) == 1 (the weights must sum to 1)
67+
- sum(w[i] * μ[i] for i in 1:m) >= thresholdreturn
68+
- 0 <= w[i] <= 1 for all i (weights must be between 0 and 1)
69+
70+
Where:
71+
- w is the vector of asset weights
72+
- Σ is the covariance matrix of asset returns
73+
- μ is the vector of expected returns for each asset
74+
- thresholdreturn is the minimum expected return required for the portfolio
75+
76+
# Arguments
77+
78+
- `problem::PortfolioProblem`: The problem in type of PortfolioProblem
79+
80+
# Returns
81+
82+
- `PortfolioResult`: The result of the portfolio optimization problem, containing the optimal weights, expected return, and the JuMP model used to solve the problem.
83+
84+
"""
85+
function solve(p::PortfolioProblem)::PortfolioResult
86+
87+
model = Model(Ipopt.Optimizer)
88+
89+
MOI.set(model, MOI.Silent(), true)
90+
91+
means = mean(p.returns, dims = 1)
92+
93+
covmat = cov(p.returns)
94+
95+
_, m = size(p.returns)
96+
97+
@variable(model, 0 <= w[1:m] <= 1)
98+
99+
@constraint(model, sum(w) == 1)
100+
101+
@constraint(model, sum(w[i] * means[i] for i in 1:m) >= p.thresholdreturn)
102+
103+
@objective(model, Min, w' * covmat * w)
104+
105+
optimize!(model)
106+
107+
weights = value.(w)
108+
109+
expectedreturn = sum(weights[i] * means[i] for i in 1:m)
110+
111+
return PortfolioResult(weights, expectedreturn, model)
112+
113+
end
114+
115+
116+
117+
118+
end # end of module

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ include("testpert.jl")
2121
include("testlatex.jl")
2222
include("testknapsack.jl")
2323
include("testsimplex.jl")
24+
include("testportfolio.jl")

test/testportfolio.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@testset "Portfolio Optimization" begin
2+
@testset "Markowitz Portfolio Optimization - Example 1" begin
3+
eps = 1e-5
4+
mydata = rand(100, 3)
5+
mydata[:, 3] = mydata[:, 1] * 0.6 + mydata[:, 2] * 0.3 + randn(100) * 0.1
6+
thresholdreturn = 0.1
7+
8+
problem = PortfolioProblem(mydata, thresholdreturn)
9+
10+
result = solve(problem)
11+
12+
@test result.expectedreturn >= thresholdreturn
13+
@test isapprox(sum(result.weights), 1.0; atol=eps)
14+
@test all(result.weights .>= 0.0)
15+
@test all(result.weights .<= 1.0)
16+
# @info "Optimal weights: $(result.weights)"
17+
# @info "Expected return: $(result.expectedreturn)"
18+
# println(result.model)
19+
20+
end
21+
end

0 commit comments

Comments
 (0)