Python’da Geometric Brownian Motion Kullanarak Hisse Senedi Fiyatlarını Simüle Etme

2020 yılının eylül ayı içerisinde Geometric Brownian Motion’dan faydalanarak USDTRY için yıl sonu fiyat öngörüsünde bulunmuştum.

7.9672 (sol) olan öngörümü aynı gün içinde 8.1915 (sağ) olarak güncellemiştim.

Yıl sonu USDTRY 7.4392 olarak gerçekleşti. Ardından da çalışmam ile ilgili -/+ eleştirilerimi yaptım: GBM kullanarak üzerinde çalıştığım modelin sonuçlarını hemen paylaştım. Oysa öngörüden sonra da çalışmaya devam ettim. Çalışmam aşağı yönlü hareketlerde de yüksek korelasyon gösterirken duygularımı karıştırdım. Model, seri ile uyumlu gidebilen bir öngörü serisi yarattı. Hatta dönüşleri yakalayabilmesi büyüledi beni. Diğer açıdan 9-10’ları bırakın 8.50 bile demeyen bir öngörü oldu. Simülasyon daha da zevk aldığım özel bir alan oldu benim için. Çalışmalarım devam edecek.

Bu yazı ile beraber Geometric Brownian Motion (GBM) uygulamasını yapmış olacağız. Yazı boyunca teoriye dalmaktan öte uygulama odaklı gideceğiz.

Uygulamada kullanacağımız hisse senedini MGROS olarak belirledim. Sizler aşağıda tanıyacağınız yfinance modülü ile beraber kendi seçiminiz ile de ilerleyebilirsiniz.

Modülleri dahil edelim.

import yfinance as yf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')

yfinance modülü hisse senedi fiyatlarını alabileceğiniz bir kaç yoldan biri. Gelecek yazılarda diğerlerini de tanırız.

Modül ile verileri alalım ve bazı düzenlemeler yapalım.

data_master = yf.download('MGROS.IS', start='2020-01-02', end='2021-01-01') #2020 yılı fiyatları; son günü almak için +1 ekleyin
data_master.reset_index(inplace=True) #Index olan Date'in veri çerçevesine dahil edilmesi
data_master = data_master[['Date','Adj Close']] #Date ve Adj Close sütunlarının seçilmesi
data_master = data_master.rename(columns = {'Adj Close':'Close'}) #Adj Close sütun isminin Close olarak değiştirilmesi
print(data_master.head()) #Baştaki satırları görüntüle
print(data_master.tail()) #Sondaki satırları görüntüle

2020 yılına ait ortalama getiri (µ) ve standart sapma (σ) verilerini kullanarak 2021 yılının ilk 3 ayı hakkında bir yol oluşturmaya çalışacağız. O nedenle yukarıdaki gibi adımları tekrar edecek; bu defa 2021 yılının Ocak ayı verilerini alacağız.


#Tarih aralığı geniş tutulup (2020-01-02; 2021-01-30) veri çerçeveleri ayrılabilirdi fakat bu şekilde göstermeyi tercih ettim.

data_pred = yf.download('MGROS.IS', start='2021-01-04', end='2021-01-30')
data_pred.reset_index(inplace=True)
data_pred = data_pred[['Date','Adj Close']]
data_pred = data_pred.rename(columns = {'Adj Close':'Close'})
print(data_pred.head())
print(data_pred.tail())

Sıra formülü koda dökmekte. Bununla ilgili üzerinde açıklamaları olan şu görseli hazırladım:

Hisse senetlerinin ortalama getirisini temsil eden µ ve getirilerin standart sapmasını temsil eden σ’yı hesaplamak için aşağıdaki fonksiyonu yazabiliriz.

def dailyReturn(ClosePrice):
    r = []
    for i in range(len(ClosePrice)-1):
        today = ClosePrice[i+1]
        yesterday = ClosePrice[i]
        daily_return = (today - yesterday)/yesterday
        r.append(daily_return)
    return r

