├── Chapter5-Numerical Examples
├── electreIII.py
├── maut.py
├── prometheeII.py
└── topsis.py
├── Genetic Algorithm Model.ipynb
├── Goal Programming Model.ipynb
├── MCDA Portfolio Selection.ipynb
├── MCDA Scripts
├── electreIII.py
├── maut.py
├── prometheeII.py
└── topsis.py
├── Multiobjective PROMETHEE Model.ipynb
├── Multiobjective Portfolio Optimization.ipynb
└── README.md
/Chapter5-Numerical Examples/electreIII.py:
--------------------------------------------------------------------------------
1 | #Filename: electreIII.py
2 | #Description: Implementation of MCDA method ELECTRE III
3 | #Author: Elissaios Sarmas. November 3, 2019.
4 | import csv
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 | import math
8 |
9 | #Section 1: Read Input Data such as Alternatives, Criteria, Weights and the Decision Matrix
10 |
11 | criteria = 3
12 | alternatives = 4
13 |
14 | optimizationType = [0, 0, 0]
15 | vetoThreshold = [20, 5, 30]
16 | preferenceThreshold = [10, 2, 15]
17 | indifferenceThreshold = [5, 1, 5]
18 | weights = [0.4, 0.1, 0.5]
19 | alternativeName = ['Security1', 'Security2', 'Security3', 'Security4']
20 | decisionMatrix = [[20, 5, 20], [15, 3, 5], [12, 2, 10], [10, 4, 35]]
21 |
22 | #Section 2: Calculation of the Agreement Table for Electre ΙΙΙ Method
23 |
24 | concordanceTable = [[0 for i in range(alternatives)] for y in range(alternatives)]
25 | for k in range(criteria):
26 | if optimizationType[k] == 0:
27 | for i in range(alternatives):
28 | for j in range(alternatives):
29 | if i!=j:
30 | if decisionMatrix[j][k] - decisionMatrix[i][k] <= indifferenceThreshold[k]:
31 | concordanceTable[i][j] = round(concordanceTable[i][j] + 1.0 * weights[k],2)
32 | elif decisionMatrix[j][k] - decisionMatrix[i][k] <= preferenceThreshold[k]:
33 | concordanceTable[i][j] = round(concordanceTable[i][j] + ((decisionMatrix[i][k] - decisionMatrix [j][k] + preferenceThreshold[k])*1.0 / (preferenceThreshold[k]-indifferenceThreshold[k])) * weights[k],2)
34 | else:
35 | concordanceTable[i][j] = round(concordanceTable[i][j] + 0.0 * weights[k],2)
36 | elif optimizationType[k] == 1:
37 | for i in range(alternatives):
38 | for j in range(alternatives):
39 | if i!=j:
40 | if decisionMatrix[i][k] - decisionMatrix[j][k] <= indifferenceThreshold[k]:
41 | concordanceTable[i][j] = round(concordanceTable[i][j] + 1.0 * weights[k],2)
42 | elif decisionMatrix[i][k] - decisionMatrix[j][k] <= preferenceThreshold[k]:
43 | concordanceTable[i][j] = round(concordanceTable[i][j] + ((decisionMatrix[j][k] - decisionMatrix [i][k] + preferenceThreshold[k])*1.0 / (preferenceThreshold[k]-indifferenceThreshold[k])) * weights[k],2)
44 | else:
45 | concordanceTable[i][j] = round(concordanceTable[i][j] + 0.0 * weights[k],2)
46 |
47 | print("Agreement Table")
48 | print(concordanceTable)
49 |
50 | #Section 3: Calculation of the Disagreement Tables for Electre III Method
51 |
52 | sumOfWeights = sum(weights)
53 | discordanceTable = [[[0 for k in range(criteria)] for i in range(alternatives)] for j in range(alternatives)]
54 | for k in range(criteria):
55 | if optimizationType[k] == 0:
56 | for i in range(alternatives):
57 | for j in range(alternatives):
58 | if i!=j:
59 | if decisionMatrix[j][k] - decisionMatrix[i][k] <= preferenceThreshold[k]:
60 | discordanceTable[i][j][k] = 0
61 | elif decisionMatrix[j][k] - decisionMatrix[i][k] <= vetoThreshold[k]:
62 | discordanceTable[i][j][k] = round(((decisionMatrix[j][k] - decisionMatrix [i][k] - preferenceThreshold[k])*1.0 / (vetoThreshold[k]-preferenceThreshold[k])),2)
63 | else:
64 | discordanceTable[i][j][k] = 1
65 | elif optimizationType[k] == 1:
66 | for i in range(alternatives):
67 | for j in range(alternatives):
68 | if i!=j:
69 | if decisionMatrix[i][k] - decisionMatrix[j][k] <= preferenceThreshold[k]:
70 | discordanceTable[i][j][k] = 0
71 | elif decisionMatrix[i][k] - decisionMatrix[j][k] <= vetoThreshold[k]:
72 | discordanceTable[i][j][k] = round(((decisionMatrix[j][k] - decisionMatrix [i][k] + preferenceThreshold[k])*1.0 / (vetoThreshold[k]-preferenceThreshold[k])),2)
73 | else:
74 | discordanceTable[i][j][k] = 1
75 |
76 | print("Disagreement Table")
77 | print(discordanceTable)
78 |
79 | #Section 4: Calculation of reliability indexes
80 |
81 | reliabilityTable = [[0 for i in range(alternatives)] for y in range(alternatives)]
82 | for i in range(alternatives):
83 | for j in range(alternatives):
84 | if i!=j:
85 | reliabilityTable[i][j] = concordanceTable[i][j]
86 | for k in range(criteria):
87 | if concordanceTable[i][j] < discordanceTable[i][j][k]:
88 | reliabilityTable[i][j] = reliabilityTable[i][j] * ((1 - discordanceTable[i][j][k]) / (1 - concordanceTable[i][j]))
89 | else:
90 | reliabilityTable[i][j] = 1
91 |
92 | print("Reliability Table")
93 | print(reliabilityTable)
94 |
95 | #Section 5: Calculation of Dominance Table
96 |
97 | d = 0.8
98 | dominanceTable = [[0 for i in range(alternatives)] for y in range(alternatives)]
99 | for i in range(alternatives):
100 | for j in range(alternatives):
101 | if i!=j and reliabilityTable[i][j] >= d:
102 | dominanceTable[i][j] = 1
103 |
104 | print("Dominance Table")
105 | print(dominanceTable)
106 |
107 | #Section 6: Concordance, Disconcordance and Net Credibility Degrees
108 |
109 | phiPlus = [round(sum(x),3) for x in reliabilityTable ]
110 | phiMinus = [round(sum(x),3) for x in zip(*reliabilityTable)]
111 | phi = [round((x1 - x2),3) for (x1, x2) in zip(phiPlus, phiMinus)]
112 |
113 | print("Positive Flow")
114 | print(phiPlus)
115 | print("Negative Flow")
116 | print(phiMinus)
117 | print("Net Flow")
118 | print(phi)
119 |
120 | plt.bar(alternativeName, phi, color = 'b', edgecolor = 'black')
121 | plt.xlabel('Alternatives')
122 | plt.ylabel('Net Credibility Index')
123 | plt.title('Electre III Method', fontsize=16)
124 | ax = plt.gca()
125 | ax.set_facecolor('red')
126 | plt.grid()
127 |
128 | result = [[0 for x in range(2)] for y in range(alternatives)]
129 | for i in range(alternatives):
130 | result[i][0] = alternativeName[i]
131 | result[i][1] = phi[i]
132 |
133 | result = sorted(result, key=lambda tup: tup[1], reverse=True)
134 |
135 | print(result)
136 | plt.show()
--------------------------------------------------------------------------------
/Chapter5-Numerical Examples/maut.py:
--------------------------------------------------------------------------------
1 | #Filename: maut.py
2 | #Description: Implementation of MCDA method MAUT
3 | #Author: Elissaios Sarmas. November 3, 2019.
4 | import csv
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 | import math
8 |
9 |
10 | criteria = 3
11 | alternatives = 4
12 |
13 | optimizationType = [0, 0, 0]
14 | weights = [0.4, 0.1, 0.5]
15 | alternativeName = ['Security1', 'Security2', 'Security3', 'Security4']
16 | decisionMatrix = [[20, 5, 20], [15, 3, 5], [12, 2, 10], [10, 4, 35]]
17 |
18 | maxValue = np.max(decisionMatrix, axis = 0)
19 | minValue = np.min(decisionMatrix, axis = 0)
20 |
21 | normalisedMatrix = [[0 for y in range(criteria)] for x in range(alternatives)]
22 | for i in range(alternatives):
23 | for j in range(criteria):
24 | if optimizationType[j] == 0:
25 | normalisedMatrix[i][j] = (decisionMatrix[i][j] - minValue[j])*1.0 / (maxValue[j] - minValue[j])
26 | elif optimizationType[j] == 1:
27 | normalisedMatrix[i][j] = (maxValue[j] - decisionMatrix[i][j])*1.0 / (maxValue[j] - minValue[j])
28 |
29 | print(normalisedMatrix)
30 |
31 | utilityScore = [0 for x in range(alternatives)]
32 | utilityScorePer = [0 for x in range(alternatives)]
33 |
34 | for i in range(alternatives):
35 | tempSum = 0
36 | for j in range(criteria):
37 | tempSum += normalisedMatrix[i][j] * weights[j]
38 | utilityScore[i] = tempSum
39 | utilityScorePer[i] = round(round(tempSum,4) * 100,2)
40 |
41 | print(utilityScore)
42 |
43 | plt.bar(alternativeName, utilityScore, color = 'b', edgecolor = 'black')
44 | plt.xlabel('Alternatives')
45 | plt.ylabel('Utility Score')
46 | plt.title('MAUT Method', fontsize=16)
47 | ax = plt.gca()
48 | ax.set_facecolor('red')
49 | plt.grid()
50 |
51 | result = [[0 for x in range(2)] for y in range(alternatives)]
52 | for i in range(alternatives):
53 | result[i][0] = alternativeName[i]
54 | result[i][1] = utilityScore[i]
55 |
56 | result = sorted(result, key=lambda tup: tup[1], reverse=True)
57 |
58 | print(result)
59 | plt.show()
--------------------------------------------------------------------------------
/Chapter5-Numerical Examples/prometheeII.py:
--------------------------------------------------------------------------------
1 | #Filename: prometheeII.py
2 | #Description: Implementation of MCDA method PROMETHEE II
3 | #Author: Elissaios Sarmas. November 3, 2019.
4 |
5 | import csv
6 | import numpy as np
7 | import matplotlib.pyplot as plt
8 | import math
9 |
10 |
11 | def usualCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
12 | if optimizationType[k] == 0:
13 | for i in range(alternatives):
14 | for j in range(alternatives):
15 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
16 | if decisionMatrix[i][k] > decisionMatrix[j][k]:
17 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
18 | elif optimizationType[k] == 1:
19 | for i in range(alternatives):
20 | for j in range(alternatives):
21 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
22 | if decisionMatrix[j][k] > decisionMatrix[i][k]:
23 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
24 |
25 | def quasiCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
26 | if optimizationType[k] == 0:
27 | for i in range(alternatives):
28 | for j in range(alternatives):
29 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
30 | if decisionMatrix[i][k] - decisionMatrix[j][k] > indifferenceThreshold[k]:
31 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
32 | elif optimizationType[k] == 1:
33 | for i in range(alternatives):
34 | for j in range(alternatives):
35 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
36 | if decisionMatrix[j][k] - decisionMatrix[i][k] > indifferenceThreshold[k]:
37 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
38 |
39 | def linearPreferenceCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
40 | if optimizationType[k] == 0:
41 | for i in range(alternatives):
42 | for j in range(alternatives):
43 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
44 | if decisionMatrix[i][k] - decisionMatrix[j][k] > preferenceThreshold[k]:
45 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
46 | else:
47 | evaluationTable[i][j] = evaluationTable[i][j] + ((decisionMatrix[i][k] - decisionMatrix [j][k])*1.0 / preferenceThreshold[k]) * weights[k]
48 | elif optimizationType[k] == 1:
49 | for i in range(alternatives):
50 | for j in range(alternatives):
51 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
52 | if decisionMatrix[j][k] - decisionMatrix[i][k] > preferenceThreshold[k]:
53 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
54 | else:
55 | evaluationTable[i][j] = evaluationTable[i][j] + ((decisionMatrix[j][k] - decisionMatrix [i][k])*1.0 / preferenceThreshold[k]) * weights[k]
56 |
57 |
58 |
59 | def levelCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
60 | if optimizationType[k] == 0:
61 | for i in range(alternatives):
62 | for j in range(alternatives):
63 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
64 | if decisionMatrix[i][k] - decisionMatrix[j][k] > preferenceThreshold[k]:
65 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
66 | elif decisionMatrix[i][k] - decisionMatrix[j][k] <= indifferenceThreshold[k]:
67 | evaluationTable[i][j] = evaluationTable[i][j] + 0.0 * weights[k]
68 | else:
69 | evaluationTable[i][j] = evaluationTable[i][j] + 0.5 * weights[k]
70 | elif optimizationType[k] == 1:
71 | for i in range(alternatives):
72 | for j in range(alternatives):
73 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
74 | if decisionMatrix[j][k] - decisionMatrix[i][k] > preferenceThreshold[k]:
75 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
76 | elif decisionMatrix[j][k] - decisionMatrix[i][k] <= indifferenceThreshold[k]:
77 | evaluationTable[i][j] = evaluationTable[i][j] + 0.0 * weights[k]
78 | else:
79 | evaluationTable[i][j] = evaluationTable[i][j] + 0.5 * weights[k]
80 |
81 |
82 | def linearPreferenceAndIndifferenceCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
83 | if optimizationType[k] == 0:
84 | for i in range(alternatives):
85 | for j in range(alternatives):
86 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
87 | if decisionMatrix[i][k] - decisionMatrix[j][k] > preferenceThreshold[k]:
88 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
89 | elif decisionMatrix[i][k] - decisionMatrix[j][k] > indifferenceThreshold[k]:
90 | evaluationTable[i][j] = evaluationTable[i][j] + ((decisionMatrix[i][k] - decisionMatrix [j][k] - indifferenceThreshold[k])*1.0 / (preferenceThreshold[k]-indifferenceThreshold[k])) * weights[k]
91 | else:
92 | evaluationTable[i][j] = evaluationTable[i][j] + 0.0 * weights[k]
93 | elif optimizationType[k] == 1:
94 | for i in range(alternatives):
95 | for j in range(alternatives):
96 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
97 | if decisionMatrix[j][k] - decisionMatrix[i][k] > preferenceThreshold[k]:
98 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
99 | elif decisionMatrix[j][k] - decisionMatrix[i][k] > indifferenceThreshold[k]:
100 | evaluationTable[i][j] = evaluationTable[i][j] + ((decisionMatrix[j][k] - decisionMatrix [i][k] - indifferenceThreshold[k])*1.0 / (preferenceThreshold[k]-indifferenceThreshold[k])) * weights[k]
101 | else:
102 | evaluationTable[i][j] = evaluationTable[i][j] + 0.0 * weights[k]
103 |
104 |
105 | #Read Input
106 |
107 | criteria = 3
108 | alternatives = 4
109 |
110 | optimizationType = [0, 0, 0]
111 | criterion = [5, 5, 5]
112 | preferenceThreshold = [10, 2, 15]
113 | indifferenceThreshold = [5, 1, 5]
114 | weights = [0.4, 0.1, 0.5]
115 | alternativeName = ['Security1', 'Security2', 'Security3', 'Security4']
116 | decisionMatrix = [[20, 5, 20], [15, 3, 5], [12, 2, 10], [10, 4, 35]]
117 |
118 | evaluationTable = [[0.0 for i in range(alternatives)] for y in range(alternatives)]
119 |
120 | for k in range(criteria):
121 | if criterion[k] == 1:
122 | usualCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
123 | elif criterion[k] == 2:
124 | quasiCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
125 | elif criterion[k] == 3:
126 | linearPreferenceCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
127 | elif criterion[k] == 4:
128 | levelCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
129 | elif criterion[k] == 5:
130 | linearPreferenceAndIndifferenceCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
131 |
132 |
133 | for i in range(alternatives):
134 | for j in range(alternatives):
135 | evaluationTable[i][j] = round(evaluationTable[i][j],2)
136 |
137 | print(evaluationTable)
138 |
139 | sumOfLines = np.sum(evaluationTable, axis=1)
140 | sumOfColumns = np.sum(evaluationTable, axis=0)
141 |
142 | phiPlus = sumOfLines*1.0 / (alternatives - 1)
143 | phiMinus = sumOfColumns*1.0 / (alternatives - 1)
144 | phi = phiPlus - phiMinus
145 |
146 | print("Positive Flow")
147 | print(phiPlus)
148 | print("Negative Flow")
149 | print(phiMinus)
150 | print("Net Flow")
151 | print(phi)
152 |
153 | i = np.arange(alternatives)
154 | plt.bar(alternativeName, phi, color = 'b', edgecolor = 'black')
155 | plt.xlabel('Alternatives')
156 | plt.ylabel('Net Flow')
157 | plt.title('PROMETHEE Method', fontsize=16)
158 | ax = plt.gca()
159 | ax.set_facecolor('red')
160 | plt.grid()
161 |
162 | result = [[0 for x in range(2)] for y in range(alternatives)]
163 | for i in range(alternatives):
164 | result[i][0] = alternativeName[i]
165 | result[i][1] = round(phi[i],3)
166 |
167 | result = sorted(result, key=lambda tup: tup[1], reverse=True)
168 | print(result)
169 | plt.show()
--------------------------------------------------------------------------------
/Chapter5-Numerical Examples/topsis.py:
--------------------------------------------------------------------------------
1 | #Filename: topsis.py
2 | #Description: Implementation of MCDA method TOPSIS
3 | #Author: Elissaios Sarmas. November 3, 2019.
4 | import csv
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 | import math
8 |
9 | #Section 1: Read Input Data such as Alternatives, Criteria, Weights, Veto Thresholds and the Decision Matrix
10 |
11 | criteria = 3
12 | alternatives = 4
13 |
14 | optimizationType = [0, 0, 0]
15 | weights = [0.4, 0.1, 0.5]
16 | alternativeName = ['Security1', 'Security2', 'Security3', 'Security4']
17 | decisionMatrix = [[20, 5, 20], [15, 3, 5], [12, 2, 10], [10, 4, 35]]
18 |
19 | #Section 2: Calculation of the Normalised Decision Matrix
20 |
21 | normalisedDecisionMatrix = [[0 for i in range(criteria)] for y in range(alternatives)]
22 | for j in range(criteria):
23 | sumOfPows = 0
24 | for i in range(alternatives):
25 | sumOfPows = sumOfPows + math.pow(decisionMatrix[i][j],2)
26 | sqSumOfPows = math.sqrt(sumOfPows)
27 | for i in range(alternatives):
28 | normalisedDecisionMatrix[i][j] = decisionMatrix[i][j]*1.0 / sqSumOfPows
29 | print("Normalised Matrix")
30 | print(normalisedDecisionMatrix)
31 |
32 | #Section 3: Calculation of Weighted Decision Matrix
33 |
34 | weightedDecisionMatrix = [[0 for i in range(criteria)] for y in range(alternatives)]
35 | for j in range(criteria):
36 | for i in range(alternatives):
37 | weightedDecisionMatrix[i][j] = normalisedDecisionMatrix[i][j] * weights[j]
38 | print("weighted Matrix")
39 | print(weightedDecisionMatrix)
40 |
41 | #Section 4: Calculation of Ideal and Non-Ideal Solutions
42 |
43 | idealSolution = [0 for i in range(criteria)]
44 | nonIdealSolution = [0 for i in range(criteria)]
45 | for j in range(criteria):
46 | maxValue = -100000000000
47 | minValue = 100000000000
48 | for i in range(alternatives):
49 | if weightedDecisionMatrix[i][j] < minValue:
50 | minValue = weightedDecisionMatrix[i][j]
51 | if weightedDecisionMatrix[i][j] > maxValue:
52 | maxValue = weightedDecisionMatrix[i][j]
53 | if optimizationType[j] == 0:
54 | idealSolution[j] = maxValue
55 | nonIdealSolution[j] = minValue
56 | elif optimizationType[j] == 1:
57 | idealSolution[j] = minValue
58 | nonIdealSolution[j] = maxValue
59 |
60 | print("Ideal Solution")
61 | print(idealSolution)
62 | print("Non Ideal Solution")
63 | print(nonIdealSolution)
64 |
65 | #Section 5: Calculation of Separation Distance of each alternative
66 |
67 | sPlus = [0 for i in range(alternatives)]
68 | sMinus = [0 for i in range(alternatives)]
69 | for i in range(alternatives):
70 | sumPlusTemp = 0
71 | sumMinusTemp = 0
72 | for j in range(criteria):
73 | sumPlusTemp = sumPlusTemp + math.pow(idealSolution[j]-weightedDecisionMatrix[i][j],2)
74 | sumMinusTemp = sumMinusTemp + math.pow(nonIdealSolution[j]-weightedDecisionMatrix[i][j],2)
75 | sPlus[i] = math.sqrt(sumPlusTemp)
76 | sMinus[i] = math.sqrt(sumMinusTemp)
77 |
78 | print("sPlus")
79 | print(sPlus)
80 | print("sMinus")
81 | print(sMinus)
82 |
83 | #Section 6: Relative Closeness of each alternative to the ideal solution
84 |
85 | C = [0 for i in range(alternatives)]
86 | C2 = [0 for i in range(alternatives)]
87 | for i in range(alternatives):
88 | C2[i] = round(round(sMinus[i]*1.0 / (sMinus[i] + sPlus[i]),4) * 100,2) #percentage
89 | C[i] = sMinus[i]*1.0 / (sMinus[i] + sPlus[i])
90 |
91 | print("C")
92 | print(C)
93 |
94 | plt.bar(alternativeName, C, color = 'b', edgecolor = 'black')
95 | plt.xlabel('Alternatives')
96 | plt.ylabel('Relative Closeness')
97 | plt.title('TOPSIS Method', fontsize=16)
98 | ax = plt.gca()
99 | ax.set_facecolor('red')
100 | plt.grid()
101 |
102 | result = [[0 for x in range(2)] for y in range(alternatives)]
103 | for i in range(alternatives):
104 | result[i][0] = alternativeName[i]
105 | result[i][1] = C2[i]
106 |
107 | result = sorted(result, key=lambda tup: tup[1], reverse=True)
108 |
109 | print(result)
110 | plt.show()
--------------------------------------------------------------------------------
/Genetic Algorithm Model.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Genetic algorithm model"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 1,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "import numpy as np\n",
17 | "import pandas as pd\n",
18 | "import matplotlib.pyplot as plt\n",
19 | "import openpyxl \n",
20 | "from scipy.optimize import differential_evolution\n",
21 | "from scipy.optimize import LinearConstraint, minimize\n",
22 | "from pandas_datareader import data\n",
23 | "from matplotlib import rc\n",
24 | "rc('font', **{'family': 'serif', 'serif': ['Computer Modern']})\n",
25 | "rc('text', usetex=True)"
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "metadata": {},
31 | "source": [
32 | "### Securities Input Data\n",
33 | "\n",
34 | "The input data of this model are the names and the values of the securities. The next step involves the calculation of the arithmetic return for the selected securities."
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": 2,
40 | "metadata": {},
41 | "outputs": [
42 | {
43 | "name": "stdout",
44 | "output_type": "stream",
45 | "text": [
46 | "============ Stock Returns ============ \n",
47 | "\n"
48 | ]
49 | },
50 | {
51 | "data": {
52 | "text/html": [
53 | "
\n",
54 | "\n",
67 | "
\n",
68 | " \n",
69 | " \n",
70 | " Symbols | \n",
71 | " ACN | \n",
72 | " NOC | \n",
73 | " IBM | \n",
74 | " MSI | \n",
75 | " MSCI | \n",
76 | " ORA | \n",
77 | "
\n",
78 | " \n",
79 | " Date | \n",
80 | " | \n",
81 | " | \n",
82 | " | \n",
83 | " | \n",
84 | " | \n",
85 | " | \n",
86 | "
\n",
87 | " \n",
88 | " \n",
89 | " \n",
90 | " 2016-01-05 | \n",
91 | " -0.006334 | \n",
92 | " 0.010055 | \n",
93 | " 0.008554 | \n",
94 | " -0.017290 | \n",
95 | " -0.006892 | \n",
96 | " -0.013472 | \n",
97 | "
\n",
98 | " \n",
99 | " 2016-01-06 | \n",
100 | " -0.011376 | \n",
101 | " 0.012297 | \n",
102 | " -0.017403 | \n",
103 | " -0.013985 | \n",
104 | " -0.004957 | \n",
105 | " -0.010242 | \n",
106 | "
\n",
107 | " \n",
108 | " 2016-01-07 | \n",
109 | " -0.010515 | \n",
110 | " -0.011885 | \n",
111 | " -0.005060 | \n",
112 | " -0.014336 | \n",
113 | " -0.014518 | \n",
114 | " 0.007186 | \n",
115 | "
\n",
116 | " \n",
117 | " 2016-01-08 | \n",
118 | " -0.002707 | \n",
119 | " 0.004737 | \n",
120 | " -0.003889 | \n",
121 | " -0.004642 | \n",
122 | " 0.004911 | \n",
123 | " 0.013699 | \n",
124 | "
\n",
125 | " \n",
126 | " 2016-01-11 | \n",
127 | " -0.010052 | \n",
128 | " -0.006939 | \n",
129 | " -0.010287 | \n",
130 | " 0.005130 | \n",
131 | " -0.011785 | \n",
132 | " 0.006475 | \n",
133 | "
\n",
134 | " \n",
135 | " ... | \n",
136 | " ... | \n",
137 | " ... | \n",
138 | " ... | \n",
139 | " ... | \n",
140 | " ... | \n",
141 | " ... | \n",
142 | "
\n",
143 | " \n",
144 | " 2018-12-24 | \n",
145 | " -0.031485 | \n",
146 | " -0.023837 | \n",
147 | " -0.023111 | \n",
148 | " -0.039567 | \n",
149 | " -0.035735 | \n",
150 | " -0.006997 | \n",
151 | "
\n",
152 | " \n",
153 | " 2018-12-26 | \n",
154 | " -0.013033 | \n",
155 | " -0.026096 | \n",
156 | " -0.017288 | \n",
157 | " -0.012801 | \n",
158 | " -0.004623 | \n",
159 | " 0.000979 | \n",
160 | "
\n",
161 | " \n",
162 | " 2018-12-27 | \n",
163 | " 0.017136 | \n",
164 | " 0.019953 | \n",
165 | " 0.018426 | \n",
166 | " 0.010593 | \n",
167 | " 0.030964 | \n",
168 | " 0.000000 | \n",
169 | "
\n",
170 | " \n",
171 | " 2018-12-28 | \n",
172 | " 0.028444 | \n",
173 | " 0.044666 | \n",
174 | " 0.038458 | \n",
175 | " 0.016626 | \n",
176 | " 0.046553 | \n",
177 | " 0.026398 | \n",
178 | "
\n",
179 | " \n",
180 | " 2018-12-31 | \n",
181 | " -0.004326 | \n",
182 | " 0.008037 | \n",
183 | " -0.007792 | \n",
184 | " 0.004622 | \n",
185 | " 0.001025 | \n",
186 | " 0.009907 | \n",
187 | "
\n",
188 | " \n",
189 | "
\n",
190 | "
753 rows × 6 columns
\n",
191 | "
"
192 | ],
193 | "text/plain": [
194 | "Symbols ACN NOC IBM MSI MSCI ORA\n",
195 | "Date \n",
196 | "2016-01-05 -0.006334 0.010055 0.008554 -0.017290 -0.006892 -0.013472\n",
197 | "2016-01-06 -0.011376 0.012297 -0.017403 -0.013985 -0.004957 -0.010242\n",
198 | "2016-01-07 -0.010515 -0.011885 -0.005060 -0.014336 -0.014518 0.007186\n",
199 | "2016-01-08 -0.002707 0.004737 -0.003889 -0.004642 0.004911 0.013699\n",
200 | "2016-01-11 -0.010052 -0.006939 -0.010287 0.005130 -0.011785 0.006475\n",
201 | "... ... ... ... ... ... ...\n",
202 | "2018-12-24 -0.031485 -0.023837 -0.023111 -0.039567 -0.035735 -0.006997\n",
203 | "2018-12-26 -0.013033 -0.026096 -0.017288 -0.012801 -0.004623 0.000979\n",
204 | "2018-12-27 0.017136 0.019953 0.018426 0.010593 0.030964 0.000000\n",
205 | "2018-12-28 0.028444 0.044666 0.038458 0.016626 0.046553 0.026398\n",
206 | "2018-12-31 -0.004326 0.008037 -0.007792 0.004622 0.001025 0.009907\n",
207 | "\n",
208 | "[753 rows x 6 columns]"
209 | ]
210 | },
211 | "metadata": {},
212 | "output_type": "display_data"
213 | }
214 | ],
215 | "source": [
216 | "companyName = ['ACN', 'NOC', 'IBM', 'MSI', 'MSCI', 'ORA']\n",
217 | "startDate = '2016-01-01'\n",
218 | "endDate = '2018-12-31'\n",
219 | "\n",
220 | "historicalValues = data.DataReader(companyName, 'yahoo', startDate, endDate)\n",
221 | "stockValues = historicalValues['Open']\n",
222 | "numOfDates = stockValues.shape[0]\n",
223 | "numSecurities = stockValues.shape[1]\n",
224 | "stockValues = stockValues.fillna(method='ffill')\n",
225 | "\n",
226 | "numPeriods = numOfDates - 1\n",
227 | "stockValuesArray = pd.DataFrame(stockValues).to_numpy()\n",
228 | "secReturns = np.empty(shape = (numPeriods, numSecurities))\n",
229 | "for i in range(numSecurities):\n",
230 | " for j in range(numPeriods):\n",
231 | " secReturns[j][i] = (stockValuesArray[j+1][i]-stockValuesArray[j][i])/stockValuesArray[j][i]\n",
232 | "returnDates = stockValues.index[1:]\n",
233 | "stockReturns = pd.DataFrame(secReturns, index=returnDates, columns=stockValues.columns) \n",
234 | "print(\"============ Stock Returns ============ \\n\")\n",
235 | "display(stockReturns)"
236 | ]
237 | },
238 | {
239 | "cell_type": "markdown",
240 | "metadata": {},
241 | "source": [
242 | "### Market Index Input Data\n",
243 | "\n",
244 | "The same process is followed for the market index. In this case we select the NASDAQ Composite index (^IXIC) because the seleted securities belong to NASDAQ stock exchange. Therefore, we calculate the returns of the market index for the selected time horizon."
245 | ]
246 | },
247 | {
248 | "cell_type": "code",
249 | "execution_count": 3,
250 | "metadata": {
251 | "scrolled": true
252 | },
253 | "outputs": [
254 | {
255 | "data": {
256 | "text/plain": [
257 | "Date\n",
258 | "2016-01-04 4582.049805\n",
259 | "2016-01-05 4573.540039\n",
260 | "2016-01-06 4528.120117\n",
261 | "2016-01-07 4346.100098\n",
262 | "2016-01-08 4404.180176\n",
263 | " ... \n",
264 | "2018-12-21 4672.770020\n",
265 | "2018-12-24 4650.419922\n",
266 | "2018-12-27 4689.200195\n",
267 | "2018-12-28 4641.049805\n",
268 | "2018-12-31 4708.740234\n",
269 | "Name: Open, Length: 767, dtype: float64"
270 | ]
271 | },
272 | "metadata": {},
273 | "output_type": "display_data"
274 | },
275 | {
276 | "name": "stdout",
277 | "output_type": "stream",
278 | "text": [
279 | "============ Stock Returns ============ \n",
280 | "\n"
281 | ]
282 | },
283 | {
284 | "data": {
285 | "text/html": [
286 | "\n",
287 | "\n",
300 | "
\n",
301 | " \n",
302 | " \n",
303 | " | \n",
304 | " 0 | \n",
305 | "
\n",
306 | " \n",
307 | " Date | \n",
308 | " | \n",
309 | "
\n",
310 | " \n",
311 | " \n",
312 | " \n",
313 | " 2016-01-05 | \n",
314 | " -0.001857 | \n",
315 | "
\n",
316 | " \n",
317 | " 2016-01-06 | \n",
318 | " -0.009931 | \n",
319 | "
\n",
320 | " \n",
321 | " 2016-01-07 | \n",
322 | " -0.040198 | \n",
323 | "
\n",
324 | " \n",
325 | " 2016-01-08 | \n",
326 | " 0.013364 | \n",
327 | "
\n",
328 | " \n",
329 | " 2016-01-11 | \n",
330 | " -0.016114 | \n",
331 | "
\n",
332 | " \n",
333 | " ... | \n",
334 | " ... | \n",
335 | "
\n",
336 | " \n",
337 | " 2018-12-24 | \n",
338 | " -0.014793 | \n",
339 | "
\n",
340 | " \n",
341 | " 2018-12-26 | \n",
342 | " -0.014646 | \n",
343 | "
\n",
344 | " \n",
345 | " 2018-12-27 | \n",
346 | " -0.013062 | \n",
347 | "
\n",
348 | " \n",
349 | " 2018-12-28 | \n",
350 | " -0.017298 | \n",
351 | "
\n",
352 | " \n",
353 | " 2018-12-31 | \n",
354 | " -0.003262 | \n",
355 | "
\n",
356 | " \n",
357 | "
\n",
358 | "
753 rows × 1 columns
\n",
359 | "
"
360 | ],
361 | "text/plain": [
362 | " 0\n",
363 | "Date \n",
364 | "2016-01-05 -0.001857\n",
365 | "2016-01-06 -0.009931\n",
366 | "2016-01-07 -0.040198\n",
367 | "2016-01-08 0.013364\n",
368 | "2016-01-11 -0.016114\n",
369 | "... ...\n",
370 | "2018-12-24 -0.014793\n",
371 | "2018-12-26 -0.014646\n",
372 | "2018-12-27 -0.013062\n",
373 | "2018-12-28 -0.017298\n",
374 | "2018-12-31 -0.003262\n",
375 | "\n",
376 | "[753 rows x 1 columns]"
377 | ]
378 | },
379 | "metadata": {},
380 | "output_type": "display_data"
381 | }
382 | ],
383 | "source": [
384 | "historicalValues = data.DataReader('^FCHI', 'yahoo', startDate, endDate)\n",
385 | "indexValues = historicalValues['Open']\n",
386 | "numOfDates = stockValues.shape[0]\n",
387 | "indexValues = indexValues.fillna(method='ffill')\n",
388 | "display(indexValues)\n",
389 | "\n",
390 | "numPeriods = numOfDates - 1\n",
391 | "indexValuesArray = pd.DataFrame(indexValues).to_numpy()\n",
392 | "marketReturns = np.empty(shape = (numPeriods))\n",
393 | "for j in range(numPeriods):\n",
394 | " marketReturns[j] = (indexValuesArray[j+1]-indexValuesArray[j])/indexValuesArray[j]\n",
395 | "indexReturns = pd.DataFrame(marketReturns, index=returnDates) \n",
396 | "print(\"============ Stock Returns ============ \\n\")\n",
397 | "display(indexReturns)\n"
398 | ]
399 | },
400 | {
401 | "cell_type": "markdown",
402 | "metadata": {},
403 | "source": [
404 | "### Multiobjective Function\n",
405 | "\n",
406 | "In this point, we define the multiobjective function losses, which is about to be optimised. This function calculates the number of times that the portfolio return is smaller than the selected marfket index return for all dates during the selected time period. This function needs to be minimised, as the target is to maximise the number of times that the portfolio offers a better return."
407 | ]
408 | },
409 | {
410 | "cell_type": "code",
411 | "execution_count": 4,
412 | "metadata": {},
413 | "outputs": [],
414 | "source": [
415 | "def losses(x):\n",
416 | " losingTimes = 0\n",
417 | " portfReturn = [0 for i in range(numPeriods)]\n",
418 | " for i in range(numPeriods): \n",
419 | " for j in range(numSecurities):\n",
420 | " portfReturn[i] += x[j] * secReturns[i][j]\n",
421 | " if portfReturn[i] < marketReturns[i]:\n",
422 | " losingTimes += 1 \n",
423 | " return losingTimes"
424 | ]
425 | },
426 | {
427 | "cell_type": "markdown",
428 | "metadata": {},
429 | "source": [
430 | "Finally, the function differential_evolution solves this evolutionary problem. The list of variables bounds sets the limits for each security's percentage in the portfolio. The function LinearConstraint imposes the capital completeness constraint. The parameters of the model are the following (according to the scipy documentation):\n",
431 | "\n",
432 | "* maxiter: The maximum number of generations over which the entire population is evolved.\n",
433 | "* popsizeint: A multiplier for setting the total population size. The population has popsize * len(x) individuals.\n",
434 | "* mutation: The mutation constant. In the literature this is also known as differential weight, being denoted by F.\n",
435 | "* recombinationfloat: The recombination constant, should be in the range [0, 1]. Also known as the crossover probability, being denoted by CR. Increasing this value allows a larger number of mutants to progress into the next generation, but at the risk of population stability."
436 | ]
437 | },
438 | {
439 | "cell_type": "code",
440 | "execution_count": 5,
441 | "metadata": {},
442 | "outputs": [
443 | {
444 | "name": "stderr",
445 | "output_type": "stream",
446 | "text": [
447 | "/home/elissaios/anaconda3/lib/python3.7/site-packages/scipy/optimize/_differentialevolution.py:812: UserWarning: differential evolution didn't find a solution satisfying the constraints, attempting to polish from the least infeasible solution\n",
448 | " \" infeasible solution\", UserWarning)\n",
449 | "/home/elissaios/anaconda3/lib/python3.7/site-packages/scipy/optimize/_hessian_update_strategy.py:187: UserWarning: delta_grad == 0.0. Check if the approximated function is linear. If the function is linear better results can be obtained by defining the Hessian as zero instead of using quasi-Newton approximations.\n",
450 | " 'approximations.', UserWarning)\n"
451 | ]
452 | },
453 | {
454 | "name": "stdout",
455 | "output_type": "stream",
456 | "text": [
457 | "ACN : 0.2115\n",
458 | "NOC : 0.1565\n",
459 | "IBM : 0.1265\n",
460 | "MSI : 0.2572\n",
461 | "MSCI : 0.125\n",
462 | "ORA : 0.1233\n",
463 | "Percentage that portfolio beats the index: 0.5179282868525896\n"
464 | ]
465 | },
466 | {
467 | "data": {
468 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAA7sAAAI/CAYAAAC2zM6MAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nO3dzZIc530v6N9fwTXVBKSNI8yPhhzhJdWAvD9iU7O2BRBzAWLD3suEeAOmm9ReB6BvgAQ4+3PQ9A0IhLz0xASaEhXhDU2woZjtxDuLyiILher6yOrqAhLPE1HRVfn5VmV2d/7q/chqrQUAAACG5AfbLgAAAACcNWEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYnJe2XQCAF01V7SQ5SHI9yU6S427WcZLD1tpxt9xBkvuttQdbKeiAVdXDJJdbaydLLPtFRsfpQpK3hn48nrf32/2e3EiyNzH5aGqxC0keJbnXWvvwvMrWR1XtJ7mW5GGSS5n4m7Didp6r4wiwCWp2Ac5RVb2X5MskFzO6AL3UWnu7tfZ2kptJbnYX70lyuK1ybkJV3aqqh13Y32Y59pPsZvSFwzLeSnIro+DwzDuDz/m5er+ttduttcsZBd4k+XD8OzXxuJxRgHy7+2z2Tt/i9nTn5q3W2o0ktzM6R2/23Nzc4/is/D4CbJKwC3BOqupekvczqlG8OV2r2Fo76S5yJ2tlngtVdbWqdhcsdiWjkHnhHIo0z7Xu5/VlFu6O093NFefMzf2cFx2r7v1O14w+Dx51Px/Omtn9fr2d5CTJ5+cZ8pb8/UhG4fQw+e44XEvPL72WOG+fld9HgI0RdgHOQVXdSbKfUdCd2ySxtXY73zdtfl4svJDvatde6dMk84xdSPIgyd6SAeS5ssTnPLj3vKIPMvoi6eNz3OfCz7wL37tJ7o+ntdbubur35Rn6fQTYGGEXYMOq6mqSq0lurnBh+e4Gi7QJq9SSbk13LD7pHsn3TV8HZcHnvNSxGrBxrfX+Oe5zmc98XMN6br8j2/59BNg0YRdg88bNEpceGKe7CH0uBpTp+j8+k30gZ7jeWrubUX/IZPl+u4PwnB2rjZgIeOfSjNlnDrA9wi7ABnUXurvp1wfyk8WLbE9V7XQ1pZ9vuyzL6JqJPkqe6Je60w0KNGjP27HapImm6xttvuszB9g+tx4C2KxxkFq5lnZeTXAXoq9nNBjPTka3KLk1eXuRqVuPXM4o6B1k1EzyUkYh/NSm1Uvs4yDJ2xmFhr0kd6pqPEhQusGAxtu6l+8Hw5l5G5QujL7fvfwmoxGrH3Z9mM/CQZI7E69vZXR8bmTNAZm6UPOzfF/ubzIaHOhqRp/3tYxuIXM0sc74/Y4/3yRPH/duuc+7ZXaTvNL93M/omFxorV3rlj3tc176WM14b+NbZc09bxacb5cn1nvQLX8w8b5/ltG5dR4DY41rWZ/Y1waOx1Kfefdly81834x5crle58wiK/4+rrUvgK1qrXl4eHh4bOiRUaBqSd47w20eJPkiyc7EtJ2MLkoPpqYddvvfny5Dt51v19lHN32/28funDLvJHmvW25vxvy9btv7s9Y7o8/t3oxpbfSvcOG6u3PKfpjkixnTHk59RrtTr5/4fCem35sxffpYHkzs57vPfonPeeGxmjgeLaOwvtR5s+z5Nl5u2c93heN7tdvGwYLlHk4em00ejxU/87mfwaplnLfNJc+Tlfbl4eHh8Sw+NGMG2Kxxk8kzGQima4J5K8m7bWJwme75YZLD8S1VumnfDcTUnq6R+TSjZrxX++5jWW3xbVA+T3K3PV2z906S99e9L2r3nmbVrt/t5l+dMW/Z7b6X0Qi/kz5IsjvebmvtqHU1od1ndy9Tn+94uW7enanpJ930JLnWvq/t/iDJ2+NtL/E5r2rp82bZ8y2j9/bB1LrHGdWAbmzAsKra62qfjzOqaR5P39jxOMOyr1zGeeadJ2e9L4BtEnYBNmt80XtWg+HcSnLSZjQ77C64dzIKiNN+P2P58YXs9G1R+u6jl6o67LY5HRiT7++duq4bGb2vabcm5vcxDuFPhJuJz3bWKLwfJzme9fl2bifZnxHAx5/FF5P7mfEFwVm6Nz1hznkzad75lukQ1TlesM1l3aiqw4nHre7WX9czakb99tT+n4fj0beMz/q+ADZKn12AzRpfVF9ctGBV3croYn/6gv+kje6JmYyaEc67mB73q5y2Sk1T3330tZ/Re3wqALXRyMlnUVO5N6u2rbV2VFUnGV2875wSwtYxK6xfzfejQT+ltXbSlel6Zr/387wvat99zVtv06OMfzKjVnme5+F4rFvGZ3VfABsl7AJs1icZNXNdOOJva+272sWuueVeRk0ij7ppuxPzT7tlzlFGgyNNWyrErbmPvvaywQDUDQC029XuzfIoo5rlgySrDr4zec/WycHBxjW+T+xz4vNddDwe5fTb1Zxn2O0b/uetd5bnzlqeh+NxRmV85vYFcB6EXYANaq09qKrjJHsr1hzez+hiclbN4IN2+gjFZzVy8dr72FBNaR/Xklw+rSzdBf7DjGqqVgq7XS3XjYz6Md+e2Mdhkg833MT4zDxDx+qF4TMH2Dx9dgE272b383CdjUw0wz2r/r+b3sdpNcPTHuRs+mqe5sK8UNG95wcZfSHRtxyXkxxU1XtV9V5GfUNvTi808fku2s9pA2ptyrLHalC2fDyW+szPs4zP8PkJ0IuwC7BhE/1OD7omtcu4cMr0u1nQJHqNwLbuPvoG5KOMRvc9df0VPrfp9a5m9sBU09YZqOpya+24tfbhxGNeEDjKnM93ogn0U4NDnaGNfWHyHDqv47HOZ36e58yzcH4CnAlhF+ActNauZXQReW9RcOtC32n94d7NqP/pzG10F6Lr9qVbdR/j2qALE8vsZPm+jB9k1EdwZs1336Dbub5MU+KJJtt9ajivrLj8jYzC/WnH6XrmNyNfx7rHaog2fTzO4jM/z3Nmm+cnwJkSdgHOSWvt7Yz6hN6rqjuzQlw37eOMmsXOGp34JMnbSW5NX4x2ta37XU3yKp4YKXrVfXRNH48y6hs7dpCnR3Qe12w9USvc7e9akndOuefvXp++r922Vgn+4xrmWeF6XPaZNe7dLW72usfuvNr17vO6luTO9HLdvvfz5Gc5Nt73ohrCmZ/zxL6XOVbLWDjC+IrrXcjpLRqWMV53pXJt+nj0+P146jNYo4zzztvTfh/77gvgmVOttW2XAeCF0tXqHGRUQ7Kb0SBUxxmF23vjGpMurD2YdcucbhvjmtCH3bqPJkNoVd3LqNZxp5t/P8m1blClWxldtI4vZh9k1M/0aGL9hfuYWvZO9z4eduWe3NZ0WY662u557ykZ3ZJopRqkLqDfmXhvxxmNaj2zJq1b/jBPNt08SnKrtXZ33uc4sf4Xme24285TA1917/f9fD868cUk30wvO/HZTpfh1vSxWOFzXuVYLXXerLHeQUa1iXsT824te9xnrD/exkq3INrE8Zixziqf+Z3pz2DZMs7Z5vh4LHueLLUvgGeVsAsAPXWDUV3MKOgcT83bzSjgHWZGmAAANkvYBYAeuiadt1prlxYst5vkYWutzqdkAECizy4ArGPZ+6S6nyoAnDNhFwB66Ppc3u/6o87U1ereyWiEawDgHGnGDABr6Aaout69/GZi1sXu9e3xYFYAwPkZdNj90Y9+1F5//fVtFwMAAIAN+OKLL/67tfbjWfNeOu/CnKfXX3899+/f33YxAAAA2ICq+tNp8/TZBQAAYHCEXQAAAAZH2AUAAGBwhF0AAAAGR9gFAABgcIRdAAAABkfYBQAAYHCEXQAAAAZH2AUAAGBwhF0AAAAGR9gFAABgcIRdAAAABkfYBQAAYHCEXQAAAAZH2AUAAGBwhF0AAAAGR9gFAABgcIRdAAAABkfYBQAAYHCEXQAAAAZH2AUAAGBwXtp2AQCA59urr72WP3/11baLMUh//eqr+epPf9p2MQCeS8IuALCWP3/1VT77z//adjEG6Zd/+1fbLgLAc0szZgAAAAZH2AUAAGBwhF0AAAAGR9gFAABgcIRdAAAABkfYBQAAYHCEXQAAAAZn62G3qq5W1X5VvXfK/IPucTgx7XA877zKCQAAwPNjq2G3qvaSpLV2lORk/Hpi/n6So9ba7SS73eskOaiqh0mOz7XAAAAAPBe2XbN7PclJ9/w4yf7U/N2Jacfd6yS51lq71IVkAAAAeMJLW97/TpJHE68vTs7sanTH9pJ8Mn5eVUmy11r7cKMlBAAA4Lmz7ZrdpXTNm++11h4kSWvtw65W9+JE02YAAABIsv2we5LkQvd8J8k3pyy3P67B7Qa0utpN/ybfN21ON/+gqu5X1f2vv/56E2UGAADgGbftsPtJvg+ru0mOkqSqdsYLVNXBRNDdz6jv7riv7qUk9yc32Fq73Vq70lq78uMf/3jDxQcAAOBZtNWwO26W3IXYk/HrJJ9PTD+sqodV9e3EOu90tbsPJ9YBAACAJNsfoGp6EKrxtMvdz6MkryyzDgAAAIxtuxkzAAAAnDlhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYnJe2XYAX3auvvZY/f/XVtosxSH/96qv56k9/2nYxAACALRB2t+zPX32Vz/7zv7ZdjEH65d/+1baLAAAAbIlmzAAAAAyOsAsAAMDgCLsAAAAMjrALAADA4Ai7AAAADI6wCwAAwOAIuwAAAAyOsAsAAMDgCLsAAAAMjrALAADA4Ai7AAAADI6wCwAAwOAIuwAAAAyOsAsAAMDgCLsAAAAMzkvbLkBVXU1ykmSvtfbhjPkH3dNLrbWby6wDAADAi22rNbtVtZckrbWjJCfj1xPz95MctdZuJ9mtqv1F6wAAAMC2mzFfz6iGNkmOk+xPzd+dmHbcvV60DgAAAC+4bTdj3knyaOL1xcmZXY3u2F6ST5JcnrcOAAAAbLtmdyldU+V7rbUHSyx7UFX3q+r+119/fQ6lAwAA4Fmz7bB7kuRC93wnyTenLLc/MRDV3HVaa7dba1daa1d+/OMfn3V5AQAAeA5sO+x+klE/3HQ/j5KkqnbGC1TVwTjodgNWzVwHAAAAxrYadsfNkrsQezLRTPnziemHVfWwqr5dsA4AAAAk2f4AVdODUI2nXe5+HiV5ZZl1AAAAYGzbzZgBAADgzAm7AAAADI6wCwAAwOAIuwAAAAyOsAsAAMDgCLsAAAAMjrALAADA4Ai7AAAADI6wCwAAwOAIuwAAAAyOsAsAAMDgCLsAAAAMjrALAADA4Ai7AAAADI6wCwAAwOAIuwAAAAyOsAsAAMDgCLsAAAAMjrALAADA4Ai7AAAADI6wCwAAwOAIuwAAAAyOsAsAAMDgCLsAAAAMjrALAADA4KwVdqvq5ar6eVW9OTHtzXnrAAAAwKb1DrtV9a9JTpLcSXJjYtbjqvr1ugUDAACAvnqF3ar65+7pK621i0mOxvNaa1+21n5bVb86iwICAADAql7qud5Ja+2jiddtxjLf9tw2AAAArKVvM+ZvlljmjZ7bBgAAgLX0Dbt/N/W6nnhR9XqSn/TcNgAAAKylbzPmW1X1+yT/kuQP6ZoxdyH3WpKDJJfPoHwAAACwsl5ht7X2ZVXdSHI7yV6SVvVd5e5Rkl+01v5yNkUEAACA1fSt2U1r7UGSK1X1RkaBN0ketNa+PJOSAQAAQE+9wm5VfZDkk9baf3ThdmbArao3k+wnudta+2PvUgIAAMAK+g5Qddxa+495C1TVT5N8ntHgVbe6/rwAAACwcX3Dbqrq5ar6dfd4ecYi/5rkoLX2UWvt/0hytXcpAQAAYAV9w+79JA+S/CLJ/5nkixmB90qSLyZeP+65LwAAAFhJ3wGq3mmtfXcf3arazeh2Q7+dWGYnyaOJ163nvgAAAGAlvfvsTr5orR1nRs2t2w8BAACwDX3D7u7ki1P67AIAAMBW9G3GfL+qfp9R391kdHuha8l3ozDfSPKHqnpzYtTmV9YqKQAAACypV9htrX1WVZVRP93jjAaqelRV72bUV/dOa+0fq+p/VtX9jILwjbMqNAAAAMzTt2Y3rbW7Se5OTf546vXNjALxb1prf+y7LwAAAFhF77A7S1X9MMk7GY28fNxa+/ckH53lPgAAAGCRMw27rbXH6Wp3q+p3Sf79LLcPAAAAy+gddqvqzSSHSS5k1E930oUkn65RLgAAAOitV9itqreS3Ooex0l+luT33ezdJGmtab4MAADAVvSt2b3aWvvJ+EVVtdba/zW5QFX9w/Q0AAAAOA8/6Lneg6nXF6vq5alpj3tuGwAAANbSN+xO+zTJ+1PTfnpG2wYAAICV9A2796vqrar6f6rq190ozJer6ndV9fOq+lVG/XgBAADg3PXqs9ta+0NVvZHkdpLPusnXktxNcpTk2ySXz6SEAAAAsKLetx5qrX2Z5KOJ14+TvH0WhQIAAIB1nFWfXQAAAHhm9L3P7puttf+YmvbDJO8k2UnybWvt386gfAAAALCyvs2YbyT5p8kJXTPmj8evq+pXAi8AAADboBkzAAAAg7NUzW5VvZWkTUzaraqfn7L4Tka3HdpJomYXAACAc7dsM+bjJHtJrie5mlHwvTRn+XuttX+aMx8AAAA2Zqmw291m6Mskn1XVe0neEGYBAAB4VvXps3sro5reueY0cwYAAICNWjnsttYet9Y+mrdMVb2c5GbvUgEAAMAa+t5n9+Ukd5O8ddoieXJAKwAAADg3fW899G9J7iS5kuTCjMdPkvzhLAoIAAAAq+pVs5vRaMsfz5n/uKo+6LltAAAAWEvfmt1HixZorX3Wc9sAAACwlr5h96SqXp+3QFX9uue2AQAAYC19mzG3JFer6lKSL/J0Te+FJDeS/HaNsgEAAEAvfcPu3e7ncZKfzZi/k+SNntsGAACAtfQNu8ettSvzFqiqT3tuGwAAANbSt8/utSWWudlz2wAAALCWXmG3tfZlklTVy1X186p6czxv/Hy8DAAAAJy3vjW7qap/TXKS5E5Gg1GNPTYSMwAAANvUK+xW1T93T19prV1McjSe11r7srX226r61VkUEJjv1ddeS1V5bODx6muvbfvwAgDQU98Bqk5aax9NvG4zlvm257aBFfz5q6/y2X/+17aLMUi//Nu/2nYRAADoqW8z5m+WWMathwAAANiKvmH376Ze1xMvql5P8pOe2wYAAIC19G3GfKuqfp/kX5L8IV0z5i7kXktykOTyGZQPAAAAVtYr7LbWvqyqG0luJ9lL0qq+q9w9SvKL1tpfzqaIAAAAsJq+NbtprT1IcqWqdpP8tJv8wP11AQAA2Lbe99kda60dt9Y+6x5fVtWbq6xfVVerar+q3puzzN7U68Pu50G/UgMAADBkfe+z+3JV/XNV/X9V9eup2Y9nTDttO3tJ0lo7SnIyHWq7ZfaTfDw1+aCqHiY57lF8AAAABq5vze473X12f5GpINpa+7K19tuq+ocltnM9yUn3/DjJ/vQCXRB+NDX5WmvtUjcPAAAAntC3z24lSWvt8zX3v5Mng+zFJdfb6wbE2mutfbhmGQAAABiYvjW7bYlldntue/HOW/uwq9W92DVzBgAAgO/0Dbs/qarXTpvZDVL1kyW2c5LkQvd8J8k3i1boBrS62r38JlOhuqoOqup+Vd3/+uuvlygCAAAAQ9O3GfMHSb6oqjtJ7uX7gaJ2k1zLqO/t5SW280mSKxPrHiVJVe201k5OWed4Yn+XktyanNlau53R/X9z5cqVZWqgAQAAGJheNbuttccZhdRLGQXUh93jKKOa2iuttb8ssZ0HyXcjLp+MXyf5ri9wV4t7ZVyb2y3zTvf64cQ6AAAAkKR/zW66mtd3kqSqftpN+0OP7dyeMe3yxPO7Se4uWgcAAADGeofdqnp5XHvbJ+QCAADApvRqxlxV/zvJl2dcFgAAADgTfUdjvpMN3loIAAAA1tE37D5K8sq8Barqdz23DQAAAGvp22f3YZKrVXWxe348Y5n93qUCAACANfQNu/+eZCezQ24yuv3QD3tuGwAAANbSN+wet9auzFugqj7tuW0AAABYS98+u+8usczNntsGAACAtfQKu+P76lbVy1X186p6czxv/Ly15tZEAAAAbEXfmt1U1b8mOcnoNkQ3JmY9rqpfr1swAAAA6KtX2K2qf+6evtJau5jkaDyvtfZla+23VfWrsyggAAAArKrvAFUnrbWPJl63Gct823PbAAAAsJa+zZi/WWKZN3puGwAAANbSN+z+3dTreuJF1etJftJz2wAAALCWvs2Yb1XV75P8S5I/pGvG3IXca0kOklw+g/IBAADAynqF3dbal1V1I8ntJHtJWtV3lbtHSX7RWvvL2RQRAAAAVtO3ZjettQdJrlTVGxkF3iR54P66AAAAbFvvsDvWhVsBFwAAgGfG0mG3qt5Mcj3Jfyf5rLX2x00VCgAAANax1GjMVfVBkgdJbib5KMnDqvr7TRYMAAAA+loYdqvqpxmNsPx2kle6x/UkH1bVy5stHgAAAKxumWbMv0ny9tTAU3er6tuMbjH0242UDAAAAHpaphlzzRphubX2eZIfnX2RAAAAYD3LhN1Hc+Z9c1YFAQAAgLOyTNhtPecBAADAViw1GnMfVfW7TW0bAAAA5llmgKorVfU/ktSMeZeq6uczpu8k2V+rZAAAANDTMmH3cpKjzA67SXLjlOmaOAMAALAVy4TdBxndZ3feQFXTLib5tFeJAAAAYE3LhN2jWbceWuBxVR31KRAAAACsa+EAVa213/TZcN/1AAAAYF0bG40ZAAAAtkXYBQAAYHCEXQAAAAZH2AUAAGBwhF0AAAAGR9gFAABgcIRdAAAABueldTdQVS8nudK9vN9a+8u62wTg/Lz62mv581dfbbsYg/TXr76ar/70p20XAwBeSL3Dbhdy/y3J1YnJraqOktxorf1xzbIBcA7+/NVX+ew//2vbxRikX/7tX227CADwwurVjLmqfpjkbpJ7SV5prf2gtfaDJH+T5PMkd7owDAAAAOeub83uu0mutdYeT05srR0n+bCq7iZ5v3sAAMCpdKfYHN0peJH1DbuPp4PupNbacVUd99w2AAAvEN0pNkd3Cl5kfUdjbkss88Oe2wYAAIC19K3ZfaWqXj5t5OWuv+6P+hcLAABgOZrCb87z3BS+b9i9neRuVf0uyefj0NuF3HeS3Ejy1tkUEQAA4HSawm/O89wUvlfYba09rqobSW4l+ayqJps1P0jyjvvtAgAAsC2977PbWvsyyS+q6o0ku0l2kjzopgMAAMDW9A67Y124fSrgVtWvWmv/tu72AQAAYFULw25VvZ4krbU/Tkz7hyW2fTOJsAsAAMC5W6Zm90GSh0l+NjFtHGJPu5fuTpI31igXAAAA9LZM2H0rycnUtOPW2pV5K1XVp71LBQAAAGtYGHZba3+YMXmZ2wrdXL04AAAAsL4f9FmptfZ4icV+2WfbAAAAsK65NbtV9fOM+t/2cSPJb3uuCwAAAL0tasb8myT7GQ1StQoDVAEAALA1i8LuSZJL3b10V2KAKgAAALZlUZ/dm32C7njdnusBAADAWubW7C4TdKvq5STj2xDdb639Zdl1AQAAYBN6jcacjEJu11T5JMlR9/i2qv5XVb1+NsUDAACA1fUKu1X1wyR3k9xL8kpr7QettR8k+Zsknye509X4AgAAwLnrW7P7bpJrrbWPJ++521o7bq19mOR6kvfPooAAAACwqr5h9/FkyJ3WWjtOctxz2wAAALCWvmG3LbHMD3tuGwAAANbSN+y+Mq9PbjfvRz23DQAAAGvpG3ZvJ7lbVX8/GXq7EZp/ldEgVf9yFgUEAACAVc29z+5pWmuPq+pGkltJPquqyWbND5K8M77fLgAAAJy3hWG3qt5Nsttae2J05dbal0l+UVVvJNlNspPkQTcdAAAAtmaZmt1LSR5OTqiqX7fWfpt8F3qfCrhV9bLaXQAAALZhmT67/91a+3hq2qUl1jvsUR4AAABY2zI1u1VVv0tyL8lJN223qv5hwXr7a5UMAAAAeloYdltrH3X9dv8xyYVu8m6Si3NW20nyxvrFAwAAgNUtNRpz14z5u6bMVfU/W2v/OG+dqvp0zbIBAABAL33vs3tniWVu9tw2AAAArKVX2G2tfb7EMm5BBAAAwFYs1Yx5WlW92Vr7j6lpP0zyTkb9db9trf3bGZQPAAAAVtYr7Ca5keSfJie01h7nyX69vxJ4AQAA2Ia+fXYBAADgmbVUzW5VvZWkTUzaraqfn7L4TpKfdT/V7AIAAHDulm3GfJxkL8n1JFczCr6X5ix/r7X2T3PmAwAAwMYse5/dL5N8meSzqnovyRvCLAAAAM+qPn12b2VU03smqupqVe13Ifq0ZfZWXQcAAIAX18pht7X2uLX2UVW9vO7OxyG2tXaU5GQ61HbL7OfJUZ4XrgMAAMCLrddozFX1vzNq1ryu60lOuufHSfanF+hC7aNV1gEAAODF1vfWQ3eS7J7B/nfyZJC9uKF1AAAAeIH0DbuPkrwyb4Gq+l3PbQMAAMBalr310LSHSa5W1cXu+awBq5ZpXnyS5EL3fCfJN+uuU0xjPHMAABLySURBVFUHSQ6S5NVXX11icwAAAAxN37D77xkFzdNGZb6Q5IdLbOeTJFe657tJjpKkqnZaayerrDPWWrud5HaSXLlypS1RBgAAAAambzPm49baD1prPznlcSHJZ4s20lp7kHw34vLJ+HWSz8fLVNXVJFe6n/PWAQAAgCT9a3bfXWKZm8tsqKuJnZ52eeL53SR3F60DAAAAY73CbmvtD+Pn3f12x82K77fW/tItcxa3JgIAAICV9W3GnKp6uao+zWjAqKPu8W1V/a+qev1sigcAAACr6xV2q+qHGTUtvpfkla7/7g+S/E1G/W3vdDW+AAAAcO761uy+m+Raa+3j1trj8cTW2nFr7cMk15O8fxYFBAAAgFX1DbuPJ0PutNbacU6/LREAAABsVN+wu8z9a5e5zy4AAACcub5h95V5fXK7eT/quW0AAABYS9+wezvJ3ar6+8nQ243Q/KuMBqn6l7MoIAAAAKyq7312H1fVjSS3knxWVZPNmh8keWd8v10AAAA4b73CbpK01r5M8ouqeiPJbpKdJA+66QAAALA1S4fdqnozo1sK/XeSz1prf0y+C70CLgAAAM+MpfrsVtUHGTVPvpnkoyQPq+rvN1kwAAAA6Gth2K2qnya5luTtJK90j+tJPpw3IjMAAABsyzLNmH+T5O2pvrh3q+rbJAdJfruRkgEAAEBPyzRjrlmDTrXWPo976QIAAPAMWibsPpoz75uzKggAAACclWXCbus5DwAAALZiqdGY+6iq321q2wAAADDPMgNUXamq/5GkZsy7VFU/nzF9J8n+WiUDAACAnpYJu5eTHGV22E2SG6dM18QZAACArVgm7D7I6D678waqmnYxyae9SgQAAABrWibsHs269dACj6vqqE+BAAAAYF0LB6hqrf2mz4b7rgcAAADr2thozAAAALAtwi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIMj7AIAADA4wi4AAACDI+wCAAAwOMIuAAAAgyPsAgAAMDjCLgAAAIOz9bBbVVerar+q3lt2flUddj8PzqucAAAAPD+2Gnarai9JWmtHSU7Gr5eYf1BVD5Mcn2d5AQAAeD5su2b3epKT7vlxkv0l519rrV3qQjAAAAA84aUt738nyaOJ1xeXnL9XVUmy11r7cHPFAwAA4Hm07ZrdXlprH3a1uher6ona4Ko6qKr7VXX/66+/3lIJAQAA2KZth92TJBe65ztJvlk0vxuw6mo37Zsku5MrtNZut9autNau/PjHP95QsQEAAHiWbTvsfpLvw+pukqMkqaqdOfOPx8sluZTk/rmUFAAAgOfGVsNua+1BknRNkU/Gr5N8ftr8bto7Xe3uw4l1AAAAIMn2B6hKa+32jGmXF8x/ahoAAACMbbsZMwAAAJw5YRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYHGEXAACAwRF2AQAAGBxhFwAAgMERdgEAABgcYRcAAIDBEXYBAAAYnJe2XYCquprkJMlea+3DZeYvWgcAAIAX21ZrdqtqL0laa0dJTsav581ftA4AAABsuxnz9YxqaJPkOMn+EvMXrQMAAMALbtthdyfJo4nXF5eYv2gdAAAAXnBb77N71qrqIMlB9/L/rar/e5vlWcYv//avtl2EZf0oyX9vuxCrqKptF+FcOIc2xzn0zHEOPaOcQ5vjHHrmOIeeUc6hzXnGz6HXTpux7bB7kuRC93wnyTdLzj91ndba7SS3z7ykpKrut9aubLscPL+cQ6zLOcS6nEOsyznEupxD52fbYfeTJOMDvZvkKEmqaqe1dnLa/FOmAQAAQJIt99ltrT1IkqraT3Iyfp3k89Pmz1kHAAAAkmy/Znfc7Hh62uUF8zVT3g6fO+tyDrEu5xDrcg6xLucQ63IOnZNqrW27DAAAAHCmtn3rIQAAADhzwu4Lqqp2q+pwyWUPu8d7VXXQ3d4p3fOd7vl7VfWwqr6Ysf5BN+9eVe2d7TvhvPQ9xlW10617deLx3hL7W3je8fzpzofDqvq2O2fem5g3nv5Fd9zHj8OqujV93LvpD6vq4YJ9HlZV6346dwZi3eM/cX4ddD93xufjvPOU588mz5VTtnHq/y7n1jCscm2z4H/b/hL7WvqanRlaax4v4CPJfpJvFyyzl+Rekv0Z8w6SfJtkZ2La1SRfJHlvxvJXJ5f1eD4fqx7j7hy6M2P6Tjd9d8Z2VjrvPJ7PR3f8D1eYfpDk4YzpV5M8TLI3Z1+Hs9b1eP4ffY9/klvTf39mnWOnnY8ez9/jHM6VVa+ZnFvP6aPntc1p/9vuJTlYsL+F1+wepz/U7L64jpPcr6qrc5a5k+RGa+2p2zu10wcJezfJYVXtTk0/aaPbSfH8W+oYd9+K30ny7vSx715/kNEf+Wl9zjuGZfqe6+Njv3vKt+C3ktyYtaFu+VnnGcPR5/jvttaOJyd059jxjGUZjk2eK/53vQDWuLY5zWFG5+U8y1yzcwph9wVUVXvdH+55f/RvJTma/gM/ZdZI2Q+66Yt+cXlOrXCMDzM6h2Z+ydFt53iqGWuv847hm2hS+GjG7LtJ3jnH4vBsWen4d+fS9Jd1Y3fOpEQ8qzZyrvjf9UJZ+dpmgblda5a5Zmc+YffFdCFJWmt3k+yf0odtmdqQW7N+2VtrN5Jc8Q3UcC15jN/JqMnzPA+SXJ943fu8Y/A+TnK7zbi3ench8NS33t3fNjV1A7fq8e/+fhxX1Z3p/39q4IZtg+eK/10vjj7XNjN159T7ST6cs9gy1+zMIey+YLpfksmakbsZ9SWZXmY3Cy4SF3yD+W6Sj/1SDtqpx7ibtpPk/oJt/D6jvi9ndd4xDD+rqv3ucbWq7iT5ffcly2lmfet9xfnywlj1+F/rfo4HCTosAyi+KM70XPG/68XR59pmyu7E/7ZbGbUOuNlauzlnf3Ov2VlM2H3xXJmqGdlIs4juG6j7GTX3YICWPMYXFmxm0XxeTL9vrR11j7uttWsZ1a6c+m16dz5e8QXbi2nV499aO+nOq1eS3MzoAvaL8ci5DJdzhTPQ99rmeOJ/242MrsHnnYfncs0+dMLui+ftyWHPM/rmaXfyW8pxs52c3k8lyRN96E5zLcmBb8sHbeYx7s6hkyw4h5Jcyqi5z1medwxQd4H6qPs2/DSfpvvWuzsnF337zrCsfPy7IHO3u/C8lNHge/7GDN+ZnSv+d704+lzbLNje3cxvBbnwmp3FhN0XSDd67q3W2oeTj4wGTZj+pugoi/sbzL03WPdH4Wa64dl7Fptn2IJj/GmStxdsYj/JJxOv1z7vGLQHmX/8J7/1vqB/3AtnqePf3bPyqfNo3J8zyZXNFZFnxFmfK/53vTj6XNvM8ygzzo0Vr9mZQ9h9sYxHdJt2K1OjE3bfXO7NuL3MpIVNULtfzJOMOuAzQHOO8c2MBlOYeQ51FxA73frjbZ3Jecdg7WbOt+Xj5l6+9X4xrXj8T7tYHdfSMWBnfa743/VCWfnaZoGTJD+bMX3pa3bmE3ZfEF0TiZl/aCf+6E+PrHstya1Z32p2fVU+nZp82h/5dzO7oz7Pn6WPcfdN+fgceqLWt/sncTOzLyJWPe8YnovTE7qL0qsZ3cNw0vQ5eSvJx7Pudckg9T3+V6f/xowvXg0kNFibPlf873oBrHFtc5r7+X6gzt2q2ul5zc4pXtp2Adi87g/vYZKdqro/feuOiUEWPq6q3fG3Ud1yb3cjD76d5JuMvoF66vYMVXWY5L2qupTRyHLfNQlqrT2oqmW/4eIZ1ecYt9aOqup+Rv16Jy8KdltrM/8ZrHLe8fyZuNXCfkZ9cL8Z/83pzrH9PH2PwksZXahenvz7dco5eTcTYbm7ILiRUT+nwyQfaN48DH2PfzfrRjd/8jy72A1ENPc85fmzyXNlbNn/Xc6t59+q1zbduXPa8b6Z0fX31YwC7nF6XLNzumqtbbsMAAAAcKY0YwYAAGBwhF0AAAAGR9gFAABgcIRdAAAABkfYBQAAYHCEXQAAAAZH2AUAAGBwhF0AWFJV7VbVrao6nHjsdfPe23b55qmqvar6tqp2N7kOADwrXtp2AQDgedAFvjtJ3mqtnUxMf6+q3k9yvLXCLeckM8rYva/d1trRsusAwPNAzS4ALOdWkpuTQTdJWmsfbqk8K2mtHbfWLrfWpsPrXo91AOCZJ+wCwHKuJLl/yrwPzrMgZ+z6tgsAAJsg7ALA8vZnTWytPUjyzVnvrOsjvF9VOxPTdrq+tDPLssK2d7p+xlfXLigAPIOqtbbtMgDAM6+qDpMcZNRn98ESy+8muZHk90kuJNmZbvLchdj3kzycmPxpa+2kW/9mt89rrbW73TpXu3X2WmvVTdtL8nGS3SRvdT8vdOu93e3n8276u621u13QvdRt/yjf98291Vp7MGudZd9bF8R3Murzu5vkUZKftdZuLvrcAOCsCLsAsKSqupdR7e6DjALivVkDO43DZ2vt8sS0wyQZB74uTH6R5O3JPrFV9d5UcHyYUV/hu1Pb/2Icdiemt4wC8t2MAuaXSd4Y9zOuqm/zdHB9mOTGKQNUPbXOovfWBeGrU+9hN6MQ/fasfQDAJmjGDABL6sLatYxqQQ+S3OtuzXMwtejHebof7wdJ3ptokvxxkrtTQXc/yeHUeid52mkDRh0nudgNLHXSWntlakCtPgNNTa+z6L091by6e4/3euwbAHpz6yEAWEFXwzlZy3kjya2qetQ1D97NaITjB1PrnVTVSUYDXR1l1Fd2uqbzfkY1s+t4uHiRflZ4bw+rKnkyzN/eVLkAYBZhFwCWUFU7M2479CDJjS7oHWYUgse38tnrwvCkT5M8mpj+RK1pt/11b2W0ydsELXxvrbXjqrqW0edxWFXHGTVhfi5u0QTAcAi7ALCc93N6reutJO9NTpjsFzthskZ4HRfWXP9Us0L9tHnvbWL+uJZ7P8nNqnpbn10AzpM+uwCwnFMD6mS/23RNfLugd9ry42bApy6zwM7iRXp7Z868he+tqg7G/ZK7vsO3W2uXkuxO3kIJADZN2AWA5eyfViPbTT9Kvgu+4z6508vtTmzjbkaDXc1bJhmNqjztyoplX2TnlOdPWPK9zRykqltvYzXSADBN2AWA5Zxk1D/3iSDX1VZ+nNFAVWM3umWna0CvTtTqvpvZAXpymWQ0ivHPZhVoxvYXhclZQfZoavvTTZin11nmvb0/Yz8XpmrAAWCj3GcXAJZQVQettdvdbYYuT80+nA5yXQg+zGh05OOMwt7tBcvM7A9bVbcyuifvo4wC7adJvs0omL6bUfPiw4xqXI+THLXWbkysvzs1/3CyLBPbz3j6vHXmvbequtqVZ3+ivDuZus0SAGyasAsAAMDgaMYMAADA4Ai7AAAADI6wCwAAwOAIuwAAAAyOsAsAAMDgCLsAAAAMjrALAADA4Ai7AAAADI6wCwAAwOAIuwAAAAyOsAsAAMDg/P+AEfq5s0pcyQAAAABJRU5ErkJggg==\n",
469 | "text/plain": [
470 | ""
471 | ]
472 | },
473 | "metadata": {
474 | "needs_background": "light"
475 | },
476 | "output_type": "display_data"
477 | }
478 | ],
479 | "source": [
480 | "bounds = [(0,1) for i in range(numSecurities)]\n",
481 | "constraints = LinearConstraint(np.ones(numSecurities), 1, 1)\n",
482 | "result = differential_evolution(losses, bounds, maxiter=10, polish=True, popsize=30, mutation=1.95,\n",
483 | " updating='deferred', recombination=0.9, constraints=constraints)\n",
484 | "\n",
485 | "for i in range(numSecurities):\n",
486 | " print(companyName[i],\": \", np.round(result.x[i],4))\n",
487 | "print(\"Percentage that portfolio beats the index:\", (numPeriods-result.fun)/numPeriods)\n",
488 | "\n",
489 | "plt.figure(figsize=(16,9))\n",
490 | "plt.bar(companyName, result.x, color = 'lightblue', edgecolor = 'black', width=0.6)\n",
491 | "plt.xlabel(r\"Securities\", fontsize=22)\n",
492 | "plt.ylabel(r\"Portfolio Percentage\", fontsize=22)\n",
493 | "plt.title(r\"Genetic Algorithm Portfolio\", fontsize=28)\n",
494 | "plt.xticks(fontsize=18, rotation=0)\n",
495 | "plt.savefig(\"barplot13.png\", dpi=300)"
496 | ]
497 | }
498 | ],
499 | "metadata": {
500 | "kernelspec": {
501 | "display_name": "Python 3",
502 | "language": "python",
503 | "name": "python3"
504 | },
505 | "language_info": {
506 | "codemirror_mode": {
507 | "name": "ipython",
508 | "version": 3
509 | },
510 | "file_extension": ".py",
511 | "mimetype": "text/x-python",
512 | "name": "python",
513 | "nbconvert_exporter": "python",
514 | "pygments_lexer": "ipython3",
515 | "version": "3.7.4"
516 | }
517 | },
518 | "nbformat": 4,
519 | "nbformat_minor": 2
520 | }
521 |
--------------------------------------------------------------------------------
/Goal Programming Model.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Goal Programming methodology \n",
8 | "\n"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": 1,
14 | "metadata": {},
15 | "outputs": [
16 | {
17 | "name": "stdout",
18 | "output_type": "stream",
19 | "text": [
20 | "Using Python-MIP package version 1.6.2\n"
21 | ]
22 | }
23 | ],
24 | "source": [
25 | "from mip import *\n",
26 | "import pandas as pd\n",
27 | "import numpy as np\n",
28 | "import matplotlib.pyplot as plt\n",
29 | "from pandas_datareader import data\n",
30 | "from matplotlib import rc\n",
31 | "rc('font', **{'family': 'serif', 'serif': ['Computer Modern']})\n",
32 | "rc('text', usetex=True)"
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "metadata": {},
38 | "source": [
39 | "## Input\n",
40 | "\n",
41 | "The input of this method includes the following variables:\n",
42 | "\n",
43 | "* numSecurities (integer) is the number of securities,
\n",
44 | "* companyName (str list) contains the names of the securities,
\n",
45 | "* betaIndex (float list) contains the beta index for each security,
\n",
46 | "* DYIndex (float list) contains the dividend yield for each security."
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": 2,
52 | "metadata": {},
53 | "outputs": [],
54 | "source": [
55 | "numSecurities = 6\n",
56 | "companyName = ['ACN', 'NOC', 'IBM', 'MSI', 'MSCI', 'ORA']\n",
57 | "betaIndex = [0.8, 1.36, 0.59, 1.12, 1.05, 1.15]\n",
58 | "Rev = [32.89, 77.86, 7.63, 1.48, 43.22, 39.53]\n",
59 | "DYIndex = [1.44, 4.59, 1.33, 1.22, 1.74, 1.76]"
60 | ]
61 | },
62 | {
63 | "cell_type": "markdown",
64 | "metadata": {},
65 | "source": [
66 | "## Model Parameters\n",
67 | "\n",
68 | "The parameters of the model are defined below:\n",
69 | "\n",
70 | "* numPortfolios : Number of portfolios to be constructed.
\n",
71 | "* minSecurities : Minimum number of securities to participate in each portfolio.
\n",
72 | "* maxSecurities : Maximum number of securities to participate in each portfolio.
\n",
73 | "* lowerBound : Minimum value of the weight of each security.
\n",
74 | "* upperBound : Maximum value of the weight of each security.
\n",
75 | "* capitalThreshold : Threshold that determines the Billions needed to consider a security as a high capitalisation investment\n",
76 | "\n",
77 | "The target values of the goal programming model are set as follows:\n",
78 | "\n",
79 | "* betaGoal : The target value for portfolio beta\n",
80 | "* DYGoal : The target value for portfolio Dividend Yield\n",
81 | "* highCapGoal : The target value for the percentage of high capitalisation securities participating in the portfolio"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": 3,
87 | "metadata": {},
88 | "outputs": [],
89 | "source": [
90 | "minSecurities = 5\n",
91 | "maxSecurities = 6\n",
92 | "lowerBound = 0.1\n",
93 | "upperBound = 0.5\n",
94 | "betaGoal = 0.8\n",
95 | "DYGoal = 1.7\n",
96 | "highCapGoal = 0.5\n",
97 | "capitalThreshold = 40"
98 | ]
99 | },
100 | {
101 | "cell_type": "markdown",
102 | "metadata": {},
103 | "source": [
104 | "The model is constructed below:"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": 4,
110 | "metadata": {},
111 | "outputs": [
112 | {
113 | "name": "stdout",
114 | "output_type": "stream",
115 | "text": [
116 | "================= Model output ================= \n",
117 | "Solution status : OptimizationStatus.OPTIMAL \n",
118 | "\n",
119 | "Objective function = 0.2268382352941175 \n",
120 | "\n",
121 | "ACN : 0.09999999999999999\n",
122 | "NOC : 0.1\n",
123 | "IBM : 0.29999999999999993\n",
124 | "MSI : 0.10000000000000002\n",
125 | "MSCI : 0.4000000000000001\n",
126 | "ORA : 0.0\n",
127 | "\n",
128 | "Portfolio beta = 0.925\n",
129 | "Portfolio dividend yield = 1.82\n",
130 | "High capitalisation percentage 0.5000000000000001\n"
131 | ]
132 | },
133 | {
134 | "data": {
135 | "image/png": "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\n",
136 | "text/plain": [
137 | ""
138 | ]
139 | },
140 | "metadata": {
141 | "needs_background": "light"
142 | },
143 | "output_type": "display_data"
144 | }
145 | ],
146 | "source": [
147 | "m = Model()\n",
148 | "\n",
149 | "high = [0 for i in range(numSecurities)]\n",
150 | "for i in range(numSecurities):\n",
151 | " if Rev[i] > capitalThreshold:\n",
152 | " high[i] =1\n",
153 | "\n",
154 | "onoff = [ m.add_var(var_type=BINARY) for i in range(numSecurities) ]\n",
155 | "weights = [ m.add_var(var_type=CONTINUOUS) for i in range(numSecurities) ]\n",
156 | "\n",
157 | "d1P = m.add_var(var_type=CONTINUOUS)\n",
158 | "d1M = m.add_var(var_type=CONTINUOUS)\n",
159 | "d2P = m.add_var(var_type=CONTINUOUS)\n",
160 | "d2M = m.add_var(var_type=CONTINUOUS)\n",
161 | "d3P = m.add_var(var_type=CONTINUOUS)\n",
162 | "d3M = m.add_var(var_type=CONTINUOUS)\n",
163 | "\n",
164 | "w1P = 1\n",
165 | "w1M = 1\n",
166 | "w2P = 1\n",
167 | "w2M = 1\n",
168 | "w3P = 1\n",
169 | "w3M = 1\n",
170 | "\n",
171 | "m += xsum(weights[i] for i in range(numSecurities)) == 1\n",
172 | "m += xsum(onoff[i] for i in range(numSecurities)) <= maxSecurities\n",
173 | "m += xsum(onoff[i] for i in range(numSecurities)) >= minSecurities\n",
174 | "for i in range(numSecurities):\n",
175 | " m += weights[i] - lowerBound * onoff[i] >= 0\n",
176 | " m += weights[i] - upperBound * onoff[i] <= 0 \n",
177 | "\n",
178 | "m += xsum(weights[i] * betaIndex[i] for i in range(numSecurities)) + d1M - d1P == betaGoal\n",
179 | "m += xsum(weights[i] * DYIndex[i] for i in range(numSecurities)) + d2M - d2P == DYGoal\n",
180 | "m += xsum(weights[i] * high[i] for i in range(numSecurities)) + d3M - d3P == highCapGoal\n",
181 | "\n",
182 | "m.objective = minimize((w1P * d1P + w1M * d1M) / betaGoal + (w2P * d2P + w2M * d2M) / DYGoal + (w3P * d3P + w3M * d3M) / highCapGoal) \n",
183 | "\n",
184 | "status = m.optimize()\n",
185 | "\n",
186 | "print(\"================= Model output ================= \")\n",
187 | "\n",
188 | "print(\"Solution status : \", status, \"\\n\")\n",
189 | "obj = m.objective_value\n",
190 | "print(\"Objective function = \", obj, \"\\n\")\n",
191 | "\n",
192 | "\n",
193 | "for i in range(numSecurities):\n",
194 | " print(companyName[i],\": \", weights[i].x * onoff[i].x)\n",
195 | "print()\n",
196 | "\n",
197 | "\n",
198 | "print(\"Portfolio beta = \", sum(weights[i].x * betaIndex[i] for i in range(numSecurities)))\n",
199 | "print(\"Portfolio dividend yield = \", sum(weights[i].x * DYIndex[i] for i in range(numSecurities)))\n",
200 | "print(\"High capitalisation percentage\", sum(weights[i].x * high[i] for i in range(numSecurities)))\n",
201 | "\n",
202 | "portfolio = [0 for i in range(numSecurities)]\n",
203 | "for i in range(numSecurities):\n",
204 | " portfolio[i] = onoff[i].x * weights[i].x\n",
205 | "\n",
206 | "plt.figure(figsize=(16,9))\n",
207 | "plt.bar(companyName, portfolio, color = 'lightblue', edgecolor = 'black', width=0.6)\n",
208 | "plt.xlabel(r\"Securities\", fontsize=22)\n",
209 | "plt.ylabel(r\"Portfolio Percentage\", fontsize=22)\n",
210 | "plt.title(r\"Goal Programming Portfolio\", fontsize=28)\n",
211 | "plt.xticks(fontsize=18, rotation=0)\n",
212 | "plt.savefig(\"barplot12.png\", dpi=300)"
213 | ]
214 | }
215 | ],
216 | "metadata": {
217 | "kernelspec": {
218 | "display_name": "Python 3",
219 | "language": "python",
220 | "name": "python3"
221 | },
222 | "language_info": {
223 | "codemirror_mode": {
224 | "name": "ipython",
225 | "version": 3
226 | },
227 | "file_extension": ".py",
228 | "mimetype": "text/x-python",
229 | "name": "python",
230 | "nbconvert_exporter": "python",
231 | "pygments_lexer": "ipython3",
232 | "version": "3.7.4"
233 | }
234 | },
235 | "nbformat": 4,
236 | "nbformat_minor": 2
237 | }
238 |
--------------------------------------------------------------------------------
/MCDA Scripts/electreIII.py:
--------------------------------------------------------------------------------
1 | #Filename: electreIII.py
2 | #Description: Implementation of MCDA method ELECTRE III
3 | #Author: Elissaios Sarmas. November 3, 2019.
4 | import csv
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 | import math
8 |
9 | inputname = "elec3.csv"
10 |
11 | file = open(inputname, "rt")
12 | list1 = list(csv.reader(file))
13 |
14 | #Section 1: Read Input Data such as Alternatives, Criteria, Weights and the Decision Matrix
15 |
16 | criteria = int(list1[0][1])
17 | alternatives = int(list1[1][1])
18 | weights = [0 for y in range(criteria)]
19 | optimizationType = [0 for y in range(criteria)]
20 | preferenceThreshold = [0 for y in range(criteria)]
21 | indifferenceThreshold = [0 for y in range(criteria)]
22 | vetoThreshold = [0 for y in range(criteria)]
23 | for i in range(criteria):
24 | optimizationType[i] = int(list1[2][i+1])
25 | weights[i] = float(list1[3][i+1])
26 | vetoThreshold[i] = float(list1[4][i+1])
27 | preferenceThreshold[i] = float(list1[5][i+1])
28 | indifferenceThreshold[i] = float(list1[6][i+1])
29 | decisionMatrix = [[0 for y in range(criteria)] for x in range(alternatives)]
30 | companyName = ["" for i in range(alternatives)]
31 | for i in range(alternatives):
32 | companyName[i] = list1[i+16][0]
33 | for j in range(criteria):
34 | decisionMatrix[i][j] = float(list1[i+16][j+1])
35 |
36 | #Section 2: Calculation of the Agreement Table for Electre ΙΙΙ Method
37 |
38 | sumOfWeights = sum(weights)
39 | agreementTable = [[0 for i in range(alternatives)] for y in range(alternatives)]
40 | for k in range(criteria):
41 | if optimizationType[k] == 0:
42 | for i in range(alternatives):
43 | for j in range(alternatives):
44 | if i!=j:
45 | if decisionMatrix[j][k] - decisionMatrix[i][k] <= indifferenceThreshold[k]:
46 | agreementTable[i][j] = round(agreementTable[i][j] + 1.0 * weights[k],2)
47 | elif decisionMatrix[j][k] - decisionMatrix[i][k] <= preferenceThreshold[k]:
48 | agreementTable[i][j] = round(agreementTable[i][j] + ((decisionMatrix[i][k] - decisionMatrix [j][k] + preferenceThreshold[k])*1.0 / (preferenceThreshold[k]-indifferenceThreshold[k])) * weights[k],2)
49 | else:
50 | agreementTable[i][j] = round(agreementTable[i][j] + 0.0 * weights[k],2)
51 | elif optimizationType[k] == 1:
52 | for i in range(alternatives):
53 | for j in range(alternatives):
54 | if i!=j:
55 | if decisionMatrix[i][k] - decisionMatrix[j][k] <= indifferenceThreshold[k]:
56 | agreementTable[i][j] = round(agreementTable[i][j] + 1.0 * weights[k],2)
57 | elif decisionMatrix[i][k] - decisionMatrix[j][k] <= preferenceThreshold[k]:
58 | agreementTable[i][j] = round(agreementTable[i][j] + ((decisionMatrix[j][k] - decisionMatrix [i][k] + preferenceThreshold[k])*1.0 / (preferenceThreshold[k]-indifferenceThreshold[k])) * weights[k],2)
59 | else:
60 | agreementTable[i][j] = round(agreementTable[i][j] + 0.0 * weights[k],2)
61 |
62 | #Section 3: Calculation of the Disagreement Tables for Electre III Method
63 |
64 | sumOfWeights = sum(weights)
65 | disagreementTable = [[[0 for k in range(criteria)] for i in range(alternatives)] for j in range(alternatives)]
66 | for k in range(criteria):
67 | if optimizationType[k] == 0:
68 | for i in range(alternatives):
69 | for j in range(alternatives):
70 | if i!=j:
71 | if decisionMatrix[j][k] - decisionMatrix[i][k] <= preferenceThreshold[k]:
72 | disagreementTable[i][j][k] = 0
73 | elif decisionMatrix[j][k] - decisionMatrix[i][k] <= vetoThreshold[k]:
74 | disagreementTable[i][j][k] = round(((decisionMatrix[j][k] - decisionMatrix [i][k] - preferenceThreshold[k])*1.0 / (vetoThreshold[k]-preferenceThreshold[k])),2)
75 | else:
76 | disagreementTable[i][j][k] = 1
77 | elif optimizationType[k] == 1:
78 | for i in range(alternatives):
79 | for j in range(alternatives):
80 | if i!=j:
81 | if decisionMatrix[i][k] - decisionMatrix[j][k] <= indifferenceThreshold[k]:
82 | disagreementTable[i][j][k] = 0
83 | elif decisionMatrix[i][k] - decisionMatrix[j][k] <= vetoThreshold[k]:
84 | disagreementTable[i][j][k] = round(((decisionMatrix[j][k] - decisionMatrix [i][k] + preferenceThreshold[k])*1.0 / (vetoThreshold[k]-preferenceThreshold[k])),2)
85 | else:
86 | disagreementTable[i][j][k] = 1
87 |
88 |
89 | #Section 4: Calculation of reliability indexes
90 |
91 | reliabilityTable = [[0 for i in range(alternatives)] for y in range(alternatives)]
92 | for i in range(alternatives):
93 | for j in range(alternatives):
94 | if i!=j:
95 | reliabilityTable[i][j] = agreementTable[i][j]
96 | for k in range(criteria):
97 | if agreementTable[i][j] < disagreementTable[i][j][k]:
98 | reliabilityTable[i][j] = reliabilityTable[i][j] * ((1 - disagreementTable[i][j][k]) / (1 - agreementTable[i][j]))
99 | else:
100 | reliabilityTable[i][j] = 1
101 |
102 | #Section 5: Calculation of Dominance Table
103 |
104 | d = 0.8
105 | dominanceTable = [[0 for i in range(alternatives)] for y in range(alternatives)]
106 | for i in range(alternatives):
107 | for j in range(alternatives):
108 | if i!=j and reliabilityTable[i][j] >= d:
109 | dominanceTable[i][j] = 1
110 |
111 | #Section 6: Concordance, Disconcordance and Net Credibility Degrees
112 |
113 | phiPlus = [round(sum(x),3) for x in reliabilityTable ]
114 | phiMinus = [round(sum(x),3) for x in zip(*reliabilityTable)]
115 | phi = [round((x1 - x2),3) for (x1, x2) in zip(phiPlus, phiMinus)]
116 |
117 | print(phi)
118 |
119 | plt.bar(companyName, phi, color = 'b', edgecolor = 'black')
120 | plt.xlabel('Alternatives')
121 | plt.ylabel('Net Credibility Index')
122 | plt.title('Electre III Method', fontsize=16)
123 | ax = plt.gca()
124 | ax.set_facecolor('red')
125 | plt.grid()
126 |
127 | result = [[0 for x in range(2)] for y in range(alternatives)]
128 | for i in range(alternatives):
129 | result[i][0] = companyName[i]
130 | result[i][1] = phi[i]
131 |
132 | result = sorted(result, key=lambda tup: tup[1], reverse=True)
133 |
134 | print(result)
135 | plt.show()
--------------------------------------------------------------------------------
/MCDA Scripts/maut.py:
--------------------------------------------------------------------------------
1 | #Filename: maut.py
2 | #Description: Implementation of MCDA method MAUT
3 | #Author: Elissaios Sarmas. November 3, 2019.
4 | import csv
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 | import math
8 |
9 | inputname = "topsis.csv"
10 |
11 | file = open(inputname, "rt")
12 | list1 = list(csv.reader(file))
13 |
14 | criteria = int(list1[0][1])
15 | alternatives = int(list1[1][1])
16 | weights = [0 for y in range(criteria)]
17 | optimizationType = [0 for y in range(criteria)]
18 | for i in range(criteria):
19 | optimizationType[i] = int(list1[2][i+1])
20 | weights[i] = float(list1[3][i+1])
21 | decisionMatrix = [[0 for y in range(criteria)] for x in range(alternatives)]
22 | companyName = ["" for i in range(alternatives)]
23 | for i in range(alternatives):
24 | companyName[i] = list1[i+16][0]
25 | for j in range(criteria):
26 | decisionMatrix[i][j] = float(list1[i+16][j+1])
27 |
28 | maxValue = np.max(decisionMatrix, axis = 0)
29 | minValue = np.min(decisionMatrix, axis = 0)
30 |
31 | normalisedMatrix = [[0 for y in range(criteria)] for x in range(alternatives)]
32 | for i in range(alternatives):
33 | for j in range(criteria):
34 | if optimizationType[j] == 0:
35 | normalisedMatrix[i][j] = (decisionMatrix[i][j] - minValue[j])*1.0 / (maxValue[j] - minValue[j])
36 | elif optimizationType[j] == 1:
37 | normalisedMatrix[i][j] = (maxValue[j] - decisionMatrix[i][j])*1.0 / (maxValue[j] - minValue[j])
38 |
39 | utilityScore = [0 for x in range(alternatives)]
40 | utilityScorePer = [0 for x in range(alternatives)]
41 |
42 | for i in range(alternatives):
43 | tempSum = 0
44 | for j in range(criteria):
45 | tempSum += normalisedMatrix[i][j] * weights[j]
46 | utilityScore[i] = tempSum
47 | utilityScorePer[i] = round(round(tempSum,4) * 100,2)
48 |
49 | print(utilityScore)
50 |
51 | plt.bar(companyName, utilityScore, color = 'b', edgecolor = 'black')
52 | plt.xlabel('Alternatives')
53 | plt.ylabel('Utility Score')
54 | plt.title('MAUT Method', fontsize=16)
55 | ax = plt.gca()
56 | ax.set_facecolor('red')
57 | plt.grid()
58 |
59 | result = [[0 for x in range(2)] for y in range(alternatives)]
60 | for i in range(alternatives):
61 | result[i][0] = companyName[i]
62 | result[i][1] = utilityScore[i]
63 |
64 | result = sorted(result, key=lambda tup: tup[1], reverse=True)
65 |
66 | print(result)
67 | plt.show()
--------------------------------------------------------------------------------
/MCDA Scripts/prometheeII.py:
--------------------------------------------------------------------------------
1 | #Filename: prometheeII.py
2 | #Description: Implementation of MCDA method PROMETHEE II
3 | #Author: Elissaios Sarmas. November 3, 2019.
4 |
5 | import csv
6 | import numpy as np
7 | import matplotlib.pyplot as plt
8 | import math
9 |
10 | inputname = "promethee.csv"
11 |
12 | def usualCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
13 | if optimizationType[k] == 0:
14 | for i in range(alternatives):
15 | for j in range(alternatives):
16 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
17 | if decisionMatrix[i][k] > decisionMatrix[j][k]:
18 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
19 | elif optimizationType[k] == 1:
20 | for i in range(alternatives):
21 | for j in range(alternatives):
22 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
23 | if decisionMatrix[j][k] > decisionMatrix[i][k]:
24 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
25 |
26 | def quasiCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
27 | if optimizationType[k] == 0:
28 | for i in range(alternatives):
29 | for j in range(alternatives):
30 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
31 | if decisionMatrix[i][k] - decisionMatrix[j][k] > indifferenceThreshold[k]:
32 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
33 | elif optimizationType[k] == 1:
34 | for i in range(alternatives):
35 | for j in range(alternatives):
36 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
37 | if decisionMatrix[j][k] - decisionMatrix[i][k] > indifferenceThreshold[k]:
38 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
39 |
40 | def linearPreferenceCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
41 | if optimizationType[k] == 0:
42 | for i in range(alternatives):
43 | for j in range(alternatives):
44 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
45 | if decisionMatrix[i][k] - decisionMatrix[j][k] > preferenceThreshold[k]:
46 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
47 | else:
48 | evaluationTable[i][j] = evaluationTable[i][j] + ((decisionMatrix[i][k] - decisionMatrix [j][k])*1.0 / preferenceThreshold[k]) * weights[k]
49 | elif optimizationType[k] == 1:
50 | for i in range(alternatives):
51 | for j in range(alternatives):
52 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
53 | if decisionMatrix[j][k] - decisionMatrix[i][k] > preferenceThreshold[k]:
54 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
55 | else:
56 | evaluationTable[i][j] = evaluationTable[i][j] + ((decisionMatrix[j][k] - decisionMatrix [i][k])*1.0 / preferenceThreshold[k]) * weights[k]
57 |
58 |
59 |
60 | def levelCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
61 | if optimizationType[k] == 0:
62 | for i in range(alternatives):
63 | for j in range(alternatives):
64 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
65 | if decisionMatrix[i][k] - decisionMatrix[j][k] > preferenceThreshold[k]:
66 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
67 | elif decisionMatrix[i][k] - decisionMatrix[j][k] <= indifferenceThreshold[k]:
68 | evaluationTable[i][j] = evaluationTable[i][j] + 0.0 * weights[k]
69 | else:
70 | evaluationTable[i][j] = evaluationTable[i][j] + 0.5 * weights[k]
71 | elif optimizationType[k] == 1:
72 | for i in range(alternatives):
73 | for j in range(alternatives):
74 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
75 | if decisionMatrix[j][k] - decisionMatrix[i][k] > preferenceThreshold[k]:
76 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
77 | elif decisionMatrix[j][k] - decisionMatrix[i][k] <= indifferenceThreshold[k]:
78 | evaluationTable[i][j] = evaluationTable[i][j] + 0.0 * weights[k]
79 | else:
80 | evaluationTable[i][j] = evaluationTable[i][j] + 0.5 * weights[k]
81 |
82 |
83 | def linearPreferenceAndIndifferenceCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType):
84 | if optimizationType[k] == 0:
85 | for i in range(alternatives):
86 | for j in range(alternatives):
87 | if i!=j and decisionMatrix[i][k] >= decisionMatrix[j][k]:
88 | if decisionMatrix[i][k] - decisionMatrix[j][k] > preferenceThreshold[k]:
89 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
90 | elif decisionMatrix[i][k] - decisionMatrix[j][k] > indifferenceThreshold[k]:
91 | evaluationTable[i][j] = evaluationTable[i][j] + ((decisionMatrix[i][k] - decisionMatrix [j][k] - indifferenceThreshold[k])*1.0 / (preferenceThreshold[k]-indifferenceThreshold[k])) * weights[k]
92 | else:
93 | evaluationTable[i][j] = evaluationTable[i][j] + 0.0 * weights[k]
94 | elif optimizationType[k] == 1:
95 | for i in range(alternatives):
96 | for j in range(alternatives):
97 | if i!=j and decisionMatrix[j][k] >= decisionMatrix[i][k]:
98 | if decisionMatrix[j][k] - decisionMatrix[i][k] > preferenceThreshold[k]:
99 | evaluationTable[i][j] = evaluationTable[i][j] + 1.0 * weights[k]
100 | elif decisionMatrix[j][k] - decisionMatrix[i][k] > indifferenceThreshold[k]:
101 | evaluationTable[i][j] = evaluationTable[i][j] + ((decisionMatrix[j][k] - decisionMatrix [i][k] - indifferenceThreshold[k])*1.0 / (preferenceThreshold[k]-indifferenceThreshold[k])) * weights[k]
102 | else:
103 | evaluationTable[i][j] = evaluationTable[i][j] + 0.0 * weights[k]
104 |
105 | file = open(inputname, "rt")
106 | list1 = list(csv.reader(file))
107 |
108 | #Read Input
109 |
110 | criteria = int(list1[0][1])
111 | alternatives = int(list1[1][1])
112 | weights = [0 for y in range(criteria)]
113 | optimizationType = [0 for y in range(criteria)]
114 | criterion = [0 for y in range(criteria)]
115 | preferenceThreshold = [0 for y in range(criteria)]
116 | indifferenceThreshold = [0 for y in range(criteria)]
117 | for i in range(criteria):
118 | optimizationType[i] = int(list1[2][i+1])
119 | weights[i] = float(list1[3][i+1])
120 | criterion[i] = int(list1[7][i+1])
121 | preferenceThreshold[i] = float(list1[5][i+1])
122 | indifferenceThreshold[i] = float(list1[6][i+1])
123 | decisionMatrix = [[0 for y in range(criteria)] for x in range(alternatives)]
124 | companyName = ["" for i in range(alternatives)]
125 | for i in range(alternatives):
126 | companyName[i] = list1[i+16][0]
127 | for j in range(criteria):
128 | decisionMatrix[i][j] = float(list1[i+16][j+1])
129 |
130 | evaluationTable = [[0.0 for i in range(alternatives)] for y in range(alternatives)]
131 |
132 | for k in range(criteria):
133 | if criterion[k] == 1:
134 | usualCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
135 | elif criterion[k] == 2:
136 | quasiCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
137 | elif criterion[k] == 3:
138 | linearPreferenceCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
139 | elif criterion[k] == 4:
140 | levelCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
141 | elif criterion[k] == 5:
142 | linearPreferenceAndIndifferenceCriterion(evaluationTable, k, alternatives, decisionMatrix, indifferenceThreshold, preferenceThreshold, weights, optimizationType)
143 |
144 |
145 | for i in range(alternatives):
146 | for j in range(alternatives):
147 | evaluationTable[i][j] = round(evaluationTable[i][j],2)
148 |
149 | sumOfLines = np.sum(evaluationTable, axis=1)
150 | sumOfColumns = np.sum(evaluationTable, axis=0)
151 |
152 | phiPlus = sumOfLines*1.0 / (alternatives - 1)
153 | phiMinus = sumOfColumns*1.0 / (alternatives - 1)
154 | phi = phiPlus - phiMinus
155 |
156 | print(phi)
157 |
158 |
159 |
160 | i = np.arange(alternatives)
161 | plt.bar(i+1, phi, color = 'b', edgecolor = 'black')
162 | plt.xlabel('Alternatives')
163 | plt.ylabel('Net Flow')
164 | plt.title('PROMETHEE Method', fontsize=16)
165 | ax = plt.gca()
166 | ax.set_facecolor('red')
167 | plt.grid()
168 |
169 | result = [[0 for x in range(2)] for y in range(alternatives)]
170 | for i in range(alternatives):
171 | result[i][0] = companyName[i]
172 | result[i][1] = round(phi[i],2)
173 |
174 | result = sorted(result, key=lambda tup: tup[1], reverse=True)
175 | print(result)
176 | plt.show()
177 |
--------------------------------------------------------------------------------
/MCDA Scripts/topsis.py:
--------------------------------------------------------------------------------
1 | #Filename: topsis.py
2 | #Description: Implementation of MCDA method TOPSIS
3 | #Author: Elissaios Sarmas. November 3, 2019.
4 | import csv
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 | import math
8 |
9 | inputname = "topsis2.csv"
10 |
11 | file = open(inputname, "rt")
12 | list1 = list(csv.reader(file))
13 |
14 | #Section 1: Read Input Data such as Alternatives, Criteria, Weights, Veto Thresholds and the Decision Matrix
15 |
16 | criteria = int(list1[0][1])
17 | alternatives = int(list1[1][1])
18 | weights = [0 for y in range(criteria)]
19 | optimizationType = [0 for y in range(criteria)]
20 | for i in range(criteria):
21 | optimizationType[i] = int(list1[2][i+1])
22 | weights[i] = float(list1[3][i+1])
23 | decisionMatrix = [[0 for y in range(criteria)] for x in range(alternatives)]
24 | companyName = ["" for i in range(alternatives)]
25 | for i in range(alternatives):
26 | companyName[i] = list1[i+16][0]
27 | for j in range(criteria):
28 | decisionMatrix[i][j] = float(list1[i+16][j+1])
29 |
30 | #Section 2: Calculation of the Normalised Decision Matrix
31 |
32 | normalisedDecisionMatrix = [[0 for i in range(criteria)] for y in range(alternatives)]
33 | for j in range(criteria):
34 | sumOfPows = 0
35 | for i in range(alternatives):
36 | sumOfPows = sumOfPows + math.pow(decisionMatrix[i][j],2)
37 | sqSumOfPows = math.sqrt(sumOfPows)
38 | for i in range(alternatives):
39 | normalisedDecisionMatrix[i][j] = decisionMatrix[i][j]*1.0 / sqSumOfPows
40 |
41 | #Section 3: Calculation of Weighted Decision Matrix
42 |
43 | weightedDecisionMatrix = [[0 for i in range(criteria)] for y in range(alternatives)]
44 | for j in range(criteria):
45 | for i in range(alternatives):
46 | weightedDecisionMatrix[i][j] = normalisedDecisionMatrix[i][j] * weights[j]
47 |
48 | #Section 4: Calculation of Ideal and Non-Ideal Solutions
49 |
50 | idealSolution = [0 for i in range(criteria)]
51 | nonIdealSolution = [0 for i in range(criteria)]
52 | for j in range(criteria):
53 | maxValue = -100000000000
54 | minValue = 100000000000
55 | for i in range(alternatives):
56 | if weightedDecisionMatrix[i][j] < minValue:
57 | minValue = weightedDecisionMatrix[i][j]
58 | if weightedDecisionMatrix[i][j] > maxValue:
59 | maxValue = weightedDecisionMatrix[i][j]
60 | if optimizationType[j] == 0:
61 | idealSolution[j] = maxValue
62 | nonIdealSolution[j] = minValue
63 | elif optimizationType[j] == 1:
64 | idealSolution[j] = minValue
65 | nonIdealSolution[j] = maxValue
66 |
67 | #Section 5: Calculation of Separation Distance of each alternative
68 |
69 | sPlus = [0 for i in range(alternatives)]
70 | sMinus = [0 for i in range(alternatives)]
71 | for i in range(alternatives):
72 | sumPlusTemp = 0
73 | sumMinusTemp = 0
74 | for j in range(criteria):
75 | sumPlusTemp = sumPlusTemp + math.pow(idealSolution[j]-weightedDecisionMatrix[i][j],2)
76 | sumMinusTemp = sumMinusTemp + math.pow(nonIdealSolution[j]-weightedDecisionMatrix[i][j],2)
77 | sPlus[i] = math.sqrt(sumPlusTemp)
78 | sMinus[i] = math.sqrt(sumMinusTemp)
79 |
80 | #Section 6: Relative Closeness of each alternative to the ideal solution
81 |
82 | C = [0 for i in range(alternatives)]
83 | C2 = [0 for i in range(alternatives)]
84 | for i in range(alternatives):
85 | C2[i] = round(round(sMinus[i]*1.0 / (sMinus[i] + sPlus[i]),4) * 100,2) #percentage
86 | C[i] = sMinus[i]*1.0 / (sMinus[i] + sPlus[i])
87 |
88 | print(C)
89 |
90 | plt.bar(companyName, C, color = 'b', edgecolor = 'black')
91 | plt.xlabel('Alternatives')
92 | plt.ylabel('Relative Closeness')
93 | plt.title('TOPSIS Method', fontsize=16)
94 | ax = plt.gca()
95 | ax.set_facecolor('red')
96 | plt.grid()
97 |
98 | result = [[0 for x in range(2)] for y in range(alternatives)]
99 | for i in range(alternatives):
100 | result[i][0] = companyName[i]
101 | result[i][1] = C2[i]
102 |
103 | result = sorted(result, key=lambda tup: tup[1], reverse=True)
104 |
105 | print(result)
106 | plt.show()
--------------------------------------------------------------------------------
/Multiobjective PROMETHEE Model.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "## MOIP PROMETHEE flow 3-Dimensional Model\n",
8 | "\n",
9 | "In this paragraph there is a presentation of the 3-Dimensional model involving the PROMETHEE flow. The 3 objective function will be the portfolio beta index, the portfolio dividend yield and the PROMETHEE flow. The input data for this method are the beta indexes, the PROMETHEE net flows and the dividend yield of each security:"
10 | ]
11 | },
12 | {
13 | "cell_type": "code",
14 | "execution_count": 1,
15 | "metadata": {},
16 | "outputs": [
17 | {
18 | "name": "stdout",
19 | "output_type": "stream",
20 | "text": [
21 | "Using Python-MIP package version 1.6.2\n"
22 | ]
23 | }
24 | ],
25 | "source": [
26 | "from mip import *\n",
27 | "import pandas as pd\n",
28 | "import numpy as np\n",
29 | "import matplotlib.pyplot as plt\n",
30 | "from matplotlib import rc\n",
31 | "rc('font', **{'family': 'serif', 'serif': ['Computer Modern']})\n",
32 | "rc('text', usetex=True)\n",
33 | "\n",
34 | "\n",
35 | "numSecurities = 6\n",
36 | "companyName = ['ACN', 'NOC', 'IBM', 'MSI', 'MSCI', 'ORA']\n",
37 | "betaIndex = [0.8, 1.36, 0.59, 1.12, 1.05, 1.15]\n",
38 | "Rev = [32.89, 77.86, 7.63, 1.48, 43.22, 39.53]\n",
39 | "DYIndex = [1.44, 4.59, 1.33, 1.22, 1.74, 1.76]\n",
40 | "promIndex = [0.3078, -0.0306, 0.0558, -0.0849, -0.1289, -0.1191]"
41 | ]
42 | },
43 | {
44 | "cell_type": "markdown",
45 | "metadata": {},
46 | "source": [
47 | "### Model Parameters\n",
48 | "\n",
49 | "The model contains the following parameters:\n",
50 | "\n",
51 | "* minSecurities : Minimum number of securities to participate in each portfolio.
\n",
52 | "* maxSecurities : Maximum number of securities to participate in each portfolio.
\n",
53 | "* lowerBound : Minimum value of the weight of each security.
\n",
54 | "* upperBound : Maximum value of the weight of each security.
"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": 2,
60 | "metadata": {},
61 | "outputs": [],
62 | "source": [
63 | "minSecurities = 6\n",
64 | "maxSecurities = 10\n",
65 | "lowerBound = 0.05\n",
66 | "upperBound = 0.3"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "metadata": {},
72 | "source": [
73 | "### Determination of the objectives target values\n",
74 | "\n",
75 | "We solve the 1-objective optimisation problem for each one of the objective functions, in order to find their target values. Firstly, we solve the problem of minimising the portfolio beta."
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": 3,
81 | "metadata": {},
82 | "outputs": [
83 | {
84 | "name": "stdout",
85 | "output_type": "stream",
86 | "text": [
87 | "OptimizationStatus.OPTIMAL \n",
88 | "\n",
89 | "Minimum Beta = 0.861 \n",
90 | "\n"
91 | ]
92 | }
93 | ],
94 | "source": [
95 | "m = Model()\n",
96 | "\n",
97 | "onoff = [ m.add_var(var_type=BINARY) for i in range(numSecurities) ]\n",
98 | "weights = [ m.add_var(var_type=CONTINUOUS) for i in range(numSecurities) ]\n",
99 | "\n",
100 | "m += xsum(weights[i] for i in range(numSecurities)) == 1\n",
101 | "m += xsum(onoff[i] for i in range(numSecurities)) <= maxSecurities\n",
102 | "m += xsum(onoff[i] for i in range(numSecurities)) >= minSecurities\n",
103 | "for i in range(numSecurities):\n",
104 | " m += weights[i] - lowerBound * onoff[i] >= 0\n",
105 | " m += weights[i] - upperBound * onoff[i] <= 0 \n",
106 | "\n",
107 | "m.objective = minimize(xsum(weights[i] * betaIndex[i] for i in range(numSecurities))) \n",
108 | "\n",
109 | "status = m.optimize()\n",
110 | "\n",
111 | "print(status, \"\\n\")\n",
112 | "minBeta = m.objective_value\n",
113 | "print(\"Minimum Beta = \", minBeta, \"\\n\")"
114 | ]
115 | },
116 | {
117 | "cell_type": "markdown",
118 | "metadata": {},
119 | "source": [
120 | "Secondly, we solve the maximisation problem of the PROMETHEE flow function."
121 | ]
122 | },
123 | {
124 | "cell_type": "code",
125 | "execution_count": 4,
126 | "metadata": {},
127 | "outputs": [
128 | {
129 | "name": "stdout",
130 | "output_type": "stream",
131 | "text": [
132 | "OptimizationStatus.OPTIMAL \n",
133 | "\n",
134 | "Maximum PROMETHEE flow = 0.084785 \n",
135 | "\n"
136 | ]
137 | }
138 | ],
139 | "source": [
140 | "m = Model()\n",
141 | "\n",
142 | "onoff = [ m.add_var(var_type=BINARY) for i in range(numSecurities) ]\n",
143 | "weights = [ m.add_var(var_type=CONTINUOUS) for i in range(numSecurities) ]\n",
144 | "\n",
145 | "m += xsum(weights[i] for i in range(numSecurities)) == 1\n",
146 | "m += xsum(onoff[i] for i in range(numSecurities)) <= maxSecurities\n",
147 | "m += xsum(onoff[i] for i in range(numSecurities)) >= minSecurities\n",
148 | "for i in range(numSecurities):\n",
149 | " m += weights[i] - lowerBound * onoff[i] >= 0\n",
150 | " m += weights[i] - upperBound * onoff[i] <= 0 \n",
151 | "\n",
152 | "m.objective = maximize(xsum(weights[i] * promIndex[i] for i in range(numSecurities))) \n",
153 | "\n",
154 | "status = m.optimize()\n",
155 | "\n",
156 | "print(status, \"\\n\")\n",
157 | "maxProm = m.objective_value\n",
158 | "print(\"Maximum PROMETHEE flow = \", maxProm, \"\\n\")"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "metadata": {},
164 | "source": [
165 | "Finally, we solve the problem of maximising the portfolio dividend yield."
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": 5,
171 | "metadata": {},
172 | "outputs": [
173 | {
174 | "name": "stdout",
175 | "output_type": "stream",
176 | "text": [
177 | "OptimizationStatus.OPTIMAL \n",
178 | "\n",
179 | "Maximum DY = 2.5395 \n",
180 | "\n"
181 | ]
182 | }
183 | ],
184 | "source": [
185 | "m = Model()\n",
186 | "\n",
187 | "onoff = [ m.add_var(var_type=BINARY) for i in range(numSecurities) ]\n",
188 | "weights = [ m.add_var(var_type=CONTINUOUS) for i in range(numSecurities) ]\n",
189 | "\n",
190 | "m += xsum(weights[i] for i in range(numSecurities)) == 1\n",
191 | "m += xsum(onoff[i] for i in range(numSecurities)) <= maxSecurities\n",
192 | "m += xsum(onoff[i] for i in range(numSecurities)) >= minSecurities\n",
193 | "for i in range(numSecurities):\n",
194 | " m += weights[i] - lowerBound * onoff[i] >= 0\n",
195 | " m += weights[i] - upperBound * onoff[i] <= 0 \n",
196 | "\n",
197 | "m.objective = maximize(xsum(weights[i] * DYIndex[i] for i in range(numSecurities))) \n",
198 | "\n",
199 | "status = m.optimize()\n",
200 | "\n",
201 | "print(status, \"\\n\")\n",
202 | "maxDY = m.objective_value\n",
203 | "print(\"Maximum DY = \", maxDY, \"\\n\")"
204 | ]
205 | },
206 | {
207 | "cell_type": "markdown",
208 | "metadata": {},
209 | "source": [
210 | "### Minimax objective Optimisation Problem\n",
211 | "\n",
212 | "In the following cell we set the final problem as a goal programming optimisation problem with the minimax objective. The results for a random selection of offsets is presented below:"
213 | ]
214 | },
215 | {
216 | "cell_type": "code",
217 | "execution_count": 6,
218 | "metadata": {},
219 | "outputs": [
220 | {
221 | "name": "stdout",
222 | "output_type": "stream",
223 | "text": [
224 | "OptimizationStatus.OPTIMAL \n",
225 | "\n",
226 | "Q = 0.03350039947771772 \n",
227 | "\n",
228 | "ACN : 0.2805866288365215\n",
229 | "NOC : 0.3\n",
230 | "IBM : 0.16550896312890123\n",
231 | "MSI : 0.049999999999999996\n",
232 | "MSCI : 0.15390440803457714\n",
233 | "ORA : 0.05\n",
234 | "\n",
235 | "Portfolio beta = 1.005219219751575\n",
236 | "Portfolio PROMETHEE flow = 0.056381686302817036\n",
237 | "Portfolio DY = 2.417965336466194\n"
238 | ]
239 | },
240 | {
241 | "data": {
242 | "image/png": "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\n",
243 | "text/plain": [
244 | ""
245 | ]
246 | },
247 | "metadata": {
248 | "needs_background": "light"
249 | },
250 | "output_type": "display_data"
251 | }
252 | ],
253 | "source": [
254 | "m = Model()\n",
255 | "\n",
256 | "w1 = 0.2\n",
257 | "w2 = 0.1\n",
258 | "w3 = 0.7\n",
259 | "\n",
260 | "onoff = [ m.add_var(var_type=BINARY) for i in range(numSecurities) ]\n",
261 | "weights = [ m.add_var(var_type=CONTINUOUS) for i in range(numSecurities) ]\n",
262 | "Q = m.add_var(var_type=CONTINUOUS)\n",
263 | "\n",
264 | "m += xsum(weights[i] for i in range(numSecurities)) == 1\n",
265 | "m += xsum(onoff[i] for i in range(numSecurities)) <= maxSecurities\n",
266 | "m += xsum(onoff[i] for i in range(numSecurities)) >= minSecurities\n",
267 | "for i in range(numSecurities):\n",
268 | " m += weights[i] - lowerBound * onoff[i] >= 0\n",
269 | " m += weights[i] - upperBound * onoff[i] <= 0 \n",
270 | " \n",
271 | "m += w1 * ((xsum(weights[i] * betaIndex[i] for i in range(numSecurities))) - minBeta) / minBeta <= Q\n",
272 | "m += w2 * (maxProm - (xsum(weights[i] * promIndex[i] for i in range(numSecurities)))) / maxProm <= Q\n",
273 | "m += w3 * (maxDY - (xsum(weights[i] * DYIndex[i] for i in range(numSecurities)))) / maxDY <= Q\n",
274 | "\n",
275 | "\n",
276 | "m.objective = minimize(Q) \n",
277 | "\n",
278 | "status = m.optimize()\n",
279 | "\n",
280 | "print(status, \"\\n\")\n",
281 | "minQ = m.objective_value\n",
282 | "print(\"Q = \", minQ, \"\\n\")\n",
283 | "\n",
284 | "for i in range(numSecurities):\n",
285 | " print(companyName[i],\": \", weights[i].x * onoff[i].x)\n",
286 | "print()\n",
287 | "\n",
288 | "print(\"Portfolio beta = \", sum(weights[i].x * betaIndex[i] for i in range(numSecurities)))\n",
289 | "print(\"Portfolio PROMETHEE flow = \", sum(weights[i].x * promIndex[i] for i in range(numSecurities)))\n",
290 | "print(\"Portfolio DY = \", sum(weights[i].x * DYIndex[i] for i in range(numSecurities)))\n",
291 | "\n",
292 | "plt.figure(figsize=(16,9))\n",
293 | "for i in range(numSecurities):\n",
294 | " plt.bar(companyName[i], weights[i].x, color = 'lightblue', edgecolor = 'black', width=0.6)\n",
295 | "plt.xlabel(r\"Securities\", fontsize=22)\n",
296 | "plt.ylabel(r\"Portfolio Percentage\", fontsize=22)\n",
297 | "plt.title(r\"PROMETHEE Flow Portfolio\", fontsize=28)\n",
298 | "plt.xticks(fontsize=18, rotation=0)\n",
299 | "plt.savefig(\"barplot14.png\", dpi=300)"
300 | ]
301 | }
302 | ],
303 | "metadata": {
304 | "kernelspec": {
305 | "display_name": "Python 3",
306 | "language": "python",
307 | "name": "python3"
308 | },
309 | "language_info": {
310 | "codemirror_mode": {
311 | "name": "ipython",
312 | "version": 3
313 | },
314 | "file_extension": ".py",
315 | "mimetype": "text/x-python",
316 | "name": "python",
317 | "nbconvert_exporter": "python",
318 | "pygments_lexer": "ipython3",
319 | "version": "3.7.4"
320 | }
321 | },
322 | "nbformat": 4,
323 | "nbformat_minor": 2
324 | }
325 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Multicriteria-Portfolio-Construction-with-Python
2 | Source code for Multicriteria Portfolio Construction with Python
3 |
--------------------------------------------------------------------------------