Çeşitlendirmenin (diversification), yani portföye farklı enstrümanlar koymanın, farklı sektörlerden olsun, farklı ülkelerden olsun, iyi bir şey olduğu hep tavsiye edilir, kulağa küpe kuralı “yumurtaları aynı sepete koymamak’’, teknik anlamda bu söz portföydeki bir enstrümanın tamamen çöktüğü durumda (sepetin düşüp yumurtaların kırılması) kayıpların sınırlanacağı çağrıştırmasını yapar. Mesela [4, sf. 115]’e göre, ABD borsalarında hisselerin artık getirisinin ortalama %3 olduğu bilinir. Eğer yıllık %20 standart sapmayı baz alırsak, Sharpe oranı (SR) %3 bölü %20 = 0.15. Eğer aynı ülkede ama farklı sektörlerde çeşitlendirirsek aşağı yukarı SR = 0.2 elde edebiliriz. Eğer farklı ülkelere çeşitlersek SR 0.25’e çıkabiliriz. Eğer yatırım sınıfını da çeşitlersek, yani tahviller, senetler, baz ürünlere yatırım yapmak üzere, o zaman SR 0.4 elde edebiliriz.
İçinde birbiri ile ilintisi olmayan (uncorrelated) varlıklar olan bir portföyün -ki çeşitlenmiş olmanın teknik tercümesi bu aslında- toplam standart sapması daha düşüktür. Sharpe oranını hesaplarken standart sapmaya böldüğümüz için daha ufak değer daha büyük SR anlamına gelir. Matematiksel olarak sadece bir portföy düşünelim içinde iki varlık olsun, gelecekteki getirilerini \(R_1,R_2\)’yi bildiğimizi farzedelim (tarihi veriden kestiriyoruz mesela), bu varlıklar \(w_1,w_2\) ağırlıkları üzerinden birleştiriliyor olsun, toplam portföy getirisi,
\[ R_p = w_1 R_1 + w_2 R_2 \]
\(R_1,R_2\) rasgele değişkenler. Tüm portföyün beklentisi,
\[ E(R_p) = E(w_1 R_1 + w_2 R_2) \]
\[ = w_1 E(R_1) + w_2 E(R_2) \]
Portföyün varyansı için [5, sf. 73],
\[Var(w_1 R_1 + w_2 R_2) = w_1^2Var(R_1) + w_2^2 Var(R_2) + w_1w_2Cov(R_1,R_2) \qquad (1)\]
Diyelim ki \(w_1,w_2\) eşit; O zaman formülden açık bir şekilde görülüyor ki üstteki varyansın azalacağı durumlardan biri iki enstrümanın hiç ilintili olmadığı durumdur, çünkü bu durumda iki getirinin kovaryansı sıfır olur; \(Cov(R_1,R_2)=0\), üstteki formüldeki 3. terim tamamen yokolur, böylece portföy varyansı azalır. Varyans azalınca Sharpe oranı artar.
N Tane Enstrüman
Çok boyutlu hesap için portföy ağırlıkları \(w = [w_1,..,w_n]^T\), getiriler \(R = [R_1,..,R_n]^T\) vektörleri içinde olsun,
\[ R_p = w^T R \]
Beklenti
\[ E(R_p) = E(w^T R) = w^TE(R) \]
Varyans
\[ Var(R_p) = E\big( (R_p-E(R_p))(R-E(R_p))^T \big) \]
\[ = E\big( (R_p-w^TE(R))(R_p-w^TE(R))^T \big) \]
\[ = E\big( (w^TR-w^TE(R))(w^TR-w^TE(R))^T \big) \]
\[ = w^T E\big( (R-E(R))(R-E(R))^Tw \big) \]
\[ = w^T cov (R) w \]
Yine iki enstrüman üzerinden matris formunu kontrol edelim, varyanslar için \(\sigma_1^2,\sigma_2^2\) kullanırsak,
\[ Var(R_p) = \left[\begin{array}{cc} w_1 & w_2 \end{array}\right] \left[\begin{array}{rr} \sigma_1^2 & \sigma_{12} \\ \sigma_{2,1}^2 & \sigma_2^2 \end{array}\right] \left[\begin{array}{r} w_1 \\ w_2 \end{array}\right] \qquad (2) \]
\[ = \left[\begin{array}{cc} w_1 \sigma_1^2 + w_2 \sigma_{2,1} & w_1 \sigma_{1,2} + w_2\sigma_2^2 \end{array}\right] \left[\begin{array}{r} w_1 \\ w_2 \end{array}\right] \]
\[ = w_1^2\sigma_1^2 + w_1w_2\sigma_{2,1} + w_1w_2\sigma_{1,2} + w_2^2\sigma_2^2\]
\[ = w_1^2\sigma_1^2 + 2 w_1w_2\sigma_{1,2} + w_2^2\sigma_2^2\]
Portföy Ağırlıklarını Hesaplamak
Portföy varyansını, riskini azaltmak enstrümanlar arası korelasyonu azaltmak ile mümkün, bunu bir yana koyalım. Bir ek yöntem enstrümanlara ayrılmış sermayeyi, yani bölüştürme ağırlıklarını optimal bir seviyeye getirmektir. Matematiksel olarak
\[ \textrm{minimize et } w^T \Sigma w \]
\[ R_p^T w = \mu, \quad w^T 1 = 1 \textrm{ şartlarına uyarak }\]
ki \(\mu\) hedeflenen getiri.
Verimli Sınır (Efficient Frontier)
Markovitz’in buluşlarından biri (getirisinin standart sapması) farklı ağırlıklar üzerinden hesaplanan portföyün riskini ve getirisini grafiklediğinde mümkün tüm getirilerin bir sınır oluşturduğunu görmesiydi, buna verimli sınır ismi verildi. Görsel olarak düşünürsek herhangi verili bir risk için dikey yukarı çıkıp bu sınıra geliyoruz, ve bu sınırdaki risk (ve onu ortaya çıkartan ağırlıklar) elde edebileceğimiz en iyi sonuç. Altta üç şirket üzerinde bu hesabı görebiliriz.
import pandas as pd
= pd.read_csv("companies.csv",index_col=0,parse_dates=True)
df print (df.head())
MSFT AAPL KO
Date
2010-01-04 26.045432 28.141855 23.509276
2010-01-05 26.053846 28.190509 23.224888
2010-01-06 25.893956 27.742101 23.216647
2010-01-07 25.624666 27.690818 23.158944
2010-01-08 25.801387 27.874915 22.730305
Şirketler Microsoft, Apple, ve Coca-Cola.
import scipy as sp
import scipy.optimize as scopt
import scipy.stats as spstats
def calc_annual_returns(daily_returns):
= np.exp(daily_returns.groupby(lambda date: date.year).sum())-1
grouped return grouped
def calc_portfolio_var(returns, weights):
= np.cov(returns.T,ddof=0)
sigma = (weights * sigma * weights.T).sum()
var return var
def sharpe_ratio(returns, weights, risk_free_rate = 0.015):
= returns.columns.size
n = calc_portfolio_var(returns, weights)
var = returns.mean()
means return (means.dot(weights) - risk_free_rate)/np.sqrt(var)
def negative_sharpe_ratio_n_minus_1_stock(weights,returns,risk_free_rate):
= np.append(weights, 1-np.sum(weights))
weights2 return -sharpe_ratio(returns, weights2, risk_free_rate)
def optimize_portfolio(returns, risk_free_rate):
= np.ones(returns.columns.size-1,dtype=float) * 1.0 / returns.columns.size
w0 = scopt.fmin(negative_sharpe_ratio_n_minus_1_stock,
w1 =(returns, risk_free_rate))
w0, args= np.append(w1, 1 - np.sum(w1))
final_w = sharpe_ratio(returns, final_w, risk_free_rate)
final_sharpe return (final_w, final_sharpe)
def objfun(W, R, target_ret):
= np.mean(R,axis=0)
stock_mean = np.dot(W,stock_mean)
port_mean =np.cov(R.T)
cov= np.dot(np.dot(W,cov),W.T)
port_var = 2000*abs(port_mean-target_ret)
penalty return np.sqrt(port_var) + penalty
def calc_daily_returns(df):
return np.log(df/df.shift(1))
def calc_efficient_frontier(returns):
= []
result_means = []
result_stds = []
result_weights = returns.mean()
means = means.min(), means.max()
min_mean, max_mean = returns.columns.size
nstocks
for r in np.linspace(min_mean, max_mean, 100):
= np.ones(nstocks)/nstocks
weights = [(0,1) for i in np.arange(nstocks)]
bounds = ({'type': 'eq','fun': lambda W: np.sum(W) - 1})
constraints = scopt.minimize(objfun, weights, (returns, r),
results ='SLSQP',constraints = constraints,
method= bounds)
bounds if not results.success: raise Exception(result.message)
round(r,4)) # 4 decimal places
result_means.append(np.=np.round(np.std(np.sum(returns*results.x,axis=1)),6)
std_
result_stds.append(std_)round(results.x, 5))
result_weights.append(np.
return {'Means': result_means, 'Stds': result_stds, 'Weights': result_weights}
def calc_port_perf(w, ret, covs):
= np.sum( ret*w )
port_ret = np.sqrt(np.dot(w.T, np.dot(covs, w)))
port_std return port_ret, port_std
def plot_all_possible_portfolios(ann_rets):
= []
res for i in range(1000):
= np.random.random(len(df.columns))
w /= np.sum(w)
w = calc_port_perf(w, ann_rets.mean(),ann_rets.cov())
r,s
res.append([r,s])= pd.DataFrame(res,columns=['return','sigma'])
vw return vw
def plot_efficient_frontier(ef_data):
u'Verimli Sınır')
plt.title(u'Portföy Standart Sapması (Risk))')
plt.xlabel(u'Portföy Getirisi')
plt.ylabel('Stds'], ef_data['Means'], '--'); plt.plot(ef_data[
Günlük getiriler,
= calc_daily_returns(df)
daily_returns print (daily_returns.head())
MSFT AAPL KO
Date
2010-01-04 NaN NaN NaN
2010-01-05 0.000323 0.001727 -0.012171
2010-01-06 -0.006156 -0.016034 -0.000355
2010-01-07 -0.010454 -0.001850 -0.002489
2010-01-08 0.006873 0.006626 -0.018682
Yıllık getiri,
= calc_annual_returns(daily_returns)
annual_returns print (annual_returns.head())
MSFT AAPL KO
2010 -0.079441 0.507219 0.189366
2011 -0.045157 0.255580 0.094586
2012 0.057989 0.325669 0.065276
2013 0.442980 0.080695 0.172330
2014 0.275646 0.406225 0.052661
Eşit ağırlıklar üzerinden oluşturulmuş portföyün varyansı,
= np.array([1/3.,1/3.,1/3.])
eq_weights print (calc_portfolio_var(annual_returns,weights=eq_weights))
0.00287954016535
Sharpe Oranı
print (sharpe_ratio(annual_returns,eq_weights))
3.20109292169
Farklı (rasgele) portföy ağırlıklarının oluşturacağı risk / getiri grafiği,
= plot_all_possible_portfolios(annual_returns)
vw ='sigma',y='return',kind='scatter')
vw.plot(x'tser_port_04.png') plt.savefig(
Şimdi optimizasyon ile global bir optimal nokta bulalım,
print (optimize_portfolio(annual_returns, 0.0003))
Optimization terminated successfully.
Current function value: -7.829867
Iterations: 38
Function evaluations: 74
(array([ 0.02615542, 0.76347385, 0.21037072]), 7.8298669383774486)
Verimli sınır,
= calc_efficient_frontier(annual_returns) frontier_data
print (frontier_data['Stds'][:5])
print (frontier_data['Means'][:5])
for x in frontier_data['Weights'][:5]: print (x)
[0.055842999999999997, 0.053446, 0.052564, 0.051706000000000002, 0.050871]
[0.1148, 0.1169, 0.11890000000000001, 0.12089999999999999, 0.1229]
[ 0. 0. 1.]
[ 0.06407 0.00512 0.93081]
[ 0.06733 0.01497 0.9177 ]
[ 0.07228 0.02469 0.90303]
[ 0.07493 0.03458 0.89049]
= plot_all_possible_portfolios(annual_returns)
vw ='sigma',y='return',kind='scatter')
vw.plot(x
plot_efficient_frontier(frontier_data)'tser_port_03.png') plt.savefig(
Bootstrap
Dikkat: üstteki optimizasyonu olduğu gibi kullanmak sayısal olarak pek ise yaramayabilir. Mesela, üç yatırım var, S&P 500, NASDAQ ve 20 yıllık ABD tahvili. Bu varlıkları basit bir şekilde kullanacağız, onları sadece alıp elde tutacağız. Eğer tek periyot (eldeki tüm veriyi) Markowitz optimizasyonu üzerinden ağırlıkları hesaplarsak, ağırlıklar çok ekstrem, stabil olmuyor. Çözüm için bootstrap tekniği kullanılır; veriden ardı ardına örneklem alırız, yani veriden yeni veri yaratırız, beklediğimiz o ki örneklemlerin dağılımı “gerçek’’ verinin dağılımı ile benzer olacak. Bu işlemin yan etkisi veriyi fazlalaştırmak, ardından yapılan hesabın ortalamasını alınca stabiliteye daha yaklaşmış olmak.
Zaman serisinden örneklem almanın değişik yolları var, bir tanesi blok örneklem yöntemi, kesintisiz zaman seri parçaları almak. Bir diğeri noktalar ardı ardına olsun olmasın verinin rasgele noktalarından örneklem toplamak. Alttaki ikinci yöntemi takip ediyor,
import zipfile, pandas as pd
import numpy as np, random, datetime
from scipy.optimize import minimize
def create_dull_pd_matrix(dullvalue=0.0, dullname="A",
=datetime.date(1970,1,1),
startdate=datetime.datetime.now().date(), index=None):
enddateif index is None:
=pd.date_range(startdate, enddate)
index=np.array([dullvalue]*len(index))
dullvalue=pd.DataFrame(dullvalue, index, columns=[dullname])
ansreturn ans
def addem(weights):
return 1.0 - sum(weights)
def variance(weights, sigma):
return (np.matrix(weights)*sigma*np.matrix(weights).transpose())[0,0]
def neg_SR(weights, sigma, mus):
=(np.matrix(weights)*mus)[0,0]
estreturn=(variance(weights,sigma)**.5)
std_devreturn -estreturn/std_dev
def equalise_vols(returns, default_vol):
=(default_vol/16.0)/returns.std(axis=0)
factors=create_dull_pd_matrix(dullvalue=factors,
facmat=returns.columns,
dullname=returns.index)
index=returns*facmat
norm_returns=returns.columns
norm_returns.columnsreturn norm_returns
def markosolver(returns, default_vol, default_SR):
=equalise_vols(returns, default_vol)
use_returns=use_returns.cov().values
sigma= [use_returns[asset_name].mean() for asset_name in use_returns.columns]
mus =np.array([mus], ndmin=2)
mus=mus.transpose()
mus=use_returns.shape[1]
number_assets=[1.0/number_assets]*number_assets
start_weights=[(0.0,1.0)]*number_assets
bounds=[{'type':'eq', 'fun':addem}]
cdict=minimize(neg_SR, start_weights,
ans
(sigma, mus),='SLSQP',
method=bounds,
bounds=cdict,
constraints=0.00001)
tolreturn ans['x']
def generate_fitting_dates(data, rollyears):
=data.index[0]
start_date=data.index[-1]
end_date=list(pd.date_range(start_date, end_date, freq="12M"))+[end_date]
yearstarts=[]
periodsfor tidx in range(len(yearstarts))[1:-1]:
=yearstarts[tidx]
period_start=yearstarts[tidx+1]
period_end=start_date
fit_start=period_start
fit_end
periods.append([fit_start, fit_end, period_start, period_end])
return periods
def bootstrap_portfolio(returns_to_bs,monte_carlo,
monte_length,default_vol,default_SR):
=[]
weightlistfor unused_index in range(monte_carlo):
=[int(random.uniform(0,1)*len(returns_to_bs)) \
bs_idxfor i in range(monte_length)]
=returns_to_bs.iloc[bs_idx,:]
returns=markosolver(returns, default_vol=default_vol, default_SR=default_SR)
weight
weightlist.append(weight)
=list(np.mean(weightlist, axis=0))
theweights_meanreturn theweights_mean
def optimise_over_periods(data,rollyears, monte_carlo,monte_length):
=generate_fitting_dates(data, rollyears=rollyears)
fit_periods=[]
weight_listfor fit_tuple in fit_periods:
=data[fit_tuple[0]:fit_tuple[1]]
period_subset_data=bootstrap_portfolio(period_subset_data,
weights=monte_carlo,
monte_carlo=monte_length,
monte_length=0.2, default_SR=1.0 )
default_vol
=[fit_tuple[2]+datetime.timedelta(seconds=1),
dindex3]-datetime.timedelta(seconds=1)]
fit_tuple[=pd.DataFrame([weights]*2,
weight_row=dindex,
index=data.columns)
columns
weight_list.append(weight_row)
=pd.concat(weight_list, axis=0)
weight_dfreturn weight_df
import zipfile, pandas as pd, random, boot
= ['SP500','NASDAQ','US20']
symbols = pd.DataFrame()
df with zipfile.ZipFile('../tser_070_voltar/legacycsv.zip', 'r') as z:
for symbol in symbols:
= '%s_price.csv' % symbol
f = pd.read_csv(z.open(f),sep=',',
df[symbol] =0,
index_col=True)['PRICE']
parse_dates
'SP500'] = df.SP500.pct_change()
df['NASDAQ'] = df.NASDAQ.pct_change()
df['US20'] = df.US20.pct_change()
df[
= df[(df.index >= '1999-08-02') & (df.index <= '2015-04-22')]
df
0)
random.seed(
=boot.optimise_over_periods(df,rollyears=20, monte_carlo=20,monte_length=250) w
w.plot()'tser_port_01.png') plt.savefig(
Bu sonuç [1. sf. 172]’dakine yakın, ağırlıklar çok daha stabil.
Kodda oynaklığın standardize edildiğine dikkat; tüm zaman serilerinin oynaklığı eşitleniyor, ve maksimize edilmeye uğraşılan getiri.