- Python ile Portföy Yönetimi Dersleri
- 1. Giriş
- 2. Kullanılacak Kütüphaneler ve Sabit Değişkenler
- 3. Veri Kaynakları
- 4. Getiri
- 5. Risk
- 6. Portföy Getirisinin ve Riskinin Ölçülmesi
- 6.1. Portföy Getirisi
- 6.2. Portföy Getirisinin Ölçülmesi
- 6.3. Portföy Riski
- 6.4. Portföy Riskinin Ölçülmesi
- 6.5. Portföyün Çeşitlendirilmesi ve Riskin Düşürülmesi
- 6.6. Portföyde Hedeflenen Beklenen Getiriye Göre Ağırlıkların Optimize Edilmesi
- 6.7. Portföy Riskinin Ağırlıkların Optimize Edilmesi ile Minimize Edilmesi
- 7. Etkin Sınır
- 8. Portföy Performans Metrikleri
- 9. Riske Maruz Değer (VaR)
- 10. Portföy Optimizasyonu için Python Paketlerinin İncelenmesi
Amaç: Finans ve Portföy Yönetimi alanına Python programlama dili ile katkı sağlamak ve Türkçe kaynak yaratmak.
Varsayım: Okuyucu, Python programlama dilini rahatlıkla kullanabilmektedir. Aynı zamanda İstatistik ve portföy yönetimiyle ilgili terimlere ilişkin temel düzeyde bir bilgi birikimine sahiptir.
Dikkat: Kendi deneyimlerim ve araştırmalarım doğrultusunda sunulan bilgiler, yalnızca genel bilgilendirme amaçlıdır ve yatırım tavsiyesi olarak yorumlanmamalıdır.
Yararlandığım kaynaklar için teşekkür: Finansal Risk Yönetimi; Burak Saltoğlu, Yatırım El Kitabı-Sermaye Pazarları, Menkul Değerlere Yatırım ve Portföy Yönetimi; Mehmet Şükrü Tekbaş, Finansal Ekonometri; Nilgün Çil Yavuz. Ayrıca, piyasanın üstünde bir kaynağa sahip şirketim RiskTürk'e ve bu kaynakların yaratılmasında ciddi payları olan Product Manager'ımız S.K. ve Manager'ımız Göktay Öncel'e çok teşekkür ederim.
Derslerde, aşağıdaki kütüphaneler kullanılacaktır.
import yfinance as yf
from isyatirimhisse import veri_cek # opsiyonel
import pandas as pd
import numpy as np
import statsmodels.api as sm
from arch import arch_model
from scipy.optimize import minimize
from scipy.stats import norm
from pypfopt import EfficientFrontier, risk_models, expected_returns, plotting
from pypfopt.discrete_allocation import DiscreteAllocation, get_latest_prices
from empyrial import empyrial, Engine, get_report
from tabulate import tabulate # opsiyonel
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight') # opsiyonel
Derslerde, iş günü 250 ve risksiz faiz oranı 0.1 (%10) varsayılacaktır.
is_gunu = 250 # İş günü 252 olarak da girilebiliyor
risksiz_faiz_orani = 0.1
Hata almamak için yukarıdaki kütüphaneleri çalıştırmanızı ve sabit değişkenleri tanımlamanızı rica ederim.
yfinance
, Ran Aroussi tarafından geliştirilen açık kaynak bir Python kütüphanesidir ve Yahoo Finance üzerinde bulunan finansal verilere erişmek için kullanılmaktadır.
Derslerde ana veri kaynağı olarak yfinance
kütüphanesini kullanacağız. Bu nedenle, nihai fonksiyonu oluşturma aşamalarını göreceğiz ki pakete de hakim olalım.
THYAO.IS
hisse senedine ait günlük verileri yfinance
kütüphanesi ile çekelim ve veri çerçevesine aktaralım.
sembol = 'THYAO.IS'
baslangic_tarih = '2023-01-02'
bitis_tarih = '2023-07-29'
tarihsel = yf.download(tickers=sembol, start=baslangic_tarih, end=bitis_tarih)
veriler_df = pd.DataFrame(tarihsel).reset_index()
THYAO.IS
ve GARAN.IS
hisse senetlerine ait günlük verileri yfinance
kütüphanesi ile çekelim ve veri çerçevesine aktaralım.
semboller = ['THYAO.IS', 'GARAN.IS']
baslangic_tarih = '2023-01-02'
bitis_tarih = '2023-07-29'
veriler_df = pd.DataFrame()
for sembol in semboller:
tarihsel = yf.download(tickers=sembol, start=baslangic_tarih, end=bitis_tarih)
veriler_df_alt = pd.DataFrame(tarihsel).reset_index()
veriler_df_alt['Sembol'] = sembol.replace('.IS', '')
veriler_df = pd.concat([veriler_df,veriler_df_alt])
THYAO.IS
ve GARAN.IS
hisse senetlerine ait günlük verileri yfinance
kütüphanesi ile çekelim ve Date
ve Close
sütunlarını alıp bir veri çerçevesine semboller yan yana olacak şekilde ekleyelim.
semboller = ['THYAO.IS', 'GARAN.IS']
baslangic_tarih = '2023-01-02'
bitis_tarih = '2023-07-29'
liste = []
for sembol in semboller:
tarihsel = yf.download(tickers=sembol, start=baslangic_tarih, end=bitis_tarih)
veriler_df_alt = pd.DataFrame(tarihsel).reset_index()
veriler_df_alt = veriler_df_alt[['Date', 'Close']].rename(columns={'Close': f'{sembol.replace(".IS", "")}'})
liste.append(veriler_df_alt)
veriler_df = liste[0]
for i in range(1, len(liste)):
veriler_df = pd.merge(veriler_df, liste[i], on='Date', how='outer')
Ders boyunca veri çekeceğiz. Her defasında yukarıdaki kodları yazmak yorucu olacaktır. Süreci kolaylaştırmak adına bir fonksiyon yazabiliriz. Fonksiyonun ismine yfinance_veri_cek()
diyelim.
def yfinance_veri_cek(sembol, baslangic_tarih, bitis_tarih, frekans):
liste = []
for s in sembol:
tarihsel = yf.download(
tickers=s,
start=baslangic_tarih,
end=bitis_tarih,
interval=frekans
)
veriler_df_alt = pd.DataFrame(tarihsel).reset_index()
veriler_df_alt = veriler_df_alt[['Date','Adj Close']].rename(columns={'Date':'Tarih','Adj Close':f'{s.replace(".IS","")}'})
liste.append(veriler_df_alt)
veriler_df = liste[0]
for i in range(1,len(liste)):
veriler_df = pd.merge(veriler_df, liste[i], on='Tarih', how='outer')
return veriler_df
yfinance_veri_cek()
fonksiyonunu kullanarak THYAO.IS
ve GARAN.IS
hisse senetlerine ait aylık verileri çekelim.
semboller = ['THYAO.IS', 'GARAN.IS']
baslangic_tarih = '2023-01-01'
bitis_tarih = '2023-08-01'
frekans = '1mo'
"""
Bazı frekans bilgileri:
1h: 1 saatlik,
1d: 1 günlük,
1wk: 1 haftalık,
1mo: 1 aylık
"""
veriler_df = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
yfinance
kütüphanesi ile borsa endekslerine ait verileri de çekip kullanacağız fakat bilindiği üzere 2020 yılında endekslerden iki sıfır atıldı. yfinance
verilerinde ise herhangi bir düzeltme yapılmamış. Sonrasında bir düzeltme yapılırsa bu kısmı dikkate almayabilirsiniz. Tabi nihai fonksiyonun da güncellenmesi gerekir.
sembol = ['XU100.IS']
baslangic_tarih = '2020-01-02'
bitis_tarih = '2021-01-01'
frekans = '1d'
veriler_df = yfinance_veri_cek(
sembol = sembol,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
plt.plot(
'Tarih',
'XU100',
data=veriler_df,
color='red'
)
plt.title('BIST 100 Hatalı Veriler')
plt.show()
Aşağıdaki fonksiyonu, endeks verileri söz konusu olduğu zaman düzeltme amaçlı kullanacağız.
def endeks_basamak_kontrol(df_duzeltilecek):
tarih = '2020-07-27'
problem_df = df_duzeltilecek[df_duzeltilecek['Tarih'] < tarih]
normal_df = df_duzeltilecek[df_duzeltilecek['Tarih'] >= tarih]
xu_sutunlar = [xu for xu in problem_df.columns if xu.startswith('XU')]
for xu_sutun in xu_sutunlar:
for index, value in enumerate(problem_df[xu_sutun]):
problem_df.at[index, xu_sutun] = value / 100
veriler_df = pd.concat([problem_df, normal_df], ignore_index=True)
return veriler_df
Test edelim.
duzeltilmis_df = endeks_basamak_kontrol(df_duzeltilecek=veriler_df)
plt.plot(
'Tarih',
'XU100',
data=duzeltilmis_df,
color='red'
)
plt.title('BIST 100 Hatasız Veriler')
plt.show()
Fonksiyonları ayrı ayrı çalıştırmamak için yfinance_veri_cek()
ve endeks_basamak_kontrol()
fonksiyonlarını yfinance_veri_cek()
çatısında birleştirebiliriz.
def yfinance_veri_cek(sembol, baslangic_tarih, bitis_tarih, frekans):
liste = []
for s in sembol:
tarihsel = yf.download(
tickers=s,
start=baslangic_tarih,
end=bitis_tarih,
interval=frekans
)
veriler_df_alt = pd.DataFrame(tarihsel).reset_index()
veriler_df_alt = veriler_df_alt[['Date','Adj Close']].rename(columns={'Date':'Tarih','Adj Close':f'{s.replace(".IS","")}'})
liste.append(veriler_df_alt)
veriler_df = liste[0]
for i in range(1,len(liste)):
veriler_df = pd.merge(veriler_df, liste[i], on='Tarih', how='outer')
tarih = '2020-07-27'
df_problem = veriler_df[veriler_df['Tarih'] < tarih]
df_normal = veriler_df[veriler_df['Tarih'] >= tarih]
xu_sutunlar = [xu for xu in df_problem.columns if xu.startswith('XU')]
for xu_sutun in xu_sutunlar:
for index, value in enumerate(df_problem[xu_sutun]):
df_problem.at[index, xu_sutun] = value / 100
veriler_df = pd.concat([df_problem, df_normal], ignore_index=True)
return veriler_df
Test edelim.
semboller = ['XU100.IS','XU030.IS']
baslangic_tarih = '2020-01-02'
bitis_tarih = '2023-07-29'
frekans = '1d'
veriler_df = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
veriler_df = veriler_df.dropna()
plt.plot(
'Tarih',
'XU100',
data=veriler_df,
color='black'
)
plt.plot(
'Tarih',
'XU030',
data=veriler_df,
color='blue'
)
plt.title('Endekslerin (BIST 100 ve BIST 30) Günlük Veri Kontrolü')
plt.show()
Yukarıdaki fonksiyon günlük frekansta çalışmaktadır. Aylık frekans için fonksiyona son bir şekil verebiliriz. Diğer frekansları okuyucuya bırakıyorum. Derslerde, günlük ve aylık frekansları kullanacağız.
def yfinance_veri_cek(sembol, baslangic_tarih, bitis_tarih, frekans):
liste = []
for s in sembol:
tarihsel = yf.download(
tickers=s,
start=baslangic_tarih,
end=bitis_tarih,
interval=frekans
)
veriler_df_alt = pd.DataFrame(tarihsel).reset_index()
veriler_df_alt = veriler_df_alt[['Date','Adj Close']].rename(columns={'Date':'Tarih','Adj Close':f'{s.replace(".IS","")}'})
liste.append(veriler_df_alt)
veriler_df = liste[0]
for i in range(1,len(liste)):
veriler_df = pd.merge(veriler_df, liste[i], on='Tarih', how='outer')
if frekans == '1d':
tarih = '2020-07-27'
df_problem = veriler_df[veriler_df['Tarih'] < tarih]
df_normal = veriler_df[veriler_df['Tarih'] >= tarih]
xu_sutunlar = [xu for xu in df_problem.columns if xu.startswith('XU')]
for xu_sutun in xu_sutunlar:
for index, value in enumerate(df_problem[xu_sutun]):
df_problem.at[index, xu_sutun] = value / 100
veriler_df = pd.concat([df_problem, df_normal], ignore_index=True)
elif frekans == '1mo':
tarih = '2020-07-01'
df_problem = veriler_df[veriler_df['Tarih'] < tarih]
df_normal = veriler_df[veriler_df['Tarih'] >= tarih]
xu_sutunlar = [xu for xu in df_problem.columns if xu.startswith('XU')]
for xu_sutun in xu_sutunlar:
for index, value in enumerate(df_problem[xu_sutun]):
df_problem.at[index, xu_sutun] = value / 100
veriler_df = pd.concat([df_problem, df_normal], ignore_index=True)
return veriler_df
Test edelim.
semboller = ['XU100.IS','XU030.IS']
baslangic_tarih = '2020-01-01'
bitis_tarih = '2023-08-01'
frekans = '1mo'
df = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
df = df.dropna()
plt.plot(
'Tarih',
'XU100',
data=df,
color='black'
)
plt.plot(
'Tarih',
'XU030',
data=df,
color='blue'
)
plt.title('Endekslerin (BIST 100 ve BIST 30) Aylık Veri Kontrolü')
plt.show()
Derslerde sürekli olarak getiri hesaplayacağız. Bunu da tekrar tekrar hesaplamamak için fonksiyona ekleyelim.
def yfinance_veri_cek(sembol, baslangic_tarih, bitis_tarih, frekans, getiri=True, log_getiri=True):
liste = []
for s in sembol:
tarihsel = yf.download(
tickers=s,
start=baslangic_tarih,
end=bitis_tarih,
interval=frekans
)
veriler_df_alt = pd.DataFrame(tarihsel).reset_index()
veriler_df_alt = veriler_df_alt[['Date','Adj Close']].rename(columns={'Date':'Tarih','Adj Close':f'{s.replace(".IS","")}'})
liste.append(veriler_df_alt)
veriler_df = liste[0]
for i in range(1,len(liste)):
veriler_df = pd.merge(veriler_df, liste[i], on='Tarih', how='outer')
if frekans == '1d':
tarih = '2020-07-27'
df_problem = veriler_df[veriler_df['Tarih'] < tarih]
df_normal = veriler_df[veriler_df['Tarih'] >= tarih]
xu_sutunlar = [xu for xu in df_problem.columns if xu.startswith('XU')]
for xu_sutun in xu_sutunlar:
for index, value in enumerate(df_problem[xu_sutun]):
df_problem.at[index, xu_sutun] = value / 100
veriler_df = pd.concat([df_problem, df_normal], ignore_index=True)
elif frekans == '1mo':
tarih = '2020-07-01'
df_problem = veriler_df[veriler_df['Tarih'] < tarih]
df_normal = veriler_df[veriler_df['Tarih'] >= tarih]
xu_sutunlar = [xu for xu in df_problem.columns if xu.startswith('XU')]
for xu_sutun in xu_sutunlar:
for index, value in enumerate(df_problem[xu_sutun]):
df_problem.at[index, xu_sutun] = value / 100
veriler_df = pd.concat([df_problem, df_normal], ignore_index=True)
veriler_df = veriler_df.set_index('Tarih')
if getiri and log_getiri:
veriler_df = veriler_df.apply(lambda x: np.log(x / x.shift(1)))
elif getiri and not log_getiri:
veriler_df = veriler_df.apply(lambda x: x / x.shift(1) - 1)
elif not getiri:
veriler_df
veriler_df = veriler_df.reset_index()
veriler_df = veriler_df.dropna()
veriler_df = veriler_df.reset_index(drop=True)
return veriler_df
Test edelim.
# Getiri hesaplasın ve logaritmik getiri olsun
semboller = ['XU100.IS', 'THYAO.IS']
baslangic_tarih = '2018-06-01'
bitis_tarih = '2023-08-01'
frekans = '1mo'
veriler_df = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
# Getiri hesaplasın ve basit getiri olsun
semboller = ['XU100.IS', 'THYAO.IS']
baslangic_tarih = '2018-06-01'
bitis_tarih = '2023-08-01'
frekans = '1mo'
log_getiri = False
veriler_df = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans,
log_getiri = log_getiri
)
# Getiri hesaplamasın
semboller = ['XU100.IS', 'THYAO.IS']
baslangic_tarih = '2018-06-01'
bitis_tarih = '2023-08-01'
frekans = '1mo'
getiri = False
veriler_df = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans,
getiri = getiri
)
Alternatif bir veri kaynağı olan İş Yatırım
'ın web sitesinden verileri almak için isyatirimhisse
kütüphanesi kullanılabilir. Elde edilecek çıktılar ile yfinance_veri_cek()
fonksiyonuna ait çıktılar aynı olacaktır.
- PyPI: https://pypi.org/project/isyatirimhisse/
- GitHub Repo: https://github.com/urazakgul/isyatirimhisse
Getiri, bir finansal varlığın zaman içinde elde ettiği kazanç veya geliri ifade eder ve genellikle yüzde olarak ifade edilir.
Bir varlığın
Yukarıdaki getiri hesaplamasında sadece sermaye kazancı (değer artışı) dikkate alınmış olup hisse senetleri için temettü ödemesi (kar payı) dikkate alınmamıştır.
Temettü ödemesi dahil edilmiş formül aşağıdaki gibi olacaktır.
Yukarıdaki formülde, sol taraf sermaye kazancının getirisini, sağ taraf ise temettü getirisini temsil etmektedir.
Örneğin, bir hisse senedini Haziran 2023'te 100 ₺'den aldığımızı ve aynı hisse senedini Temmuz 2023'te 120 ₺'den sattığımızı varsayalım. İlgili dönemde de herhangi bir temettü ödemesi olmasın.
1 aylık basit getiri,
Örneğimizden devam edelim ve aynı dönemde 5 ₺'lik bir temettü ödemesinin yapıldığını varsayalım. Bu durumda ise getiri,
Finansal varlığın,
Örneğin, bir hisse senedini Mayıs 2023'te 80 ₺'den aldığımızı ve aynı hisse senedini Temmuz 2023'te 120 ₺'den sattığımızı varsayalım. Haziran 2023 fiyatı 100 ₺ idi.
2 aylık basit getiriyi iki yoldan heaplayabiliriz.
Birincisi, basit getiri şeklinde hesaplayabiliriz.
İkincisi, bileşik getiriyi kullanabiliriz.
Mayıs ayından Haziran ayına:
Haziran ayından Temmuz ayına:
Logaritmik getiri, getirinin sürekli bileşik getiri olarak kabul edilmesini sağlar. Bu nedenle, farklı dönemlerin getirileri toplandığında daha doğru sonuçlar elde edilir.
Temettü ödemesi dikkate alındığında formül aşağıdaki gibi olacaktır.
Örneğin, bir hisse senedini Haziran 2023'te 100 ₺'den aldığımızı ve aynı hisse senedini Temmuz 2023'te 120 ₺'den sattığımızı varsayalım. İlgili dönemde de herhangi bir temettü ödemesi olmasın.
1 aylık logaritmik getiri,
Örneğimizden devam edelim ve aynı dönemde 5 ₺'lik bir temettü ödemesinin yapıldığını varsayalım. Bu durumda ise getiri,
THYAO.IS
hisse senedinin Aralık/2021 ile Aralık/2022 tarihleri arasındaki aylık fiyatlarından logaritmik getiriyi hesaplayalım.
sembol = ['THYAO.IS']
baslangic_tarih = '2021-12-01'
bitis_tarih = '2023-01-01'
frekans = '1mo'
veriler_df = yfinance_veri_cek(
sembol = sembol,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
THYAO.IS
hisse senedinin 2022 yılı aylık verilerinden yıllık sürekli bileşik getirisini THYAO
sütununu toplayarak hesaplayabiliriz. Aynı zamanda, benzer sonuca getirilerin ortalamasını aylık frekansta veriler olduğu için 12 ile çarptığımızda da ulaşabiliriz.
yillik_surekli_bilesik_getiri_toplam = (veriler_df['THYAO'].sum()) * 100
yillik_surekli_bilesik_getiri_ortalama_12 = (veriler_df['THYAO'].mean() * 12) * 100
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['THYAO',yillik_surekli_bilesik_getiri_toplam,yillik_surekli_bilesik_getiri_ortalama_12]],
headers=['Yıllık Sürekli Bileşik Getiri (Toplam), %','Yıllık Sürekli Bileşik Getiri (Çarpım, 12), %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Nominal getiri, bir finansal varlığın kazandırdığı mutlak miktarı gösterirken, reel getiri, enflasyonun yarattığı satın alma gücü kaybını hesaba katarak gerçek getiriyi ölçer.
Yukarıda, logaritmik getiriden enflasyon oranını çıkarmış oluyoruz.
Daha önce hesapladığımız THYAO.IS
hisse senedine ait aylık logaritmik getirileri enflasyondan arındıralım.
# Ocak/2022 - Aralık/2022 Aylık Enflasyon Verileri
enflasyon = pd.Series([11.1,4.81,5.46,7.25,2.98,4.95,2.37,1.46,3.08,3.54,2.88,1.18]) / 100
veriler_df = pd.concat([veriler_df, enflasyon.to_frame(name='Enflasyon')], axis=1)
THYAO
sütunu değerlerinden Enflasyon
sütunu değerlerini çıkardığımızda reel getirilere ulaşacağız.
veriler_df['ReelGetiri'] = veriler_df['THYAO'] - veriler_df['Enflasyon']
yillik_surekli_bilesik_reel_getiri = (veriler_df['ReelGetiri'].sum()) * 100
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['THYAO',yillik_surekli_bilesik_getiri_toplam,yillik_surekli_bilesik_reel_getiri]],
headers=['Yıllık Sürekli Bileşik Getiri, %','Yıllık Sürekli Bileşik Reel Getiri, %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Yıllık sürekli bileşik getiri enflasyondan arındırılınca %144.1'e düşmüştür.
Nominal getiri ile reel getiri arasındaki farkı görsel olarak da inceleyebiliriz.
plt.plot(
'Tarih',
'THYAO',
data=veriler_df,
color='blue',
label='Nominal Getiri'
)
plt.plot(
'Tarih',
'ReelGetiri',
data=veriler_df,
color='red',
label='Reel Getiri'
)
plt.ylabel('Getiri (%)', fontsize='12')
plt.title('THYAO Hisse Senedi Aylık Getirileri (Ocak/2022 - Aralık/2022)', fontsize='14')
plt.legend(fontsize='xx-small', loc='lower right')
plt.show()
Beklenen Getiri, geçmiş getirilerden yola çıkarak hesaplanan ve gelecekte beklenen getiridir.
Çalışacağımız verileri alalım ve beklenen getiri hesaplama yöntemlerine bakalım.
sembol = ['THYAO.IS']
baslangic_tarih = '2018-08-16'
bitis_tarih = '2023-07-29'
frekans = '1d'
veriler_df = yfinance_veri_cek(
sembol = sembol,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
plt.hist(
veriler_df['THYAO'] * 100,
color='red',
bins='auto'
)
plt.title('THYAO Getirileri (16/08/2018 - 28/07/2023)')
plt.yticks([])
plt.axvline(x=0, color='black', linestyle='--')
plt.show()
Bu yöntemde, geçmiş getirilerin ortalamasını alıyoruz.
beklenen_getiri_ortalama_gunluk = (veriler_df['THYAO'].mean()) * 100
Yukarıda hesaplanan getiri günlüktür.
Bunu, iki yöntemden biri ile yıllıklandırabiliriz.
Birincisi, günlük beklenen getiriyi iş günü ile çarpmak olabilir.
beklenen_getiri_ortalama_yillik_1 = beklenen_getiri_ortalama_gunluk * is_gunu
İkincisi, aşağıdaki formülü kullanmak olabilir.
beklenen_getiri_ortalama_yillik_2 = ((1 + beklenen_getiri_ortalama_gunluk/100)**is_gunu - 1) * 100
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['THYAO',beklenen_getiri_ortalama_gunluk,beklenen_getiri_ortalama_yillik_1,beklenen_getiri_ortalama_yillik_2]],
headers=['Beklenen Getiri (Günlük), %','Beklenen Getiri (Yıllık), %','Beklenen Getiri (Yıllık), %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Bu yöntem, belirli bir durumun gerçekleşmesi olasılığına göre farklı senaryoların ağırlıklarını dikkate alarak beklenen değeri tahmin etmeyi sağlar.
Aşağıda, THYAO.IS
hisse senedi için 12 analistin 1 yıllık fiyat tahminleri bulunmaktadır.
İki tane durum belirleyelim: Yükselecek ve Düşecek. Bu olasılıklar ise sırasıyla %55 ve %45 olsun. Bu senaryoda beklenen getiri,
durum_dusecek_olasilik = 0.45
getiri_dusecek = -0.4659
durum_yukselecek_olasilik = 0.55
getiri_yukselecek = 0.4879
cari_fiyat = 198.2
beklenen_getiri = (durum_dusecek_olasilik * getiri_dusecek) + (durum_yukselecek_olasilik * getiri_yukselecek)
beklenen_fiyat = cari_fiyat * (1 + beklenen_getiri)
Beklenen getiri ve beklenen fiyat aşağıdaki gibi olacaktır.
print(tabulate(
[[
'THYAO',
durum_dusecek_olasilik*100,
getiri_dusecek*100,
durum_yukselecek_olasilik*100,
getiri_yukselecek*100,
beklenen_getiri*100,
cari_fiyat,
beklenen_fiyat
]],
headers=[
'Düşme\nOlasılığı, %',
'Beklenen\nDüşme, %',
'Yükselme\nOlasılığı, %',
'Beklenen\nYükselme, %',
'Beklenen\nGetiri, %',
'Cari\nFiyat, TL',
'Beklenen\nFiyat, TL'
],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
CAPM (Capital Asset Pricing Model), finansal varlıkların beklenen getirisini hesaplamak ve fiyatlandırmak için kullanılan bir modeldir. CAPM, varlıkların getirisi ile sistematik risk (piyasa riski) arasındaki ilişkiyi tanımlar.
Uygulamada, hisse senedi THYAO.IS
, endeks XU100.IS
olacaktır.
sembol = ['XU100.IS','THYAO.IS']
baslangic_tarih = '2018-07-17'
bitis_tarih = '2023-07-29'
frekans = '1d'
veriler_df = yfinance_veri_cek(
sembol = sembol,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
zaman = range(len(veriler_df))
plt.scatter(
'XU100',
'THYAO',
data=veriler_df,
c=zaman,
cmap='Reds'
)
plt.xlabel('BIST 100')
plt.ylabel('THYAO')
plt.title('BIST 100 ve THYAO Getirileri (17/07/2018 - 28/07/2023)')
plt.show()
Beklenen getiri ve beta değerini hesaplayacak fonksiyonları yazalım.
def beklenen_getiri_hesapla(df, hisse_senedi, endeks, risksiz_faiz_orani, is_gunu):
piyasa_getirisi = df[endeks].mean() * is_gunu
beta = beta_hesapla(df, hisse_senedi, endeks)
beklenen_getiri = risksiz_faiz_orani + beta * (piyasa_getirisi - risksiz_faiz_orani)
beklenen_getiri *= 100
return beklenen_getiri
def beta_hesapla(df, hisse_senedi, endeks):
X = df[endeks]
X = sm.add_constant(X)
y = df[hisse_senedi]
model = sm.OLS(y, X).fit()
beta = model.params[endeks]
return beta
Beklenen getiriyi hesaplayalım.
beklenen_getiri = beklenen_getiri_hesapla(
df = veriler_df,
hisse_senedi = 'THYAO',
endeks = 'XU100',
risksiz_faiz_orani = risksiz_faiz_orani,
is_gunu = is_gunu
)
Piyasa getirisi, XU100'ün ortalaması olarak alındığı için buradan ~0.002 gelecektir. Yıllıklandırılmış değeri ise ~0.41 olacaktır. Bunun yanında beta ~1.2 hesaplanacaktır. Yani, beta değeri 1.2 olan bir hisse senedinin (örneğimizde THYAO), piyasa endeksi olan XU100'deki %1'lik bir artışta, %1.2 oranında artması beklenir. Aynı şekilde, piyasa endeksi düşerse, hisse senedi de daha fazla düşebilir. Değerleri yerine koyarsak,
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['THYAO',beklenen_getiri]],
headers=['Beklenen Getiri (Yıllık), %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Risk, belirsizlikle ilişkili olumsuz sonuçların ortaya çıkma olasılığıdır ve bir yatırımın veya finansal bir enstrümanın beklenen getirisine veya değerine ilişkin belirsizlikleri olarak ifade edilebilir. Riski, piyasa riski ve şirkete özgü risk toplamı şeklinde yazabiliriz. Piyasa riski, finansal piyasalardaki genel dalgalanmalardan kaynaklanan riski ifade eder. Bu risk genellikle tüm piyasaları ve şirketleri etkileyebilir. Piyasa riski, makroekonomik faktörler, siyasi olaylar, faiz oranlarındaki değişiklikler, döviz kurlarındaki dalgalanmalar, emtia fiyatlarındaki değişimler gibi geniş çaplı etkenlerden kaynaklanır. Örnek bir piyasa riski senaryosu, genel ekonomik durumun kötüye gitmesi ve tüketici harcamalarının düşmesiyle ortaya çıkabilir. Bu durumda, birçok şirketin satışları azalabilir, karlılıkları etkilenebilir ve hisse senetlerinin değeri düşebilir. Bu tür bir piyasa riski, genellikle tüm endüstrilerde faaliyet gösteren şirketleri etkiler. Şirkete özgü risk ise belirli bir şirketin faaliyetleri, yönetimi, finansal durumu veya sektöründeki faktörler gibi şirketle ilgili özel faktörlerden kaynaklanan riski ifade eder. Bu risk, yalnızca belirli bir şirketi etkiler ve genellikle diğer şirketlerden farklıdır. Örnek olarak, bir şirketin ürününün talebinde ani bir düşüş yaşaması veya pazar payını kaybetmesi, şirkete özgü riske örnek olabilir. Ayrıca, bir şirketin yönetim ekibindeki hatalı kararlar veya operasyonel sorunlar da şirkete özgü riske katkıda bulunabilir. Bu tür bir risk, genellikle belirli bir sektörde veya şirkette faaliyet gösteren yatırımcıları etkiler, diğer sektörler veya şirketler üzerinde aynı etkiyi doğurmaz.
Risk ve volatilite arasında yakın bir ilişki vardır, çünkü volatilite, finansal piyasalardaki fiyat dalgalanmalarının ölçüsüdür ve riskin bir göstergesidir. Bir finansal varlık veya piyasa ne kadar hızlı ve büyük fiyat değişiklikleri gösterirse, o kadar yüksek bir volatilite seviyesine sahip olduğu söylenir.
Volatilite, gerçekleşen (tarihi) ve tahmini olmak üzere iki türe ayrılabilir.
THYAO.IS
hisse senedinin logaritmik getirilerinden aşağıdaki yöntemleri kullanarak volatilite hesaplayalım.
sembol = ['THYAO.IS']
baslangic_tarih = '2018-08-16'
bitis_tarih = '2023-07-29'
frekans = '1d'
veriler_df = yfinance_veri_cek(
sembol = sembol,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
Varyans, bir veri kümesindeki değerlerin ortalama değerinden ne kadar uzaklaştığını ölçen bir istatistiksel ölçümdür. Varyans, her veri noktasının ortalama değerden farkını alıp bu farkların karelerinin ortalamasını hesaplayarak elde edilir. Varyans, veri noktalarının dağılımının ne kadar geniş olduğunu gösterir.
Varyans, aşağıdaki gibi hesaplanabilir.
varyans = np.var(veriler_df['THYAO'], ddof=1)
Standart sapma varyansın kareköküdür. Standart sapma, veri noktalarının ortalama değerden ne kadar uzaklaştığını ölçerken, birimlerin orijinal veri birimleriyle uyumlu olmasını sağlar. Standart sapma, varyansın karekökü olarak hesaplanır ve genellikle varyansın yanında kullanılır.
Standart sapma, varyansa ait değerin karekökü olacaktır.
standart_sapma = np.sqrt(varyans) * 100
# veya
standart_sapma = np.std(veriler_df['THYAO'], ddof=1) * 100
Standart sapmayı yıllıklandırabiliriz.
standart_sapma_yillik = standart_sapma * np.sqrt(is_gunu)
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['THYAO',standart_sapma,standart_sapma_yillik]],
headers=['Standart Sapma (Günlük), %','Standart Sapma (Yıllık), %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Yukarıda, 250 ile çarpmak yerine 250'nin karekökü ile çarptık. Bunu aşağıdaki gibi açıklayabiliriz.
EWMA (Exponentially Weighted Moving Average), verilerin zaman içindeki değişimlerini takip etmek ve olaylara hızla tepki verebilmek için kullanılan bir istatistiksel yöntemdir. Önceki verilere daha az ağırlık verirken, daha yeni verilere daha fazla ağırlık vererek hesaplanır.
EWMA hesaplamak için aşağıdaki formül kullanılır:
Logaritmik getirileri kullanarak EWMA değerini hesaplayalım. Bunun için bir fonksiyon yazabiliriz.
def ewma_volatilite_hesapla(getiri, lambda_deger=0.94):
getiri_kare = getiri**2
agirlik = [1 - lambda_deger]
for i in range(1, len(getiri_kare)):
agirlik.append(agirlik[i-1] * lambda_deger)
agirlik_getiri = getiri_kare * np.array(agirlik)
ewma_varyans = np.sum(agirlik_getiri)
ewma_volatilite = ewma_varyans**0.5
return ewma_volatilite
Sırasıyla günlük ve yıllık EWMA değerlerine bakalım.
ewma_volatilite_gunluk = ewma_volatilite_hesapla(veriler_df['THYAO']) * 100
ewma_volatilite_yillik = ewma_volatilite_gunluk * np.sqrt(is_gunu)
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['THYAO',ewma_volatilite_gunluk,ewma_volatilite_yillik]],
headers=['EWMA (Günlük), %','EWMA (Yıllık), %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Hareketli EWMA volatilite hesaplayalım.
test_sayisi = 250
ewma_vol = []
for i in range(test_sayisi, len(veriler_df)):
veriler_df_alt = veriler_df['THYAO'][i - test_sayisi:i]
ewma_deger = ewma_volatilite_hesapla(getiri=veriler_df_alt)
ewma_vol.append(ewma_deger)
veriler_df_ewma = veriler_df.copy()
veriler_df_ewma = veriler_df_ewma.iloc[test_sayisi:, :]
veriler_df_ewma['Tarih'] = veriler_df['Tarih'].iloc[test_sayisi:]
veriler_df_ewma['EWMA_Vol'] = ewma_vol
plt.plot(
'Tarih',
'THYAO',
data=veriler_df_ewma,
color='blue',
alpha=.3,
linewidth=.5,
label='Logaritmik Getiri'
)
plt.plot(
'Tarih',
'EWMA_Vol',
data=veriler_df_ewma,
color='red',
linewidth=1,
label='Volatilite'
)
plt.axhline(y=0, linewidth=1, color='black')
plt.title('THYAO Hisse Senedine Ait EWMA Volatilite', fontsize=14)
plt.xticks(rotation=45)
plt.legend(fontsize=12)
plt.show()
GARCH (Generalized Autoregressive Conditional Heteroskedasticity), volatilitenin zaman içinde değişebileceğini ve geçmiş volatilitenin gelecekteki volatilite üzerinde etkisi olduğunu kabul eder. Bu model, zaman serisi verilerindeki heteroskedastisiteyi (varyansın değişken olması) ele alır ve varyansın otoregresif bir yapıya sahip olduğunu varsayar.
Bu uygulamada, GARCH(1,1) modelini kullanacağız.
def garch_volatilite_hesapla(getiri):
model = arch_model(getiri, vol='Garch', p=1, q=1)
model_fit = model.fit()
forecast = model_fit.forecast(horizon=1)
garch_volatilite = np.sqrt(forecast.variance.values[-1, :][0])
return garch_volatilite
Sırasıyla günlük ve yıllık GARCH değerlerine bakalım.
garch_volatilite_gunluk = garch_volatilite_hesapla(veriler_df['THYAO']) * 100
garch_volatilite_yillik = garch_volatilite_gunluk * np.sqrt(is_gunu)
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['THYAO',garch_volatilite_gunluk,garch_volatilite_yillik]],
headers=['GARCH(1,1) (Günlük), %','GARCH(1,1) (Yıllık), %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Hareketli tahmin yapalım.
test_sayisi = 250
garch_tahminler = []
for i in range(test_sayisi, len(veriler_df)):
train = veriler_df['THYAO'][i - test_sayisi:i]
garch_model = arch_model(train, vol='Garch', p=1, q=1)
model_fit = garch_model.fit(disp='off')
tahmin = model_fit.forecast(horizon=1)
garch_tahminler.append(np.sqrt(tahmin.variance.values[-1,:][0]))
veriler_df_garch = veriler_df.copy()
veriler_df_garch = veriler_df_garch.iloc[test_sayisi:, :]
veriler_df_garch['Tarih'] = veriler_df['Tarih'].iloc[test_sayisi:]
veriler_df_garch['GARCH_Tahmin'] = garch_tahminler
plt.plot(
'Tarih',
'THYAO',
data=veriler_df_garch,
color='blue',
alpha=.3,
linewidth=.5,
label='Logaritmik Getiri'
)
plt.plot(
'Tarih',
'GARCH_Tahmin',
data=veriler_df_garch,
color='red',
linewidth=1,
label='Volatilite Tahmini'
)
plt.axhline(y=0, linewidth=1, color='black')
plt.title('THYAO Hisse Senedi GARCH(1,1) Volatilite Tahminleri', fontsize=14)
plt.xticks(rotation=45)
plt.legend(fontsize=12)
plt.show()
Elimizde, 200 ₺'den aldığımız 1 lot THYAO ve 40 ₺'den aldığımız 1 lot EREGL hisse senetlerinin olduğunu varsayalım ve bu hisse senetlerini sırasıyla 250 ₺'den ve 70 ₺'den satmış olalım. Bu işlemden karımız ve işlemin getirisi ne olmuştur?
Getirileri ise bireysel olarak hesaplayalım.
Peki, portföyün getirisi (
Eğer hesapladığımız iki getiriyi toplarsak yanlış hesaplamış oluruz. Yani, portföyün getirisi
Biz, varsayımımızda 200 ₺'den THYAO ve 40 ₺'den EREGL aldık. Her ne kadar EREGL hisse senedinden %75 getiri elde etsek de bu hisse senedinin portföydeki payı
O halde, yukarıdaki işlemi nasıl yazabiliriz?
Yukarıda
Daha önce karımızı 80₺ olarak hesaplamıştık. Eğer karı toplam yatırıma bölersek yine yaklaşık olarak %34 değerine (portföy getiri) ulaşırız.
2 varlıklı bir portföy için getiri hesaplamasını aşağıdaki gibi genelleştirebiliriz.
Daha genel bir ifadeyle, k tane varlığın olduğu bir portföyün getirisini aşağıdaki gibi yazabiliriz.
Portföyümüzde şu 10 adet hisse senedi olsun: AEFES, ASELS, BIMAS, EREGL, FROTO, GARAN, PETKM, SISE, THYAO ve TOASO.
semboller=['AEFES.IS', 'ASELS.IS', 'BIMAS.IS', 'EREGL.IS', 'FROTO.IS', 'GARAN.IS', 'PETKM.IS', 'SISE.IS', 'THYAO.IS', 'TOASO.IS']
baslangic_tarih='2018-08-16'
bitis_tarih='2023-07-29'
portfoy = yfinance_veri_cek(
sembol=semboller,
baslangic_tarih=baslangic_tarih,
bitis_tarih=bitis_tarih,
frekans='1d'
)
portfoy = portfoy.set_index('Tarih')
portfoy.describe()
Ağırlıkları eşit olacak şekilde belirleyelim.
hisse_senedi_n = len(semboller)
agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
Çıktı aşağıdaki gibi olacaktır.
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
Beklenen getiriyi hesaplayacak fonksiyonu yazalım.
def portfoy_getiri(df, agirliklar, yuzde=True, yilliklandir=True, isgunu=250):
portfoy_beklenen_getiri_gunluk = np.sum(agirliklar * df.mean())
if yuzde:
portfoy_beklenen_getiri_gunluk *= 100
else:
portfoy_beklenen_getiri_gunluk
if yilliklandir:
portfoy_beklenen_getiri_yillik = portfoy_beklenen_getiri_gunluk * isgunu
else:
portfoy_beklenen_getiri_yillik = None
return portfoy_beklenen_getiri_gunluk, portfoy_beklenen_getiri_yillik
Çalıştırabiliriz.
portfoy_beklenen_getiri_gunluk, portfoy_beklenen_getiri_yillik = portfoy_getiri(df=portfoy, agirliklar=agirliklar)
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['Portföy-10',portfoy_beklenen_getiri_gunluk,portfoy_beklenen_getiri_yillik]],
headers=['Beklenen Getiri (Günlük), %','Beklenen Getiri (Yıllık), %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Hisse senetlerinin beklenen getirilerini de hesaplayalım.
hisse_senedi_beklenen_getiri = (portfoy.mean() * is_gunu) * 100
Şimdi görselleştirebiliriz.
df_portfoy_10 = {
'Hisse Senedi': portfoy.columns,
'Beklenen Getiri': hisse_senedi_beklenen_getiri
}
df_portfoy_10 = pd.DataFrame(df_portfoy_10).reset_index(drop=True)
df_portfoy_10_portfoy = pd.DataFrame({'Hisse Senedi': ['PORTFÖY-10'], 'Beklenen Getiri': [portfoy_beklenen_getiri_yillik]})
df_portfoy_10 = pd.concat([df_portfoy_10, df_portfoy_10_portfoy]).reset_index(drop=True)
df_portfoy_10_sirala = df_portfoy_10.sort_values(by='Beklenen Getiri')
plt.barh(
'Hisse Senedi',
'Beklenen Getiri',
data=df_portfoy_10_sirala,
color=['red' if x == 'PORTFÖY-10' else 'gray' for x in df_portfoy_10_sirala['Hisse Senedi']]
)
plt.xlabel('Yıllıklandırılmış Beklenen Getiri', fontsize = 12)
plt.title('Hisse Senetleri ve Portföy Beklenen Getirileri', fontsize = 14)
plt.show()
Portföy getirisinde olduğu gibi portföy riskinde de risk için hesapladığımız standart sapmaları toplayıp portföy riskine ulaşamayız.
İki varlıklı portföyün riskini aşağıdaki gibi yazabiliriz.
Yukarıda,
Standart sapma ise aşağıdaki gibi olacaktır.
Örneğin, portföyümüzde bulunan 1 lot THYAO ve 1 lot EREGL hisse senetlerinin ağırlıkları %60 ve %40, standart sapmaları %30 ve %25 ve kovaryans değeri 0.0005 olsun. Bu portföyün riski ne olur?
Portföydeki varlık sayısı üç olduğunda portföyün riski aşağıdaki gibi olacaktır.
Varyansın karekökünü aldığımızda standart sapmaya ulaşıyorduk.
Portföyde k adet varlık söz konusu olduğu zaman aşağıdaki gibi yazabiliriz.
Burada,
Portföyümüzde şu 10 adet hisse senedi olsun: AEFES, ASELS, BIMAS, EREGL, FROTO, GARAN, PETKM, SISE, THYAO ve TOASO.
semboller=['AEFES.IS', 'ASELS.IS', 'BIMAS.IS', 'EREGL.IS', 'FROTO.IS', 'GARAN.IS', 'PETKM.IS', 'SISE.IS', 'THYAO.IS', 'TOASO.IS']
baslangic_tarih='2018-08-16'
bitis_tarih='2023-07-29'
portfoy = yfinance_veri_cek(
sembol=semboller,
baslangic_tarih=baslangic_tarih,
bitis_tarih=bitis_tarih,
frekans='1d'
)
portfoy = portfoy.set_index('Tarih')
portfoy.describe()
Ağırlıkları eşit olacak şekilde belirleyelim.
hisse_senedi_n = len(semboller)
agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
Çıktı aşağıdaki gibi olacaktır.
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
Riski hesaplayacak fonksiyonu yazalım.
def portfoy_risk(df, agirliklar, yuzde=True, yilliklandir=True, isgunu=250):
varkov_matris = df.cov()
portfoy_varyans = np.dot(np.transpose(agirliklar), np.dot(varkov_matris, agirliklar))
portfoy_standart_sapma_gunluk = np.sqrt(portfoy_varyans)
if yuzde:
portfoy_standart_sapma_gunluk *= 100
else:
portfoy_standart_sapma_gunluk
if yilliklandir:
portfoy_standart_sapma_yillik = portfoy_standart_sapma_gunluk * np.sqrt(isgunu)
else:
portfoy_standart_sapma_yillik = None
return portfoy_standart_sapma_gunluk, portfoy_standart_sapma_yillik
Çalıştıralım.
portfoy_standart_sapma_gunluk, portfoy_standart_sapma_yillik = portfoy_risk(df=portfoy, agirliklar=agirliklar)
Sonuçları bir tabloda toparlayalım.
print(tabulate(
[['Portföy-10',portfoy_standart_sapma_gunluk,portfoy_standart_sapma_yillik]],
headers=['Risk (Günlük), %','Risk (Yıllık), %'],
tablefmt='fancy_grid',
stralign='center',
numalign='center',
floatfmt='.1f'
))
Hisse senedi özelinde de standart sapmalara bakalım.
hisse_senedi_standart_sapma = (np.std(portfoy) * np.sqrt(is_gunu)) * 100
Yukarıda görüldüğü üzere çeşitlendirilmiş portföyün standart sapması hisse senetlerinin bireysel standart sapmalarından daha düşüktür.
Standart sapmaları görselleştirelim.
df_portfoy_10 = {
'Hisse Senedi': portfoy.columns,
'Standart Sapma': hisse_senedi_standart_sapma
}
df_portfoy_10 = pd.DataFrame(df_portfoy_10).reset_index(drop=True)
df_portfoy_10_portfoy = pd.DataFrame({'Hisse Senedi': ['PORTFÖY-10'], 'Standart Sapma': [portfoy_standart_sapma_yillik]})
df_portfoy_10 = pd.concat([df_portfoy_10, df_portfoy_10_portfoy]).reset_index(drop=True)
df_portfoy_10_sirala = df_portfoy_10.sort_values(by='Standart Sapma')
plt.barh(
'Hisse Senedi',
'Standart Sapma',
data=df_portfoy_10_sirala,
color=['red' if x == 'PORTFÖY-10' else 'gray' for x in df_portfoy_10_sirala['Hisse Senedi']]
)
plt.xlabel('Yıllıklandırılmış Standart Sapma', fontsize = 12)
plt.title('Hisse Senetleri ve Portföy Standart Sapmaları', fontsize = 14)
plt.show()
Portföyün çeşitlendirilmesi ve riskin düşürülmesi, yatırımcıların portföylerini farklı varlık sınıflarında ve farklı şirketlerin hisse senetlerinde çeşitlendirerek risklerini dağıtmalarını ifade eder. Böylelikle, yatırımcılar tek bir hisse senedine veya sektöre aşırı şekilde bağımlı olmaktan kaçınarak portföylerini daha dengeli hale getirirler.
Örneğimizde, AEFES, ASELS, BIMAS, EREGL, FROTO, GARAN, PETKM, SISE, THYAO ve TOASO olmak üzere 10 adet hisse senedini almıştık. Portföyümüze hisse senedi ekledikçe riskin ne olabileceğine bakalım.
Bunun için öncelikle hisseleri birer artırarak portföye ekleyelim.
hisse_senetleri = [s.replace('.IS','') for s in semboller]
portfoyler = []
for i in range(1, len(hisse_senetleri) + 1):
portfoyler.append(hisse_senetleri[0:i])
Çıktı aşağıdaki gibi olacaktır.
[['AEFES'], ['AEFES', 'ASELS'], ['AEFES', 'ASELS', 'BIMAS'], ['AEFES', 'ASELS', 'BIMAS', 'EREGL'], ['AEFES', 'ASELS', 'BIMAS', 'EREGL', 'FROTO'], ['AEFES', 'ASELS', 'BIMAS', 'EREGL', 'FROTO', 'GARAN'], ['AEFES', 'ASELS', 'BIMAS', 'EREGL', 'FROTO', 'GARAN', 'PETKM'], ['AEFES', 'ASELS', 'BIMAS', 'EREGL', 'FROTO', 'GARAN', 'PETKM', 'SISE'], ['AEFES', 'ASELS', 'BIMAS', 'EREGL', 'FROTO', 'GARAN', 'PETKM', 'SISE', 'THYAO'], ['AEFES', 'ASELS', 'BIMAS', 'EREGL', 'FROTO', 'GARAN', 'PETKM', 'SISE', 'THYAO', 'TOASO']]
Portföye önce AEFES, sonra AEFES-ASELS, sonra AEFES-ASELS-BIMAS eklendi ve bu şekilde devam etmektedir. Şimdi her bir portföyün riskini daha önce hesapladığımız gibi hesaplayabiliriz.
portfoyler_risk = []
for p in portfoyler:
df = portfoy[p]
hisse_senedi_n = len(df.columns)
agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
varkov_matris = df.cov()
portfoy_varyans = np.dot(np.transpose(agirliklar), np.dot(varkov_matris, agirliklar))
portfoy_standart_sapma = np.sqrt(portfoy_varyans)
portfoy_standart_sapma_yillik = portfoy_standart_sapma * np.sqrt(is_gunu)
portfoyler_risk.append(portfoy_standart_sapma_yillik)
Çıktı aşağıdaki gibi olacaktır.
[0.3874127756261798,
0.34201414234857913,
0.29175300863030074,
0.2835333625137374,
0.2856976141596776,
0.2839223650934056,
0.28529635012630866,
0.2874982241043029,
0.29190074231683183,
0.2923724379922857]
Görselleştirelim ve riskin portföye hisse senedi eklendikçe nasıl değiştiğini görelim.
df_portfoyler_risk = pd.DataFrame({
'HisseSenediSayisi': range(1, len(portfoyler) + 1),
'YillikStandartSapma': portfoyler_risk
})
plt.plot(
'HisseSenediSayisi',
'YillikStandartSapma',
data=df_portfoyler_risk
)
plt.title('Hisse Senedi Sayısı ve Risk (Yıllıklandırılmış Standart Sapma)', fontsize=13)
plt.show()
Riskin, %39'dan başlayıp %34'e düştüğünü, ardından %28'lerde bir süre gidip son iki portföyde %29'lara bir miktar çıktığını görüyoruz. Çok genel bir ifadeyle, çeşitlendirme arttıkça riskin düştüğünü söyleyebiliriz ancak bu bizim örneğimizde hisse senedi ekledikçe riskin sürekli düşeceği anlamına gelmeyecek.
Portföyde ağırlıkların optimize edilmesi, bir yatırım portföyünün bileşen varlıklarının optimal dağılımını belirlemeyi ifade eder.
Portföyümüzde, THYAO
ve EREGL
hisse senetleri olsun. Hisse senetlerinin beklenen getirilerinin sırasıyla %55 ve %25 olduğunu ve beklediğimiz getirinin ise %35 olduğunu varsayalım.
Portföyün beklenen getirisi şudur:
Portföyümüzdeki hisse senetleri ile yazalım.
Aşağıdaki gibi tekrar yazabiliriz.
Değerleri yerine koyalım ve gerekli işlemleri yapalım.
Bakalım istediğimiz beklenen getiri olan %35'e ulaşabiliyor muyuz?
Daha genel bir ifadeyle, yatırımcının beklediği (hedeflenen) getiriyi aşağıdaki gibi yazabiliriz.
Uygulamamıza dönüp seçtiğimiz 10 adet hisse senedinin optimal ağırlıklarını bulalım.
Öncelikle beklenen getirileri hesaplayacak bir fonksiyon yazalım.
def portfoy_getiri(df, agirliklar, is_gunu=250):
portfoy_beklenen_getiri = np.dot(np.transpose(agirliklar), df.mean()) * is_gunu
return portfoy_beklenen_getiri
Her bir hisse senedinin başlangıç ağırlıkları verelim. Başlangıç ağırlıkları daha önce hesapladığımız gibi eşit olacaktır. Uygulamamızda 10 tane hisse senedi olduğu için her birinin ağırlığı 0.1 olacaktır.
hisse_senedi_n = len(portfoy.columns)
baslangic_agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
Test amaçlı portföyümüzün yıllıklandırılmış beklenen getirisi hesaplayalım.
portfoy_getiri(df=portfoy, agirliklar=baslangic_agirliklar, is_gunu=is_gunu)
Çıktı aşağıdaki gibi olacaktır.
0.4543850368681283
Fakat bizim hedeflediğimiz beklenen getirinin 0.5 veya %50 olduğunu varsayalım. Bunun için de bir fonksiyon yazalım.
def optimize_portfoy(df, baslangic_agirliklar, hedeflenen_beklenen_getiri, is_gunu=250):
def portfoy_getiri(agirliklar):
portfoy_beklenen_getiri = np.dot(np.transpose(agirliklar), df.mean()) * is_gunu
return portfoy_beklenen_getiri
hisse_senedi_n = len(baslangic_agirliklar)
sinirlar = tuple((0, 1) for i in range(hisse_senedi_n))
kisitlar = (
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1},
{'type': 'eq', 'fun': lambda x: x.dot(df.mean()) * is_gunu - hedeflenen_beklenen_getiri}
)
sonuc = minimize(fun=portfoy_getiri, x0=baslangic_agirliklar, bounds=sinirlar, constraints=kisitlar)
return sonuc
Yukarıda ilk satır, her bir ağırlık için 0 ile 1 arasında bir sınırlama belirler. Bu sınırlar, optimize edilen ağırlıkların bu değer aralığı içinde kalmasını sağlar. Böylece, optimize edilen ağırlıklar 0 ile 1 arasında olacak ve bu sınırlara uygun olacak şekilde hesaplanacaktır.
Yukarıda ikinci satırda, ilk kısıt, ağırlıkların toplamının 1 olmasını sağlamak için kullanılmaktadır. 'type': 'eq'
ifadesi, eşitlik kısıtını belirtir. lambda w: np.sum(w) - 1
ifadesi ise ağırlıkların toplamının 1 olmasını kontrol eden bir fonksiyon tanımlar. Bu fonksiyonun sonucunun 0 olması beklenir. İkinci kısıt, hedeflenen beklenen getiri değerini kontrol etmek için kullanılır. 'type': 'eq'
ifadesi yine eşitlik kısıtını belirtir. lambda x: x.dot(df.mean()) * is_gunu - hedeflenen_beklenen_getiri
ifadesi, ağırlıkların beklenen getirisiyle hedeflenen getiri değeri arasındaki farkı kontrol eden bir fonksiyonu temsil eder. Bu fonksiyonun sonucunun 0 olması beklenir.
Çalıştıralım.
hisse_senedi_n = len(portfoy.columns)
baslangic_agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
sonuc = optimize_portfoy(
df=portfoy,
baslangic_agirliklar=baslangic_agirliklar,
hedeflenen_beklenen_getiri=0.5,
is_gunu=is_gunu
)
Çıktı aşağıdaki gibi olacaktır.
message: Optimization terminated successfully
success: True
status: 0
fun: 0.49999999992878136
x: [ 6.018e-02 5.711e-02 8.394e-02 7.085e-02 1.781e-01
8.641e-02 4.517e-02 1.123e-01 1.385e-01 1.674e-01]
nit: 2
jac: [ 3.636e-01 3.566e-01 4.178e-01 3.879e-01 6.325e-01
4.234e-01 3.293e-01 4.825e-01 5.422e-01 6.081e-01]
nfev: 22
njev: 2
Çıktıda, fun
hedeflenen beklenen getiriyi ve x
ağırlıkları göstermektedir. Bu ağırlıklar ile portföyümüzün beklenen getirisini alalım ki bu değeri 0.5 veya %50 bekliyoruz.
portfoy_getiri(df=portfoy, agirliklar=sonuc['x'], is_gunu=is_gunu)
Çıktı, 0.49999999992878136
olacaktır.
Hisse senetlerinin aldığı ağırlıkları görselleştirelim.
df_portfoy_agirliklar = pd.DataFrame({
'HisseSenedi': portfoy.columns,
'Agirlik': sonuc['x']
})
df_portfoy_agirliklar_sirala = df_portfoy_agirliklar.sort_values(by='Agirlik')
plt.barh(
'HisseSenedi',
'Agirlik',
data=df_portfoy_agirliklar_sirala
)
plt.xlabel('Ağırlık', fontsize = 12)
plt.title('Hisse Senetleri ve Optimal Ağırlıkları', fontsize = 14)
plt.figtext(1, -0.05, 'Hedeflenen Beklenen Getiri %50', fontsize=10, ha='right', color='gray')
plt.show()
Portföy riskinin minimize edilmesi, yatırımcının portföyündeki risk düzeyini azaltmayı hedefleyen bir stratejidir.
Portföyümüzde, THYAO
ve EREGL
hisse senetleri olsun. Hisse senetlerinin toplam risklerinin sırasıyla %45 ve %35 ve kovaryanslarının 0.0005 olduğunu varsayalım. Riski minimize eden bir portföy yapalım.
Portföyün riski aşağıdaki gibi hesaplanır.
Hisse senetlerini yerine koyalım.
Toplam riskleri de yerleştirelim.
Risk aşağıda minimize olacaktır.
Değerleri yerine koyalım.
Portföyün riskine bakalım.
Varyansını bulduk. Standart sapmasına bakalım.
Bulduğumuz risk, hisse senetlerinin kendi risklerinden daha düşük çıkmıştır.
Uygulamamıza dönüp seçtiğimiz 10 adet hisse senedinden oluşturduğumuz portföyün riskini minimize edelim.
def portfoy_risk(df, agirliklar, is_gunu=250):
varkov_matris = df.cov()
portfoy_varyans = np.dot(np.transpose(agirliklar), np.dot(varkov_matris, agirliklar))
portfoy_standart_sapma = np.sqrt(portfoy_varyans)
portfoy_standart_sapma_yillik = portfoy_standart_sapma * np.sqrt(is_gunu)
return portfoy_standart_sapma_yillik
Optimize edecek bir fonksiyon yazalım.
def optimize_portfoy(df, hedeflenen_beklenen_getiri, is_gunu=250):
hisse_senedi_n = len(df.columns)
baslangic_agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
sinirlar = tuple((0, 1) for i in range(hisse_senedi_n))
kisitlar = ({'type': 'eq', 'fun': lambda w: np.sum(w) - 1})
def portfoy_getiri(agirliklar):
portfoy_beklenen_getiri = np.dot(np.transpose(agirliklar), df.mean()) * is_gunu
return portfoy_beklenen_getiri
def portfoy_risk(agirliklar):
varkov_matris = df.cov()
portfoy_varyans = np.dot(np.transpose(agirliklar), np.dot(varkov_matris, agirliklar))
portfoy_standart_sapma = np.sqrt(portfoy_varyans)
portfoy_standart_sapma_yillik = portfoy_standart_sapma * np.sqrt(is_gunu)
return portfoy_standart_sapma_yillik
sonuc = minimize(fun=portfoy_risk, x0=baslangic_agirliklar, bounds=sinirlar, constraints=kisitlar)
return sonuc
Çalıştıralım.
hisse_senedi_n = len(portfoy.columns)
sonuc = optimize_portfoy(
df=portfoy,
hedeflenen_beklenen_getiri=0.5,
is_gunu=is_gunu
)
Çıktı aşağıdaki gibi olacaktır.
message: Optimization terminated successfully
success: True
status: 0
fun: 0.2677300832650254
x: [ 1.882e-01 2.601e-02 3.896e-01 1.481e-01 4.878e-02
8.505e-02 4.819e-02 1.800e-02 6.370e-19 4.797e-02]
nit: 7
jac: [ 2.675e-01 2.676e-01 2.679e-01 2.679e-01 2.677e-01
2.675e-01 2.676e-01 2.679e-01 2.701e-01 2.676e-01]
nfev: 77
njev: 7
Çıktıda, fun
portföyün riskini ve x
ağırlıkları göstermektedir.
Hisse senetlerini eşit ağırlıklar ile portföye ekleseydik alacağımız risk aşağıdaki gibi olacaktı.
portfoy_risk(df=portfoy, agirliklar=baslangic_agirliklar, is_gunu=is_gunu)
Çıktı, 0.2923724379922857
olacaktır. Yani, %29.2'lik bir risk ölçtük.
portfoy_risk(df=portfoy, agirliklar=sonuc['x'], is_gunu=is_gunu)
Hisse senetlerini, ağırlıklarını optimize ederek portföye eklersek de alacağımız risk 0.2677300832650254
olacaktır. Yani, %26.8 risk olup eşit ağırlıklı portföyden daha azdır.
Hisse senetlerinin aldığı ağırlıkları görselleştirelim.
df_portfoy_agirliklar = pd.DataFrame({
'HisseSenedi': portfoy.columns,
'Agirlik': sonuc['x']
})
df_portfoy_agirliklar_sirala = df_portfoy_agirliklar.sort_values(by='Agirlik')
plt.barh(
'HisseSenedi',
'Agirlik',
data=df_portfoy_agirliklar_sirala
)
plt.xlabel('Ağırlık', fontsize = 12)
plt.title('Hisse Senetleri ve Optimal Ağırlıkları', fontsize = 14)
plt.figtext(1, -0.05, 'Portföy riski minimize edilmiştir', fontsize=10, ha='right', color='gray')
plt.show()
Modern Portföy Teorisi (MPT) tarafından öne sürülen etkin sınır, farklı varlıkların veya yatırımların farklı getiri ve risk profillerine sahip olduğu durumlarda, bu varlıkların farklı ağırlıklarla bir araya getirilerek en iyi toplam portföyün elde edilebileceği bir grafiksel gösterimdir. Etkin sınır, yatırımcıların risk ve getiri tercihlerini dikkate alarak en iyi portföy kombinasyonunu belirlemelerine yardımcı olur.
Etkin sınır, iki temel bileşenin birleşiminden oluşur:
i. Verimlilik Çizgisi (Efficient Frontier): Bu, farklı risk düzeylerinde elde edilebilecek maksimum getiriyi temsil eden bir eğridir. Daha yüksek getiri için daha fazla risk almak gerektiği, düşük riskle ancak daha düşük getiriyle daha güvenli yatırımlar yapılabileceği şeklinde ifade edilebilir.
ii. Riskli Varlıkların ve Risk-Free Varlığın (Risk-Free Asset) Birleşimi: Etkin sınır, riskli varlıkların (örneğin, hisse senetleri veya tahviller gibi) belirli ağırlıklarla birleştirilmesiyle elde edilebilecek portföyleri gösterir. Ayrıca, riskli varlıklarla risksiz bir varlık (genellikle riskli olmayan devlet tahvilleri gibi kabul edilir) arasında bir bileşim de içerir. Böylece, belirli bir risk düzeyinde yatırımcıların bekledikleri maksimum getiriyi elde edebilecekleri noktalar etkin sınıra denk gelir.
Türk borsasından çıkalım ve bu uygulamada S&P 500 endeksinden şu 10 adet hisse senedini kullanalım: AAPL, AMZN, GOOGL, META, MSFT, NFLX, NVDA, TSLA, WMT ve XOM.
Risksiz faiz oranını 0 varsayalım.
semboller=['AAPL', 'AMZN', 'GOOGL', 'META', 'MSFT', 'NFLX', 'NVDA', 'TSLA', 'WMT', 'XOM']
baslangic_tarih='2018-08-08'
bitis_tarih='2023-07-29'
portfoy = yfinance_veri_cek(
sembol=semboller,
baslangic_tarih=baslangic_tarih,
bitis_tarih=bitis_tarih,
frekans='1d'
)
portfoy = portfoy.set_index('Tarih')
Uygulamada 5000 adet portföy oluşturacağız. Portföylerde yer alan hisse senetlerine ait ağırlıklar rassal seçilecek.
np.random.seed(34)
portfoy_sayisi = 5000
tum_agirliklar = np.zeros((portfoy_sayisi, len(portfoy.columns)))
getiriler = np.zeros(portfoy_sayisi)
riskler = np.zeros(portfoy_sayisi)
sharpe_oranlari = np.zeros(portfoy_sayisi)
for i in range(portfoy_sayisi):
agirliklar = np.array(np.random.random(len(portfoy.columns)))
agirliklar = agirliklar / np.sum(agirliklar)
tum_agirliklar[i,:] = agirliklar
getiriler[i] = np.sum((portfoy.mean() * agirliklar * is_gunu))
riskler[i] = np.sqrt(np.dot(agirliklar.T, np.dot(portfoy.cov() * is_gunu, agirliklar)))
sharpe_oranlari[i] = getiriler[i] / riskler[i]
Maksimum Sharpe Oranı ve bu oranın konumuna bakalım.
print(f'Maksimum Sharpe Oranı: {sharpe_oranlari.max()}')
print(f'Maksimum Sharpe Oranının Konumu: {sharpe_oranlari.argmax()}')
Çıktı aşağıdaki gibi olacaktır.
Maksimum Sharpe Oranı: 0.8934701385137666
Maksimum Sharpe Oranının Konumu: 3269
Yani, en iyi portföyün ~0.89 Sharpe oranı ile 3269. indekste olduğunu söyleyebiliriz. Bu indeks ya da portföydeki hisse senetlerine ait ağırlıklara bakalım.
print(tum_agirliklar[sharpe_oranlari.argmax(), :])
Çıktı aşağıdaki gibi olacaktır.
[0.1554362 0.01960557 0.04405643 0.00208792 0.06859701 0.01086181
0.01673748 0.23522273 0.29690283 0.15049203]
Etkin sınıra bakalım.
maks_sharpe_getiri = getiriler[sharpe_oranlari.argmax()]
maks_sharpe_risk = riskler[sharpe_oranlari.argmax()]
plt.scatter(
riskler,
getiriler,
c = sharpe_oranlari,
cmap='viridis'
)
plt.colorbar(label='Sharpe Oranı')
plt.xlabel('Risk')
plt.ylabel('Getiri')
plt.scatter(
maks_sharpe_risk,
maks_sharpe_getiri,
c='red',
s=50
)
plt.show()
Portföy ağırlıklarını optimize edelim.
Önce aşağıdaki fonksiyonları tanımlayacağız.
i. Getiri, risk ve sharpe oranını veren fonksiyon
ii. Negatif Sharpe Oranını hesaplayan fonksiyon
iii. Ağırlıkların toplamını kontrol edecek fonksiyon
def getiri_risk_sharpe(agirliklar):
agirliklar = np.array(agirliklar)
getiri = np.sum(portfoy.mean() * agirliklar) * is_gunu
risk = np.sqrt(np.dot(agirliklar.T, np.dot(portfoy.cov() * is_gunu, agirliklar)))
sharpe_orani = getiri / risk
return np.array([getiri, risk, sharpe_orani])
def negatif_sharpe(agirliklar):
return -getiri_risk_sharpe(agirliklar=agirliklar)[2]
def agirlik_toplam_kontrol(agirliklar):
return np.sum(agirliklar) - 1
Kısıtları oluşturalım.
hisse_senedi_n = len(portfoy.columns)
kisitlar = ({'type': 'eq', 'fun': agirlik_toplam_kontrol})
sinirlar = tuple((0, 1) for i in range(hisse_senedi_n))
baslangic_agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
Çalıştıralım.
sonuc = minimize(fun=negatif_sharpe, x0=baslangic_agirliklar, method='SLSQP', bounds=sinirlar, constraints=kisitlar)
Çıktı aşağıdaki gibi olacaktır.
message: Optimization terminated successfully
success: True
status: 0
fun: -0.9681806506065556
x: [ 3.123e-01 1.705e-16 0.000e+00 0.000e+00 2.279e-02
0.000e+00 1.079e-01 1.652e-01 3.917e-01 1.246e-16]
nit: 10
jac: [-1.614e-04 6.205e-01 2.367e-01 4.828e-01 1.081e-04
7.658e-01 4.973e-05 4.950e-04 -1.000e-04 3.698e-02]
nfev: 112
njev: 10
Getiri, risk ve Sharpe oranını alalım.
getiri_risk_sharpe(sonuc.x)
Çıktı aşağıdaki gibi olacaktır.
array([0.26461927, 0.273316 , 0.96818065])
Yaklaşık 0.97'lik bir Sharpe oranı almış olduk ki bu ilk bulduğumuz 0.89'luk Sharpe oranından daha yüksektir.
Portföy performansı, bir yatırım portföyünün belirli bir zaman aralığında elde ettiği getirinin ölçüsüdür. Bu, portföyde bulunan varlıkların fiyat değişimleri ve elde edilen gelirlerle belirlenir. Yatırımcılar, portföy performansını değerlendirmek için genellikle getiri ve risk metriklerini kullanırlar.
Portföy performanslarını değerlendirirken portföylerin taşıdığı risk düzeyi büyük bir önem arz eder ve bu nedenle getirilerin içerdikleri risk faktörlerine göre düzeltilerek değerlendirilmeleri gerekmektedir.
Metrikleri incelerken şu senaryoda üreteceğimiz verileri kullanacağız: 10 adet hisse senedini kullanarak 10 farklı portföy oluşturacağız. Bu 10 farklı portföy 3 ile 7 arasında bulunan rassal olarak seçilmiş adette hisse senetlerinden oluşacak.
np.random.seed(34)
def rastgele_hisseler():
semboller = ['AEFES.IS', 'ASELS.IS', 'BIMAS.IS', 'EREGL.IS', 'FROTO.IS', 'GARAN.IS', 'PETKM.IS', 'SISE.IS', 'THYAO.IS', 'TOASO.IS']
adet = np.random.randint(3, 8)
return np.random.choice(semboller, adet, replace=False)
portfoyler = []
for i in range(10):
portfoy_hisseleri = rastgele_hisseler()
portfoyler.append(portfoy_hisseleri)
baslangic_tarih='2018-08-16'
bitis_tarih='2023-07-29'
frekans = '1d'
portfoy_verileri = []
for portfoy in portfoyler:
df_portfoy = yfinance_veri_cek(
sembol=portfoy,
baslangic_tarih=baslangic_tarih,
bitis_tarih=bitis_tarih,
frekans=frekans
)
df_portfoy.set_index('Tarih', inplace=True)
portfoy_verileri.append(df_portfoy)
portfoy_getirileri = []
for df_portfoy in portfoy_verileri:
df_getiri = np.log(df_portfoy / df_portfoy.shift(1)).dropna()
portfoy_getirileri.append(df_getiri)
Portföyün getirisi ile risksiz faiz (risksiz getiri) arasındaki fark (ek getiri) portföyün standart sapma ile ölçülen riskine bölünür.
Portföyün risksiz faiz üzerinde sağlamış olduğu getiri aynı risk düzeyine sahip olan portföyler arasında bir sıralama imkanı verir.
10 adet portföyün Sharpe oranlarını hesaplayıp karşılaştıralım.
portfoy_isimleri = []
sharpe_oranlari = []
for i, portfoy in enumerate(portfoy_getirileri, 1):
portfoy_isim = f"Portföy_{i}"
portfoy_isimleri.append(portfoy_isim)
hisse_senedi_n = len(portfoy.columns)
agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
portfoy_beklenen_getiri = np.dot(np.transpose(agirliklar), portfoy.mean()) * is_gunu
varkov_matris = portfoy.cov()
portfoy_varyans = np.dot(np.transpose(agirliklar), np.dot(varkov_matris, agirliklar))
portfoy_standart_sapma = np.sqrt(portfoy_varyans) * np.sqrt(is_gunu)
sharpe_orani = (portfoy_beklenen_getiri - risksiz_faiz_orani) / portfoy_standart_sapma
sharpe_oranlari.append(sharpe_orani)
df_sharpe = pd.DataFrame({
'Portfoy': portfoy_isimleri,
'Sharpe Orani': sharpe_oranlari
})
Görselleştirelim.
df_sharpe_sirala = df_sharpe.sort_values(by='Sharpe Orani')
plt.barh(
'Portfoy',
'Sharpe Orani',
data=df_sharpe_sirala
)
plt.xlabel('Sharpe Oranı', fontsize = 12)
plt.title('Portföyler ve Sharpe Oranları', fontsize = 14)
plt.show()
Treynor oranının Sharpe oranından farkı, portföy getirisi ile risksiz faiz oranı arasındaki farkın sistematik riski temsil eden beta (
10 adet portföyün Treynor oranlarını hesaplayıp karşılaştıralım.
portfoy_isimleri = []
treynor_oranlari = []
endeks = ['XU100.IS']
benchmark_veri = yfinance_veri_cek(
sembol=endeks,
baslangic_tarih=baslangic_tarih,
bitis_tarih=bitis_tarih,
frekans=frekans
)
benchmark_veri['XU100_Getiri'] = np.log(benchmark_veri['XU100'] / benchmark_veri['XU100'].shift(1))
benchmark_veri = benchmark_veri.drop(columns=['XU100']).dropna()
benchmark_veri = benchmark_veri.set_index('Tarih')
for i, portfoy in enumerate(portfoy_getirileri, 1):
portfoy_isim = f"Portföy_{i}"
portfoy_isimleri.append(portfoy_isim)
hisse_senedi_n = len(portfoy.columns)
agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
portfoy_beklenen_getiri = np.dot(np.transpose(agirliklar), portfoy.mean()) * is_gunu
df_birlestir = pd.concat([portfoy, benchmark_veri], axis=1).dropna()
X = sm.add_constant(df_birlestir.iloc[:, 1])
y = df_birlestir.iloc[:, 0]
model = sm.OLS(y, X).fit()
beta = model.params[1]
treynor_orani = (portfoy_beklenen_getiri - risksiz_faiz_orani) / beta
treynor_oranlari.append(treynor_orani)
df_treynor = pd.DataFrame({
'Portfoy': portfoy_isimleri,
'Treynor Orani': treynor_oranlari
})
Görselleştirelim.
df_treynor_sirala = df_treynor.sort_values(by='Treynor Orani')
plt.barh(
'Portfoy',
'Treynor Orani',
data=df_treynor_sirala
)
plt.xlabel('Treynor Oranı', fontsize = 12)
plt.title('Portföyler ve Treynor Oranları', fontsize = 14)
plt.show()
VaR (Value at Risk), bir portföyün belirli bir güven aralığında, belirli bir yatırım vadesinde karşı karşıya kalabileceği en yüksek kaybın parasal değeridir.
VaR, aşağı yönlü riskleri öngörmeyi amaçlayan bir ölçüttür.
Yukarıda,
Üç temel VaR yöntemi bulunmaktadır:
i. Parametrik
ii. Tarihsel Simülasyon
iii. Monte Carlo
Portföyde 1 adet hisse senedi olsun.
sembol=['THYAO.IS']
baslangic_tarih='2022-07-28'
bitis_tarih='2023-07-29'
frekans='1d'
portfoy = yfinance_veri_cek(
sembol = sembol,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
portfoy = portfoy.drop('Tarih', axis=1)
Fonksiyonu yazalım.
def var_parametrik(portfoy_deger, getiriler, guven_duzeyi, elde_tutma_suresi):
portfoy_standart_sapma = np.std(getiriler)
z_degeri = norm.ppf(guven_duzeyi)
var = portfoy_deger * portfoy_standart_sapma * z_degeri * np.sqrt(elde_tutma_suresi)
return var
Çalıştıralım.
portfoy_deger=235000
getiriler=portfoy['THYAO']
guven_duzeyi=0.95
elde_tutma_suresi=1
var_parametrik_sonuc = var_parametrik(
portfoy_deger=portfoy_deger,
getiriler=getiriler,
guven_duzeyi=guven_duzeyi,
elde_tutma_suresi=elde_tutma_suresi
)
print(f"Parametrik VaR: {var_parametrik_sonuc:.2f}")
print(f"{elde_tutma_suresi} gün sonrasının kaybı %{guven_duzeyi*100} olasılıkla {var_parametrik_sonuc:.2f} ₺'yi geçmeyecektir. Yüzdesel olarak, alınan 100 ₺'lik hisse pozisyonu {var_parametrik_sonuc / portfoy_deger * 100:.2f} ₺'lik bir VaR üretmektedir.")
Çıktı aşağıdaki gibi olacaktır.
Parametrik VaR: 12754.36
1 gün sonrasının kaybı %95.0 olasılıkla 12754.36 ₺'yi geçmeyecektir. Yüzdesel olarak, alınan 100 ₺'lik hisse pozisyonu 5.43 ₺'lik bir VaR üretmektedir.
Yukarıda 1 günlük elde tutma süresi ile hesapladık. Peki, 10 günlük elde tutma süresi olsaydı ne olurdu?
portfoy_deger=235000
getiriler=portfoy['THYAO']
guven_duzeyi=0.95
elde_tutma_suresi=10
var_parametrik_sonuc = var_parametrik(
portfoy_deger=portfoy_deger,
getiriler=getiriler,
guven_duzeyi=guven_duzeyi,
elde_tutma_suresi=elde_tutma_suresi
)
print(f"Parametrik VaR: {var_parametrik_sonuc:.2f}")
print(f"{elde_tutma_suresi} gün sonrasının kaybı %{guven_duzeyi*100} olasılıkla {var_parametrik_sonuc:.2f} ₺'yi geçmeyecektir. Yüzdesel olarak, alınan 100 ₺'lik hisse pozisyonu {var_parametrik_sonuc / portfoy_deger * 100:.2f} ₺'lik bir VaR üretmektedir.")
Çıktı aşağıdaki gibi olacaktır.
Parametrik VaR: 40332.83
10 gün sonrasının kaybı %95.0 olasılıkla 40332.83 ₺'yi geçmeyecektir. Yüzdesel olarak, alınan 100 ₺'lik hisse pozisyonu 17.16 ₺'lik bir VaR üretmektedir.
Portföyde 10 adet hisse senedi olsun. Hisse senedi sayısı birden fazla olduğu için portföyün standart sapmasını hesaplamamız gerekecektir.
semboller=['AEFES.IS', 'ASELS.IS', 'BIMAS.IS', 'EREGL.IS', 'FROTO.IS', 'GARAN.IS', 'PETKM.IS', 'SISE.IS', 'THYAO.IS', 'TOASO.IS']
baslangic_tarih='2022-07-28'
bitis_tarih='2023-07-29'
frekans='1d'
portfoy = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
portfoy = portfoy.drop('Tarih', axis=1)
Fonksiyonu yazalım.
def portfoy_standart_sapma(df, agirliklar):
varkov_matris = df.cov()
portfoy_varyans = np.dot(np.transpose(agirliklar), np.dot(varkov_matris, agirliklar))
portfoy_standart_sapma = np.sqrt(portfoy_varyans)
return portfoy_standart_sapma
hisse_senedi_n = len(semboller)
agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
def var_parametrik(portfoy_deger, guven_duzeyi, elde_tutma_suresi):
portfoy_ss = portfoy_standart_sapma(df=portfoy, agirliklar=agirliklar)
z_degeri = norm.ppf(guven_duzeyi)
var = portfoy_deger * portfoy_ss * z_degeri * np.sqrt(elde_tutma_suresi)
return var
Çalıştıralım.
portfoy_deger=100000
guven_duzeyi=0.95
elde_tutma_suresi=1
var_parametrik_sonuc = var_parametrik(
portfoy_deger=portfoy_deger,
guven_duzeyi=guven_duzeyi,
elde_tutma_suresi=elde_tutma_suresi
)
print(f"Parametrik VaR: {var_parametrik_sonuc:.2f}")
print(f"{elde_tutma_suresi} gün sonrasının kaybı %{guven_duzeyi*100} olasılıkla {var_parametrik_sonuc:.2f} ₺'yi geçmeyecektir. Yüzdesel olarak, alınan 100 ₺'lik hisse pozisyonu {var_parametrik_sonuc / portfoy_deger * 100:.2f} ₺'lik bir VaR üretmektedir.")
Çıktı aşağıdaki gibi olacaktır.
Parametrik VaR: 4110.93
1 gün sonrasının kaybı %95.0 olasılıkla 4110.93 ₺'yi geçmeyecektir. Yüzdesel olarak, alınan 100 ₺'lik hisse pozisyonu 4.11 ₺'lik bir VaR üretmektedir.
Yukarıda 1 günlük elde tutma süresi ile hesapladık. Peki, 10 günlük elde tutma süresi olsaydı ne olurdu?
portfoy_deger=100000
guven_duzeyi=0.95
elde_tutma_suresi=10
var_parametrik_sonuc = var_parametrik(
portfoy_deger=portfoy_deger,
guven_duzeyi=guven_duzeyi,
elde_tutma_suresi=elde_tutma_suresi
)
print(f"Parametrik VaR: {var_parametrik_sonuc:.2f}")
print(f"{elde_tutma_suresi} gün sonrasının kaybı %{guven_duzeyi*100} olasılıkla {var_parametrik_sonuc:.2f} ₺'yi geçmeyecektir. Yüzdesel olarak, alınan 100 ₺'lik hisse pozisyonu {var_parametrik_sonuc / portfoy_deger * 100:.2f} ₺'lik bir VaR üretmektedir.")
Çıktı aşağıdaki gibi olacaktır.
Parametrik VaR: 12999.90
10 gün sonrasının kaybı %95.0 olasılıkla 12999.90 ₺'yi geçmeyecektir. Yüzdesel olarak, alınan 100 ₺'lik hisse pozisyonu 13.00 ₺'lik bir VaR üretmektedir.
Parametrik VaR hesaplamasında kullanılan volatilite, standart sapma yerine EWMA, GARCH veya Örtük (Implied) de olabilir.
Tarihsel simülasyon yöntemi, parametrik yöntemin aksine herhangi bir parametrik dağılım kullanmaz. Bu yöntem ile portföyün son hali geçmiş N zaman boyunca sabit tutulur. Böylece, hipotetik getiriler geçmiş dönem fiyatlarından türetilir.
Tarihsel simülasyonun çalışma sıralaması şöyledir: Portföydeki varlıkların getirileri hesaplanır ve bu getiriler ağırlıklandırılır. Ağırlıklandırılan bu değerler ise portföyün bugünkü değeri ile çarpılıp toplanır. Toplanan değerler arasından en kötü N. değere bakılır. Örneğin, 250 günlük bir seride %99 güven düzeyinde VaR aranıyorsa, bu en kötü %1'inci değer olup 250 x 0.01 sonucundan 2.5'inci değere denk gelmektedir. Burada, farklı yaklaşımlar mevcuttur. Bir yol, 2 ile 3'ün ortalaması alınabilir. Diğer bir yol yukarıya yuvarlamak olabilir.
Portföyde 2 adet hisse senedi olsun.
semboller=['THYAO.IS','EREGL.IS']
baslangic_tarih='2022-07-28'
bitis_tarih='2023-07-29'
frekans='1d'
portfoy = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
portfoy = portfoy.drop('Tarih', axis=1)
Fonksiyonu yazalım.
def var_tarihsel_simulasyon(df, agirliklar, portfoy_deger, guven_duzeyi, elde_tutma_suresi):
portfoy_agirlikli_getiri = (df * agirliklar).sum(axis=1) * portfoy_deger
portfoy_agirlikli_getiri = portfoy_agirlikli_getiri.sort_values()
sira = int(len(portfoy_agirlikli_getiri) * (1 - guven_duzeyi))
en_kotu_n_deger = portfoy_agirlikli_getiri.iloc[sira]
var = en_kotu_n_deger * np.sqrt(elde_tutma_suresi)
return var
Çalıştıralım.
df=portfoy
hisse_senedi_n = len(semboller)
agirliklar = [1 / hisse_senedi_n] * hisse_senedi_n
portfoy_deger=100000
guven_duzeyi=0.99
elde_tutma_suresi=1
var_tarihsel_simulasyon_sonuc = var_tarihsel_simulasyon(
df=df,
agirliklar=agirliklar,
portfoy_deger=portfoy_deger,
guven_duzeyi=guven_duzeyi,
elde_tutma_suresi=elde_tutma_suresi
)
print(f"Tarihsel Simülasyon VaR: {np.abs(var_tarihsel_simulasyon_sonuc):.2f} ₺")
print(f"{elde_tutma_suresi} gün sonrasının kaybı %{guven_duzeyi*100} olasılıkla {np.abs(var_tarihsel_simulasyon_sonuc):.2f} ₺'yi geçmeyecektir.")
Çıktı aşağıdaki gibi olacaktır.
Tarihsel Simülasyon VaR: 7178.16 ₺
1 gün sonrasının kaybı %99.0 olasılıkla 7178.16 ₺'yi geçmeyecektir.
Monte Carlo yöntemi, rastgele sayılar üreterek ve olası farklı senaryoları modelleyerek VaR hesaplamalarını gerçekleştirir.
Rastgele sayıları üretmeden önce Cholesky Ayrışımı yöntemine değinmek yerinde olabilir.
Cholesky Ayrışımı, rastgele değişkenleri korelasyonlu olarak simüle etmek için kullanılan bir yöntemdir. Monte Carlo simülasyonunda gerçekte olan ilişkileri daha iyi temsil etmek adına korelasyonu tanıtmak için kullanılır.
semboller=['THYAO.IS','EREGL.IS']
baslangic_tarih='2022-07-28'
bitis_tarih='2023-07-29'
frekans='1d'
portfoy_getiri = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans
)
portfoy_getiri = portfoy_getiri.drop('Tarih', axis=1)
portfoy_fiyat = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = frekans,
getiri=False
)
portfoy_fiyat = portfoy_fiyat.drop('Tarih', axis=1).iloc[1:].reset_index(drop=True)
Korelasyon matrisini hesaplayalım
korelasyon_matrisi = portfoy_getiri.corr()
Cholesky faktorizasyonu kullanarak korelasyon matrisine dönüştürelim.
cholesky_faktor = np.linalg.cholesky(korelasyon_matrisi)
1 000 adet rastgele getiriler oluşturalım ve korelasyon matrisini kullanarak dönüştürelim.
np.random.seed(34)
simulasyon_n = 1000
rastgele_sayilar = np.random.normal(size=(simulasyon_n, len(portfoy_getiri.columns)))
simulasyon_getiriler = pd.DataFrame(np.matmul(rastgele_sayilar, cholesky_faktor.T), columns=portfoy_getiri.columns, index=range(simulasyon_n))/100
Korelasyonu kontrol edelim.
simulasyon_getiriler.corr()
Son fiyatları alalım. Çünkü bu fiyatlar üzerinden simülasyon yapacağız.
son_fiyatlar = {}
for sutun in portfoy_fiyat.columns:
hisse_ismi = sutun
son_fiyat = portfoy_fiyat[sutun].iloc[-1]
son_fiyatlar[hisse_ismi] = son_fiyat
hisseler = portfoy_getiri.columns.tolist()
suruklenmeler = [portfoy_getiri[hisse].mean() for hisse in hisseler]
difuzyonlar = [np.std(portfoy_getiri[hisse]) for hisse in hisseler]
Elde tutma süresini belirleyelim.
dt = 1
GBM hesaplayacak fonksiyonu yazalım.
def gbm(baslangic_fiyat, suruklenme, difuzyon):
rastgele_sayilar = np.random.normal(size=simulasyon_n)
getiriler = baslangic_fiyat * (suruklenme * dt) + baslangic_fiyat * (difuzyon * np.sqrt(dt) * rastgele_sayilar)
return getiriler
Hesaplayalım.
for hisse, baslangic_fiyat, suruklenme, difuzyon in zip(hisseler, son_fiyatlar.values(), suruklenmeler, difuzyonlar):
getiriler = gbm(baslangic_fiyat, suruklenme, difuzyon)
simulasyon_getiriler[hisse + '_Sim'] = baslangic_fiyat + getiriler
VaR hesaplama aşamasına geçebiliriz. Nominal ve portföy değerini belirleyelim.
nominal = 1000
portfoy_deger = sum(son_fiyatlar[hisse_ismi] * nominal for hisse_ismi in son_fiyatlar)
sim_columns = [sutun for sutun in simulasyon_getiriler.columns if sutun.endswith('_Sim')]
simulasyon_getiriler['Portfoy_Sim'] = simulasyon_getiriler[sim_columns].sum(axis=1) * nominal
simulasyon_getiriler['Degisim'] = simulasyon_getiriler['Portfoy_Sim'] - portfoy_deger
VaR sonucuna bakalım.
var = np.abs(np.percentile(simulasyon_getiriler['Degisim'].iloc[1:], 1))
print(f"VaR ({dt} günlük elde tutma süresi):", round(var, ndigits=2),"₺'dir.")
Çıktı aşağıdaki gibi olacaktır.
VaR (1 günlük elde tutma süresi): 17643.19 ₺'dir.
BIST 100'den seçtiğimiz 10 adet hisse senedi ile paketi inceleyelim.
semboller = ['AEFES.IS', 'ASELS.IS', 'BIMAS.IS', 'EREGL.IS', 'FROTO.IS', 'GARAN.IS', 'PETKM.IS', 'SISE.IS', 'THYAO.IS', 'TOASO.IS']
baslangic_tarih = '2018-08-16'
bitis_tarih = '2023-07-29'
portfoy = yfinance_veri_cek(
sembol = semboller,
baslangic_tarih = baslangic_tarih,
bitis_tarih = bitis_tarih,
frekans = '1d',
getiri=False
)
portfoy = portfoy.set_index('Tarih')
Beklenen getiri ve varyans-kovaryansı hesaplayalım.
beklenen_getiri = expected_returns.mean_historical_return(
prices = portfoy,
returns_data = False,
frequency = 250,
compounding = False,
log_returns = True
)
Yukarıda, prices
parametresi verilerin ham halini (fiyatları) almaktadır. Eğer veri seti getirilerden oluşuyorsa returns_data
parametresi True
değerini almalıdır. Değeri False
olursa ham halini kullanacaktır. İş günü varsayılan olarak 252
'dir ancak biz 250
kullandığımız için değiştirebiliriz. Eğer geometrik ortalama hesaplanmak isteniyorsa compounding
parametresi varsayılan değer olan True
olarak bırakılmalıdır. Değeri False
olursa aritmetik olarak hesaplayacaktır. Son parametre olan log_returns
, fiyatların logaritmik getiri cinsinden hesaplanıp hesaplanmayacığını belirlemektedir. Değeri True
olursa logaritmik olarak hesaplayacaktır. Aksi halde, False
olarak ayarlanmalıdır.
beklenen_getiri
varkov_matris = risk_models.sample_cov(
prices = portfoy,
returns_data = False,
frequency = 250,
log_returns = True
)
Yukarıdaki fonksiyonda yer alan parametreler ile bir önceki fonksiyonda yer alan parametreler birbirine benzemektedir. Farklı olarak burada varyans-kovaryans matrisini oluşturduk.
Matrisi görselleştirebiliriz.
plotting.plot_covariance(
cov_matrix=varkov_matris,
plot_correlation=False,
show_tickers=True,
)
plt.title('Kovaryans Matrisi')
plt.grid(False)
plt.show()
Yukarıdaki fonksiyonda yer alan plot_correlation
parametresi False
ise kovaryansı, True
ise korelasyonu gösterir. show_tickers
parametresi True
ise hisse senetlerinin isimlerini gösterir. Değer False
olarak ayarlandığında ise hisse senetlerinin isimlerini göstermez.
plot_correlation
parametresini True
yapalım.
plotting.plot_covariance(
cov_matrix=varkov_matris,
plot_correlation=True,
show_tickers=True,
)
plt.title('Korelasyon Matrisi')
plt.grid(False)
plt.show()
Maksimum Sharpe oranı ile maksimize edelim.
etkin_sinir = EfficientFrontier(
expected_returns=beklenen_getiri,
cov_matrix=varkov_matris,
weight_bounds=(0,1)
)
ham_agirliklar = etkin_sinir.max_sharpe()
temizlenmis_agirliklar = etkin_sinir.clean_weights()
etkin_sinir.portfolio_performance(verbose=True)
etkin_sinir
değişkeninde expected_returns
parametresi ile portföydeki varlıkların beklenen getirilerini içeren bir diziyi, cov_matrix
ile portföydeki varlıkların varyans-kovaryans matrisini ve weight_bounds
ile de portföydeki her bir varlığın ağırlığının (yatırım miktarının) 0 ile 1 arasında olması gerektiğini belirttik. Genel olarak EfficientFrontier
fonksiyonu, portföy optimizasyonu için kullanılan bir sınıftır ve yukarıda verilen beklenen getiri ve varyans-kovaryans matrisiyle birlikte portföyün etkin sınırını oluşturur.
max_sharpe()
, etkin sınırdaki en yüksek Sharpe oranına sahip portföy ağırlıklarını bulmayı sağlar. clean_weights()
, elde edilen portföy ağırlıklarını temizler ve düzenler, böylece küçük ağırlıkları sıfıra yakın değerlere ayarlar. Bu, uygulanabilirlik ve anlaşılabilirlik açısından önemlidir. Son olarak, portfolio_performance()
, optimize edilmiş portföyün performansını gösterir. İçerisine verdiğimiz verbose=True
ifadesi, daha fazla detay içeren bilgilerin görüntülenmesini sağlar.
Çıktı aşağıdaki gibi olacaktır.
Expected annual return: 52.6%
Annual volatility: 29.8%
Sharpe Ratio: 1.70
(0.5258814133722242, 0.2975661767437558, 1.7000635586612909)
%52.6'lık bir beklenen yıllık getiri, %29.8'lik bir yıllık volatilite (risk) ve 1.70'lik bir Sharpe oranı verdiğini görüyoruz. Sharpe oranından, portföyün riskli varlık kullanarak elde ettiği her bir birimlik ek getiri için ortalama olarak 1.70 birimlik risk aldığını okuyabiliriz.
Portföydeki hisse senetlerinin ağırlıklarına da bakalım.
print(temizlenmis_agirliklar)
Çıktı aşağıdaki gibi olacaktır.
OrderedDict([('AEFES', 0.01151), ('ASELS', 0.0), ('BIMAS', 0.31799), ('EREGL', 0.0), ('FROTO', 0.23069), ('GARAN', 0.05357), ('PETKM', 0.0), ('SISE', 0.04017), ('THYAO', 0.14509), ('TOASO', 0.20098)])
Aşağıdaki gibi daha derli toplu da görebiliriz.
hisseler = list(temizlenmis_agirliklar.keys())
agirlik_degerleri = list(temizlenmis_agirliklar.values())
df_portfoy_agirliklar = pd.DataFrame({
'HisseSenedi': hisseler,
'Agirlik': agirlik_degerleri
})
Aynı ağırlıkları görselleştirebiliriz.
plotting.plot_weights(weights=temizlenmis_agirliklar)
Portföy büyüklüğü belirtilerek (örneğimizde 100 000 ₺) hangisinden ne kadarlık bir alım yapılabileceği görülebilir. Öncelikle aşağıdaki gibi her bir hisse senedinin son fiyatını almalıyız.
son_fiyatlar = get_latest_prices(portfoy)
Dağıtalım.
dagitim_yap = DiscreteAllocation(
weights=temizlenmis_agirliklar,
latest_prices=son_fiyatlar,
total_portfolio_value=100000
)
dagitim, kalan = dagitim_yap.greedy_portfolio()
print("Tamsayı Dağılımı:", dagitim)
print("Kalan: {:.2f}₺".format(kalan))
Yukarıdaki kod bloğunda, daha önce elde edilen temizlenmiş ağırlıklar ve varlık fiyatları kullanılarak bir tamsayı dağılımı yapılıyor. Amaç, belirli bir toplam portföy değeri içinde varlıklara uygun bir şekilde yatırım yapmaktır.
DiscreteAllocation
sınıfı, verilen ağırlıklar ve varlık fiyatlarıyla tamsayı dağılımı yapmayı sağlar. greedy_portfolio()
, portföydeki varlıklara tamsayı birimlerde yatırım yapmak için açgözlü bir algoritma kullanır. Bu yöntem, temizlenmiş ağırlıklara ve fiyatlara dayanarak uygun varlık dağılımını hesaplar. dagitim
, tamsayılarla ifade edilen varlık dağılımını içeren bir sözlüktür. kalan
, tamsayı dağılımı sonucunda kullanılmayan tutarı belirtir.
Çıktı aşağıdaki gibi olacaktır.
Tamsayı Dağılımı: {'BIMAS': 146, 'FROTO': 24, 'TOASO': 73, 'THYAO': 62, 'GARAN': 126, 'SISE': 81, 'AEFES': 13}
Kalan: 85.20₺
max_sharpe()
, etkin sınırdaki en yüksek Sharpe oranına sahip portföy ağırlıklarını bulmayı sağlıyordu. Tabi, paketin sağladığı tek yöntem bu değil. Minimum oynaklıkta (volatilite) bir portföyü bulmaya çalışan min_volatility()
yöntemi de kullanılabilir.
İlgili yöntemi kullanmak için aşağıdaki gibi değiştirmemiz yeterli olacaktır.
# ham_agirliklar = etkin_sinir.max_sharpe()
ham_agirliklar = etkin_sinir.min_volatility()
Çıktı aşağıdaki gibi olacaktır.
Expected annual return: 41.8%
Annual volatility: 26.8%
Sharpe Ratio: 1.49
(0.4184631387690548, 0.2677298014863866, 1.4883032690304208)
%41.8'lik bir beklenen yıllık getiri, %26.8'lik bir yıllık volatilite (risk) ve 1.49'luk bir Sharpe oranı verdiğini görüyoruz. Sharpe oranından, portföyün riskli varlık kullanarak elde ettiği her bir birimlik ek getiri için ortalama olarak 1.49 birimlik risk aldığını okuyabiliriz.
Ağırlıklara bakalım.
plotting.plot_weights(weights=temizlenmis_agirliklar)
Portföydeki dağılımı yapalım.
son_fiyatlar = get_latest_prices(portfoy)
dagitim_yap = DiscreteAllocation(
weights=temizlenmis_agirliklar,
latest_prices=son_fiyatlar,
total_portfolio_value=10000
)
dagitim, kalan = dagitim_yap.greedy_portfolio()
print("Tamsayı Dağılımı:", dagitim)
print("Kalan: {:.2f}₺".format(kalan))
Çıktı aşağıdaki gibi olacaktır.
Tamsayı Dağılımı: {'BIMAS': 179, 'AEFES': 207, 'EREGL': 384, 'GARAN': 200, 'FROTO': 5, 'PETKM': 276, 'TOASO': 18, 'ASELS': 35, 'SISE': 34}
Kalan: 13.63₺
empyrial
paketi ilk bakışta iki güzelliği ile karşılıyor.
i. Verileri çekmek için ekstradan fonksiyon yazmanıza veya kod çalıştırmanıza gerek yok. Kaynak kodlardan yfinance
paketini kullandıkları görülebilir.
ii. Portföyü benchmark ile karşılaştırma imkanı sunuyorlar. Benchmark kısaca, bir yatırım portföyünün performansını ölçmek ve değerlendirmek için kullanılan referans bir ölçüttür.
Tüm bunlar Engine
isimli sınıfta oluyor. O halde, Toronto Menkul Kıymetler Borsası TSX'ten seçtiğimiz şu 10 adet hisse senedi ile paketi incelemeye başlayalım: AC.TO (Air Canada), CNR.TO (Canadian National Railway Company), DOL.TO (Dollarama Inc.), ENB.TO (Enbridge Inc.), NA.TO (National Bank of Canada), QSR.TO (Restaurant Brands International Inc.), RY.TO (Royal Bank of Canada), SHOP.TO (Shopify Inc.), T.TO (TELUS Corporation) ve TD.TO (The Toronto-Dominion Bank). Benchmark'ımız ise S&P/TSX Composite Index olacak.
portfoy = Engine(
start_date="2018-08-08",
end_date="2023-07-29",
portfolio=['AC.TO', 'CNR.TO', 'DOL.TO', 'ENB.TO', 'NA.TO', 'QSR.TO', 'RY.TO', 'SHOP.TO', 'T.TO', 'TD.TO'],
weights=[0.1]*10,
benchmark=["^GSPTSE"]
)
empyrial(
my_portfolio=portfoy,
rf=0,
confidence_value=0.95
)
Yukarıda, Engine
sınıfı, hisselerin bir portföy oluşturduğu bir motor oluşturuyor. start_date
ve end_date
parametreleri arasında belirtilen tarihler arasında portföyün performansını değerlendirecektir. Bunun yanında, portfolio
parametresi ile hisseleri, weights
ile ağırlıkları ve benchmark
ile referans ölçütünü belirliyoruz.
empyrial()
fonksiyonunda, my_portfolio
parametresi Engine
üzerinden tanımladığınız portföyü temsil eder. rf
parametresi risksiz faiz oranını temsil eder. confidence_value
Value-at-Risk (VaR) hesaplamalarında kullanılan güven düzeyini temsil eder. VaR, belirli bir süre içinde ve belirli bir güven düzeyinde portföyün maksimum potansiyel kaybını ölçer. confidence_value
parametresinin değeri 0.95, fonksiyonun VaR'ı %95 güven düzeyinde hesaplayacağını gösterir.
Çıktıları inceleyelim.
Backtest tablosu:
- Annual Return (Yıllık Getiri): Portföyün bir yıl içinde elde ettiği ortalama getiri yüzdesidir.
- Cumulative Return (Toplam Getiri): Portföyün başlangıç tarihinden itibaren elde ettiği toplam getiri yüzdesidir.
- Annual Volatility (Yıllık Volatilite): Portföyün yıllık getirisinin standart sapması.
- Winning Day Ratio (Kazanan Gün Oranı): Portföydeki kazanan günlerin toplam gün sayısına oranıdır. Kazanan gün, portföyün değerinin arttığı günleri temsil eder.
- Sharpe Ratio: Portföyün fazladan alınan risk oranında elde ettiği getiriyi ölçen bir metriktir. Daha yüksek Sharpe oranı, daha iyi risk-getiri profilini gösterir.
- Calmar Ratio: Portföyün yıllık getirisinin maksimum çekilme (en kötü dönemdeki kayıp) oranına bölünmesiyle hesaplanan bir metriktir.
- Information Ratio: Portföyün fazladan alfa (piyasa üstü performans) üretme yeteneğini, alfa riski ile karşılaştırır.
- Stability (Kararlılık): Portföyün getirisinin istikrarını ölçer. Daha yüksek kararlılık, daha az dalgalanma ve daha güvenilir getiri demektir.
- Max Drawdown (Maksimum Çekilme): Portföyün başlangıç değerine kıyasla en büyük düşüş yüzdesidir.
- Sortino Ratio: Sharpe oranına benzer ancak yalnızca olumsuz getiri için standart sapmayı kullanır. Risk getiri profilini daha iyi değerlendirir.
- Skew: Portföy getirisinin olasılık dağılımının simetrisizliğini ölçer. Pozitif skew, olumlu ayrık getiri eğilimini gösterir.
- Kurtosis: Portföy getirisinin olasılık dağılımının kuyruklarının yüksekliğini ölçer. Yüksek kurtosis, aşırı olayların olasılığını artırabilir.
- Tail Ratio: Portföy getirisinin kuyruklarının genişliğini ve kurtosis ile ilişkisini ölçer.
- Common Sense Ratio: Yatırımcıların sıkça kullandığı teknikleri ve göstergeleri birleştirerek portföy performansını değerlendiren bir orandır.
- Daily Value at Risk (Günlük Risk Altında Tutulacak Değer): Bir gün içindeki maksimum potansiyel kaybı ölçen bir metrik.
- Alpha: Portföyün beklenen getirisinden bağımsız olarak, ekstra getiri sağlama yeteneğini ölçer.
- Beta: Piyasa endeksi ile portföyün hareketlerinin korelasyonunu ve piyasa üzerindeki duyarlılığını ölçer.
Getiriler grafiği:
Kümülatif getiriler ve benchmark karşılaştırması grafiği:
Yıl sonu getirileri ve benchmark karşılaştırması grafiği:
Aylık getiriler ısı grafiği:
Çekilme grafiği:
En kötü 5 çekilme dönemi grafiği:
6 aylık kayan volatilite grafiği:
6 aylık kayan Sharpe grafiği:
6 ve 12 aylık benchmark'a göre kayan beta grafiği:
Hisse senetlerinin ağırlıklarının grafiği:
Paket ile beraber yapılacak başka bir güzellik "Calendar Rebalancing". Calendar Rebalancing, yatırım portföyünün belirli zaman aralıklarında (genellikle yıllık, altı aylık, üç aylık veya aylık) orijinal varlık dağılımına (ağırlıklarına) geri döndürülmesi işlemidir. Bu yöntem, portföydeki varlık ağırlıklarının zaman içinde farklı performanslar sergilemesi sonucu ortaya çıkan dağılım bozulmalarını düzeltmeyi amaçlar. Örneğin, eğer belirlenen stratejiye göre %50 hisse senedi ve %50 tahvil içeren bir portföy, hisse senedi performansının tahvillerden daha hızlı olduğu bir dönemde %60 hisse senedi ve %40 tahvil durumuna gelirse, rebalans işlemi ile portföy tekrar %50-%50 dağılıma getirilir.
portfoy = Engine(
start_date="2018-08-08",
end_date="2023-07-29",
portfolio=['AC.TO', 'CNR.TO', 'DOL.TO', 'ENB.TO', 'NA.TO', 'QSR.TO', 'RY.TO', 'SHOP.TO', 'T.TO', 'TD.TO'],
weights=[0.1]*10,
benchmark=["^GSPTSE"],
rebalance="1y" # Diğerleri: 2y, 1y, 6m, quarterly ve monthly
)
empyrial(
my_portfolio=portfoy,
rf=0,
confidence_value=0.95
)
Belirli bir risk seviyesi için en yüksek beklenen getiriyi veya belirli bir beklenen getiri seviyesi için en düşük riski sunan optimal portföylerin kümesi olan etkin sınır çalıştırılabilir.
portfoy = Engine(
start_date="2018-08-08",
end_date="2023-07-29",
portfolio=['AC.TO', 'CNR.TO', 'DOL.TO', 'ENB.TO', 'NA.TO', 'QSR.TO', 'RY.TO', 'SHOP.TO', 'T.TO', 'TD.TO'],
weights=[0.1]*10,
benchmark=["^GSPTSE"],
optimizer="EF" # Efficient Frontier
)
empyrial(
my_portfolio=portfoy,
rf=0,
confidence_value=0.95
)
Burada sadece hangi hisselerin seçildiğini ve bu hisselerin ağırlıklarını görelim.
Ortalama-varyans analizi, beklenen getiri karşısında varyans olarak ifade edilen riski dengelemek için bir süreçtir. Yatırımcılar, farklı ödül seviyeleri karşılığında ne kadar risk almak istediklerini değerlendirirler. Ortalama-varyans analizi, yatırımcılara belirli bir risk düzeyinde en büyük ödülü bulmalarına olanak tanır.
portfoy = Engine(
start_date="2018-08-08",
end_date="2023-07-29",
portfolio=['AC.TO', 'CNR.TO', 'DOL.TO', 'ENB.TO', 'NA.TO', 'QSR.TO', 'RY.TO', 'SHOP.TO', 'T.TO', 'TD.TO'],
weights=[0.1]*10,
benchmark=["^GSPTSE"],
optimizer="MEANVAR",
max_vol=0.25
)
empyrial(
my_portfolio=portfoy,
rf=0,
confidence_value=0.95
)
Burada sadece hangi hisselerin seçildiğini ve bu hisselerin ağırlıklarını görelim.
Hierarchical Risk Parity (HRP), portföy yönetiminde kullanılan bir risk yönetimi stratejisidir. Bu yöntem, portföydeki varlıkların riskine dayalı olarak ağırlıklarını belirlemek için bir hiyerarşi oluşturur. Amacı, portföydeki riski dengeli bir şekilde dağıtarak daha istikrarlı bir performans elde etmektir.
portfoy = Engine(
start_date="2018-08-08",
end_date="2023-07-29",
portfolio=['AC.TO', 'CNR.TO', 'DOL.TO', 'ENB.TO', 'NA.TO', 'QSR.TO', 'RY.TO', 'SHOP.TO', 'T.TO', 'TD.TO'],
weights=[0.1]*10,
benchmark=["^GSPTSE"],
optimizer="HRP"
)
empyrial(
my_portfolio=portfoy,
rf=0,
confidence_value=0.95
)
Burada sadece hangi hisselerin seçildiğini ve bu hisselerin ağırlıklarını görelim.
Minimum varyans, bir portföyün en düşük riskle elde edilebilecek getiriyi ifade eden bir kavramdır.
portfoy = Engine(
start_date="2018-08-08",
end_date="2023-07-29",
portfolio=['AC.TO', 'CNR.TO', 'DOL.TO', 'ENB.TO', 'NA.TO', 'QSR.TO', 'RY.TO', 'SHOP.TO', 'T.TO', 'TD.TO'],
weights=[0.1]*10,
benchmark=["^GSPTSE"],
optimizer="MINVAR"
)
empyrial(
my_portfolio=portfoy,
rf=0,
confidence_value=0.95
)
Burada sadece hangi hisselerin seçildiğini ve bu hisselerin ağırlıklarını görelim.
Kalan diğer birçok özelliğinin yanında son olarak rapor almayı da görelim. Hem .pdf formatında hem de .png formatında vermektedir.
get_report(my_portfolio=portfoy)