import numpy as np
import pandas as pd
from pylab import plt, mpl
from scipy.optimize import minimize
plt.style.use('seaborn-v0_8')
mpl.rcParams['savefig.dpi']=300
mpl.rcParams['font.family'] = 'serif'
np.set_printoptions(precision=5, suppress=True,
formatter={'float': lambda x: f'{x:6.3f}'})
url='ma 10 cp _1 .csv'
raw = pd.read_csv(url, index_col=0, parse_dates=True).dropna()
raw.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 259 entries, 2018-01-05 to 2022-12-30
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ACB 259 non-null int64
1 BID 259 non-null int64
2 FPT 259 non-null int64
3 HDB 259 non-null int64
4 HPG 259 non-null int64
5 MBB 259 non-null int64
6 PLX 259 non-null int64
7 SAB 259 non-null int64
8 SSI 259 non-null int64
9 VIC 259 non-null int64
dtypes: int64(10)
memory usage: 22.3 KB
symbols = ['ACB','BID', 'FPT','HDB','HPG','MBB','PLX','SAB','SSI','VIC']
rets = np.log(raw[symbols] / raw[symbols].shift(1)).dropna()
(raw[symbols] / raw[symbols].iloc[0]).plot(figsize=(10, 6));
weights = len(rets.columns) * [1 / len(rets.columns)]
def port_return(rets, weights):
return np.dot(rets.mean(), weights) * 52
port_return(rets, weights)
-0.09182403043677012
def port_volatility(rets, weights):
return np.dot(weights, np.dot(rets.cov() * 52 , weights)) ** 0.5
port_volatility(rets, weights)
0.26253260925188804
def port_sharpe(rets, weights):
return port_return(rets, weights) / port_volatility(rets, weights)
port_sharpe(rets, weights)
-0.34976238075121996
w = np.random.random((1000, len(symbols)))
w = (w.T / w.sum(axis=1)).T
w[:5]
array([[ 0.088, 0.105, 0.089, 0.143, 0.145, 0.196, 0.036, 0.021,
0.106, 0.070],
[ 0.172, 0.020, 0.178, 0.156, 0.061, 0.166, 0.103, 0.015,
0.120, 0.010],
[ 0.168, 0.131, 0.121, 0.200, 0.012, 0.076, 0.163, 0.006,
0.004, 0.120],
[ 0.081, 0.021, 0.115, 0.124, 0.153, 0.003, 0.144, 0.106,
0.090, 0.163],
[ 0.035, 0.095, 0.037, 0.100, 0.170, 0.126, 0.112, 0.090,
0.080, 0.155]])
pvr = [(port_volatility(rets[symbols], weights),
port_return(rets[symbols], weights))
for weights in w]
pvr = np.array(pvr)
psr = pvr[:, 1] / pvr[:, 0]
plt.figure(figsize=(10, 6))
fig = plt.scatter(pvr[:, 0], pvr[:, 1],
c=psr, cmap='coolwarm')
cb = plt.colorbar(fig)
cb.set_label('Sharpe ratio')
plt.xlabel('expected volatility')
plt.ylabel('expected return')
plt.title(' | '.join(symbols));
bnds = len(symbols) * [(0, 1),]
bnds
[(0, 1),(0, 1),(0, 1),(0, 1),(0, 1),(0, 1),(0, 1),(0, 1),(0, 1),(0, 1)]
cons = {'type': 'eq', 'fun': lambda weights: weights.sum() - 1}
opt_weights = {}
for year in range(2018, 2022):
rets_ = rets[symbols].loc[f'{year}-01-01':f'{year}-12-31']
ow = minimize(lambda weights: -port_sharpe(rets_, weights),
len(symbols) * [1 / len(symbols)],
bounds=bnds,
constraints=cons)['x']
opt_weights[year] = ow
opt_weights
{2018: array([ 0.000, 0.499, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
0.000, 0.501]),
2019: array([ 0.000, 0.171, 0.515, 0.000, 0.000, 0.000, 0.000, 0.000,
0.000, 0.314]),
2020: array([ 0.000, 0.000, 0.000, 0.000, 0.454, 0.000, 0.000, 0.000,
0.546, 0.000]),
2021: array([ 0.027, 0.000, 0.838, 0.000, 0.000, 0.000, 0.000, 0.000,
0.135, 0.000])}
res = pd.DataFrame()
for year in range(2018,2022):
rets_ = rets[symbols].loc[f'{year}-01-01':f'{year}-12-31']
epv = port_volatility(rets_, opt_weights[year])
epr = port_return(rets_, opt_weights[year])
esr = epr / epv
rets_ = rets[symbols].loc[f'{year + 1}-01-01':f'{year + 1}-12-31']
rpv = port_volatility(rets_, opt_weights[year])
rpr = port_return(rets_, opt_weights[year])
rsr = rpr / rpv
res = pd.concat([res, pd.DataFrame({'epv': epv, 'epr': epr, 'esr':
esr,
'rpv': rpv, 'rpr': rpr, 'rsr': rsr},
index=[year + 1])])
res
epv epr esr rpv rpr rsr
2019 0.362510 0.205161 0.565945 0.169198 0.246944 1.459496
2020 0.148001 0.275497 1.861449 0.251253 -0.000268 -0.001068
2021 0.412817 0.575185 1.393315 0.347598 0.295699 0.850690
2022 0.286489 0.445871 1.556325 0.355344 -0.322437 -0.907394
res.mean()
epv 0.302455
epr 0.375428
esr 1.344259
rpv 0.280848
rpr 0.054984
rsr 0.350431
dtype: float64
res[['epv', 'rpv']].corr()
epv rpv
epv 1.000000 0.132481
rpv 0.132481 1.000000
res[['epv', 'rpv']].plot(kind='bar', figsize=(10, 6),
title='Expected vs. Realized Portfolio Volatility');
res[['epr', 'rpr']].corr()
epr rpr
epr 1.000000 -0.039914
rpr -0.039914 1.000000
res[['epr', 'rpr']].plot(kind='bar', figsize=(10, 6),
title='Expected vs. Realized Portfolio Return');
res[['esr', 'rsr']].corr()
esr rsr
esr 1.000000 -0.751587
rsr -0.751587 1.000000
res[['esr', 'rsr']].plot(kind='bar', figsize=(10, 6),
title='Expected vs. Realized Sharpe Ratio');
r = 0.005
market = 'HPG'
rets = np.log(raw / raw.shift(1)).dropna()
res = pd.DataFrame()
for sym in rets.columns[:5]:
print('\n' + sym)
print(54 * '=')
for year in range(2018, 2022):
rets_ = rets.loc[f'{year}-01-01':f'{year}-12-31']
muM = rets_[market].mean() * 52
cov = rets_.cov().loc[sym, market]
var = rets_[market].var()
beta = cov / var
rets_ = rets.loc[f'{year + 1}-01-01':f'{year + 1}-12-31']
muM = rets_[market].mean() * 52
mu_capm = r + beta * (muM - r)
mu_real = rets_[sym].mean() * 52
res = pd.concat([res, pd.DataFrame({'symbol': sym,
'mu_capm': mu_capm,
'mu_real': mu_real},
index=[year + 1])], sort=True)
print('{} | beta: {:.3f} | mu_capm: {:6.3f} | mu_real: {:6.3f}'
.format(year + 1, beta, mu_capm, mu_real))
ACB
======================================================
2019 | beta: 0.308 | mu_capm: -0.082 | mu_real: -0.271
2020 | beta: -0.088 | mu_capm: -0.043 | mu_real: 0.209
2021 | beta: 0.390 | mu_capm: 0.047 | mu_real: 0.205
2022 | beta: 0.084 | mu_capm: -0.076 | mu_real: -0.463
BID
======================================================
2019 | beta: 0.415 | mu_capm: -0.113 | mu_real: 0.301
2020 | beta: 0.052 | mu_capm: 0.034 | mu_real: 0.035
2021 | beta: 0.529 | mu_capm: 0.062 | mu_real: -0.255
2022 | beta: 0.253 | mu_capm: -0.241 | mu_real: 0.040
FPT
======================================================
2019 | beta: 0.203 | mu_capm: -0.053 | mu_real: 0.317
2020 | beta: 0.065 | mu_capm: 0.041 | mu_real: 0.025
2021 | beta: 0.413 | mu_capm: 0.050 | mu_real: 0.453
2022 | beta: 0.536 | mu_capm: -0.515 | mu_real: -0.194
HDB
======================================================
2019 | beta: 0.446 | mu_capm: -0.122 | mu_real: -0.118
2020 | beta: 0.050 | mu_capm: 0.032 | mu_real: -0.126
2021 | beta: 0.521 | mu_capm: 0.061 | mu_real: 0.262
2022 | beta: 0.232 | mu_capm: -0.220 | mu_real: -0.673
HPG
======================================================
2019 | beta: 1.000 | mu_capm: -0.279 | mu_real: -0.279
2020 | beta: 1.000 | mu_capm: 0.555 | mu_real: 0.555
2021 | beta: 1.000 | mu_capm: 0.113 | mu_real: 0.113
2022 | beta: 1.000 | mu_capm: -0.965 | mu_real: -0.965
sym = 'HDB'
res[res['symbol'] == sym].corr()
C:\Users\php31\AppData\Local\Temp\ipykernel_20292\1491550360.py:1:
FutureWarning: The default value of numeric_only in DataFrame.corr is
deprecated. In a future version, it will default to False. Select only valid
columns or specify the value of numeric_only to silence this warning.
res[res['symbol'] == sym].corr()
mu_capm mu_real
mu_capm 1.000000 0.874742
mu_real 0.874742 1.000000
res[res['symbol'] == sym].plot(kind='bar',
figsize=(10, 6), title=sym);
grouped = res.groupby('symbol').mean()
grouped
mu_capm mu_real
symbol
ACB -0.038771 -0.079857
BID -0.064552 0.030268
FPT -0.119263 0.150493
HDB -0.061972 -0.163616
HPG -0.144148 -0.144148
grouped.plot(kind='bar', figsize=(10, 6), title='Average Values');