Dedication model without borrowing#

DedicationNoBorrow.py

"""
## GAMSSOURCE: https://www.gams.com/latest/finlib_ml/libhtml/finlib_DedicationNoBorrow.html
## LICENSETYPE: Demo
## MODELTYPE: LP


Dedication model without borrowing

Dedication.gms:  Dedication model without borrowing.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 2.4
Last modified: Apr 2008.
"""

from __future__ import annotations

import os

import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    # Bond data. Prices, coupons and maturities from the Danish market
    bond_data_recs = pd.DataFrame(
        np.array([
            [112.35, 2006, 8],
            [105.33, 2003, 8],
            [111.25, 2007, 7],
            [107.30, 2004, 7],
            [107.62, 2011, 6],
            [106.68, 2009, 6],
            [101.93, 2002, 6],
            [101.30, 2005, 5],
            [101.61, 2003, 5],
            [100.06, 2002, 4],
        ]),
        columns=["Price", "Maturity", "Coupon"],
        index=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
    )

    bond_data_recs = bond_data_recs.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )

    # Define container
    m = Container(
        system_directory=os.getenv("SYSTEM_DIRECTORY", None),
        delayed_execution=int(os.getenv("DELAYED_EXECUTION", False)),
    )

    # SETS #
    Time = Set(
        m,
        name="Time",
        records=[str(i) for i in range(2001, 2012)],
        description="Time periods",
    )

    Bonds = Set(
        m,
        name="Bonds",
        records=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
        description="Bonds universe",
    )

    # ALIASES #
    t = Alias(m, name="t", alias_with=Time)
    i = Alias(m, name="i", alias_with=Bonds)

    # SCALARS #
    Now = Parameter(m, name="Now", description="Current year")
    Horizon = Parameter(m, name="Horizon", description="End of the Horizon")

    Now[...] = 2001
    Horizon[...] = Card(t) - 1

    # PARAMETER #
    tau = Parameter(m, name="tau", domain=t, description="Time in years")

    # Note: time starts from 0
    tau[t] = Ord(t) - 1

    Price = Parameter(m, name="Price", domain=i, description="Bond prices")
    Coupon = Parameter(m, name="Coupon", domain=i, description="Coupons")
    Maturity = Parameter(
        m, name="Maturity", domain=i, description="Maturities"
    )
    rf = Parameter(m, name="rf", domain=t, description="Reinvestment rates")
    F = Parameter(m, name="F", domain=[t, i], description="Cashflows")
    BondData = Parameter(
        m, name="BondData", domain=[i, "*"], records=bond_data_recs
    )
    Liability = Parameter(
        m, name="Liability", domain=t, description="Stream of liabilities"
    )

    # Copy/transform data. Note division by 100 to get unit data, and
    # subtraction of "Now" from Maturity date (so consistent with tau):
    Price[i] = BondData[i, "Price"] / 100
    Coupon[i] = BondData[i, "Coupon"] / 100
    Maturity[i] = BondData[i, "Maturity"] - Now

    # Calculate the ex-coupon cashflow of Bond i in year t:
    F[t, i] = (
        Number(1).where[tau[t] == Maturity[i]]
        + Coupon[i].where[(tau[t] <= Maturity[i]) & (tau[t] > 0)]
    )

    # For simplicity, we set the short term rate to be 0.03 in each period
    rf[t] = 0.04

    Liability.setRecords(
        np.array([
            0,
            80000,
            100000,
            110000,
            120000,
            140000,
            120000,
            90000,
            50000,
            75000,
            150000,
        ])
    )

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=i,
        description="Face value purchased",
    )
    surplus = Variable(
        m,
        name="surplus",
        type="positive",
        domain=t,
        description="Amount of money reinvested",
    )
    v0 = Variable(m, name="v0", type="free", description="Upfront investment")

    # EQUATION #
    CashFlowCon = Equation(
        m,
        name="CashFlowCon",
        domain=t,
        description="Equations defining the cashflow balance",
    )

    CashFlowCon[t] = (
        Sum(i, F[t, i] * x[i])
        + (v0 - Sum(i, Price[i] * x[i])).where[tau[t] == 0]
        + ((1 + rf[t.lag(1)]) * surplus[t.lag(1)]).where[tau[t] > 0]
        == surplus[t] + Liability[t].where[tau[t] > 0]
    )

    Dedication = Model(
        m,
        name="Dedication",
        equations=[CashFlowCon],
        problem="LP",
        sense=Sense.MIN,
        objective=v0,
    )
    Dedication.solve()

    print("Objective Function Variable: ", round(v0.records.level[0], 3))


if __name__ == "__main__":
    main()