-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathq2.m
More file actions
312 lines (264 loc) · 10.7 KB
/
q2.m
File metadata and controls
312 lines (264 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
%% =================================
% Load Data and Define Market (Q2)
% =================================
clear; close all; clc; format short
% Toggle saving figures (1 = save, 0 = do not save)
figswitch = 1;
filename = 'Prices.xlsx';
imgDir = 'Images/'; % Directory for saving figures
txtDir = 'Results/'; % Directory for saving results
txtFilename = fullfile(txtDir, 'Q2.txt');
% Ensure directories exist
if ~exist(imgDir, 'dir'), mkdir(imgDir); end
if ~exist(txtDir, 'dir'), mkdir(txtDir); end
% Load dataset
dataset = readtable(filename, 'MissingRule', 'omitrow');
colLabels = dataset.Properties.VariableNames;
tickers = colLabels(2:end); % Extract tickers
histPrices = dataset{:, 2:end}; % Historical prices
histDates = dataset{:, 1}; % Historical dates
[NObs, NAsset] = size(histPrices);
% Compute Asset Log-Returns
ret = log(histPrices(2:end, :) ./ histPrices(1:end-1, :));
% Split Data: In-Sample (Training) and Out-of-Sample (Test)
train = ret(1:floor(size(ret,1)/2), :);
test = ret(floor(size(ret,1)/2)+1:end, :);
%% ===========================
% Bottom-Up Approach to Portfolio Risk (Parametric)
% ===========================
% Equally Weighted Portfolio (EQ)
w_eq = ones(NAsset, 1) / NAsset;
% Estimate mean vector and covariance matrix from training data
MeanV = mean(train)';
Sigma = cov(train);
% For parametric VaR at alpha=0.95
alpha = 0.95;
z = norminv(1 - alpha, 0, 1);
% Compute portfolio variance and VaR for the equally weighted portfolio
sg2p = w_eq' * Sigma * w_eq;
VaR_w = - z * sqrt(sg2p);
% Compute Marginal VaR (Euler’s decomposition)
MVaR_w = - z * Sigma * w_eq / sqrt(sg2p);
% Component VaR (CVaR) and percentage
CVaR_w = w_eq .* MVaR_w;
CVaR_w_p = CVaR_w / VaR_w;
% Plot Equally Weighted Comp. Var %
h = figure();
StockNames = categorical(tickers);
bar(StockNames, CVaR_w_p);
title('Component VaR (%) - Equally Weighted Portfolio');
xlabel('Asset'); ylabel('Component VaR (%)');
if figswitch, print(h, '-dpng', fullfile(imgDir, 'ComponentVaR_EW_Param.png')); end
%% ===========================
% Portfolio Optimization
% ===========================
% 1. Equally Weighted: w_eq (already defined)
% 2. Risk Parity (Parametric)
% Minimizes std of parametric component VaRs
x0 = w_eq;
w_rp = fmincon(@(x) std(w_rp_component(x, Sigma)), x0, ...
[], [], ones(1, NAsset), 1, zeros(NAsset,1), ones(NAsset,1));
sg2rp = w_rp' * Sigma * w_rp;
MVaR_rp = - z * Sigma * w_rp / sqrt(sg2rp);
CVaR_rp = w_rp .* MVaR_rp;
CVaR_rp_p = CVaR_rp / sum(CVaR_rp);
% 3. Maximum Diversification (MD)
x0 = w_eq;
w_md = fmincon(@(x) - (x' * sqrt(diag(Sigma)) / sqrt(x' * Sigma * x)), x0, ...
[], [], ones(1, NAsset), 1, zeros(NAsset,1), ones(NAsset,1));
sg2d = w_md' * Sigma * w_md;
MVaR_d = - z * Sigma * w_md / sqrt(sg2d);
CVaR_d = w_md .* MVaR_d;
CVaR_d_p = CVaR_d / sum(CVaR_d);
%% ===========================
% Risk Parity (Non-Parametric)
% ===========================
% Minimise std of non-parametric component VaRs (via the small-window approach in notes)
alpha_np = 0.95; % VaR confidence
epsilon_window = 0.001; % half-window around portfolio VaR
% Define initial guess
x0 = w_eq;
% Solve with fmincon
w_rp_np = fmincon(@(x) w_rp_np_obj(x, train, alpha_np, epsilon_window), x0, ...
[], [], ones(1, NAsset), 1, zeros(NAsset,1), ones(NAsset,1));
% Once we have w_rp_np, we can compute its non-parametric component VaRs
% for train set:
cVaR_np_rp_vec = compute_nonparam_cVaR_vector(w_rp_np, train, alpha_np, epsilon_window);
% Sum of those cVaRs is portfolio VaR, so let's see the percentages:
VaR_rp_np = EmpiricalVaR(w_rp_np, train, alpha_np);
CVaR_rp_np_p = cVaR_np_rp_vec / VaR_rp_np;
%% Tables
% Table of portfolio weights
T_w = table(w_eq, w_rp, w_md, w_rp_np, ...
'VariableNames', {'EQ','RP_param','MD','RP_nonparam'}, 'RowNames', tickers);
disp('Portfolio Weights (All Approaches):');
disp(T_w);
% Component VaR for Risk Parity (Non-Parametric)
T_Component_g = table(w_eq, MVaR_w, CVaR_w, CVaR_w_p, ...
'VariableNames', {'Weights','MVaR','CVaR','CVaR%'}, 'RowNames', tickers);
disp('Component VaR for Equally Weighted Portfolio (Parametric):');
disp(T_Component_g);
% Component VaR for Risk Parity (Parametric)
T_Component_RP = table(w_rp, MVaR_rp, CVaR_rp, CVaR_rp_p, ...
'VariableNames', {'Weights','MVaR','CVaR','CVaR%'}, 'RowNames', tickers);
disp('Component VaR for Risk Parity Portfolio (Parametric):');
disp(T_Component_RP);
% Component VaR for Maximum Diversification Portfolio (Parametric)
T_Component_MD = table(w_md, MVaR_d, CVaR_d, CVaR_d_p, ...
'VariableNames', {'Weights','MVaR','CVaR','CVaR%'}, 'RowNames', tickers);
disp('Component VaR for Maximum Diversification Portfolio (Parametric):');
disp(T_Component_MD);
% Component VaR for Risk Parity (Non-Parametric)
VaR_rp_np = EmpiricalVaR(w_rp_np, train, alpha_np);
CVaR_rp_np_p = cVaR_np_rp_vec / VaR_rp_np;
T_Component_RPnp = table(w_rp_np, cVaR_np_rp_vec, CVaR_rp_np_p, ...
'VariableNames', {'Weights','CVaR','CVaR%'}, 'RowNames', tickers);
disp('Component VaR for Risk Parity Portfolio (Non-Parametric):');
disp(T_Component_RPnp);
%% ===========================
% Out-of-Sample Performance
% ===========================
% Evaluate the 4 portfolios: EQ, RP_param, MD, RP_nonparam
% 1) Equally Weighted
portRet_EQ = test * w_eq;
Sharpe_EQ = (mean(portRet_EQ) / std(portRet_EQ))* sqrt(252);
cumRet_EQ = exp(cumsum(portRet_EQ));
MaxDD_EQ = maxdrawdown(cumRet_EQ);
% 2) Risk Parity (Parametric)
portRet_RP = test * w_rp;
Sharpe_RP = (mean(portRet_RP) / std(portRet_RP))* sqrt(252);
cumRet_RP = exp(cumsum(portRet_RP));
MaxDD_RP = maxdrawdown(cumRet_RP);
% 3) Maximum Diversification
portRet_MD = test * w_md;
Sharpe_MD = (mean(portRet_MD) / std(portRet_MD))* sqrt(252);
cumRet_MD = exp(cumsum(portRet_MD));
MaxDD_MD = maxdrawdown(cumRet_MD);
% 4) Risk Parity (Non-Parametric)
portRet_RPnp = test * w_rp_np;
Sharpe_RPnp = (mean(portRet_RPnp) / std(portRet_RPnp))* sqrt(252);
cumRet_RPnp = exp(cumsum(portRet_RPnp));
MaxDD_RPnp = maxdrawdown(cumRet_RPnp);
% Define test dates for plotting
testDates = histDates(floor(size(ret,1)/2)+2:end);
%% Compute VaR Violations (95% confidence level)
alpha_viol = 0.95;
% For parametric portfolios, compute VaR using the test return standard deviation
z_viol = norminv(1 - alpha_viol, 0, 1);
% Parametric VaR for Equally Weighted, RP (parametric) and MD portfolios:
VaR_EQ_95 = - z_viol * std(portRet_EQ);
VaR_RP_95 = - z_viol * std(portRet_RP);
VaR_MD_95 = - z_viol * std(portRet_MD);
% For the non-parametric Risk Parity portfolio, use empirical VaR:
VaR_RPnp_95 = EmpiricalVaR(w_rp_np, test, alpha_viol);
% Count violations: violation if the port. ret. is less than negative VaR.
viol_EQ = sum(portRet_EQ < -VaR_EQ_95);
viol_RP = sum(portRet_RP < -VaR_RP_95);
viol_MD = sum(portRet_MD < -VaR_MD_95);
viol_RPnp = sum(portRet_RPnp < -VaR_RPnp_95);
%% Out-of-Sample Performance in Table
FinalRet_EQ = cumRet_EQ(end);
FinalRet_RP = cumRet_RP(end);
FinalRet_MD = cumRet_MD(end);
FinalRet_RPnp = cumRet_RPnp(end);
T_Performance = table([Sharpe_EQ; Sharpe_RP; Sharpe_MD; Sharpe_RPnp], ...
[MaxDD_EQ; MaxDD_RP; MaxDD_MD; MaxDD_RPnp], ...
[viol_EQ; viol_RP; viol_MD; viol_RPnp], ...
[FinalRet_EQ; FinalRet_RP; FinalRet_MD; FinalRet_RPnp], ...
'VariableNames', {'SharpeRatio','MaxDrawdown','VaRViolations','FinalCumReturn'}, ...
'RowNames', {'EQ','RP_param','MD','RP_nonparam'});
disp('Out-of-Sample Performance Metrics:');
disp(T_Performance);
%% ===========================
% Visualisations for Out-of-Sample Performance
% ===========================
strategies = categorical({'EQ','RP\_param','MD','RP\_nonparam'});
% 1. Cumulative Returns Plot
h2 = figure();
plot(testDates, cumRet_EQ, 'LineWidth', 1.5); hold on;
plot(testDates, cumRet_RP, 'LineWidth', 1.5);
plot(testDates, cumRet_MD, 'LineWidth', 1.5);
plot(testDates, cumRet_RPnp, 'LineWidth', 1.5);
title('Cumulative Returns - Out-of-Sample');
xlabel('Date'); ylabel('Cumulative Return');
legend('EQ', 'RP\_param', 'MD', 'RP\_nonparam','Location','best');
if figswitch, print(h2, '-dpng', fullfile(imgDir, 'CumulativeReturns.png')); end
% 2. Sharpe Ratios Bar Chart
h3 = figure();
b1 = bar(strategies, [Sharpe_EQ, Sharpe_RP, Sharpe_MD, Sharpe_RPnp]);
b1.FaceColor = 'flat';
colors = [
0, 0.4470, 0.7410; % Blue (EQ)
0.8500, 0.3250, 0.0980; % Orange (RP_param)
0.9290, 0.6940, 0.1250; % Yellow (MD)
0.4940, 0.1840, 0.5560; % Purple (RP_nonparam)
];
b1.CData = colors;
title('Sharpe Ratios Comparison');
xlabel('Portfolio Strategy'); ylabel('Annualized Sharpe Ratio');
if figswitch, print(h3, '-dpng', fullfile(imgDir, 'SharpeRatios.png')); end
% 3. Maximum Drawdowns Bar Chart
h4 = figure();
b2 = bar(strategies, [MaxDD_EQ, MaxDD_RP, MaxDD_MD, MaxDD_RPnp]);
b2.FaceColor = 'flat';
b2.CData = colors;
title('Maximum Drawdown Comparison');
xlabel('Portfolio Strategy'); ylabel('Maximum Drawdown');
if figswitch, print(h4, '-dpng', fullfile(imgDir, 'MaxDrawdowns.png')); end
% 4. VaR Violations Bar Chart (95% Confidence)
h5 = figure();
b3 = bar(strategies, [viol_EQ, viol_RP, viol_MD, viol_RPnp]);
b3.FaceColor = 'flat';
b3.CData = colors;
title('Number of VaR Violations (95% Confidence)');
xlabel('Portfolio Strategy'); ylabel('Violations Count');
if figswitch, print(h5, '-dpng', fullfile(imgDir, 'VaRViolations.png')); end
%% ================
% Functions
% ================
function mdd = maxdrawdown(cumReturns)
peak = cumReturns(1);
mdd = 0;
for i = 1:length(cumReturns)
if cumReturns(i) > peak
peak = cumReturns(i);
else
dd = (cumReturns(i) - peak) / peak;
if dd < mdd
mdd = dd;
end
end
end
end
function compVaR = w_rp_component(w, Sigma)
% Parametric risk parity component VaRs
portVar = w' * Sigma * w;
z = norminv(0.01, 0, 1);
margVaR = - z * Sigma * w / sqrt(portVar);
compVaR = w .* margVaR;
end
function VaR_empirical = EmpiricalVaR(w, R, alpha)
% Historical simulation VaR
portRet = R * w;
VaR_empirical = - prctile(portRet, (1 - alpha)*100);
end
function cVaR_vec = compute_nonparam_cVaR_vector(w, R, alpha, epsilon)
% "Small window" method around the portfolio VaR
portRet = R*w;
VaR_p = - prctile(portRet, (1 - alpha)*100);
lowBound = -VaR_p - epsilon;
upBound = -VaR_p + epsilon;
idx = find(portRet >= lowBound & portRet <= upBound);
if isempty(idx)
cVaR_vec = zeros(length(w),1);
return;
end
condMean = mean(R(idx,:), 1); % 1xN
margVaR = - condMean; % 1xN
cVaR_vec = w(:) .* margVaR(:);
end
function objVal = w_rp_np_obj(w, R, alpha, epsilon)
% Non-parametric risk parity objective: minimize std of cVaR_i
cVaR_vec = compute_nonparam_cVaR_vector(w, R, alpha, epsilon);
objVal = std(cVaR_vec);
end