Maximizing long-term investments
Introduction
This example presents the task of optimizing a long-term fixed-income investment portfolio. The goal is to maximize the final capital over a given planning horizon by reinvesting income from bonds with different maturities and interest rates. The study demonstrates the use of linear programming methods to formalize and solve this problem.
Setting the task
Let's consider an investment model with a fixed initial capital distributed among bonds with deterministic returns over a given time horizon. Each bond is characterized by a fixed interest rate with annual capitalization and the payment of a nominal value with accumulated interest income at maturity. The objective function of the model is aimed at maximizing total capital at the end of the investment period.
Additionally, the model may include a restriction on portfolio diversification, which sets the maximum share of a single investment in the total amount of capital at the time of placement.
The research methodology involves a step-by-step consideration of the problem: from a special case with a limited set of tools to a generalized formulation. The mathematical formalization of the problem is implemented within the framework of the linear programming apparatus, which makes it possible to formulate and solve the corresponding optimization problem of maximizing final well-being.
Introductory example
Let's look at a demo example with an initial capital of 1,000 rubles and an investment horizon of 5 years. The model includes four bonds with different characteristics: bond 1 is purchased in the first year with a maturity of 4 years and a yield of 2%; bond 2 is available in the fifth year with a maturity of 1 year and a yield of 4%; bonds 3 and 4 are purchased in the second year with maturities of 4 and 3 years, respectively, at a yield of 6%.
To model the non-invested funds, five one-year zero-yield bonds were introduced, which forms an equivalent model of nine investment instruments.
Add the necessary libraries and visualization function:
using Plots, Random, JuMP, HiGHS
include("plotInvest.jl")
We visualize this task using horizontal rectangles that represent the available purchase periods for each bond. The numbers of the bonds with interest rates are located along the lines. The investment periods in years are shown in the columns.
# Time period in years
Period = 5
# Number of bonds
Number of bonds = 4
# The initial amount of money
Summa_0 = 1000
# Total number of purchase options
All possible purchases = Number of bonds + Period
# Purchase Periods
Purchase Year = [1; 2; 3; 4; 5; 1; 5; 2; 2]
# Bond durations
Repayment period = [1; 1; 1; 1; 1; 4; 1; 4; 3]
# Bond sale periods
Year of repayment = Year of purchase .+ Repayment period .- 1
# Interest rates as a percentage
Percentages = [0; 0; 0; 0; 0; 2; 4; 6; 6]
# Yield after one year, including interest
Profitability per day = 1 .+ Percentages ./ 100
# Task visualization
p1 = plotInvest(Number of bonds, Year of purchase, Maturity, Interest)
display(p1)
Variable solutions
Let's represent the solution variables by the vector x, where — the amount in rubles invested in bond k, for k = 1, ...,9. Upon repayment, the payment for the investment equal to:
# Creating an optimization model
model = Model(HiGHS.Optimizer)
# Variable solutions - investment amounts
@variable(model, x[1:All possible purchases] >= 0)
# Total return
Total return = Profitability per day . Repayment period
Target function
The purpose of optimization is to maximize the total capital generated by reinvesting income from redeemable bonds. As the graphical visualization demonstrates, the cash flows received in the interim periods are reinvested and contribute to the formation of the final well-being.
Let's create a target function and a task to maximize.
# Creating an optimization task (maximizing)
@objective(model, Max, x[5] * Total Return[5] + x[7] * Total Return[7] + x[8] * Total profitability[8])
Linear constraints
The model assumes an annual allocation of available capital between investment instruments. At the initial stage, seed capital is placed, and in subsequent periods, cash flows from redeemable bonds are reinvested.
Let's make a system of equations:
# Restrictions on investments
@constraint(model, restriction_investment1, x[1] + x[6] == Summa_0)
@constraint(model, restriction_investment2, x[2] + x[8] + x[9] == Profitability per day[1] * x[1])
@constraint(model, restriction_investment3, x[3] == Profitability per day[2] * x[2])
@constraint(model, restriction_investment4, x[4] == Profitability per day[3] * x[3])
@constraint(model, restriction_investment5, x[5] + x[7] == Profitability per day[4] * x[4] + Total Return[6] * x[6] + Total return[9] * x[9])
Decision
We will solve this problem without restrictions on the amount that can be invested in one bond.
optimize!(model)
solution = value.(x)
value of the target function = objective_value(model)
Visualization of the solution
Let's display the value of the return on investment.
println("After $5 years, the income from the initial $(Sum_0) ₽ will be $(round(value of the target function, digits=2)) ₽")
Visualize the solution:
p2 = plotInvest(Number of bonds, Year of purchase, Maturity, Interest, solution)
display(p2)
Optimal investments with limitations
To ensure portfolio diversification, the model introduces a limit on the maximum share of investments in a single asset relative to the total capital of the current period, including proceeds from redeemable securities.
# Creating a model for a problem with constraints
model_with_ constraints = Model(HiGHS.Optimizer)
# Decision variables for a constrained model
@variable(model with constraints, x2[1:All possible purchases] >= 0)
# Target function
@objective(model with constraints, Max, x2[5] * Total Return[5] + x2[7] * Total Return[7] + x2[8] * Total profitability[8])
# Basic investment restrictions
@constraint(model with constraints, investment1, x2[1] + x2[6] == Summa_0)
@constraint(model with constraints, investment2, x2[2] + x2[8] + x2[9] == Profitability per day[1] * x2[1])
@constraint(model with constraints, investment3, x2[3] ==Profitability per day[2] * x2[2])
@constraint(model with constraints, investment4, x2[4] ==Profitability per day[3] * x2[3])
@constraint(model with constraints, investment5, x2[5] + x2[7] ==Profitability per day[4] * x2[4] + Total Return[6] * x2[6] + Total return[9] * x2[9])
# The maximum interest rate for investments in any bond
Max Percentage = 0.6
Let's create a system of inequalities:
# Restrictions on maximum investments
@constraint(model with constraints, limit1, x2[1] <= Max Percentage * Summa_0)
@constraint(model_with_ constraints, limit2, x2[2] <= Max Percentage * (profitability Code[1] * x2[1] + Profitability Code[6] * x2[6]))
@constraint(model with constraints, limit3, x2[3] <=Max Percentage * (Profitability Code[2] *x2[2] + Profitability Code[6]^2* x2[6]+ Profitability Code[8]* x2[8] + Profitability Code[9]*x2[9]))
@constraint(model with constraints, limit4, x2[4] <= Max Percentage * (Profitability Code[3] * x2[3] + Profitability Code[6]^3 *x2[6] + Profitability Code[8]^2*x2[8] + Profitability Code[9]^2* x2[9]))
@constraint(model with constraints, limit5, x2[5] <= Max Percentage * (Profitability Code[4] * x2[4] + Profitability Code[6]^4 *x2[6]+ Profitability Code[8]^3*x2[8] + Profitability Code[9]^3 * x2[9]))
@constraint(model with constraints, limit6, x2[6] <= Max percentage * Summa_0)
@constraint(model with constraints, limit7, x2[7] <= Max Percentage * (Profitability Code[4] * x2[4] + Profitability Code[6]^4 *x2[6]+ Profitability Code[8]^3*x2[8] + Profitability Code[9]^3 * x2[9]))
@constraint(model_with_ constraints, limit8, x2[8] <= Max Percentage * (profitability Code[1] * x2[1] + Profitability Code[6] * x2[6]))
@constraint(model_with_ constraints, limit9, x2[9] <= Max Percentage * (profitability Code[1] * x2[1] + Profitability Code[6] * x2[6]))
Solving the problem with limited diversification (maximum 60% of capital per asset) demonstrates a decrease in final profitability compared to unlimited optimization. Visualization of the resulting portfolio shows the redistribution of investment flows.
# Solving a problem with constraints
optimize!(model with constraints)
solution2 = value.(x2)
goal_function_2 = objective_value(model with constraints)
Let's display the income value taking into account the restrictions:
println("After $5 years, the income from the initial $(Sum_0) ₽ will be $(round(value of goal_function2, digits=2)) ₽")
Visualize the solution:
# Visualization of a solution with constraints
p3 = plotInvest(Number of bonds, Year of purchase, Maturity, Interest, resolution2)
display(p3)
A custom-sized model
Let's move on to a generalized formulation of the problem, scaling the model to a 30-year investment horizon with a portfolio of 400 bonds with random returns in the range of 1-6%. This configuration forms a linear programming problem with 430 variables, demonstrating the applicability of the method to real investment problems.
Random.seed!(123)
# The initial amount of money
Summer_0_ Large = 1000
# Time period in years
Long_ period = 30
# Number of bonds
The number of bonds is Large = 400
# Total number of purchase options
All possible purchases are large = The number of bonds is large + The Big_ period
# Generating random repayment durations
Repayment time_ Long = rand(1:(Period_ Large-1), all possible purchases are large)
# The bonds have a maturity period of 1 year
Repayment time_ is long[1:The period is long] .= 1
# We generate random annual interest rates for each bond
Percentages are large = RAND(1:6, all possible purchases are large)
# The bonds have an interest rate of 0 (not invested)
Percentages are large[1:Period_ Long] .= 0
# Yield after one year, including interest
Profitability is small_ = 1 .+ Percentages are large ./ 100
# Calculating the yield at the end of the maturity period for each bond
Total profitability is large = Profitability is very large . Long Repayment term
# Create random purchase years for each option
Year of purchase is Large = zeros(Int, all possible purchases are large)
# The bonds are available for purchase every year
The year of purchase is Large[1:Long_ period] = 1:The Big_ period
for i in 1:The number of bonds is large
# Generating a random year to repay the bond before the end of the T-year period
The year of purchase is Big[i+Long_ period] = rand(1:(Long period - Long repayment period[i+Long period] + 1))
end
# Calculating the periods when each bond reaches maturity at the end of the year
The year of repayment is large = The year of purchase is large .+ The repayment term is long .- 1
Let's form a temporary model of investment operations, where the matrices индексы_покупки_большие and индексы_продажи_большие The acceptable periods for opening and closing positions on each financial instrument are set.
# The matrix of purchase indices
Purchase indexes are large = falses(All possible purchases are large, period is large)
for ii in 1:The Big_ period
Purchase indexes are large[:, ii] = Year of purchase are large .== ii
end
# The matrix of sales indices
Sale index_sold = falses(All possible purchases are large, the period is large)
for ii in 1:The Big_ period
The indices of the sale are large[:, ii] = The year of repayment is large .== ii
end
We set up optimization variables corresponding to bonds.
model_size = Model(HiGHS.Optimizer)
# Variable solutions
@variable(model_ big, x_ big[1:All possible purchases are large] >= 0)
Let's create an optimization objective function.
# The task of maximizing
@objective(model is large, Max, sum(x is large[sales indexes are large[:, Period is large]] .* Total profitability is large[sales indexes are large[:, Period is large]]))
# Limitations
@constraint(model big, initial investment big,
sum(x_ big[i] for i in 1:All possible purchases are large if the purchase indexes are large[i, 1]) == Sum_0_ Large)
for t in 2:The Big_ period
@constraint(the model_ is large,
sum(x_ big[i] for i in 1:All possible purchases are large if the purchase indexes are large[i, t]) ==
sum(x_ big[i] * Total profitability is large[i] for i in 1:All possible purchases are large if the sale indexes are large[i, t-1]))
end
Let's solve the problem:
@time optimize!(model_ is large)
big_resolution = value.(x_ big)
The value of the target function is large = objective_value(the model is large)
How well did the investment work?
println("After $A period of many years, the income from the initial $(sum0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0")
A solution with limited shares
Let's create an optimization problem with constraints:
# Creating a model for a problem with constraints
model_ Large with constraints = Model(HiGHS.Optimizer)
@variable(model is large with constraints, x is large with[1:All possible purchases are large] >= 0)
@objective(model is large with constraints, Max,
sum(x_ big_c[i] * Total profitability is large[i] for i in 1:All possible purchases are large if the sale indexes are large[i, period_ Large]))
# Constraints for a model with constraints
@constraint(the model is large with constraints, the initial investment is large,
sum(x_ big_c[i] for i in 1:All possible purchases are large if the purchase indexes are large[i, 1]) == Sum_0_ Large)
for t in 2:The Big_ period
@constraint(the model is large with constraints,
sum(x_ big_c[i] for i in 1:All possible purchases are large if the purchase indexes are large[i, t]) ==
sum(x_ big_c[i] * Total profitability is large[i] for i in 1:All possible purchases are large if the sale indexes are large[i, t-1]))
end
The scaled model uses a diversification standard that limits the share of a single bond in the portfolio to 0.4.
Max Percentage is Large = 0.4
Formalization of diversification constraints requires the construction of two data structures: a matrix of bond activity and a matrix of their current value, based on which the upper bound of the share of a single asset in the portfolio is set.
active_size = falses(All possible purchases are large, Period_size)
for ii in 1:The Big_ period
active big[:, ii] = (ii .>= Year of purchase Big) .& (ii .<= Repayment year_ Large)
end
We will set limits on maximum investments.:
# Restrictions on maximum investments
for i in 1:All possible purchases are large
for t in 1:The Big_ period
if the purchase indexes are large[i, t]
if t == 1
@constraint(model is large with constraints, x is large with[i] <= maxcenter is large * Sum is0 is large)
else
@constraint(model is large with constraints, x is large with[i] <= maxcenter is large *
sum(x_ big with[j] * (profitability is_ big[j] ^ sum(active big[j, 1:(t-1)]))
for j in 1:All possible purchases are large if active are large[j, t-1]))
end
end
end
end
Problem solving:
@time optimize!(the model is large with constraints)
The solution is large with constraints = value.(x_ big_c)
The value of the target function is large with constraints = objective_value(the model is large with constraints)
println("After $A period of many years, the income from the initial $(sum0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0")
Qualitative analysis of the results
To assess the effectiveness of an optimal portfolio, it is advisable to compare its profitability with the theoretical maximum — the investment of all capital in a bond with a maximum rate of 6% for the entire 30-year period. An additional metric of the analysis can be the calculation of the equivalent annual rate corresponding to the achieved level of final well-being.
# Maximum possible amount
max_size = Sum_0_size * (1 + 6/100)^The Big_ period
# Ratio (percentage)
ratio = value of the target function with large limits / maximum sum * 100
# Equivalent interest rate
equivalent_set = ((value of goal_function_ large with constraints / Sum_0_ Large)^(1/Period_ Large) -1) * 100
println("The amount received will be $(round(ratio, digits=2))% of the maximum amount of $(round(max_sum, digits=2)),")
println("which could have been obtained by investing in one bond.")
println("\Your income corresponds to $(round(equivalent value, digits=2))% annual rate for $(Long)-summer period.")
# Visualize the results
p4 = plotInvest(The number of bonds is large, the year of purchase is large, the repayment period is large, the percentages are large, the resolution is large with restrictions, false)
display(p4)
The graph shows the time structure of the investment portfolio with restrictions:
-
Horizontal green lines - selected bonds
-
The beginning of the line is the year of purchase, the end is the year of repayment
-
Numbers in curly brackets {i} are bond identifiers
-
Top-down location — investment priority
The graph shows a diversified portfolio, where investments are distributed over different years and periods in accordance with the limitation of no more than 60% per asset.
Conclusion
The conducted research demonstrates the effectiveness of linear programming methods for optimizing long-term investment portfolios. The developed model makes it possible to determine a capital allocation strategy that maximizes final well-being over a given time horizon.
Experimental calculations confirm the existence of a compromise between diversification and profitability: the introduction of a 60% limit on the share of one asset reduces the final yield by 4.3% compared with unlimited optimization. The scalability of the approach is verified on a task with a 30-year horizon and a portfolio of 400 bonds.
The directions for future research are to take into account the uncertainty of profitability and create algorithms for dynamic portfolio optimization in changing market conditions.