Portföy İdaresi
Ç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$$
(1) ile benzer sonuca vardık. Her iki durumda da enstrumanlar arasında korelasyon olmaması bir terim eksiltir, ve portföy varyansı azalır. Bunun matris tercümesi $cov (R)$'nin köşegeni dışındaki öğelerinin sıfır olması anlamına gelir, çünkü bir kovaryans matrisinde her enstrüman arasındaki kovaryanslar köşegen haricinde olan öğelerde tutulur.
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
df = pd.read_csv("companies.csv",index_col=0,parse_dates=True)
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):
grouped = np.exp(daily_returns.groupby(lambda date: date.year).sum())-1
return grouped
def calc_portfolio_var(returns, weights):
sigma = np.cov(returns.T,ddof=0)
var = (weights * sigma * weights.T).sum()
return var
def sharpe_ratio(returns, weights, risk_free_rate = 0.015):
n = returns.columns.size
var = calc_portfolio_var(returns, weights)
means = returns.mean()
return (means.dot(weights) - risk_free_rate)/np.sqrt(var)
def negative_sharpe_ratio_n_minus_1_stock(weights,returns,risk_free_rate):
weights2 = sp.append(weights, 1-np.sum(weights))
return -sharpe_ratio(returns, weights2, risk_free_rate)
def optimize_portfolio(returns, risk_free_rate):
w0 = np.ones(returns.columns.size-1,dtype=float) * 1.0 / returns.columns.size
w1 = scopt.fmin(negative_sharpe_ratio_n_minus_1_stock,
w0, args=(returns, risk_free_rate))
final_w = sp.append(w1, 1 - np.sum(w1))
final_sharpe = sharpe_ratio(returns, final_w, risk_free_rate)
return (final_w, final_sharpe)
def objfun(W, R, target_ret):
stock_mean = np.mean(R,axis=0)
port_mean = np.dot(W,stock_mean)
cov=np.cov(R.T)
port_var = np.dot(np.dot(W,cov),W.T)
penalty = 2000*abs(port_mean-target_ret)
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 = []
means = returns.mean()
min_mean, max_mean = means.min(), means.max()
nstocks = returns.columns.size
for r in np.linspace(min_mean, max_mean, 100):
weights = np.ones(nstocks)/nstocks
bounds = [(0,1) for i in np.arange(nstocks)]
constraints = ({'type': 'eq','fun': lambda W: np.sum(W) - 1})
results = scopt.minimize(objfun, weights, (returns, r),
method='SLSQP',constraints = constraints,
bounds = bounds)
if not results.success: raise Exception(result.message)
result_means.append(np.round(r,4)) # 4 decimal places
std_=np.round(np.std(np.sum(returns*results.x,axis=1)),6)
result_stds.append(std_)
result_weights.append(np.round(results.x, 5))
return {'Means': result_means, 'Stds': result_stds, 'Weights': result_weights}
def calc_port_perf(w, ret, covs):
port_ret = np.sum( ret*w )
port_std = np.sqrt(np.dot(w.T, np.dot(covs, w)))
return port_ret, port_std
def plot_all_possible_portfolios(ann_rets):
res = []
for i in range(1000):
w = np.random.random(len(df.columns))
w /= np.sum(w)
r,s = calc_port_perf(w, ann_rets.mean(),ann_rets.cov())
res.append([r,s])
vw = pd.DataFrame(res,columns=['return','sigma'])
return vw
def plot_efficient_frontier(ef_data):
plt.title(u'Verimli Sınır')
plt.xlabel(u'Portföy Standart Sapması (Risk))')
plt.ylabel(u'Portföy Getirisi')
plt.plot(ef_data['Stds'], ef_data['Means'], '--');
Günlük getiriler,
daily_returns = calc_daily_returns(df)
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,
annual_returns = calc_annual_returns(daily_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ı,
eq_weights = np.array([1/3.,1/3.,1/3.])
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,
vw = plot_all_possible_portfolios(annual_returns)
vw.plot(x='sigma',y='return',kind='scatter')
plt.savefig('tser_port_04.png')
Ş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,
frontier_data = calc_efficient_frontier(annual_returns)
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]
vw = plot_all_possible_portfolios(annual_returns)
vw.plot(x='sigma',y='return',kind='scatter')
plt.hold(True)
plot_efficient_frontier(frontier_data)
plt.savefig('tser_port_03.png')
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, util
import numpy as np, random, datetime
from scipy.optimize import minimize
def create_dull_pd_matrix(dullvalue=0.0, dullname="A",
startdate=pd.datetime(1970,1,1).date(),
enddate=datetime.datetime.now().date(), index=None):
if index is None:
index=pd.date_range(startdate, enddate)
dullvalue=np.array([dullvalue]*len(index))
ans=pd.DataFrame(dullvalue, index, columns=[dullname])
return 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):
estreturn=(np.matrix(weights)*mus)[0,0]
std_dev=(variance(weights,sigma)**.5)
return -estreturn/std_dev
def equalise_vols(returns, default_vol):
factors=(default_vol/16.0)/returns.std(axis=0)
facmat=create_dull_pd_matrix(dullvalue=factors,
dullname=returns.columns,
index=returns.index)
norm_returns=returns*facmat
norm_returns.columns=returns.columns
return norm_returns
def markosolver(returns, default_vol, default_SR):
use_returns=equalise_vols(returns, default_vol)
sigma=use_returns.cov().values
mus = use_returns[asset_name].mean() for asset_name in use_returns.columns
mus=np.array([mus], ndmin=2)
mus=mus.transpose()
number_assets=use_returns.shape[1]
start_weights=[1.0/number_assets]*number_assets
bounds=[(0.0,1.0)]*number_assets
cdict=[{'type':'eq', 'fun':addem}]
ans=minimize(neg_SR, start_weights,
(sigma, mus),
method='SLSQP',
bounds=bounds,
constraints=cdict,
tol=0.00001)
return ans['x']
def generate_fitting_dates(data, rollyears):
start_date=data.index[0]
end_date=data.index[-1]
yearstarts=list(pd.date_range(start_date, end_date, freq="12M"))+[end_date]
periods=[]
for tidx in range(len(yearstarts))[1:-1]:
period_start=yearstarts[tidx]
period_end=yearstarts[tidx+1]
fit_start=start_date
fit_end=period_start
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):
weightlist=[]
for unused_index in range(monte_carlo):
bs_idx=[int(random.uniform(0,1)*len(returns_to_bs)) \
for i in range(monte_length)]
returns=returns_to_bs.iloc[bs_idx,:]
weight=markosolver(returns, default_vol=default_vol, default_SR=default_SR)
weightlist.append(weight)
theweights_mean=list(np.mean(weightlist, axis=0))
return theweights_mean
def optimise_over_periods(data,rollyears, monte_carlo,monte_length):
fit_periods=generate_fitting_dates(data, rollyears=rollyears)
weight_list=[]
for fit_tuple in fit_periods:
period_subset_data=data[fit_tuple[0]:fit_tuple[1]]
weights=bootstrap_portfolio(period_subset_data,
monte_carlo=monte_carlo,
monte_length=monte_length,
default_vol=0.2, default_SR=1.0 )
dindex=[fit_tuple[2]+datetime.timedelta(seconds=1),
fit_tuple[3]-datetime.timedelta(seconds=1)]
weight_row=pd.DataFrame([weights]*2,
index=dindex,
columns=data.columns)
weight_list.append(weight_row)
weight_df=pd.concat(weight_list, axis=0)
return weight_df
import zipfile, pandas as pd, random
symbols = ['SP500','NASDAQ','US20']
df = pd.DataFrame()
with zipfile.ZipFile('../tser_voltar/legacycsv.zip', 'r') as z:
for symbol in symbols:
f = '%s_price.csv' % symbol
df[symbol] = pd.read_csv(z.open(f),sep=',',
index_col=0,
parse_dates=True)['PRICE']
df['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')]
random.seed(0)
w=boot.optimise_over_periods(df)
w.plot()
plt.savefig('tser_port_01.png')
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.
Yukarı