International asset allocation model#
InternationalMeanVar.py
InternationalMeanVar.gdx
"""
## GAMSSOURCE: https://www.gams.com/latest/finlib_ml/libhtml/finlib_InternationalMeanVar.html
## LICENSETYPE: Demo
## MODELTYPE: NLP
## DATAFILES: InternationalMeanVar.gdx
International asset allocation model
InternationalMeanVar.gms: International asset allocation model.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 3.5
Last modified: Apr 2008.
We use real data for the 10-year period 1990-01-01 to 2000-01-01,
23 Italian Stock indices
3 Italian Bond indices (1-3yr, 3-7yr, 5-7yr)
Italian risk-free rate (3-month cash)
7 international Govt. bond indices
5 Regions Stock Indices: (EMU, Eur-ex-emu, PACIF, EMER, NORAM)
3 risk-free rates (3-mth cash) for EUR, US, JP
US Corporate Bond Sector Indices (Finance, Energy, Life Ins.)
Exchange rates, ITL to: (FRF, DEM, ESP, GBP, US, YEN, EUR)
Also US to EUR.
"""
from __future__ import annotations
import os
from pathlib import Path
import gamspy.math as gams_math
from gamspy import (
Alias,
Container,
Equation,
Model,
Parameter,
Set,
Sum,
Variable,
)
def main():
gdx_file = (
str(Path(__file__).parent.absolute()) + "/InternationalMeanVar.gdx"
)
m = Container(
system_directory=os.getenv("SYSTEM_DIRECTORY", None),
load_from=gdx_file,
)
# SETS #
ASSETS = m.getSymbols(["ASSETS"])[0]
# ALIASES #
i, j = m.getSymbols(["i", "j"])
# SUBSETS #
IT_STOCK, IT_ALL, INT_STOCK, INT_ALL = m.getSymbols(
["IT_STOCK", "IT_ALL", "INT_STOCK", "INT_ALL"]
)
# PARAMETERS #
MAX_MU, ExpectedReturns, VarCov, RiskFree = m.getSymbols(
["MAX_MU", "MU", "Q", "RiskFreeRt"]
)
# Build more symbols
# SETS #
ACTIVE = Set(m, name="ACTIVE", domain=ASSETS)
a = Alias(m, name="a", alias_with=ACTIVE)
a1 = Alias(m, name="a1", alias_with=ACTIVE)
a2 = Alias(m, name="a2", alias_with=ACTIVE)
# Target return
# SCALARS #
MU_TARGET = Parameter(
m, name="MU_TARGET", description="Target portfolio return"
)
MU_STEP = Parameter(m, name="MU_STEP", description="Target return step")
# Assume we want 20 portfolios in the frontier
MU_STEP[...] = MAX_MU / 20
# VARIABLES #
x = Variable(
m,
name="x",
type="positive",
domain=i,
description="Holdings of assets",
)
PortVariance = Variable(
m, name="PortVariance", description="Portfolio variance"
)
# EQUATIONS #
ReturnCon = Equation(
m,
name="ReturnCon",
type="regular",
description="Equation defining the portfolio return constraint",
)
VarDef = Equation(
m,
name="VarDef",
type="regular",
description="Equation defining the portfolio variance",
)
NormalCon = Equation(
m,
name="NormalCon",
type="regular",
description="Equation defining the normalization contraint",
)
ReturnCon[...] = Sum(a, ExpectedReturns[a] * x[a]) == MU_TARGET
VarDef[...] = PortVariance == Sum([a1, a2], x[a1] * VarCov[a1, a2] * x[a2])
NormalCon[...] = Sum(a, x[a]) == 1
MeanVar = Model(
m,
name="MeanVar",
equations=[ReturnCon, VarDef, NormalCon],
problem="nlp",
sense="MIN",
objective=PortVariance,
)
with open(
"InternationalMeanVarFrontier.csv", "w", encoding="UTF-8"
) as FrontierHandle:
# Step 1: First solve only for Italian stocks:
ACTIVE[i] = IT_STOCK[i]
print("\nStep 1: Italian stock assets\n")
FrontierHandle.write('"Step 1: Italian stock assets"\n')
FrontierHandle.write('"Variance","ExpReturn",')
# Asset labels
i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
FrontierHandle.write(",".join(i_recs) + "\n")
mu = 0
while round(mu, 7) < round(MAX_MU.toList()[0], 7):
MU_TARGET[...] = mu
MeanVar.solve()
print("PortVariance: ", round(PortVariance.toValue(), 3))
FrontierHandle.write(
f"{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
)
x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
FrontierHandle.write(",".join(x_recs))
FrontierHandle.write("\n")
mu += MU_STEP.toList()[0]
#
# Step 2: Now solve for Italian stock and government indices:
#
ACTIVE[i] = IT_ALL[i]
print("\nStep 2: Italian stock and government assets\n")
FrontierHandle.write('"Step 2: Italian stock and government assets"\n')
FrontierHandle.write('"Variance","ExpReturn",')
# Asset labels
i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
FrontierHandle.write(",".join(i_recs) + "\n")
mu = 0
while round(mu, 7) < round(MAX_MU.toList()[0], 7):
MU_TARGET[...] = mu
MeanVar.solve()
print("PortVariance: ", round(PortVariance.toValue(), 3))
FrontierHandle.write(
f"{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
)
x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
FrontierHandle.write(",".join(x_recs))
FrontierHandle.write("\n")
mu += MU_STEP.toList()[0]
#
# Step 3: Italian stock plus international stock indices
#
ACTIVE[i] = INT_STOCK[i]
print("\nStep 3: Italian and international stock indices\n")
FrontierHandle.write(
'"Step 3: Italian and international stock indices"\n'
)
FrontierHandle.write('"Variance","ExpReturn",')
# Asset labels
i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
FrontierHandle.write(",".join(i_recs) + "\n")
mu = 0
while round(mu, 7) < round(MAX_MU.toList()[0], 7):
MU_TARGET[...] = mu
MeanVar.solve()
print("PortVariance: ", round(PortVariance.toValue(), 3))
FrontierHandle.write(
f"{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
)
x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
FrontierHandle.write(",".join(x_recs))
FrontierHandle.write("\n")
mu += MU_STEP.toList()[0]
#
# Step 4: Italian stock and government indices, international stock and government
# indices, plus corporate indices.
#
ACTIVE[i] = INT_ALL[i]
print("\nStep 4: All indices\n")
FrontierHandle.write('"Step 4: All indices"\n')
FrontierHandle.write('"Variance","ExpReturn",')
# Asset labels
i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
FrontierHandle.write(",".join(i_recs) + "\n")
mu = 0
while round(mu, 7) < round(MAX_MU.toList()[0], 7):
MU_TARGET[...] = mu
MeanVar.solve()
print("PortVariance: ", round(PortVariance.toValue(), 3))
FrontierHandle.write(
f"{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
)
x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
FrontierHandle.write(",".join(x_recs))
FrontierHandle.write("\n")
mu += MU_STEP.toList()[0]
#
# Step 5: All italian stock indices plus risk free
#
# VARIABLES #
z = Variable(m, name="z", type="free")
d_bar = Variable(m, name="d_bar", type="free")
# EQUATIONS #
RiskFreeReturnDef = Equation(
m, name="RiskFreeReturnDef", type="regular"
)
SharpeRatio = Equation(m, name="SharpeRatio", type="regular")
RiskFreeReturnDef[...] = (
d_bar == Sum(a, ExpectedReturns[a] * x[a]) - RiskFree
)
SharpeRatio[...] = z == d_bar / gams_math.sqrt(PortVariance)
Sharpe = Model(
m,
name="Sharpe",
equations=[RiskFreeReturnDef, VarDef, NormalCon, SharpeRatio],
problem="nlp",
sense="MAX",
objective=z,
)
Sharpe.solve()
print("\nStep 5: Tangent portfolio\n")
print("z: ", round(z.toValue(), 3))
# Write the variance and expected return for the tangent portfolio
FrontierHandle.write('"Step 5: Tangent portfolio"\n')
FrontierHandle.write('"Variance","RiskFree","z",')
# Asset labels
i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
FrontierHandle.write(",".join(i_recs) + "\n")
FrontierHandle.write(
f"{round(PortVariance.toValue(),4)},{round((d_bar.toValue()+RiskFree.toValue()),4)},{round(z.toValue(),4)},"
)
# Write the tangent portfolio.
x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
FrontierHandle.write(",".join(x_recs))
FrontierHandle.write("\n")
#
# Step 6: Include the total Italian stock index as a liability
#
#
# Build a model (very similar to the previous one)
# which attempts to track (synthesize) the Italian total stock index,
# ITMHIST, using the 23 Italian stock indices and 3 Italian bond indices
# plus the Italian risk-free asset.
#
# This is done by including ITMHIST as an asset but fixing its weight
# in the portfolio at -1. The 26 other assets then must try to balance
# out the variance of ITMHIST. In addition, we pursue different levels
# of expected return (over and above the ITMHIST return).
# Create a convenient subset containing only the general Italian stock index:
It_general = Set(
m, name="It_general", domain=ASSETS, records=["ITMHIST"]
)
# The only constraint which need to be redefined is the
# normalization constraint. Indeed, it must be se to 0.
NormalConTrack = Equation(
m,
name="NormalConTrack",
type="regular",
description=(
"Equation defining the normalization contraint for tracking"
),
)
NormalConTrack[...] = Sum(a, x[a]) == 0
MeanVarTrack = Model(
m,
name="MeanVarTrack",
equations=[ReturnCon, VarDef, NormalConTrack],
problem="nlp",
sense="MIN",
objective=PortVariance,
)
x.fx[It_general] = -1
FrontierHandle.write('"Step 6: Index tracking"\n')
print("\nStep 6: Index tracking\n")
ACTIVE[i] = IT_STOCK[i] | It_general[i]
FrontierHandle.write('"Status","Variance","ExpReturn",')
# Asset labels
i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
FrontierHandle.write(",".join(i_recs) + "\n")
# Re-estimate MU_STEP as MAX_MU is different for the tracking problem
MAX_MU[...] = 0.1587
MU_STEP[...] = MAX_MU / 20
mu = 0
while round(mu, 7) < round(MAX_MU.toList()[0], 7):
MU_TARGET[...] = mu
MeanVarTrack.solve()
print("PortVariance: ", round(PortVariance.toValue(), 3))
FrontierHandle.write(
f"{MeanVarTrack.status},{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
)
x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
FrontierHandle.write(",".join(x_recs))
FrontierHandle.write("\n")
mu += MU_STEP.toList()[0]
if __name__ == "__main__":
main()