dailyReturn adında bir fonksiyon tanımladık ve bu fonksiyon içine ClosePrice adında bir parametre aldı. İçeride, hesaplanan her bir getiriyi kaydetmek için r adında boş bir liste oluşturduk. Ardından oluşturduğumuz for döngüsü belirlediğimiz uzunlukta, satır sayısının 1 eksiği, çalıştı. today değişkeninin içine bir sonraki günü, yesterday değişkeninin içine de o günü attık ve günlük getiriyi (daily_return) (today – yesterday) / yesterday olarak hesapladık. Son olarak hesaplanan her bir değeri listelerde bulunan append metodu ile r listesine gönderdik. Fonksiyonu da değer döndürmesi için return ile kapattık.

Şimdi bu fonksiyon yardımı ile µ ve σ’yı hesaplayabiliriz.

mu = np.mean(dailyReturn(data_master['Close']))
sigma = np.std(dailyReturn(data_master['Close']))
print("""
mu: {}
sigma: {}
""".format(mu,sigma))

mu: 0.0024927400723239834
sigma: 0.025390131856304708

Şimdi birazdan yazacağımız fonksiyonun içinde bulunacak diğer parametreleri kısaca tanıyalım.

S0, hisse senedinin başlangıç fiyatını temsil edecek.

S0 = data_pred['Close'][0]
print(S0)

44.560001373291016

dt, zaman artışını temsil edecek. İlgili hisse senedinin günlük kapanış değerlerini aldığımız için bu uygulamada dt = 1 olacak.

dt = 1

T, iş gününü temsil edecek. Biz 63 iş günü için simüle değerler üreteceğiz.

T = len(data_pred) + 43

n, T / dt’yi temsil edecek. n, belirlediğimiz 63 gündeki zaman noktalarını gösteriyor. Yani, 63 / 1 = 63 nokta olacak.

n = T / dt

t, zamanı temsil ediyor.

t = np.arange(1, n + 1) #1'den 63'e kadar olacak

N ise simülasyon sayısını temsil edecek. Bu uygulamada 100 simülasyon denemek istedim.

N = 100

Son adım olarak random şoklar vereceğiz.

Günlük çalışıyoruz ve 63 günlük bir simülasyon çalıştıracağız (n = T / dt = 63 / 1 = 63). Bu da 0 ortalama ve 1 standart sapmalı normal dağılım tablosundan gelen 63 tane random şok demektir. Bunu np.random.standard_normal() ile üreteceğiz. Tüm random şokların etkisini her bir zamanda kümülatif alacağız. Bunun için de np.cumsum() kullanacağız.

Tekrar hatırlayalım:

Aynı sonuçları almak için np.random.seed(1) kullanacağımızı belirttikten sonra fonksiyona geçebiliriz.

S0 = data_pred['Close'][0]
dt = 1
T = len(data_pred) + 43
n = int(T / dt)
t = np.arange(1, n + 1)
N = 100

np.random.seed(1)
def GBM(S0, dt, T, n, t, mu, sigma):
    S = S0 * np.exp((mu - 0.5 * sigma ** 2) * t + sigma * np.cumsum(np.random.standard_normal(size=n)) * np.sqrt(dt))
    return S

GBM fonksiyonunu oluşturduk. Şimdi bir sözlük oluşturalım ve her bir listeyi bunun içine atalım.

simulation = {}
for i in range(N):
    gbm = GBM(S0, dt, T, n, t, mu, sigma)
    gbm[0] = S0
    simulation['Simulation_' + str(i)] = list(gbm)

simulation adında boş bir sözlük oluşturduk. for döngüsü ile 100 tane simülasyon çalıştırdık ve her birini boş sözlüğün içine attık. Başlangıç değer S0 olduğu için ilk indekse 0 atadık. Ardından her bir simülasyonun anahtar ismini ‘Simulation_’ + str(i) (‘Simulation_0’, ‘Simulation_1’, …) şeklinde verdik ve bunlara ait değerleri liste olarak sözlüğe attık.

simdf adında bir veri çerçevesi oluşturacağız. Biz 63 iş günü için değerler ürettik fakat ocak ayı için elimizde 20 iş günü verisi var. Bu durumda farklı uzunluktaki sütunları birleştirmiş olacağız.

