Corporate bond indexation model#

Corporate.py Corporate.gdx

"""
## GAMSSOURCE: https://www.gams.com/latest/finlib_ml/libhtml/finlib_Corporate.html
## LICENSETYPE: Requires license
## MODELTYPE: LP
## DATAFILES: Corporate.gdx


Corporate bond indexation model

Corporate.gms: Corporate bond indexation model
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 8.3
Last modified: May 2008.
"""

from __future__ import annotations

import os
from pathlib import Path

import gamspy.math as gams_math
from gamspy import (
    Card,
    Container,
    Equation,
    Model,
    Parameter,
    Sense,
    Sum,
    Variable,
)


def main():
    # Define container
    m = Container(
        system_directory=os.getenv("SYSTEM_DIRECTORY", None),
        load_from=str(Path(__file__).parent.absolute()) + "/Corporate.gdx",
    )

    # SETS #
    (
        BroadAssetClassOne,
        BroadAssetClassTwo,
        BroadAssetClassThree,
        ACTIVE,
    ) = m.getSymbols(
        [
            "BroadAssetClassOne",
            "BroadAssetClassTwo",
            "BroadAssetClassThree",
            "ACTIVE",
        ]
    )

    # ALIASES
    i, l, j, m1, m2, m3, a = m.getSymbols(
        ["i", "l", "j", "m1", "m2", "m3", "a"]
    )

    # PARAMETERS #
    AssetReturns = m.getSymbols(["AssetReturns"])[0]
    BroadWeights = Parameter(
        m,
        name="BroadWeights",
        domain=j,
        description="Weights of the broad asset classes",
    )
    AssetWeights = Parameter(
        m,
        name="AssetWeights",
        domain=i,
        description="Weights of each asset in the index",
    )

    # Assign weights randomly

    AssetWeights[i] = gams_math.uniform(1, 10)

    # DISPLAY  AssetWeights

    # Normalize the random weights
    WeightsSum = Parameter(m, name="WeightsSum")

    WeightsSum[...] = Sum(i, AssetWeights[i])

    AssetWeights[i] = AssetWeights[i] / WeightsSum

    BroadWeights["BA_1"] = Sum(m1, AssetWeights[m1])
    BroadWeights["BA_2"] = Sum(m2, AssetWeights[m2])
    BroadWeights["BA_3"] = Sum(m3, AssetWeights[m3])

    IndexReturns = Parameter(
        m,
        name="IndexReturns",
        domain=l,
        description="Index return scenarios",
    )
    BroadAssetReturns = Parameter(
        m,
        name="BroadAssetReturns",
        domain=[j, l],
        description="Broad asset class return scenarios",
    )
    Benchmark = Parameter(
        m,
        name="Benchmark",
        domain=l,
        description="Current benchmark scenario returns",
    )

    BroadAssetReturns["BA_1", l] = Sum(
        m1, AssetWeights[m1] * AssetReturns[m1, l]
    )
    BroadAssetReturns["BA_2", l] = Sum(
        m2, AssetWeights[m2] * AssetReturns[m2, l]
    )
    BroadAssetReturns["BA_3", l] = Sum(
        m3, AssetWeights[m3] * AssetReturns[m3, l]
    )

    IndexReturns[l] = Sum(j, BroadWeights[j] * BroadAssetReturns[j, l])

    CurrentWeight = Parameter(
        m,
        name="CurrentWeight",
        description="Current weight for tactcal allocation",
    )
    EpsTolerance = Parameter(m, name="EpsTolerance", description="Tolerance")
    pr = Parameter(m, name="pr", domain=l, description="Scenario probability")

    pr[l] = 1.0 / Card(l)

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=i,
        description="Percentage invested in each security",
    )
    z = Variable(
        m,
        name="z",
        type="positive",
        domain=j,
        description="Percentages invested in each broad asset class",
    )
    PortRet = Variable(
        m, name="PortRet", domain=l, description="Portfolio returns"
    )

    # EQUATIONS #
    BroadPortRetDef = Equation(
        m,
        name="BroadPortRetDef",
        domain=l,
        description="Portfolio return definition for broad asset classes",
    )
    PortRetDef = Equation(
        m,
        name="PortRetDef",
        domain=l,
        description="Portfolio return definition",
    )
    BroadNormalCon = Equation(
        m,
        name="BroadNormalCon",
        description=(
            "Equation defining the normalization contraint for broad asset"
            " classes"
        ),
    )
    NormalCon = Equation(
        m,
        name="NormalCon",
        description="Equation defining the normalization contraint",
    )
    MADCon = Equation(
        m,
        name="MADCon",
        domain=l,
        description="MAD constraints",
    )

    ObjValue = Sum(l, pr[l] * PortRet[l])

    BroadPortRetDef[l] = PortRet[l] == Sum(j, z[j] * BroadAssetReturns[j, l])

    PortRetDef[l] = PortRet[l] == Sum(a, x[a] * AssetReturns[a, l])

    MADCon[l] = PortRet[l] >= Benchmark[l] - EpsTolerance

    BroadNormalCon[...] = Sum(j, z[j]) == 1.0

    NormalCon[...] = Sum(a, x[a]) == CurrentWeight

    # MODELS #
    StrategicModel = Model(
        m,
        name="StrategicModel",
        equations=[BroadPortRetDef, MADCon, BroadNormalCon],
        problem="LP",
        sense=Sense.MAX,
        objective=ObjValue,
    )
    TacticalModel = Model(
        m,
        name="TacticalModel",
        equations=[PortRetDef, MADCon, NormalCon],
        problem="LP",
        sense=Sense.MAX,
        objective=ObjValue,
    )

    # Solve strategic model

    Benchmark[l] = IndexReturns[l]

    EpsTolerance[...] = 0.02

    StrategicModel.solve()

    print("## Strategic Asset Allocation ##")
    print("z: \n", z.records.level.to_list())

    # Solve tactical model for Broad Asset 1 (BA_1)

    CurrentWeight[...] = z.l["BA_1"]

    Benchmark[l] = BroadAssetReturns["BA_1", l]

    EpsTolerance[...] = 0.02

    ACTIVE[i] = BroadAssetClassOne[i]

    if CurrentWeight.records.value[0] > 0.05:
        TacticalModel.solve()

        print("\n## Model BA_1 ##")
        print("\nx: \n", x.records.level.tolist())

    # Solve tactical model for Broad Asset 2 (BA_2)

    CurrentWeight[...] = z.l["BA_2"]

    ACTIVE[i] = BroadAssetClassTwo[i]

    Benchmark[l] = BroadAssetReturns["BA_2", l]

    EpsTolerance[...] = 0.03

    if CurrentWeight.records.value[0] > 0.05:
        TacticalModel.solve()

        print("\n## Model BA_2 ##")
        print("\nx: \n", x.records.level.tolist())

    # Solve tactical model for Broad Asset 3 (BA_3)

    CurrentWeight[...] = z.l["BA_3"]

    ACTIVE[i] = BroadAssetClassThree[i]

    Benchmark[l] = BroadAssetReturns["BA_3", l]

    EpsTolerance[...] = 0.02

    if CurrentWeight.records.value[0] > 0.05:
        TacticalModel.solve()

        print("\n## Model BA_3 ##")
        print("\nx: \n", x.records.level.tolist())

    # Solve integrated model

    CurrentWeight[...] = 1.0

    Benchmark[l] = IndexReturns[l]

    EpsTolerance[...] = 0.02

    ACTIVE[i] = True

    TacticalModel.solve()
    print("\n## Model Integrated ##")
    print("x: \n", x.records.level.tolist())

    print(
        "\nObjective Function Value: ", round(TacticalModel.objective_value, 3)
    )


if __name__ == "__main__":
    main()