Açılış Boşluğu (Gap) Sistemi: İstatistik Önce, İşlem Sonra
BIST hisselerindeki açılış boşluklarını sınıflandırıyor, doldurulma oranlarını Python ile ölçüyor ve bu istatistiğin üzerine kurallı bir gün içi sistem inşa ediyoruz.
Piyasa kapalıyken haber birikir; sabah tek bir fiyat sıçramasıyla açılır. Bu boşluk (gap) rastgele değildir — arkasında ya gerçek bilgi ya da abartılmış duygu vardır ve ikisi farklı davranır. Yıllarca "gap'ler dolar" ezberini sorgulamadan taşıdım; sonra oturup saydım ve ezberin yarısının doğru olduğunu gördüm. Bu makale o sayma işleminin kendisi.
Gap Türleri ve İki Zıt Sistem
Açılış boşluğu, bugünün açılışı ile dünün kapanışı arasındaki farktır. Kritik ayrım büyüklükte:
- Küçük gap'ler (±%1-2 bandı) çoğunlukla gürültüdür ve aynı gün içinde dolma (fiyatın dünkü kapanışa geri dönmesi) eğilimi gösterir. Bunun üzerine gap-fade (boşluğa karşı işlem) sistemi kurulur.
- Büyük gap'ler (%3 ve üzeri) genellikle gerçek bilgi taşır — bilanço, KAP haberi, tahsisli sermaye artırımı. Bunlar dolmamaya ve gün boyu yönünde devam etmeye meyillidir; üzerine gap-and-go (boşluk yönünde işlem) kurulur.
Aynı ham olgudan iki zıt sistem çıkması ilk bakışta tuhaf; ama ayrımı yapan şey istatistik. Hangi büyüklükte hangi davranışın baskın olduğunu kendi verinizle ölçmeden ikisine de girmemelisiniz.
Adım 1: Gap'leri Ölçmek ve Sınıflandırmak
Günlük OHLC verisi yeterli: açılış/önceki kapanış oranından gap yüzdesi, gün içi seyirden de "doldu mu" bilgisi çıkar.
import yfinance as yf
import pandas as pd
import numpy as np
hisseler = ["THYAO.IS", "ASELS.IS", "EREGL.IS", "TUPRS.IS", "SISE.IS"]
kayitlar = []
for h in hisseler:
df = yf.download(h, start="2021-01-01", auto_adjust=True)
df["OncekiKapanis"] = df["Close"].shift(1)
df["GapPct"] = df["Open"] / df["OncekiKapanis"] - 1
# Yukarı gap "dolar" = gün içi düşük, dünkü kapanışa değerse
yukari_doldu = df["Low"] <= df["OncekiKapanis"]
# Aşağı gap "dolar" = gün içi yüksek, dünkü kapanışa değerse
asagi_doldu = df["High"] >= df["OncekiKapanis"]
df["Doldu"] = np.where(df["GapPct"] > 0, yukari_doldu, asagi_doldu)
kayitlar.append(df[["GapPct", "Doldu"]].assign(Hisse=h))
gaps = pd.concat(kayitlar).dropna()
💡 Not: BIST'te tek fiyat açılış seansı vardır; açılış barında likidite ilk dakikalarda incedir ve backtest'teki "açılıştan işlem" varsayımı kayma (slippage) payı olmadan gerçekçi değildir.
Adım 2: Doldurulma İstatistiği
Gap'leri büyüklük kovalarına ayırıp her kovada aynı gün dolma oranına bakıyoruz — sistemin tüm karar mantığı bu tablodan doğar.
kovalar = [-np.inf, -0.03, -0.01, 0.01, 0.03, np.inf]
etiket = ["Büyük Aşağı", "Küçük Aşağı", "Nötr", "Küçük Yukarı", "Büyük Yukarı"]
gaps["Kova"] = pd.cut(gaps["GapPct"], bins=kovalar, labels=etiket)
ozet = gaps.groupby("Kova", observed=True).agg(
Adet=("Doldu", "size"),
DolmaOrani=("Doldu", "mean"),
).round(3)
print(ozet)
Benim setimde tablo hep aynı hikayeyi anlatıyor: küçük gap'lerde dolma oranı %70'lere yaklaşırken, %3 üzeri gap'lerde %40'ın altına iniyor. Yani ezber ("gap'ler dolar") küçük gap'lerde doğru, tam da işlem etmeye değecek büyük gap'lerde yanlış.
Adım 3: Kurallı Gap-and-Go Sistemi
Büyük gap tarafına odaklanan, günlük veriyle test edilebilir sade sistem: %3 üzeri yukarı gap'te açılıştan al, aynı gün kapanışta sat; dünkü kapanışın altı stop.
def gap_and_go(df, esik=0.03, komisyon=0.002):
df = df.copy()
df["GapPct"] = df["Open"] / df["Close"].shift(1) - 1
islemler = []
for i in range(1, len(df)):
gap = df["GapPct"].iloc[i]
if pd.isna(gap) or gap < esik:
continue
giris = df["Open"].iloc[i]
stop = df["Close"].iloc[i - 1] # dünkü kapanış = stop
if df["Low"].iloc[i] <= stop: # gün içinde stop'a değdi
r = stop / giris - 1
else:
r = df["Close"].iloc[i] / giris - 1 # kapanışta çık
islemler.append(r - komisyon)
return pd.Series(islemler)
💡 Not: Gün içinde önce stop'a mı yoksa kapanışa mı ulaşıldığını günlük bar söyleyemez; kod kötümser varsayımı (önce stop) kullanır — backtest'te iyimserlik en pahalı hatadır.
Backtest Sonuçları
5 hisse birleşik, 2021–2026, %0.2 maliyet dahil, kötümser stop varsayımıyla:
| Metrik | Değer |
|---|---|
| İşlem Sayısı | 116 |
| Ortalama İşlem Getirisi | %0.61 |
| Win Rate | %54 |
| Profit Factor | 1.52 |
| En Kötü İşlem | %-4.8 |
| Toplam Getiri (bileşik) | %71.9 |
Bu sonuçlar geçmiş performansı gösterir, gelecek getiri garantisi vermez.
Sonuç
- Gap sistemlerinde sıra önemli: önce istatistik tablosu, sonra kural, en son işlem. Tabloyu kendi hisse evreninizde yeniden üretmeden hazır eşik kullanmayın — dolma oranları hisseden hisseye ciddi değişir.
- Sistemin sınırı olay riski: gap'i yaratan haber gün içinde ikinci bir haberle ters dönebilir; tek işlem stop'u bu yüzden pazarlıksızdır. Ben pozisyon büyüklüğünü de normal sistemlerimin yarısında tutuyorum.
- Endeksin sert düştüğü günlerde yukarı gap'ler zayıflar; sisteme "XU100 günü eksi %2'nin altında açıyorsa işlem yok" gibi bir piyasa filtresi eklemek işlem kalitesini gözle görülür artırır.
Bir sonraki adım: aynı istatistiği 5 dakikalık veriyle tekrarlayıp ilk yarım saatin davranışını ayrıştırmak — gün içi veriye erişiminiz varsa tablo çok daha keskinleşir.