APOLLOHELIX

Makale · 8 dk okuma

BIST'te Backtest Nasıl Yapılır? Veri, Maliyet ve Tuzaklar

Borsa İstanbul'da backtest rehberi: Python ile veri çekme, düzeltilmiş fiyat, komisyon ve kayma hesabı, tavan/taban kilidi ve hayatta kalma yanlılığı tuzakları.

Borsa İstanbul'da (BIST) backtest yapmak mantık olarak diğer piyasalardan farklı değildir: bir strateji kuralı yazarsın, geçmiş veride çalıştırırsın, sonucu ölçersin. Farklar veride, maliyette ve piyasa kurallarında başlar. Bu rehber, BIST hisseleri için sağlam bir backtest kurmanın her adımını örneklerle anlatır. Genel çerçeve için önce Backtest ve Paper Trading Rehberi yazımıza göz atabilirsin; burada BIST'e özgü ayrıntılara odaklanıyoruz.

İçindekiler

  • Backtest neden BIST'te farklı davranır?
  • Adım 1: Veriyi doğru kaynaktan al
  • Adım 2: Düzeltilmiş fiyatı anla
  • Adım 3: Basit bir stratejiyi koda dök
  • Adım 4: Maliyet ve kaymayı gerçekçi hesapla
  • Adım 5: Tavan/taban kilidi sonucu nasıl bozar?
  • Adım 6: Yanlılıkları ayıkla
  • Adım 7: Sonucu doğru oku, paper aşamasına geç
  • Sık yapılan hatalar kontrol listesi

Backtest neden BIST'te farklı davranır?

Dünyanın en likit hisselerinde backtest ile gerçek hayat arasındaki fark genelde küçüktür. BIST'te ise üç şey farkı büyütür:

  • Günlük fiyat limitleri: Hisseler bir günde yalnızca belirli bir bandın içinde hareket edebilir.
  • Hacim dağılımı: Endeksin en büyük hisseleri çok likittir, ama küçük ve orta ölçekli hisselerde alış-satış farkı geniştir.
  • Veri kalitesi: Sermaye artırımları, bölünmeler ve temettüler fiyat serisinde sıçrama yaratır. Bunlar düzeltilmezse strateji "bulmadığı" fırsatlara bakarak kâr eder.

Bu üç madde, güzel görünen ama canlıda çalışmayan backtestlerin çoğunun nedenidir.

Adım 1: Veriyi doğru kaynaktan al

Günlük (1 gün) verisiyle başlamak en kolay yoldur. Bazı ücretsiz kaynaklar BIST hisselerini hisse koduna .IS uzantısı ekleyerek sunar (örneğin THYAO.IS, GARAN.IS). Ücretsiz veri hızlı denemeler için yeterlidir, ancak eksik gün, hatalı fiyat veya gecikmiş düzeltme içerebilir; sonuçlarını mutlaka kontrol et.

Aşağıdaki Python örneği, yfinance kütüphanesiyle bir BIST hissesinin günlük verisini çeker ve temel kontrolleri yapar:

import pandas as pd
import yfinance as yf

SYMBOL = "THYAO.IS"          # BIST hisseleri ".IS" uzantısı alır
START, END = "2018-01-01", "2026-01-01"

df = yf.download(SYMBOL, start=START, end=END, auto_adjust=True, progress=False)

# Yeni yfinance sürümleri tek hisse için bile çok seviyeli sütun döndürebilir
if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

df = df[["Open", "High", "Low", "Close", "Volume"]].dropna()

# Temel veri sağlığı kontrolleri
assert df.index.is_monotonic_increasing, "Tarihler sıralı değil"
assert (df["High"] >= df["Low"]).all(), "High < Low olan satır var"
assert (df["Close"] > 0).all(), "Sıfır veya negatif fiyat var"

# Tek günde %12'den büyük hareket şüphelidir: bölünme/sermaye artırımı düzeltilmemiş olabilir
ret = df["Close"].pct_change()
print("Şüpheli günler:\n", ret[ret.abs() > 0.12])
print(df.tail())

Bu kodda auto_adjust=True bölünme ve temettü düzeltmesini uygular. Yine de şüpheli günleri elle incele: %12'den büyük günlük hareket ya gerçek bir haber ya da düzeltilmemiş bir sermaye artırımıdır. Gün içi (dakikalık) veri genelde ücretlidir ve aracı kurum ya da veri sağlayıcıdan alınır.

Strategy Engine · Backtest (konsept) STRATEJİ Sembol: ÖRNEK.IS Komisyon: ayarlı Kayma: ayarlı Tavan/taban: açık Çalıştır Brüt (mavi) vs maliyet sonrası net (sarı)
Görsel: ApolloHelix Backtest Ekranı. Konsept tasarım örneğidir; gerçek bir ekran görüntüsü ya da gerçek sonuç değildir. Ürün geliştirme aşamasındadır.

