Loading...
Searching...
No Matches
markowitz.py
Go to the documentation of this file.
1
10
11import os
12import sys
13from math import fabs
14import matplotlib.pyplot as plt
15from gams import GamsModifier, GamsWorkspace
16from pathlib import Path
17import shutil
18
19GAMS_MODEL = """
20Set
21 s 'selected stocks'
22 upper(s,s) 'parts of covar matrix'
23 lower(s,s) 'parts of covar matrix';
24
25Alias(s,t);
26
27Parameter
28 mean(s) 'mean of daily return'
29 covar(s,s) 'covariance matrix of returns (upper)';
30
31$if not set data $abort 'no include file name for data file provided'
32$gdxIn %data%
33$load s covar upper lower mean
34$gdxIn
35
36Variable
37 z 'objective variable'
38 ret 'return'
39 var 'variance'
40 x(s) 'investments';
41
42Positive Variables x;
43
44Equation
45 obj 'objective'
46 budget
47 varcon 'variance constraint'
48 retcon 'return constraint';
49
50Scalar lambda / 0 /;
51
52obj.. z =e= lambda*ret - (1-lambda)*var;
53
54budget.. sum(s, x(s)) =e= 1.0;
55
56varcon.. var =e= sum(upper(s,t), x(s)*covar(s,t)*x(t)) +
57 sum(lower(s,t), x(s)*covar(t,s)*x(t));
58
59retcon.. ret =e= sum(s, mean(s)*x(s));
60
61Model markowitz / all /;
62"""
63
64if __name__ == "__main__":
65 sys_dir = sys.argv[1] if len(sys.argv) > 1 else None
66 work_dir = sys.argv[2] if len(sys.argv) > 2 else "."
67 ws = GamsWorkspace(system_directory=sys_dir, working_directory=work_dir)
68
69 job = ws.add_job_from_string(GAMS_MODEL)
70 opt = ws.add_options()
71 opt.all_model_types = "conopt"
72
73 shutil.copy(
74 Path(ws.system_directory, "apifiles", "Data", "markowitz.gdx"),
75 Path(ws.working_directory, "markowitz.gdx"),
76 )
77
78 markowitz_path = Path(ws.working_directory, "markowitz.gdx").resolve()
79 opt.defines["data"] = f'"{markowitz_path}"'
80
81 cp = ws.add_checkpoint()
82 job.run(opt, cp)
83 mi = cp.add_modelinstance()
84 l = mi.sync_db.add_parameter("lambda", 0, "")
85 mi.instantiate("markowitz use nlp max z", GamsModifier(l))
86
87 # a list for collecting the data points
88 data_points = []
89
90 # get minimum and maximum return (lambda=0/1)
91 l.add_record().value = 0
92 mi.solve()
93 min_ret = mi.sync_db["ret"].first_record().level
94 data_points.append((min_ret, mi.sync_db["var"].first_record().level))
95 l.first_record().value = 1
96 mi.solve()
97 max_ret = mi.sync_db["ret"].first_record().level
98 data_points.append((max_ret, mi.sync_db["var"].first_record().level))
99
100 # gap that specifies the max distance (return) between two data points
101 gap = 0.02
102
103 # a list (stack) of intervals, where an interval is represented by a 2-tuple containing two data points
104 # each represented by a 2-tuple as well (lambda, return)
105 intervals = [((0.0, min_ret), (1.0, max_ret))]
106
107 # algorithm that solves the model instance for multiple lambda values
108 # using a max gap between two data points to achieve a smooth graph
109 # lambda is dynamically calculated for an even distribution of data points
110 while intervals:
111 # pick the first interval and calculate a new value for lambda
112 i = intervals.pop()
113 min_l, min_ret = i[0][0], i[0][1]
114 max_l, max_ret = i[1][0], i[1][1]
115
116 l_val = (min_l + max_l) / 2
117 l.first_record().value = l_val
118
119 # solve the model instance with the new lambda value
120 mi.solve()
121
122 # retrieve the return and variance results
123 cur_ret = mi.sync_db["ret"].first_record().level
124 data_points.append((cur_ret, mi.sync_db["var"].first_record().level))
125
126 # add new intervals if the gap is still to big
127 if fabs(cur_ret - min_ret) > gap:
128 intervals.append(((min_l, min_ret), (l_val, cur_ret)))
129 if fabs(cur_ret - max_ret) > gap:
130 intervals.append(((l_val, cur_ret), (max_l, max_ret)))
131
132 # sort data points and create two lists for the plot function
133 data_points.sort(key=lambda tup: tup[0])
134 ret = [x[0] for x in data_points]
135 var = [x[1] for x in data_points]
136 plt.plot(ret, var, marker=".", markersize=10)
137 plt.xlabel("return")
138 plt.ylabel("variance")
139 plt.savefig(Path(ws.working_directory, "figure"))