├── 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 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | "
SymbolsACNNOCIBMMSIMSCIORA
Date
2016-01-05-0.0063340.0100550.008554-0.017290-0.006892-0.013472
2016-01-06-0.0113760.012297-0.017403-0.013985-0.004957-0.010242
2016-01-07-0.010515-0.011885-0.005060-0.014336-0.0145180.007186
2016-01-08-0.0027070.004737-0.003889-0.0046420.0049110.013699
2016-01-11-0.010052-0.006939-0.0102870.005130-0.0117850.006475
.....................
2018-12-24-0.031485-0.023837-0.023111-0.039567-0.035735-0.006997
2018-12-26-0.013033-0.026096-0.017288-0.012801-0.0046230.000979
2018-12-270.0171360.0199530.0184260.0105930.0309640.000000
2018-12-280.0284440.0446660.0384580.0166260.0465530.026398
2018-12-31-0.0043260.008037-0.0077920.0046220.0010250.009907
\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 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | "
0
Date
2016-01-05-0.001857
2016-01-06-0.009931
2016-01-07-0.040198
2016-01-080.013364
2016-01-11-0.016114
......
2018-12-24-0.014793
2018-12-26-0.014646
2018-12-27-0.013062
2018-12-28-0.017298
2018-12-31-0.003262
\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": "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\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 | --------------------------------------------------------------------------------