simdf = pd.DataFrame(simulation) #Sözlüğü veri çerçevesine dönüştürme
simdf = pd.concat([simdf,data_pred['Close']], ignore_index=False, axis=1) #Farklı uzunluktaki sütunları birleştirmek için bir ayarlama; NaN olarak atayacak
simdf['days'] = np.arange(len(simdf)) #Günleri sayısal olarak atama
print(simdf.head())
print(simdf.tail())

Simüle değerleri ürettik. Şimdi bir görselleştirme yapalım. Bunu biraz farklı yapacağız çünkü simüle değerleri ve asıl değeri farklı göstermek istedim.

plt.figure(figsize=(12.5,4.5))
plt.rcParams.update({'font.size': 5})
for column in simdf.drop('days', axis=1):
   plt.plot(simdf['days'], simdf[column], marker='', color='grey', linewidth=1, alpha=0.5)
plt.plot(simdf['days'], simdf['Close'], marker='', color='orange', linewidth=3, alpha=0.8)
plt.title("GBM in Practice: Example with MGROS")
plt.suptitle("No Investment Advice. The Content is for informational purposes only.")
plt.xlabel("Jan. 04, 2021 - Mar. 31, 2021")
plt.show()
Yatırım tavsiyesi değildir. İçerik yalnızca bilgi amaçlıdır.

Son olarak asıl fiyatlarla korelasyonlu olan simüle değerlere bakalım.

simdfcorr = simdf.drop('days', axis=1)
simdfcorr = pd.DataFrame(simdf.corr())
simdfcorr = simdfcorr[['Close']]
simdfcorr = simdfcorr.sort_values(by=['Close'], ascending=False)
topcorr = simdfcorr[:6].index.tolist()
df = simdf[topcorr]
df['days'] = np.arange(len(df))

Önce days sütununu hesaplamaya girmesin diye attık ve simdifcorr adında bir veri çerçevesi oluşturduk. Ardından sütunlar arası korelasyonları alıp bunu veri çerçevesine çevirdik. Asıl fiyatların bulunduğu sütun olan Close sütununu aldık. Bu sütunu güçlü pozitif korelasyondan başlayarak sıraladık ve ilk 5 satırı aldık (Close + 5). İlk 6 satırdaki index isimlerini bir listeye attık ve bunları simdf veri çerçevesinde seçerek bir df veri çerçevesi oluşturduk. Son olarak bu veri çerçevesine görselleştirme için days sütununu atadık.

plt.figure(figsize=(12.5,4.5))
plt.rcParams.update({'font.size': 5})
for column in df.drop('days', axis=1):
   plt.plot(df['days'], df[column], marker='', color='grey', linewidth=1, alpha=0.5)
plt.plot(df['days'], df['Close'], marker='', color='orange', linewidth=3, alpha=0.8)
plt.title("GBM in Practice: Correlation of MGROS against all others (the highest ones or top 5 in a plot)")
plt.suptitle("No Investment Advice. The Content is for informational purposes only.")
plt.xlabel("Jan. 04, 2021 - Mar. 31, 2021")
plt.show()
Yatırım tavsiyesi değildir. İçerik yalnızca bilgi amaçlıdır.

Uygulamamızın sonuna geldik. Umuyorum faydanıza kullanabileceğiniz bir yazı olmuştur. Amacımız nokta atışı yapmak olmamalı. Aksine, amacımız dar bir koridorda gitmek ve doğru bir trendi takip etmek olmalı.

Görüşmek dileği ile.

