Dersin fikri: data → rules → signals → backtest
İki öğrenci düşün. Birincisi ASELS grafiğine bakıp "bence yükselecek" diyor. İkincisi şunu yazıyor: "20 günlük ortalama 50 günlük ortalamanın üstündeyse al, altına inerse sat." İkincinin cümlesini herkes aynı veriye uygulayabilir, aynı sonucu bulur ve geçmişte işe yarayıp yaramadığını test edebilir. Birincininkini test edemezsin.
Hoca dersin amacını tam bu cümleyle koyuyor: "Our goal is not to guess prices manually — it is to design repeatable algorithms."
Week 1 slaytının learning goals sayfası dersi üç adımda özetliyor:
| Adım | Ne olur | Hangi derste |
|---|---|---|
| DATA → RULES → SIGNALS | Ham fiyat verisinden açık bir kural, kuraldan her gün için bir sinyal (BUY / SELL) çıkar. | bu ders, support-resistance, indicators |
| SIGNALS → BACKTEST | Sinyaller geçmiş veride denenir: "Bu kural geçmişte işe yarar mıydı?" | rules-backtest, trade-management |
| TOOLS → AI AGENT | Hesaplamalar Python tool'larına konur; bir AI agent bu tool'ları çağırıp sonucu açıklar. | harness-mcp |
Slaytın kapanış cümlesi (s.32): "A financial analysis system is a pipeline."
Market Data → Features → Rules / Model → Backtest → MCP Agent → Explanation
- Market Data: OHLCV satırları (bu ders).
- Features: veriden türetilen sütunlar: return, SMA, RSI, support/resistance zone'ları.
- Rules / Model: feature'lardan sinyal üreten açık kural (ya da opsiyonel bir ML model).
- Backtest: kuralın geçmişteki performansı, bir benchmark ile karşılaştırılarak.
- MCP Agent → Explanation: AI agent tool'ları çağırır ve sonucu açıklar. Slayt s.26: "The LLM decides which tool to call; the financial calculations remain in deterministic Python functions."
İlk slaytın alt başlığı aynı yolu daha kısa yazıyor: market data → indicators → support/resistance → backtest → AI agent. Ders "simple rules"tan "explainable, tool-using financial agents"a doğru ilerliyor.
| Hafta | Konu | Hafta | Konu |
|---|---|---|---|
| 1 | BIST 100 & Yahoo Finance | 8 | Midterm |
| 2 | Algorithmic Trading | 9 | Engineering Economics Concepts |
| 3 | Technical Indicators | 10 | Fundamental Analysis & Valuation |
| 4 | Backtesting Strategies | 11 | Money Management |
| 5 | Time Series with Pandas | 12 | MCP AI Agent |
| 6 | Lab: BIST Data | 13 | Project Presentations |
| 7 | Lab: Support/Resistance | 14 | Project Presentations |
Grading: Midterm %20 ("written exam covering core concepts"), Quiz %10 ("short checks for weekly learning"), Project %30 ("must be implemented in Python"), Final %40 ("comprehensive final examination"). Project için proposal + final report istenir, iki kişilik grup serbest. Slayttaki not: "Python combines financial data, indicators and backtesting into one analysis pipeline."
- Rule-based ≠ guaranteed profit. Kural yazmak kârı garanti etmez, sadece test edilebilir hâle getirir. Slayt s.21: "We must test it."
- AI agent fiyat tahmin etmez ve hesap yapmaz. Hangi tool'un çağrılacağına karar verir; hesap deterministic Python fonksiyonlarında kalır.
- "Repeatable" sadece kodun çalışması demek değil: aynı veri, aynı kural ve aynı parametrelerle başkası da aynı sonucu bulmalı. Bunun için veri kaynağını, tarih aralığını ve adjustment'ları yazman gerekir (Bölüm 3).
Hocanın Week 1'de koyduğu amaç hangisi?
"SMA20 > SMA50 → BUY" kuralı pipeline'ın hangi iki halkası arasında duruyor?
BIST 100, .IS sembolleri ve 30 hisselik evren
BIST 100 is a benchmark index of major companies traded on Borsa İstanbul. Endeksin kodu XU100. "Benchmark" kıyas çubuğu demek: bir stratejinin getirisini "BIST 100 aynı dönemde ne yaptı?" sorusuyla karşılaştırırsın. Slayt s.22: "Strategy Return vs. BIST 100 Return".
Endeksteki her şirketin bir time series'i var: fiyat, volume ve başka piyasa değişkenleri, gün gün sıralanmış.
Yahoo Finance, Borsa İstanbul hisselerine ".IS" suffix'i ekler: ASELS.IS, THYAO.IS, TUPRS.IS, BIMAS.IS, GARAN.IS, EREGL.IS. Endeksin kendisi Yahoo'da XU100.IS sembolüyle bulunur.
Proje, BIST 100'ün içinden farklı sektörlere yayılmış 30 hisselik bir study universe veriyor. Sektör etiketleri ders için sadeleştirilmiş. Amaç sektör karşılaştırması yapabilmek (sector laggard senaryosu): aynı sektörde birden çok hisse olsun diye THYAO/PGSUS, BIMAS/MGROS, TCELL/TTKOM gibi çiftler var.
30 hisselik tabloyu aç (brief §5)
| Ticker | Sector | Ticker | Sector |
|---|---|---|---|
| ASELS | Technology / Defense | PGSUS | Transportation / Airlines |
| TUPRS | Petroleum / Refining | MPARK | Healthcare |
| BIMAS | Retail Trade | EKGYO | Real Estate Investment Trust |
| THYAO | Transportation / Airlines | GUBRF | Chemicals / Fertilizer |
| AKBNK | Banking | TURSG | Insurance |
| KCHOL | Holding / Investment | ENJSA | Electric Utilities |
| EREGL | Basic Metals / Steel | TTKOM | Telecommunications |
| TCELL | Telecommunications | OYAKC | Cement |
| CCOLA | Food & Beverage | AKSEN | Electric Utilities |
| MGROS | Retail Trade | ISMEN | Brokerage / Capital Markets |
| SASA | Chemicals / Plastics | ARCLK | Durables / Machinery |
| TRALT | Mining | ECILC | Healthcare / Pharmaceuticals |
| FROTO | Automotive | MAVI | Retail / Apparel |
| ENKAI | Construction | PETKM | Petrochemicals |
| TOASO | Automotive | KRDMD | Basic Metals / Steel |
Yahoo sembolü her satırda ticker + ".IS" (ör. KRDMD.IS). Brief'in notu: seçim 2026'da gözlenen BIST 100 bileşenlerine dayanıyor ve bankacılık, sanayi, perakende, ulaşım, telekom, enerji, kimya, madencilik, inşaat, sağlık, sigorta, gayrimenkul gibi sektörlere bilerek yayılıyor. Quiz'deki altı hisse (AKBNK, ASELS, TUPRS, TCELL, FROTO, EREGL) bu listenin içinden.
- Endeks üyeliği zamanla değişir. Brief: "Index membership must be re-checked at the start of each run because BIST index constituents change over time." Metodoloji ise deney süresince evreni dondurmanı istiyor: "Freeze the 30-stock universe and sector map for the experiment period." İkisi çelişmiyor: her çalıştırmanın başında listeyi kontrol eder, sonra o deney boyunca sabit tutarsın.
- Bugünkü BIST 100 listesini alıp 2024'e geriye test etmek, o tarihte endekste olmayan ama sonradan girmiş (yani sonradan başarılı olmuş) hisseleri seçmek demektir. Bu, geleceği bilerek seçim yapmaktır. (Bu not bizim; finans literatüründe adı survivorship bias.)
- ".IS"siz sembol (
ASELS) Yahoo'da Borsa İstanbul hissesi olarak bulunmaz; download boş döner. Hocanınload_stock_datafonksiyonu bu durumdaValueError("No data found ...")fırlatıyor.
Bir öğrenci backtest'ini Ekim 2026'daki BIST 100 listesiyle kuruyor ve 2024'ten itibaren test ediyor. Asıl sorun ne?
Türk Hava Yolları'nın günlük verisini yfinance ile indirmek için hangi sembolü yazarsın?
Veri kaynakları: hangi soru hangi kaynaktan?
Fiyat sana "ne oldu"yu söyler. Şirketin bilançosu "şirket nasıl gidiyor"u, faiz ve enflasyon da "ortam nasıl"ı söyler. Proje bu üç katmanı bir araya getiriyor. Slayt s.6: "Python combines these sources into one analysis pipeline."
| Kaynak | Ne verir |
|---|---|
Yahoo Finance (yfinance) | Historical prices, OHLCV, index data |
| Fintables | Financial statements, valuation ratios, company fundamentals |
| TCMB / TÜİK | Interest rates, inflation, FX and macro context |
Brief §6 tabloyu katmanlara ayırıyor:
| Layer | Example source | Use in the system |
|---|---|---|
| Market Data | Yahoo Finance / yfinance | OHLCV, adjusted prices, BIST index series, sector/peer price histories |
| Fundamentals | Fintables | Balance sheet, income statement, cash-flow statement; growth, profitability, leverage, liquidity, valuation ratios |
| Türkiye Macro | TCMB / EVDS | USD/TRY, EUR/TRY, policy-related series, exchange-rate history, monetary indicators |
| Inflation / Economy | TÜİK | CPI, monthly/annual inflation, industrial production, unemployment |
| Global Rates | Federal Reserve | Federal funds target range, FOMC policy changes |
| Financial News | Bloomberg or legally accessible licensed feeds | Timestamped headlines/articles, event tags, source metadata |
| Public Expert Commentary | Instructor-approved public YouTube videos | Speech-to-text, topic extraction, stance/context tags, source timestamps |
- Data source: Yahoo Finance / yfinance sadece örnek. Güvenilir ve yasal erişilebilen her kaynak olur. Ama şunları belgelemelisin: kaynak, tarih aralığı, uygulanan adjustment'lar ve eksik verinin nasıl ele alındığı.
- News: Şirkete özel olaylar için KAP bildirimleri uygun, çünkü resmî bildirim zaman damgası (timestamp) taşırlar. Metin, sayısal finansal verinin yerine geçmez; zaman damgalı bağlam kanıtıdır.
- TMS 29 (inflation accounting): Banka dışı şirketler 2024'ten sonra TMS 29'a göre raporluyor. Eski nominal tablolarla enflasyona göre düzeltilmiş tabloları karıştırmak yanıltıcı olur. Ana analizi birbiriyle tutarlı TMS 29 sonrası dönemlerde tut; testler 1 Ocak 2025 – Kasım 2026 verisini kullanabilir.
Brief ayrıca haber ve video malzemesinin yasal kullanılmasını istiyor: paywall, authentication, lisans kısıtı ya da platform korumaları aşılmaz.
- Yahoo Finance bilanço oranları için ana kaynak değil; dersin ayrımında ROE, P/E, Net Debt/EBITDA Fintables'tan gelir. Kur, faiz ve enflasyon ise TCMB/EVDS ve TÜİK'ten.
- TMS 29 bir fiyat verisi kuralı değil, financial statement kuralı. OHLCV'yi etkilemez; fundamental karşılaştırmalarda hangi dönemleri kullanacağını belirler.
- "Kaynağı belgele" sadece ismini yazmak değil: tarih aralığı, adjustment ve eksik veri politikası da belgenin parçası.
ASELS'in son dört çeyreklik Net Debt/EBITDA oranına ihtiyacın var. Dersin ayrımına göre kaynak hangisi?
Hocanın TMS 29 cevabı ne söylüyor?
yfinance ile veri indirmek
pip install yfinance pandas matplotlib # 1. Install packages
import yfinance as yf # 2. Import libraries
import pandas as pd
import matplotlib.pyplot as plt
df = yf.download( # 3. Download BIST data
"ASELS.IS",
start="2024-01-01",
end="2026-01-01"
)
print(df.head())
Sonuç bir pandas DataFrame: index'te Date, sütunlarda Open, High, Low, Close, Volume. Slaytın altını çizdiği cümle: "One row = one trading day." Slayt ayrıca class work için bir virtual environment ya da temiz bir notebook ortamı öneriyor.
yf.download nasıl okunur
startdahil,endhariç. 2026-10-01'i de istiyorsanend="2026-10-02"yazarsın. Efe'nin notebook'u tam bunu yapıyor.- Satırlar sadece trading day'ler: hafta sonları ve borsa tatilleri yok.
start="2024-01-01"dersen ilk satır 2024-01-02 olur, çünkü 1 Ocak tatil. df.head()ilk beş satırı,df.tail()son beş satırı basar.auto_adjustparametresi fiyatların dividend ve split'e göre düzeltilip düzeltilmeyeceğini belirler (Bölüm 8). Hocanın starter code'uauto_adjust=True, Efe'nin notebook'uauto_adjust=Falsekullanıyor. Hangisini seçtiğini her zaman açıkça yaz.
Hocanın starter code'undaki yükleme fonksiyonu (scenario 3) aynı adımları sağlamlaştırıyor:
def load_stock_data(ticker, start, end=None):
df = yf.download(ticker, start=start, end=end,
auto_adjust=True, progress=False)
if df.empty:
raise ValueError(f"No data found for {ticker}")
if isinstance(df.columns, pd.MultiIndex): # yeni yfinance: (Price, Ticker)
df.columns = df.columns.get_level_values(0)
df = df.copy()
df.index = pd.to_datetime(df.index)
needed = ["Open", "High", "Low", "Close", "Volume"]
df = df[needed].dropna()
return df
Boş sonuç kontrolü, MultiIndex sütunları tek seviyeye indirmek, index'i datetime yapmak ve eksik satırları atmak. Scenario 2'deki sürüm bunlara bir de df["Return"] = df["Close"].pct_change() ekliyor (Bölüm 9).
Not: Slayt s.9'daki "Typical output" tablosu (2025-09-01, Open 164.0, High 168.5, Low 162.7, Close 167.9) sütunların neye benzediğini gösteren bir şekil. df.head() ilk beş satırı basar, yani 2024-01-02'den başlar. Repodaki ASELS verisinde 2025-09-01'in gerçek satırı Open 183.50, High 184.90, Low 178.20, Close 178.50.
end'i hariç tutulan tarih olarak unutursan son günü kaybedersin. Quiz penceresi "1 January 2025 – 1 October 2026" idi;end="2026-10-01"yazan son günü atlar.- yfinance'in güncel sürümleri tek sembolde bile sütunları
("Close", "ASELS.IS")gibi iki seviyeli döndürüyor.df["Close"]o zaman tek sütun yerine bir DataFrame verir. Hocanın kodundakiget_level_values(0)satırı bunun için var. - yfinance'in sütun sırası alfabetik gelir (Adj Close, Close, High, Low, Open, Volume). Sütunları ismiyle seç, sırayla değil.
"pip install yfinance pandas matplotlib in a virtual environment"
(Listenin ilk maddesi. Slayt s.8 da aynısını söylüyor: "For class work, use a virtual environment or a clean notebook environment.") Ortamı kur, kurulumun çalıştığını gösteren bir kontrol satırı yaz ve virtual environment'ın neden istendiğini bir cümleyle açıkla.
İpucu 1
Üç adım var: ortamı oluştur, aktive et, paketleri kur. Python'un bunun için kendi modülü var: venv.
İpucu 2
Kontrol için üç kütüphaneyi import et ve sürümlerini yazdır. Notebook'u altı ay sonra başka bir bilgisayarda çalıştıran kişi için sürüm bilgisi neden önemli?
Çözümü göster
python3 -m venv .venv # 1. create
source .venv/bin/activate # 2. activate (Windows: .venv\Scripts\activate)
pip install yfinance pandas matplotlib # 3. install
python -c "import yfinance, pandas, matplotlib; print(yfinance.__version__, pandas.__version__, matplotlib.__version__)"
pip freeze > requirements.txt # optional: record the exact versions
Why a virtual environment: "The project's packages are installed in their own folder, so they do not clash with other projects or with the system Python, and the same versions can be installed again later from requirements.txt. This keeps the analysis reproducible."
Sürüm satırı boş bir formalite değil. Yukarıdaki tuzak kutusunda gördüğün gibi yfinance'in yeni sürümleri sütunları iki seviyeli döndürüyor; aynı kod bir sürümde çalışıp ötekinde bozulabilir.
"Download ASELS.IS from 2024-01-01 and print df.head()" (weeks/01 practice listesinden; slayt s.9'daki Example 1 ile aynı görev).
İpucu 1
Slayttaki kodu yaz. Sonra kendine sor: ilk satırın tarihi 2024-01-01 olabilir mi? O gün borsa açık mı?
İpucu 2
Çıktıda kaç satır ve hangi sütunlar olmalı? auto_adjust'ı açıkça yazmazsan fiyatlar hangi biçimde gelir?
Çözümü göster
import yfinance as yf
import pandas as pd
df = yf.download("ASELS.IS", start="2024-01-01", end="2026-01-01",
auto_adjust=False, progress=False)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
print(df.head())
Repodaki ASELS verisinden (auto_adjust=False ile indirilmiş), ilk beş satır:
| Date | Adj Close | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|---|
| 2024-01-02 | 46.76 | 46.90 | 47.12 | 45.14 | 45.44 | 46,539,543 |
| 2024-01-03 | 46.46 | 46.60 | 47.26 | 45.92 | 46.74 | 39,344,694 |
| 2024-01-04 | 47.32 | 47.46 | 47.58 | 46.56 | 46.66 | 43,861,988 |
| 2024-01-05 | 47.30 | 47.44 | 47.92 | 46.82 | 47.62 | 33,674,790 |
| 2024-01-08 | 47.50 | 47.64 | 48.24 | 47.38 | 47.64 | 43,664,505 |
Okuması: ilk satır 2024-01-02, çünkü 1 Ocak tatil ve satırlar sadece trading day'ler. 6 ve 7 Ocak hafta sonu, o yüzden 05'ten sonra 08 geliyor. Her satır bir gün: o günün ilk işlem fiyatı (Open), en yüksek (High), en düşük (Low) ve son işlem fiyatı (Close), işlem gören hisse adedi (Volume). auto_adjust=True ile indirirsen Adj Close sütunu kalkar ve dört fiyat dividend'e göre düzeltilmiş gelir: o zaman ilk satırın Close'u 46.76 olur.
Quiz penceresinin son günü 2026-10-01. Bu günü de içeren download hangisi?
yf.download("ASELS.IS", start="2024-01-01", ...) sonrası df.head()'in ilk satırı neden 2024-01-02?
OHLCV: bir satır, bir trading day
| Sütun | Slayttaki tanım | Anlamı |
|---|---|---|
| Open | First traded price | Günün ilk işlem fiyatı |
| High | Highest price | Gün içindeki en yüksek işlem fiyatı |
| Low | Lowest price | Gün içindeki en düşük işlem fiyatı |
| Close | Last traded price | Günün son işlem fiyatı |
| Volume | Trading activity | O gün el değiştiren hisse adedi (Yahoo'da adet, TL değil) |
Bu tanımlardan bir satırın her zaman sağlaması gereken eşitsizlikler çıkar:
$$\text{Low} \le \min(\text{Open}, \text{Close}) \le \max(\text{Open}, \text{Close}) \le \text{High}, \qquad \text{Volume} \ge 0$$Neden? Open ve Close o gün gerçekten işlem görmüş iki fiyat. High "en yüksek işlem fiyatı" olduğuna göre bu ikisinden küçük olamaz; Low da büyük olamaz.
Candle kur
Dört fiyatı yaz. Widget satırı önce yukarıdaki eşitsizliklerle kontrol ediyor, geçerliyse candle'ı çiziyor ve body ile wick'leri hesaplıyor.
Önce tahmin et: AKBNK'nın 2024-08-05 satırı Open 56.00, High 58.45, Low 54.65, Close 56.90. Candle yeşil mi, kırmızı mı? Üst wick mi uzun, alt wick mi? Sonra "AKBNK 2024-08-05" düğmesine bas.
- Close "günün ortalama fiyatı" değil, son işlem fiyatı. Gün içinde fiyat High'a kadar çıkıp Close'ta çok daha aşağıda bitebilir.
- Open, bir önceki günün Close'una eşit olmak zorunda değil. Arada geçen gece haber gelirse fiyat farklı açılır (gap).
- Volume fiyat değildir ve getiri hesabına girmez. Yahoo'da birimi adet; TL cinsinden işlem hacmi (turnover) değil.
Bir veri setinde şu satır var: Open 50.0, High 51.0, Low 49.5, Close 51.4. Ne dersin?
Slayttaki satır: Open 164.0, High 168.5, Low 162.7, Close 167.9. Body ve wick'ler?
Candlestick: body, wick ve renk
- Body (gövde): Open ile Close arasındaki dikdörtgen. Uzunluğu $|\text{Close} - \text{Open}|$.
- Upper wick: body'nin üstünden High'a uzanan çizgi, $\text{High} - \max(\text{Open}, \text{Close})$.
- Lower wick: body'nin altından Low'a uzanan çizgi, $\min(\text{Open}, \text{Close}) - \text{Low}$.
- Renk: slayt s.10 "Green candle: Close > Open". Close < Open ise kırmızı. Close = Open ise body yok, tek bir yatay çizgi.
Okuması: uzun bir lower wick, fiyatın gün içinde Low'a kadar düştüğünü ama kapanışa kadar oradan geri döndüğünü söyler. Candle, tek satırlık OHLC'yi tek bakışta okutan bir resim. Week 3'teki stratejiler (Volume Bubbles gibi) candle'ı temel birim olarak kullanıyor.
Renk, aynı günün Close'unu aynı günün Open'ı ile karşılaştırır; dünkü Close ile değil. Getiri ise dünkü Close'a göre hesaplanır.
AKBNK, 2024-08-02 Close 60.20. Ertesi trading day 2024-08-05: Open 56.00, High 58.45, Low 54.65, Close 56.90. Candle yeşil (56.90 > 56.00), ama günlük getiri $56.90 / 60.20 - 1 = -5.48\%$. Hisseyi cuma kapanışından tutan biri pazartesi para kaybetti.
Repodaki AKBNK verisinde (687 gerçek işlem günü) böyle "yeşil ama düşen" 22 gün, "kırmızı ama yükselen" 44 gün var.
Gerçek veride candle'lar
Bir hisse ve başlangıç tarihi seç. Bir candle'a dokun, üstüne gel ya da Önceki/Sonraki düğmeleriyle candle candle ilerle: o günün OHLCV'si, rengi ve dünkü Close'a göre getirisi aşağıda çıkar. Turuncu volume çubukları Volume = 0 olan satırları gösterir (Bölüm 7).
Önce tahmin et: 2024-08-05 civarında AKBNK'da sadece AKBNK mı sert açıldı, yoksa FROTO ve TCELL'de de aynı gün yeşil ama düşen bir candle var mı? Önce AKBNK düğmesine bas, sonra hisseyi değiştir.
Bir gün Open 30.00, Close 29.40, önceki günün Close'u 28.80. Candle rengi ve günlük getiri?
Uzun bir lower wick ve küçük bir body ne anlatır?
Veri kalitesi: tatil satırları ve Volume = 0
İndirdiğin tablo "gerçek" gibi görünür ama her satırı bir işlem gününü anlatmayabilir. Projede bunun için ayrı bir aşama var. Brief §33'ün uçtan uca senaryosunda 2. adım "Market tool loads OHLCV and XU100", 3. adım "Data Quality Agent validates dates, missing data and adjustments." Risk & Data Quality Agent'ın tanımı da "checks missing/stale data, period alignment, leakage, look-ahead bias".
- Altı hissenin hepsinde 2026-05-27, 28, 29 satırları var. Bu günlerde Volume = 0 ve Open = High = Low = Close = önceki günün Close'u (ASELS'te üç gün de 380.25). Bu tarihler Kurban Bayramı tatiline denk geliyor: işlem yok, Yahoo bir önceki fiyatı tekrar yazmış. Buna stale data denir.
- FROTO ve EREGL'de bir de 2024-04-09 satırı var (yine Volume = 0, dört fiyat eşit). Diğer dört hissede bu tarih yok. Bu yüzden FROTO ve EREGL 691 satır, diğerleri 690 satır.
- Altı dosyada da High < max(Open, Close) ya da Low > min(Open, Close) olan satır yok, aynı tarih iki kez geçmiyor.
Bu satırlarda NaN olmadığı için dropna() onları silmez. Etkileri: o günlerin pct_change'i 0 çıkar; 20 günlük volume ortalaması sıfırlarla aşağı çekilir; weekday istatistiklerinde işlem olmamış bir çarşamba "0 getirili gün" sayılır.
assert df.index.is_unique # aynı tarih iki kez yok
assert (df["High"] >= df[["Open", "Close"]].max(axis=1)).all()
assert (df["Low"] <= df[["Open", "Close"]].min(axis=1)).all()
print(df.isna().sum()) # eksik değer
zero = df[df["Volume"] == 0] # işlem olmayan satırlar
print(zero)
df = df[df["Volume"] > 0] # ya at ya da tut, ama yaz
Hocanın Teams cevabı: kaynağı, tarih aralığını, uygulanan adjustment'ları ve eksik verinin nasıl ele alındığını belgele. Volume = 0 satırlarını atmak da tutmak da savunulabilir; belgelenmemiş olması savunulamaz.
- "Eksik veri yok,
isna().sum()sıfır" demek veri temiz demek değil. Stale satırlarda değer var ama bilgi yok. - İki hisseyi tarih üzerinden birleştirirken (ör. FROTO ile AKBNK) satır sayıları farklıysa tarihler kayar. Index'i tarih olarak hizala (
join/concat), sıra numarasıyla değil.
Bir satırda Volume = 0 ve Open = High = Low = Close = dünkü Close. En makul yorum?
Close ve Adj Close: dividend ve split
Bir hisse 50 TL'de kapanıyor. Şirket hisse başına 2 TL dividend (temettü) dağıtıyor. Ertesi gün (ex-dividend date) hisse 48.50'den kapanıyor. Close'a bakarsan $48.50/50 - 1 = -3.00\%$. Ama hissedar 2 TL nakit aldı: elindeki toplam değer 48.50 + 2 = 50.50. Gerçekte +1%'e yakın kazandı.
Yahoo bunu düzeltmek için ex-date'ten önceki bütün fiyatları $1 - D/C_{\text{prev}} = 1 - 2/50 = 0.96$ ile çarpar. Düzeltilmiş dünkü fiyat 48.00 olur ve getiri $48.50/48.00 - 1 = +1.04\%$ çıkar.
| Split'e göre | Dividend'e göre | Ne zaman | |
|---|---|---|---|
| Close | düzeltilmiş | düzeltilmemiş | Trade listesi ve emir fiyatı (split öncesi günlerde adjustment geri alınarak) |
| Adj Close | düzeltilmiş | düzeltilmiş | Uzun dönem getiri: dividend'ler dahil toplam getiri |
- Split (Türkiye'de çoğunlukla bedelsiz sermaye artırımı): hisse sayısı artar, fiyat aynı oranda düşer. 1'e 2 split'te 80 TL'lik hisse ertesi gün 40.50 olursa düzeltilmemiş getiri −49.38% görünür; gerçekte 40 → 40.50, yani +1.25%. Yahoo geçmiş fiyatları bölerek bu sahte düşüşü yok eder; bu düzeltme hem Close'ta hem Adj Close'ta var.
auto_adjust=False: Close (sadece split düzeltmeli) ve ayrı bir Adj Close sütunu gelir. Efe'nin notebook'u böyle: "The prices are adjusted for splits but not for dividends."auto_adjust=True(hocanın starter code'u): Open, High, Low, Close'un dördü de dividend'e göre ölçeklenir, Adj Close sütunu kalkar.
Close, dividend düzeltmesi içermeyen, split'e göre düzeltilmiş fiyat serisidir. Son split'ten sonraki günlerde Close, borsada gerçekten oluşan kapanış fiyatıyla aynıdır. Split öncesindeki gerçek emir fiyatlarını göstermek için adjustment geri alınmalı; hisse adetleri de tutarlı biçimde işlenmelidir. 1'e 2 split örneğinde split öncesi bir Close'u 2 ile çarparsın, o günkü hisse adedini 2'ye bölersin; pozisyonun TL değeri değişmez.
Brief §6 market data katmanında açıkça "adjusted prices" diyor; Teams cevabı da "the adjustments applied" kısmının belgelenmesini istiyor.
Repodaki satır: Close 36.54, Adj Close 33.19. Oran 0.9083. Neden? AKBNK bu tarihten sonra üç kez dividend dağıtmış (ex-date'ler 2024-03-26, 2025-03-26, 2026-03-26). Her biri geçmiş fiyatları bir kez daha küçültür:
$$(1 - 0.0417)\,(1 - 0.0218)\,(1 - 0.0311) \approx 0.9083$$2024-03-26'nın kendisi: Close 46.00 → 44.28, yani −3.74%. Adj Close 41.78 → 41.97, yani +0.45%. Aradaki fark, veriden çıkan yaklaşık 1.92 TL'lik dividend.
Dividend günlerini tek tek gez
Stepper, seçtiğin hissenin Adj Close / Close oranının değiştiği günleri veriden buluyor. Her ex-date'te iki getiriyi yan yana gösteriyor; son karede 2025-01-02 → 2026-10-01 buy-and-hold'u iki yöntemle hesaplıyor.
Önce tahmin et: TUPRS 2024-04-03'te Close 7.39% düştü. Hisseyi dividend dahil tutan birinin gerçek getirisi o gün yaklaşık ne kadardı: −7%, −1.5% mi, +3% mü?
- Close ile hesaplanan uzun dönem getiri, dividend'i yüksek hisselerde gerçeği epey küçük gösterir. TUPRS 2025-01-02 → 2026-10-01 buy-and-hold: Close ile +171.77%, düzeltilmiş fiyatlarla +216.83%.
auto_adjust=Trueile indirdiğin geçmiş fiyatlar, sonradan her yeni dividend'de yeniden ölçeklenir. Aynı kod bir yıl sonra çalıştırılınca 2024'ün fiyatları farklı çıkar. Tekrarlanabilirlik için indirdiğin veriyi bir kez sakla ve sonraki çalıştırmalarda o dosyayı oku (Efe'nin notebook'u CSV varsa onu okuyor, yoksa indirip yazıyor; brief §34: "Collect and version market datasets").- Bir trade listesinde "70.25'ten aldım" diyorsan kullanacağın sütun Close'tur, çünkü dividend düzeltmesi içermez. Ama Close da split'e göre düzeltilmiş bir seridir: o tarihten sonra split olduysa, gerçek emir fiyatı için adjustment'ı geri al ve hisse adedini aynı oranla düzelt. Adj Close ise geçmişte hiç işlem görmemiş bir sayı olabilir (AKBNK 33.19 gibi).
AKBNK 2024-01-02: Close 36.54, Adj Close 33.19. Doğru açıklama hangisi?
Hocanın starter code'u auto_adjust=True ile indiriyor. Gelen Close sütunu neye karşılık gelir?
pct_change, compounding ve buy-and-hold
df["Return"] = df["Close"].pct_change()
İlk satırın dünü olmadığı için $r$ NaN çıkar. Scenario 2'nin yükleme fonksiyonu bu yüzden sonda df.dropna(subset=["Return"]) yapıyor.
Getiriler toplanmaz, çarpılır (compounding):
$$R_{0 \to n} = \prod_{t=1}^{n} (1 + r_t) - 1 = \frac{C_n}{C_0} - 1$$Pandas'ta $\prod(1 + r)$'nin her güne kadar olan değeri (1 + df["Return"]).cumprod(). Slayt s.22 equity eğrisini tam böyle kuruyor; orada çarpılan seri stratejinin getirisi (rules-backtest dersi).
Buy-and-hold: pencerenin ilk günü al, hiçbir şey yapmadan tut, son gün değerle. Efe'nin notebook'u bunu "first open → last close" olarak hesaplıyor:
$$\text{Buy\&Hold} = \frac{\text{Close}_{\text{son}}}{\text{Open}_{\text{ilk}}} - 1$$Brief §18: her stratejiyi Buy-and-Hold, XU100 ve mümkünse bir sector benchmark ile karşılaştır. Slayt s.37 de "compare with Buy-and-Hold and XU100" diyor.
| Hisse | İlk Open (2025-01-02) | Son Close (2026-10-01) | Buy&Hold % (Close) | Düzeltilmiş fiyatla |
|---|---|---|---|---|
| AKBNK | 65.45 | 67.55 | 3.21 | 8.89 |
| ASELS | 72.95 | 370.00 | 407.20 | 407.84 |
| TUPRS | 142.40 | 387.00 | 171.77 | 216.83 |
| TCELL | 93.20 | 97.70 | 4.83 | 9.03 |
| FROTO | 94.10 | 75.45 | −19.82 | −10.15 |
| EREGL | 24.50 | 36.62 | 49.47 | 52.93 |
Compounding'i satır satır izle
ASELS'in ilk beş satırı (Bölüm 4'teki df.head()). Stepper her satırda pct_change'i ve birikmiş çarpımı veriden hesaplıyor.
Getiri hesaplayıcı
Hisse ve iki tarih yaz (YYYY-MM-DD). Tarih trading day değilse widget en yakın trading day'e kaydırır ve bunu söyler. Sonuç: close-to-close getiri, günlük getirilerin toplamı (yanlış yöntem) ve buy-and-hold.
Önce tahmin et: ASELS, 2025-01-02 → 2026-10-01. Bu 441 günün günlük getirilerini toplasan, gerçek close-to-close getiriye (+403.40%) yakın bir sayı mı çıkar? Tahminini kutuya yaz, sonra hesapla.
- Günlük getirileri toplamak. +10% sonra −10%: toplam 0 görünür, gerçekte $1.10 \times 0.90 - 1 = -1\%$. Uzun pencerede hata büyür: ASELS'te toplam +181.26%, gerçek +403.40%; AKBNK'da toplam +17.54%, gerçek +2.04%. Hatanın yönü bile sabit değil.
- Close-to-close (ilk Close → son Close) ile buy-and-hold (ilk Open → son Close) aynı şey değil; ilk günün gün içi hareketi kadar fark eder. ASELS: +403.40% ile +407.20%.
- Quiz'deki "benchmark" buy-and-hold değildi. ASELS benchmark'ı +239,000 TL iken 100,000 TL ile buy-and-hold (1,370 tam hisse, 72.95'ten) +406,958.50 TL eder. Benchmark sayıları Week 3'teki Volume Bubbles stratejisinin sonuçlarıydı.
(Sadece bu derse giren kısım.) "Backtest period: 1 January 2025 - 1 October 2026 · Initial capital: 100,000 TL per stock … Required Python Workflow: • Load historical price data. • Calculate indicators or features. …"
İpucu 1
Hangi start ve end'i yazarsın? end hariç tutuluyor. Indicator'lar (ör. 20 günlük ortalama) pencerenin ilk gününde değer verebilsin diye ne yapmalısın?
İpucu 2
auto_adjust'ı ne seçeceksin ve neden? Yüklemeden sonra hangi kontrolleri yazdırırsın: satır sayısı, ilk ve son bar, buy-and-hold?
Çözümü göster
import os
TICKERS = ["AKBNK", "ASELS", "TUPRS", "TCELL", "FROTO", "EREGL"]
START, END = "2025-01-01", "2026-10-01" # backtest window (inclusive)
WARMUP_START = "2024-01-01" # indicator warm-up only
def load_prices(ticker, refresh=False):
path = os.path.join("data", f"{ticker}.csv")
if refresh or not os.path.exists(path): # download only when missing or asked
df = yf.download(f"{ticker}.IS", start=WARMUP_START, end="2026-10-02",
auto_adjust=False, progress=False)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
os.makedirs("data", exist_ok=True)
df.to_csv(path)
df = pd.read_csv(path, index_col=0, parse_dates=True) # every run reads the saved snapshot
return df[["Open", "High", "Low", "Close", "Volume"]].dropna()
DATA = {t: load_prices(t) for t in TICKERS}
for t, df in DATA.items():
w = df.loc[START:END]
print(t, len(w), w.index[0].date(), w.index[-1].date(),
round((w.Close.iloc[-1] / w.Open.iloc[0] - 1) * 100, 2))
end="2026-10-02", çünküendexclusive; böylece 2026-10-01 dahil.- Veri 2024'ten yüklenir, böylece indicator'lar 2025-01-02'de dolu olur; ama trade 2025'ten önce açılmaz. Bu, look-ahead değil: indicator sadece geçmiş günleri kullanıyor.
auto_adjust=False: trade listesinde istenen "exact buy and sell prices" dividend'e göre ölçeklenmemiş Close/Open olmalı. Bu sütunlar split'e göre düzeltilmiş; pencereden sonra split olursa gerçek emir fiyatı için adjustment geri alınır. Bu seçimi notebook'ta yaz ("adjusted for splits but not for dividends").- CSV varsa fonksiyon onu okur; yoksa bir kez indirip yazar. Yeniden indirme sadece
refresh=Trueile, bilerek olur. Her indirme farklı çıkabilir: sonradan gelen dividend'ler Adj Close'u, split'ler Close'u da geriye doğru değiştirir. Tekrarlanabilirlik, sabit ve versiyonlanmış bir snapshot'ı (git'e commit'lenmişdata/*.csv) her çalıştırmada yeniden kullanmaktan gelir.refresh=Trueile yeni veri çektiysen yeni snapshot'ı da commit'le ve indirme tarihini yaz. - Kontrol çıktısı: her hissede pencere 441 bar, 2025-01-02 → 2026-10-01; buy-and-hold yukarıdaki tablodaki gibi (ASELS 407.20, FROTO −19.82).
Perşembe +3%, cuma +2%. İki günün toplam getirisi?
Notebook'taki "Buy&Hold %" ASELS için 407.20. Bu sayı nasıl hesaplanmış?
Week 1 Mini Lab
Slayt s.27'deki mini lab, bu dersin her parçasını kullanan bir ödev. Midterm'de aynı fikirler kısa yazılı sorular olarak sorulabilir: "explain OHLCV", "write one simple trading rule in plain English". Cevabı İngilizce kur, çünkü sınav dili İngilizce.
"Task: • Choose one BIST stock (e.g., ASELS.IS). • Download two years of daily data. • Print the first five rows and explain OHLCV. • Plot the closing price. • Identify at least two possible support/resistance zones. • Write one simple trading rule in plain English.
Deliverable: • Python notebook • 1 chart • 3–5 sentences of interpretation • No real-money trading required"
İpucu 1
İlk üç madde Bölüm 4'teki koddan ibaret. Grafik için df["Close"].plot() yeter. OHLCV açıklamasında beş sütunun her biri için bir cümle yaz.
İpucu 2
Zone: fiyatın birden çok kez döndüğü bir fiyat bandı (tek bir çizgi değil). Kural: koşul + eylem, ve sadece o gün bilinen bilgiyle. Slayt s.21'deki SMA kuralını düz İngilizceyle yazabilirsin.
Çözümü göster
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
df = yf.download("ASELS.IS", start="2024-01-01", end="2026-01-01",
auto_adjust=False, progress=False)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
print(df.head())
ax = df["Close"].plot(figsize=(10, 4), title="ASELS.IS daily close")
ax.axhspan(54, 56, alpha=0.2, color="green") # support zone
ax.axhspan(188, 191, alpha=0.2, color="red") # resistance zone
ax.set_ylabel("TL")
plt.show()
OHLCV (model answer): "Each row is one trading day. Open is the first traded price of the day, High the highest and Low the lowest traded price, and Close the last traded price. Volume is the number of shares traded that day, a measure of trading activity. A candle is green when Close > Open."
İki zone (repodaki ASELS verisi, 2024-01-02 → 2025-12-31; slayt s.17–18'deki yöntemle: 5 günlük pivot'lar, 1.5% tolerance ile gruplanmış):
- ≈ 54–56 TL support: 2024 Mart–Ekim arasında 8 pivot low bu bantta (ör. 2024-03-06, 2024-08-05, 2024-09-12, 2024-10-15).
- ≈ 188–191 TL resistance: 2025 Temmuz–Kasım arasında 5 pivot high (2025-07-24, 08-01, 08-05, 08-29, 11-19) ve 2025-10-20'de bir pivot low.
Pivot ve cluster yöntemini bir sonraki ders (support-resistance) adım adım anlatıyor; mini lab'de grafiğe bakıp bu bantları işaretlemek yeterli.
Trading rule (plain English): "If the 20-day simple moving average of the close is above the 50-day average at today's close, hold the stock from the next day; if it falls below, sell." Kural açık bir koşul, bir eylem ve bir zamanlama içeriyor; sadece o gün bilinen bilgiyi kullanıyor.
Interpretation (3–5 sentences): "ASELS rose from about 47 TL to about 230 TL over the two years, so the series is strongly trending. Price bounced several times from the 54–56 TL band in 2024, which acts as a support zone. In mid-2025 it stalled repeatedly near 188–191 TL before breaking higher. A rule-based strategy is not guaranteed to be profitable, so the rule must be backtested against buy-and-hold and BIST 100. This is an educational exercise, not investment advice."
- "Buy when the price looks low" bir kural değil: "low"un ölçüsü yok, test edilemez. Kural bir koşul ve bir eylem içermeli.
- Support/resistance tek bir çizgi değil, zone. Slayt s.16: "support/resistance is usually a zone, not one exact price."
- Kuralı bugünün Close'u ile kurup bugünün Close'undan işlem yaptırmak, gün bitmeden Close'u bilmek demek. Sinyal bugün, işlem yarın (slayt s.22, look-ahead bias; rules-backtest dersi).
Mini Lab'in "simple trading rule in plain English" maddesi için en iyi cümle hangisi?
Proje, AI use policy ve sınırlar
- AI tools may support idea generation, coding assistance and understanding.
- Students remain responsible for the analysis, interpretation and final work.
- Prompts and materially used AI outputs should be documented in the appendix.
- Financial outputs must be verified against the underlying data and code.
AI ASSISTS · Python CALCULATES · Student EXPLAINS
Brief'in core design principle'ı aynı iş bölümünü projeye genişletiyor: "LLM reasons and explains. Python tools calculate. The harness controls. Backtests verify. Evidence is logged. A human makes the final decision."
Overview: "BIST 100 Stock Analysis, Prediction and AI-Agent Assisted Strategy Development": Market Data (Yahoo Finance) → Analysis (Python + Pandas) → Strategy (Technical + Fundamental) → Evaluation (Backtest + Metrics) → AI Layer (MCP Agent). Hedef: "build an explainable research prototype — not an investment recommendation system."
- Architecture & Strategy Development: data pipeline, stock universe, indicators, rules ve MCP tool'larını tanımla.
- Backtesting & Simulation: geçmiş simülasyonlar, benchmark ile karşılaştırma.
- Prediction & Evaluation: opsiyonel ML model; accuracy ve finansal performans.
- Final AI-Agent Demo: doğal dille soru sor, onaylı analiz tool'larını çalıştır.
Beklenenler (s.30): temiz Python ve reproducible data pipeline; en az bir rule-based strateji; benchmark karşılaştırmalı backtest; açık metrikler ve limitation tartışması; tool'ları güvenle kullanan basit bir MCP agent; final report + presentation + source code. Önerilen metrikler: Cumulative Return, Max Drawdown, Sharpe Ratio, Hit Rate / Accuracy, Benchmark Difference.
Term project'in brief'i bu planı "Integrated BIST 100 Agentic Financial Analytics Harness" olarak genişletiyor; teslim 24 Kasım 2026 23:59, Teams.
- Sistem "strictly for educational and research purposes": broker'a bağlanmaz, gerçek emir göndermez, gerçek para yönetmez, kişisel yatırım tavsiyesi vermez. Mini lab da "No real-money trading required" diyor.
- Historical evidence ≠ investment advice. Geçmişte işe yaramış bir kural, bugün "al" demek için gerekçe değildir (brief §38'deki yedi ayrımdan biri; hepsi vize provası dersinde).
- AI'nın verdiği bir sayıyı (ör. "ASELS 2025'te %400 yükseldi") rapora koymadan önce veriden ve koddan doğrula. Policy'nin dördüncü maddesi tam bunu istiyor.
AI asistanı raporuna "TUPRS'un buy-and-hold getirisi +216.83%" yazdı; senin notebook'un +171.77% buluyor. Policy'ye göre ne yaparsın?
Son kontrol
Kâğıt kalemle çöz, sonra cevaba bak.
Bir öğrenci yf.download("EREGL", start="2025-01-01", end="2026-10-01") yazıyor. Kaç hata var?
FROTO 2025-12-03: önceki Close 98.20, Close 90.20 (−8.15%); Adj Close'a göre getiri −2.12%. Fark neyi gösteriyor?
Bir candle'ın Open'ı 40.0, Close'u 41.0, High'ı 41.0. Ne söylersin?
Pipeline'da "hesaplamayı kim yapar?" sorusunun hocanın cevabı?
Bir hisse üç gün üst üste −10%, +10%, +10% yapıyor. Toplam getiri?