Optimization Methods in Finance: Chapter 3

finance_opt_example_3_1.ipynb Open In Colab Kaggle Gradient Open In SageMaker Studio Lab Hits

Description: Optimization Methods in Finance: Bond Dedication Problem.

Tags: amplpy, example, finance

Notebook author: Marcos Dominguez Velad <marcos@ampl.com>

Model author: N/A

References: Cornuejols, G., and Tütüncü, R. (2018). Optimization Methods in Finance (2nd edition): Bond Dedication example. Cambridge University Press.

# Install dependencies
%pip install -q amplpy
# Google Colab & Kaggle integration
from amplpy import AMPL, ampl_notebook

ampl = ampl_notebook(
    modules=["coin"],  # modules to install
    license_uuid="default",  # license to use
)  # instantiate AMPL object and register magics

Bond dedication

This is the implementation using AMPL of the Example 3.1 (Bond dedication) from the book Optimization Methods in Finance (2nd edition) by Cornuejols, G., and Tütüncü, R.

The model has been extracted from Chapter 3, titled Linear Programming Models: Asset-Liability Management.

Problem:

Suppose a pension fund needs to cover some liabilities in the next 6 years (given cash requirements), and can invest in 10 government bonds with given cash flows and current prices. The goal is to find the least expensive portfolio of bonds whose cash flows are sufficient to cover the liabilities.

Writing the model

First, let’s use AMPL to write the abstract model depending on the number of bonds, years, and other parameters from the problem. Each bond has a price and some associated cash flows.

(Use %%ampl_eval to evaluate AMPL commands)

%%ampl_eval
param bonds;
param years;

param price{1..bonds};
param cash_flow{1..years, 1..bonds} default 0;
param cash_req{1..years};

The variables in this case are

  • $x_j$: amount of bonds $j$ in the portfolio, for j = 1, 2, …, bonds.

  • $s_t$: surplus cash in year $t$, for t = 1, 2, …, years.

%%ampl_eval
var x{j in 1..bonds} >= 0;
var s{t in 1..years} >= 0;

The objective function is to minimize the price of the bonds.

%%ampl_eval
minimize Total_Price: sum{j in 1..bonds} price[j]*x[j];

Let’s write the constraints for the model (liabilities should be accomplished).

%%ampl_eval
subject to Liability1:
  sum{j in 1..bonds} cash_flow[1,j]*x[j]-s[1] = cash_req[1];

subject to Liabilities{i in 2..years}:
  sum{j in 1..bonds} cash_flow[i,j]*x[j]+s[i-1]-s[i] = cash_req[i];

Now the model is complete.

Writing the data file

In order to solve a particular instance of the problem, we could write a data file with the information related to the parameters.

(Use %%writefile to create files)

%%writefile bond_dedication.dat
param years := 6;
param cash_req :=
1 100
2 200
3 800
4 100
5 800
6 1200
;

param bonds := 10;
param price := 
1 109
2 94.8
3 99.5
4 93.1
5 97.2
6 92.9
7 110
8 104
9 102
10 95.2
;

param cash_flow :=
1 1 10
1 2 7
1 3 8
1 4 6
1 5 7
1 6 5
1 7 10
1 8 8
1 9 7
1 10 100
2 1 10
2 2 7
2 3 8
2 4 6
2 5 7
2 6 5
2 7 10
2 8 8
2 9 107
3 1 10
3 2 7
3 3 8
3 4 6
3 5 7
3 6 5
3 7 110
3 8 108
4 1 10
4 2 7
4 3 8
4 4 6
4 5 7
4 6 105
5 1 10
5 2 7
5 3 8
5 4 106
5 5 107
6 1 110
6 2 107
6 3 108
;
Overwriting bond_dedication.dat

Solve the problem

Load the data file and pick your favourite linear solver to get the solution.

%%ampl_eval
data bond_dedication.dat;

option solver cbc;

solve;
CBC 2.10.5: CBC 2.10.5 optimal, objective 2305.691648
8 iterations

Print the solution.

%%ampl_eval
display x, s, Total_Price;
:       x          s       :=
1     0         66.3772
2    11.215     32.7544
3     0          0
4     6.63385   18.3077
5     0          0
6     0          0
7     0            .
8     6.00868      .
9     0            .
10    0            .
;

Total_Price = 2305.69

Shadow prices and interest rates

We can check the shadow prices from the previous constraints to find the term structure of interest rates.

%%ampl_eval
display Liability1, Liabilities;
Liability1 = 0.83871

Liabilities [*] :=
2  0.83871
3  0.83871
4  0.696457
5  0.696457
6  0.630249
;

To extract this values to compute the term structure of interest rates: $$r_t = \frac{1}{(\lambda_t)^{1/t}}-1$$

dual_values = {}
rate = {}
# Get dual values from constraints
dual_values[1] = ampl.get_constraint("Liability1").dual()
# Elements from a vector of constraints can be accesed by [] operator
for t in range(2, 7):
    dual_values[t] = ampl.get_constraint("Liabilities")[t].dual()
# Compute rates
for t in range(1, 7):
    rate[t] = 1 / (dual_values[t]) ** (1 / t) - 1
    print("Rate", t, "=", rate[t])
Rate 1 = 0.1923076923076923
Rate 2 = 0.09192842819833746
Rate 3 = 0.06038306207109678
Rate 4 = 0.09465273090416892
Rate 5 = 0.0750312573843761
Rate 6 = 0.07997719481718946