Adım 2: Düzeltilmiş fiyatı anla

Bir şirket bedelsiz sermaye artırımı yaptığında hisse fiyatı bir gecede teorik olarak düşer; ancak yatırımcının varlığı değişmemiştir. Düzeltilmemiş bir seride bu, "bir günde %50 çöküş" gibi görünür. Trend takip eden bir strateji bu sahte çöküşte satış sinyali üretir; ortalamaya dönüş stratejisi ise "ucuz kalmış" diye alım yapar. İkisi de gerçek olmayan bir olaya tepki verir.

Pratik kural: Fiyatı her zaman bölünme, bedelsiz ve temettü için düzeltilmiş kullan; ham fiyatı yalnızca emir büyüklüğü ve fiyat adımı (tick) hesabı gibi işlem detaylarında kullan.

Adım 3: Basit bir stratejiyi koda dök

Aşağıdaki örnek, 20 ve 50 günlük hareketli ortalama kesişimiyle yalnızca eğitim amaçlı bir strateji kurar. Önemli ayrıntı: sinyal bugünün kapanışından üretilir ama işlem ertesi gün yapılır; aksi halde geleceğe bakmış olursun (look-ahead bias).

sma_fast = df["Close"].rolling(20).mean()
sma_slow = df["Close"].rolling(50).mean()

signal = (sma_fast > sma_slow).astype(int)       # 1: pozisyonda, 0: nakitte
position = signal.shift(1).fillna(0)              # sinyal ertesi gün uygulanır

daily_ret = df["Close"].pct_change().fillna(0)
gross = position * daily_ret

print("Brüt toplam getiri:", (1 + gross).prod() - 1)

Bu sonuç henüz brüttür; maliyet içermez. Bir sonraki adımda gerçeğe yaklaştırıyoruz.

Adım 4: Maliyet ve kaymayı gerçekçi hesapla

Backtest'in en sık göz ardı edilen kısmı maliyettir. Komisyon, borsa ve takas payları, vergiler ve alış-satış farkı (spread) birleşince sık işlem yapan stratejilerin kârını silebilir. Ayrıntılı anlatım için Backtestte komisyon ve kayma yazısına bak.

Gerçek bir senaryo: 100.000 TL ile tek bir gidiş-dönüş

Öğretici varsayımlar (kendi aracı kurumunun tarifesiyle değiştir; gerçek oranlar kuruma, kanala ve döneme göre farklıdır):

  • Komisyon (vergiler dahil etkin oran): her yönde binde 1 (%0,10)
  • Kayma ve spread etkisi: her yönde binde 1 (%0,10)
Kalem Hesap Tutar
Alış komisyonu 100.000 × %0,10 100 TL
Alış kayması 100.000 × %0,10 100 TL
Satış komisyonu 100.000 × %0,10 100 TL
Satış kayması 100.000 × %0,10 100 TL
Toplam gidiş-dönüş maliyeti 400 TL (%0,40)

Tek işlemde %0,40 küçük görünür. Ama yılda 100 gidiş-dönüş yapan bir strateji için bu yaklaşık %40'lık bir yıllık sürtünme demektir. İşlem başına ortalama brüt kazancın %0,5 ise, maliyet sonrası geriye yalnızca %0,1 kalır; kayma biraz daha artarsa strateji zarara döner. Bu yüzden iyi bir backtest her zaman iki eğri gösterir: brüt ve net.

Kodda maliyeti şöyle ekleyebilirsin:

COST_PER_SIDE = 0.001 + 0.001      # komisyon + kayma (öğretici varsayım)

trades = position.diff().abs().fillna(0)          # pozisyon değişince işlem var
net = gross - trades * COST_PER_SIDE

print("Net toplam getiri:", (1 + net).prod() - 1)
print("Toplam işlem sayısı:", int(trades.sum()))

Likidite uyarısı: Düşük hacimli hisselerde kayma yukarıdaki varsayımın birkaç katı olabilir. Kural olarak, emir büyüklüğün günlük hacmin küçük bir kısmını geçmemeli; geçerse kendi emrin fiyatı hareket ettirir.

Adım 5: Tavan/taban kilidi sonucu nasıl bozar?

BIST'te hisselerin bir günde gidebileceği bir fiyat bandı vardır (hisse senetlerinde genel olarak günlük yüzde 10 civarı; güncel kuralları borsanın duyurularından teyit et). Fiyat bu bandın üstüne tavan, altına taban kilitlenir.