11 thoughts on “Python’da Geometric Brownian Motion Kullanarak Hisse Senedi Fiyatlarını Simüle Etme

  • 1 Şubat 2021 tarihinde, saat 22:16
    Permalink

    Her şeyi anladım da 63 günlük başlangıç periyodunun 0 değeri kaç ? 1 ocak – 30 ocak arası tarihsel verilerden faydalanıyoruz. Sonuç grafiğindeki veriler hangi tarih aralığını kapsıyor ? 4 Ocak – 31 Mart aralığı 63 gün etmiyor zaten? Bunun mantığını anlayamadım ?

    Yanıtla
    • 2 Şubat 2021 tarihinde, saat 13:53
      Permalink

      4 Ocak-31 Mart arasında 63 *iş günü* vardır. Yani, hafta sonu ve tatiller hariç. Bu durumda index sıfırdan başladığı için 4 Ocak’a denk geliyor. Biz 2020 yılının µ ve σ değerlerinden faydalandık. Aldığımız bu değerler ile 2021’in ilk çeyreği için simüle değerler ürettik. Grafikteki turuncu renkli seri Ocak 2021 yılında gerçekleşen değerlerdir. 63 günlük simüle değerlerin üzerine koyup yönü hakkında fikir sahibi olmaya çalıştık.

      Yanıtla
  • 17 Şubat 2021 tarihinde, saat 16:35
    Permalink

    Hocam çıktı grafiklerin çözünürlükleri çok kötü, detaylı inceleyebilmek için grafiğin farklı bir şekilde çıktısını almak mümkünmüdür? Yada exel’e çıktı almak mümkünmü? Yardımlarınız için teşekkür ederim.

    Yanıtla
    • 18 Şubat 2021 tarihinde, saat 23:00
      Permalink

      Görseli indirip size mail atabilirim diyeceğim ama bunu siz de yapabilirsiniz. Bunun dışında kodları yazıp çalıştırabilirsiniz; o zaman daha net görüntü alabilirsiniz. Pc’de kayıtlı olsaydı gönderebilirdim ama maalesef.

      Yanıtla
  • 14 Mart 2021 tarihinde, saat 00:06
    Permalink

    öncelikle elinize sağlık. bu yaptıklarınızı kopyala yapıştır yapıyorum olmuyor.sizce nerde hata yapıyorum?
    birde bunu video çekip paylaşabilirmisiniz? eğer böyle bişey yapabilirseniz bunun konuları öğrenmek isteyen biri için çok makbule geçer. şimdeden teşekkürler.

    Yanıtla
    • 14 Mart 2021 tarihinde, saat 19:55
      Permalink

      Teşekkür ederim. İlgili hata detayını paylaşırsanız yardımcı olmaya çalışırım. Video konusunda bir adım atacağım. Dediğiniz gibi sizler için daha da faydalı olacaktır.

      Yanıtla
  • 14 Mart 2021 tarihinde, saat 22:53
    Permalink

    hocam nerde hata yaptığımı buldum. yfinance modülünü yüklememişim. şimdi bir sorum daha olacak bu sonda oluşturduğumuz grafiği sadece resim olarak mı görebiliyoruz. mouseyi turuncu bandın üzerinde gezdirdikçe fiyatları görme imkanımız var mı? cevabınız için teşekkürler tekrardan.

    Yanıtla
  • 31 Mart 2021 tarihinde, saat 12:42
    Permalink

    line 53, in
    gbm = GBM(S0, dt, T, n, t, mu, sigma)
    line 47, in GBM
    S = S0 * np.exp((mu – 0.5 * sigma ** 2) * t + sigma * np.cumsum(np.random.standard_normal(size=n)) * np.sqrt(dt))
    File “mtrand.pyx”, line 1394, in numpy.random.mtrand.RandomState.standard_normal
    File “_common.pyx”, line 577, in numpy.random._common.cont
    TypeError: ‘float’ object cannot be interpreted as an integer

    merhaba integer float hatasi aliyorum fonksiyonun icinde,

    47.line: S = S0 * np.exp((mu – 0.5 * sigma ** 2) * t + sigma * np.cumsum(np.random.standard_normal(size=n)) * np.sqrt(dt))
    53.line: gbm = GBM(S0, dt, T, n, t, mu, sigma)

    Yanıtla
    • 1 Mayıs 2021 tarihinde, saat 11:49
      Permalink

      Merhaba, Jupyter Notebook’ta her kod parçasını satır satır deneyebilir misiniz? Tekrar denedim benim tarafta bir problem görünmüyor.

      Yanıtla

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir