Professional Documents
Culture Documents
Assignment 1 Group 11
Assignment 1 Group 11
Group 11
02519963
02425529
02512457
02469848
02422796
Group 11
Contents
Question 1..................................................................... 3
Question 2..................................................................... 5
Question 3.................................................................... 10
Group 11
Question 1
The analysis started with the loading of several essential packages, including cvxpy, csv, numpy, math,
pandas, among others, which were necessary for data analysis and evaluation. Subsequently, we imported the
data from the given Excel file into Python. After cleaning the data, we generated daily log returns, displaying
the results in a table. Utilizing these daily log returns, we created a dataframe to calculate summary statistics
for each index, using functions for mean, standard deviation (sd), skewness, and kurtosis. The table below
presents these statistics for each index. Finally, using a similar method, we constructed the correlation matrix,
the results of which are depicted in the table below.
------------------------Summary Statistics----------------------------------
Eurostox NIKKEI22
TSX CAC DAX FTSE100 SP500 IBOVESPA
x50 5
TSX 1 0.67723 0.6597 0.66192 0.22234 0.71639 0.77141 0.68563
Normality Test
In our analysis, we evaluated the feasibility of approximating the return process by an i.i.d. Gaussian
distribution. Initial observations from skewness and kurtosis values indicated that this approximation would be
inappropriate, as these values diverged significantly from zero. To validate this, we applied the Shapiro-Wilk
normality test to each index's daily log returns. The resulting p-values, all substantially below 5%, reinforced
our conclusion that approximating the return process as i.i.d. Gaussian is not reasonable at a 95% confidence
level. We plotted each stock's log returns against a theoretical normal distribution using the stats.probplot
function and matplotlib for visualization. These plots provided a graphical evaluation of how closely the log
returns followed a normal distribution. This was further supported by visual assessments using probability plots
and D'Agostino's K-squared tests, which confirmed the departure from normality in the return distributions.
Group 11
Question 2
Using the annualized daily log returns and libraries such as cvxpy, scipy.optimize, numpy and pandas, we
solved for the optimal portfolio weights using the Markowitz mean-variance optimization framework. This
involved finding the portfolio with the minimum variance for a given level of return and the tangency portfolio,
which maximizes the Sharpe ratio (the ratio of excess return to standard deviation of return). In our process,
we defined constraints to ensure the sum of weights equals 1 (fully invested portfolio) and bounds are set to (0,
1) for the no-short selling case, which restricts the weights to be between 0 and 1. The table below shows the
calculated weights for ‘MV’ (Minimum Variance) with short selling, ‘ME_V’ (Mean-Variance) with short selling,
‘TP’ (Tangency Portfolio) with short selling ‘MV_NS’ (Minimum Variance with no short selling) ‘ME_NS’ (Mean-
Variance with no short selling) and ‘TP_NS’ (Tangency Portfolio with no short selling). In order to graph the
efficient frontier we first annualized the mean and standard deviation of returns by multiplying the mean by 252
(the typical number of trading days in a year) and the standard deviation by the square root of 252. A range of
target returns was created using np.linspace to sample between -20% and 50%. The code uses a loop to solve
the optimization problem for each target return. The tqdm library is used to create a progress bar for the loop.
We first plot the mean-variance efficient frontier with short-selling and then without short-selling. Individual
stocks are plotted as red dots, showing their annualized risk and return. The minimum variance portfolio is
identified as the portfolio with the lowest standard deviation and is marked with a green dot on the plot. The
tangency portfolio is the one that maximizes the Sharpe ratio, which is calculated using a risk-free rate of 2%
and it is marked with a yellow dot. Individual stocks are generally below the efficient frontier, indicating that
they are not as efficient as the portfolios on the frontier, which achieve diversification benefits. These plots
visually demonstrate the benefit of combining assets into portfolios to reduce risk (standard deviation) while
striving for the highest returns. Moreover, it is evident that the no short-selling constraint has as a result a
Tangency Portfolio with a much lower annualized return than the one without the constraint.
In [ ]:
# Get minimum variance portfolio and tangency portfolio in both cases
threshold = 0.000001
# mean variance portfolio with short selling
w = cp.Variable(len(stock_name))
P = cov
q = mean
A = np.ones(len(stock_name)).reshape(1, -1) # reshape to 1*n row
vector
b = np.ones(1)
prob = cp.Problem(cp.Minimize(cp.quad_form(w, P)-q.T @ w),
[A @ w == b])
prob.solve(solver=cp.SCS) mean_var = w.value
mean_var[np.abs(mean_var) < threshold] = 0
prob.solve(solver=cp.SCS)
mv_no_short = w.value
mv_no_short[np.abs(mv_no_short) < threshold] = 0
from scipy.optimize import minimize
def objective(w):
return -np.dot(w, mean) / np.sqrt(np.dot(w, np.dot(P, w)))
def constraint(w):
return np.sum(w) - 1
w0 = np.ones(len(stock_name)) / len(stock_name)
cons = {'type': 'eq', 'fun': constraint}
# bounds = [(0, 1) for i in range(len(stock_name))]
result = minimize(objective, w0, method='SLSQP', constraints=cons,tol=1e-10)
w_TP = result.x
w_TP[np.abs(w_TP) < threshold] = 0
Group 11
def objective(w):
return -np.dot(w, mean) / np.sqrt(np.dot(w, np.dot(P, w)))
def constraint(w):
return np.sum(w) - 1
w0 = np.ones(len(stock_name)) / len(stock_name)
cons = {'type': 'eq', 'fun': constraint}
bounds = [(0, 1) for i in range(len(stock_name))]
result = minimize(objective, w0, method='SLSQP',
bounds = bounds, constraints=cons,tol = 1e-10)
w_TP_noshort = result.x
df_weight = df_weight.round(5)
------------------------ Weights ----------------------------------
return_min = min(mean_anual)
return_max = max(mean_anual)
samples = 1000
target_returns = np.linspace(-0.2, 0.5, samples) weights = np.zeros((samples,
len(stock_name)))
variances = np.zeros(samples) returns = np.zeros(samples)
w = cp.Variable(len(stock_name))
P = cov_anual q = mean_anual
G = np.ones(len(stock_name)).reshape(1,-1) # reshape to 1*n row vector
A = mean_anual.copy().reshape(1, -1) # reshape to 1*n row vector
b = np.ones(1)
Group 11
# sum w = 1
for i in tqdm.tqdm(range(samples)):
prob = cp.Problem(cp.Minimize(cp.quad_form(w, P)),
[A @ w == target_returns[i], G @ w == b])
# prob.solve(solver=cp.ECOS)
prob.solve(solver=cp.ECOS)
#prob.solve(solver=cp.CVXOPT)
weights[i, :] = w.value
x= w.value
variances[i] = cp.quad_form(w, P).value
returns[i] = target_returns[i]
# get the standard deviation
standard_deviation = np.sqrt(variances)
mv[np.abs(mv) < threshold] = 0
prob.solve(solver=cp.SCS)
mv_no_short = w.value
mv_no_short[np.abs(mv_no_short) < threshold] = 0
Group 11
return_min = min(mean_anual)
return_max = max(mean_anual)
samples = 1000
target_returns = np.linspace(return_min, return_max, samples)
weights = np.zeros((samples, len(stock_name)))
variances = np.zeros(samples)
Group 11
Question 3
In our study for Question 3, we used a method that looks at portfolio changes every five years, from 2010 to
2021. This method adjusts the mix of investments as market conditions change. We found the S&P 500 to be a
favorite choice for investment, showing up often in our selected portfolios because of its strong performance
and potential for good returns. On the other hand, the Eurostoxx50's role in the portfolios changed a lot,
showing how our evaluation of it shifted over time. This work shows that picking investments based on past
performance and risk can lead to choices different from those just based on how big the companies are. Our
findings highlight the importance of being flexible and carefully evaluating how investments have done in the
past, pointing to a way of building portfolios that looks beyond just the size of the investments.
w0 = np.ones(len(stock_name)) / len(stock_name)
cons = {'type': 'eq', 'fun': constraint}
bounds = [(0, 1) for i in range(len(stock_name))]
result = minimize(objective, w0, method='SLSQP', bounds = bounds, constraint
w_TP_noshort = result.x
mv_no_short = w.value