Backtest bunu şu üç şekilde yanıltır:

  1. Tavanda alış hayali: Hisse güne tavandan açılıp tavanda kilitlendiyse, gerçekte alış emrin kuyrukta bekler ve çoğunlukla dolmaz. Backtest ise "açılış fiyatından aldım" der ve güzel bir kâr yazar. Tavan serilerini yakalayan stratejiler özellikle bu yüzden gerçekçi olmayan sonuç verir.
  2. Tabanda çıkış hayali: Stop-loss seviyen tabana denk geldiğinde taban kilitliyse satamazsın; ertesi gün de aynı olabilir. Backtest stop fiyatından çıkmış gibi hesaplarken gerçek kayıp çok daha büyük olabilir.
  3. Gap etkisi: Kilit açıldığında fiyat bir önceki kapanıştan uzak bir seviyeden işlem görür; kapanış bazlı sinyallerin işlem fiyatı varsayımı çöker.

Çözüm önerisi: Backtestte, açılış fiyatı bir önceki kapanışa göre limit bandına yakınsa o günü "işlem yapılamaz" olarak işaretle. Satışta ise taban kilidi günlerinde çıkışı sonraki işlem yapılabilir güne ertele ve zararı o fiyattan hesapla.

prev_close = df["Close"].shift(1)
limit_up = df["Open"] >= prev_close * 1.095       # tavana yakın açılış (yaklaşık eşik)
limit_dn = df["Open"] <= prev_close * 0.905       # tabana yakın açılış

tradable = ~(limit_up | limit_dn)                 # kilit günlerinde emir dolmaz kabul et
position = position.where(tradable, position.shift(1)).fillna(0)

Bu yöntem kaba bir yaklaşımdır ama sonuçlarını gereksiz iyimserlikten korur. Diğer piyasa kuralları da önemlidir: açığa satış ve vadeli işlemler (VİOP) ayrı koşullara tabidir, takas süreleri ve işlem saatleri farklıdır.

Adım 6: Yanlılıkları ayıkla

  • Hayatta kalma yanlılığı: Listeden çıkan, işlem görmeyi bırakan ya da sert düşen hisseleri veri setinden çıkarırsan sonuçlar şişer. Bugünün endeks listesini 2018'e uygulamak tipik bir hatadır. Ayrıntılar için Hayatta kalma yanlılığı nedir? yazısını oku.
  • İleriye bakış (look-ahead): Sinyali bugünün kapanışıyla üretip bugünün kapanışında işlem yapmak.
  • Aşırı uyum (overfitting): Parametreleri geçmişe en iyi uyacak şekilde ayarlamak. Veriyi eğitim ve test olarak böl; hiç dokunmadığın bir dönemde dene.
  • Rejim değişikliği: BIST'te yüksek enflasyon, kur şokları ve faiz kararları dönem dönem davranışı değiştirir; tek bir dönemde çalışan strateji diğerinde çalışmayabilir.

Adım 7: Sonucu doğru oku, paper aşamasına geç

Sadece toplam getiriye bakma. Şunları birlikte değerlendir:

  • Maksimum düşüş (en kötü tepeden dibe kayıp) ve bunu taşıyıp taşıyamayacağın,
  • İşlem sayısı (az işlem = istatistiksel olarak zayıf sonuç),
  • Net getiri (maliyet sonrası),
  • Farklı dönemlerdeki tutarlılık.

Backtest bir kanıt değil, bir eleme aracıdır: kötü fikirleri ucuza ayıklar. İyi görünen bir strateji, canlı veride gerçek zamanlı ve gerçek emir mantığıyla çalışan bir paper trading aşamasından geçmelidir. Bu aşamanın nasıl yapılacağı Backtest ve Paper Trading Rehberi'nde anlatılıyor. Kaybı sınırlamak için risk yönetimi rehberini incelemeni öneririz. Algoritmik işlemin genel resmi için Algoritmik Trading Rehberi iyi bir başlangıçtır.

Sık yapılan hatalar kontrol listesi

  • Düzeltilmiş fiyat kullandım ve şüpheli günleri elle kontrol ettim.
  • Sinyal ve işlem aynı barda değil; işlem ertesi gün.
  • Komisyon, vergi ve kaymayı ekledim; brüt ve net sonucu ayrı gösteriyorum.
  • Tavan/taban kilitli günleri işlem yapılamaz saydım.
  • Hayatta kalma yanlılığına karşı listeden çıkmış hisseleri de kapsadım.
  • Eğitim ve test dönemlerini ayırdım.
  • Sonucu paper aşamasında canlı veriyle sınamadan gerçek parayla işlem yapmayacağım.

Bu yazı eğitim amaçlıdır, yatırım tavsiyesi değildir. Geçmiş sonuçlar geleceği garanti etmez; işlem yapmak kayıp riski taşır.