← Makalelere geri dön
March 15, 2026
5 dakikalık okuma

Walk-Forward Optimizasyonu: Tek Dürüst Strateji Testi

#algotrading
#backtest
#walk-forward
#overfitting
#doğrulama
#optimizasyon

Bir stratejiyi optimize ettiniz. 12 ayrım parametresi, 9 meta-parametre — toplamda 21. Tek bir parite üzerinde 25 aylık backtest, MaxLev'de PnL +%3342 gösteriyor. Öz sermaye eğrisi neredeyse hiç düşüş yaşamadan yükseliyor. Sharpe 3'ün üzerinde. Her şey mükemmel görünüyor.

Botu başlatıyorsunuz. İki hafta sonra strateji sermayenin %18'ini kaybediyor. Bir ay sonra — %34. Tarihsel verilerde "çalışan" parametreler, belirli bir piyasa olayları dizisine uyacak şekilde ayarlanmış çıktı. Bir kalıp bulmadınız — gürültüyü ezberlediniz.

Bu klasik aşırı uyum (overfitting). Ve bunu üretime geçmeden önce tespit etmenin tek sistematik yolu Walk-Forward Optimizasyonu'dur (WFO).

Tek Eğitim/Test Bölümü Tuzağı

Eğitim/test bölümü tuzağı görselleştirmesi

Standart yaklaşım: veriyi %70 eğitim ve %30 test olarak bölmek. Eğitim üzerinde optimize etmek, test üzerinde doğrulamak. Sonuç olumlu ise — başlatmak.

Sorun şu: bu tek bir bölüm üzerindeki tek bir testtir. Sonuç, sınırı nereye çizdiğinize bağlıdır. Sınırı bir ay kaydırın — örnek dışı (out-of-sample) PnL +%40'tan -%15'e değişebilir.

Veri:     |===== Eğitim (%70) =====|== Test (%30) ==|
Bölüm 1: |===2024-01..2025-09====|==2025-10..26-01==|   OOS PnL: +%38
Bölüm 2: |===2024-01..2025-06====|==2025-07..26-01==|   OOS PnL: -%12
Bölüm 3: |===2024-04..2025-12====|==2026-01..26-04==|   OOS PnL: +%7

Üç farklı bölüm — üç farklı sonuç. Hangisine güvenilir? Hiçbirine. Tek bir eğitim/test bölümü, Monte Carlo Bootstrap yazısında açıkladığımız problemlere sahip tek noktalı tahminin ta kendisidir. Bir kontrol değil, ardışık veri segmentleri üzerinde sistematik bir kontroller serisi gereklidir.

Walk-Forward Optimizasyonu tam olarak bunun için vardır.

Walk-Forward Optimizasyonu Nedir?

Walk-forward kayan pencere diyagramı

WFO, kayan (veya genişleyen) veri pencereleri üzerinde bir stratejinin sıralı olarak optimize edilmesi ve doğrulanması prosedürüdür. Fikir şudur: periyodik olarak mevcut veriler üzerinde parametreleri yeniden optimize ettiğiniz ve ardından bir sonraki yeniden optimizasyona kadar işlem yaptığınız gerçek işlem sürecini simüle etmek.

Her "pencere" iki bölümden oluşur:

  • In-Sample (IS) — parametrelerin optimize edildiği dönem
  • Out-of-Sample (OOS) — bulunan parametrelerin uyum olmaksızın test edildiği dönem

Temel özellik: OOS dönemleri çakışmaz ve toplu olarak verinin önemli bir bölümünü kapsar. Elde edilen öz sermaye eğrisi yalnızca OOS segmentlerinden oluşturulur — bu stratejinin dürüst değerlendirmesidir.

Sabit Başlangıçlı WFO (Genişleyen Pencere)

Sabit başlangıçlı WFO genişleyen pencere görselleştirmesi

Sabit başlangıçlı WFO'da eğitim döneminin başlangıcı sabittir, sonu ise her pencereyle genişler:

Pencere 1: Eğitim [2024-01]         Test [2024-04]
Pencere 2: Eğitim [2024-01..04]     Test [2024-07]    (büyüyen eğitim)
Pencere 3: Eğitim [2024-01..07]     Test [2024-10]
Pencere 4: Eğitim [2024-01..10]     Test [2025-01]
Pencere 5: Eğitim [2024-01..2025-01]  Test [2025-04]

Avantajları:

  • Her sonraki eğitim dönemi daha fazla veri içerir — optimizasyon daha kararlıdır
  • Erken kalıplar kaybolmaz — her zaman eğitim setindedir
  • Uygulaması daha kolaydır

Dezavantajları:

  • Eski veriler mevcut kalıpları "sulandırabilir"
  • Piyasa yapısal olarak değişmişse — eski veriler zararlıdır
  • Eğitim dönemi süresiz büyür, optimizasyon süresini artırır

Kayan Pencereli WFO (Sliding Window)

Kayan pencereli WFO'da sabit uzunluklu bir eğitim dönemi veri üzerinde "kayar":

Pencere 1: Eğitim [2024-01..06]  Test [2024-07..09]
Pencere 2: Eğitim [2024-04..09]  Test [2024-10..12]
Pencere 3: Eğitim [2024-07..12]  Test [2025-01..03]
Pencere 4: Eğitim [2024-10..2025-03]  Test [2025-04..06]
Pencere 5: Eğitim [2025-01..06]  Test [2025-07..09]

Avantajları:

  • Mevcut piyasa rejimine uyum sağlar
  • Sabit optimizasyon süresi
  • Eski, alakasız veriler sonuçları etkilemez

Dezavantajları:

  • Eğitim için daha az veri — optimal parametrelerin yüksek varyansı
  • Pencere uzunluğu seçimine duyarlı
  • Nadir ama önemli olayları "unutabilir" (ani çöküşler)

Birleşimsel Arındırılmış Çapraz Doğrulama (CPCV)

Birleşimsel arındırılmış çapraz doğrulama görselleştirmesi

Marcos Lopez de Prado tarafından önerilen gelişmiş bir yöntem. Veri NN gruba bölünür, bunlardan kk tanesi test için seçilir. Standart çapraz doğrulamadan temel farkı arındırma (eğitim/test sınırındaki veriyi kaldırma) ve ambargo (veri sızıntısını önlemek için ek bir boşluk):

Kombinasyon sayısı=(Nk)\text{Kombinasyon sayısı} = \binom{N}{k}

N=10,k=2N = 10, k = 2 ile: 45 eğitim/test kombinasyonu. Her kombinasyon bir OOS sonucu üretir ve nihai tahmin tüm kombinasyonların ortalamasıdır.

from itertools import combinations
import numpy as np

def cpcv_splits(n_groups: int, k_test: int, purge_pct: float = 0.01):
    """
    Arındırma ile CPCV bölümleri oluşturur.

    Args:
        n_groups: grup sayısı
        k_test: her bölümdeki test grubu sayısı
        purge_pct: arındırma için veri fraksiyonu (eğitim/test sınırında)
    """
    groups = list(range(n_groups))
    splits = []

    for test_groups in combinations(groups, k_test):
        train_groups = [g for g in groups if g not in test_groups]
        splits.append({
            "train": train_groups,
            "test": list(test_groups),
            "purge_groups": _get_purge_groups(train_groups, test_groups),
        })

    return splits

def _get_purge_groups(train, test):
    """Arındırma için eğitim/test sınırındaki gruplar."""
    purge = set()
    for t in test:
        if t - 1 in train:
            purge.add(t - 1)
        if t + 1 in train:
            purge.add(t + 1)
    return list(purge)

CPCV, veri az olduğunda kayan WFO'dan daha iyidir, ancak hesaplama açısından daha maliyetlidir. 21 parametreli ve 25 aylık veriye sahip bir strateji için kayan WFO ile başlamanızı ve CPCV'yi ek bir kontrol olarak kullanmanızı öneririz.

Temel WFO Parametreleri

Temel WFO parametreleri görselleştirmesi

Eğitim Dönemi Uzunluğu

Çok kısa eğitim — güvenilir optimizasyon için yetersiz veri. Çok uzun — eski veriler mevcut kalıpları sulandırır.

Pratik kural: eğitim en az 200-300 işlem içermelidir. Strateji günde 2 işlem yapıyorsa:

Tmin=300 is¸lem2 is¸lem/gu¨n=150 gu¨n5 ayT_{min} = \frac{300\ \text{işlem}}{2\ \text{işlem/gün}} = 150\ \text{gün} \approx 5\ \text{ay}

Rejim geçişleri olan kripto için kayan pencerede en fazla 6-12 ay öneriyoruz.

Test Dönemi Uzunluğu

Test dönemi istatistiksel olarak anlamlı bir değerlendirme için yeterli olmalı, ancak çok uzun olmamalı — aksi takdirde parametrelerin bozulması için zaman kalır.

Kural: test = eğitimin %20-33'ü. Eğitim = 6 ay ise, test = 1,5-2 ay.

Örtüşme

Kayan WFO'da pencereler örtüşebilir. Örtüşme OOS veri noktalarının sayısını artırır ancak tahminler arasında korelasyon getirir:

Örtüşmesiz:
  Eğitim [01..06] → Test [07..09]
  Eğitim [07..12] → Test [01..03]

%50 örtüşme ile:
  Eğitim [01..06] → Test [07..09]
  Eğitim [04..09] → Test [10..12]
  Eğitim [07..12] → Test [01..03]

Öneri: eğitim döneminde %50 örtüşme — pencere sayısı ve tahminlerin bağımsızlığı arasında iyi bir denge.

Yeniden Optimizasyon Sıklığı

Parametreleri ne sıklıkta yeniden hesapladığınızı belirler. Kripto piyasasında optimal sıklık her 1-3 aydır. Daha sık yeniden optimizasyon gürültüye aşırı uyum riskini artırır; daha az sık — parametre bayatlaması riskini artırır.

Walk-Forward Verimlilik Oranı ve Bozulma Hızı

Walk-forward verimlilik oranı ve bozulma görselleştirmesi

Walk-Forward Verimlilik Oranı (WFER)

Temel WFO metriği — OOS getirilerinin IS getirilerine oranı:

WFER=PnLOOSPnLIS\text{WFER} = \frac{\text{PnL}_{OOS}}{\text{PnL}_{IS}}

Yorumlama:

WFER Yorum
> 0.8 Mükemmel sağlamlık. Parametreler yeni veriye aktarılıyor.
0.5 — 0.8 Kabul edilebilir sağlamlık. Strateji çalışıyor ancak bozulmayla.
0.3 — 0.5 Sınır durum. Kısmi aşırı uyum olası.
< 0.3 Aşırı uyum. Parametreler IS verisine uydurulmuş.
< 0 Strateji OOS'ta karsız. Tam aşırı uyum veya mantık hatası.

WFER < 0.5 ise — strateji büyük olasılıkla aşırı uyumlu. Bu bizim birincil filtremizdir.

Bozulma Hızı

Optimal parametrelerin zaman içinde ne kadar hızlı etkinliğini kaybettiğini gösterir:

Bozulma hızı=d(OOS PnL)dt\text{Bozulma hızı} = \frac{d(\text{OOS PnL})}{dt}

Pratikte: test dönemini alt aralıklara bölün ve PnL dinamiklerini takip edin:

def degradation_rate(oos_returns: np.ndarray, n_subperiods: int = 4) -> float:
    """
    Parametre bozulma hızını tahmin eder.

    OOS dönemini alt aralıklara böler ve PnL'nin alt aralık numarasına göre
    doğrusal regresyonunun eğimini hesaplar.

    Döndürür:
        eğim: negatif = bozulma, pozitif = iyileşme
    """
    chunk_size = len(oos_returns) // n_subperiods
    subperiod_pnls = []

    for i in range(n_subperiods):
        start = i * chunk_size
        end = start + chunk_size
        sub_pnl = np.sum(oos_returns[start:end])
        subperiod_pnls.append(sub_pnl)

    x = np.arange(n_subperiods)
    slope = np.polyfit(x, subperiod_pnls, 1)[0]

    return slope

Bozulma hızı güçlü biçimde negatifse — parametreler hızla bayatlar ve daha sık yeniden optimizasyon veya daha kısa eğitim dönemi gerekir.

Python'da Tam WFO Pipeline Uygulaması

WFO pipeline mimarisi görselleştirmesi

import numpy as np
import pandas as pd
from dataclasses import dataclass, field
from typing import Callable, List, Optional
import warnings

@dataclass
class WFOWindow:
    """Tek bir walk-forward penceresi."""
    window_id: int
    train_start: int         # eğitim başlangıç indeksi
    train_end: int           # eğitim bitiş indeksi (dahil değil)
    test_start: int          # test başlangıç indeksi
    test_end: int            # test bitiş indeksi (dahil değil)
    best_params: dict = field(default_factory=dict)
    is_pnl: float = 0.0     # örnek içi PnL
    oos_pnl: float = 0.0    # örnek dışı PnL
    oos_returns: np.ndarray = field(default_factory=lambda: np.array([]))
    wfer: float = 0.0       # walk-forward verimlilik oranı

@dataclass
class WFOResult:
    """Tüm WFO'nun sonucu."""
    windows: List[WFOWindow]
    aggregate_oos_pnl: float
    aggregate_is_pnl: float
    wfer: float
    degradation_rate: float
    oos_equity: np.ndarray
    oos_sharpe: float
    oos_max_dd: float
    n_windows: int
    passed: bool             # stratejinin filtreyi geçip geçmediği

class WalkForwardOptimizer:
    """
    Walk-Forward Optimizasyon pipeline'ı.

    Sabit başlangıçlı (genişleyen) ve kayan (sliding) modları destekler.
    """

    def __init__(
        self,
        data: np.ndarray,
        optimize_fn: Callable,
        evaluate_fn: Callable,
        mode: str = "rolling",         # "rolling" veya "anchored"
        train_size: int = 180,         # gün
        test_size: int = 60,           # gün
        step_size: int = 60,           # pencere adım büyüklüğü, gün
        min_trades: int = 30,          # OOS'ta min işlem sayısı
        wfer_threshold: float = 0.5,   # kabul/ret için WFER eşiği
    ):
        self.data = data
        self.optimize_fn = optimize_fn
        self.evaluate_fn = evaluate_fn
        self.mode = mode
        self.train_size = train_size
        self.test_size = test_size
        self.step_size = step_size
        self.min_trades = min_trades
        self.wfer_threshold = wfer_threshold

    def generate_windows(self) -> List[WFOWindow]:
        """Walk-forward pencerelerini oluşturur."""
        n = len(self.data)
        windows = []
        window_id = 0

        if self.mode == "rolling":
            start = 0
            while start + self.train_size + self.test_size <= n:
                w = WFOWindow(
                    window_id=window_id,
                    train_start=start,
                    train_end=start + self.train_size,
                    test_start=start + self.train_size,
                    test_end=min(start + self.train_size + self.test_size, n),
                )
                windows.append(w)
                start += self.step_size
                window_id += 1

        elif self.mode == "anchored":
            train_end = self.train_size
            while train_end + self.test_size <= n:
                w = WFOWindow(
                    window_id=window_id,
                    train_start=0,
                    train_end=train_end,
                    test_start=train_end,
                    test_end=min(train_end + self.test_size, n),
                )
                windows.append(w)
                train_end += self.step_size
                window_id += 1

        return windows

    def run(self) -> WFOResult:
        """Tam WFO pipeline'ını çalıştırır."""
        windows = self.generate_windows()
        all_oos_returns = []

        for w in windows:
            train_data = self.data[w.train_start:w.train_end]
            test_data = self.data[w.test_start:w.test_end]

            best_params, is_pnl = self.optimize_fn(train_data)
            w.best_params = best_params
            w.is_pnl = is_pnl

            oos_pnl, oos_returns = self.evaluate_fn(test_data, best_params)
            w.oos_pnl = oos_pnl
            w.oos_returns = oos_returns

            if is_pnl != 0:
                w.wfer = oos_pnl / is_pnl
            else:
                w.wfer = 0.0

            all_oos_returns.extend(oos_returns)

        all_oos = np.array(all_oos_returns)
        oos_equity = np.cumprod(1 + all_oos)
        peak = np.maximum.accumulate(oos_equity)
        max_dd = ((oos_equity - peak) / peak).min()

        aggregate_is = sum(w.is_pnl for w in windows)
        aggregate_oos = sum(w.oos_pnl for w in windows)
        wfer = aggregate_oos / aggregate_is if aggregate_is != 0 else 0

        if np.std(all_oos) > 0:
            oos_sharpe = np.mean(all_oos) / np.std(all_oos) * np.sqrt(252)
        else:
            oos_sharpe = 0

        deg_rate = self._degradation_rate(windows)

        passed = wfer >= self.wfer_threshold and aggregate_oos > 0

        return WFOResult(
            windows=windows,
            aggregate_oos_pnl=aggregate_oos,
            aggregate_is_pnl=aggregate_is,
            wfer=wfer,
            degradation_rate=deg_rate,
            oos_equity=oos_equity,
            oos_sharpe=oos_sharpe,
            oos_max_dd=max_dd,
            n_windows=len(windows),
            passed=passed,
        )

    def _degradation_rate(self, windows: List[WFOWindow]) -> float:
        """Pencere numaralarına göre OOS PnL'nin eğimi."""
        if len(windows) < 3:
            return 0.0
        pnls = [w.oos_pnl for w in windows]
        x = np.arange(len(pnls))
        slope = np.polyfit(x, pnls, 1)[0]
        return slope

Kullanım Örneği

import numpy as np

np.random.seed(42)
prices = 100 * np.cumprod(1 + np.random.normal(0.0002, 0.02, 750))

def my_optimize(train_data):
    """
    Eğitim verisi üzerinde stratejiyi optimize et.
    (best_params, is_pnl) döndürür.
    """
    best_pnl = -np.inf
    best_params = {}

    for fast in range(5, 30, 5):
        for slow in range(20, 100, 10):
            if fast >= slow:
                continue
            pnl, _ = _run_strategy(train_data, fast, slow)
            if pnl > best_pnl:
                best_pnl = pnl
                best_params = {"fast": fast, "slow": slow}

    return best_params, best_pnl

def my_evaluate(test_data, params):
    """
    Sabit parametrelerle test verisi üzerinde stratejiyi değerlendir.
    (oos_pnl, oos_returns) döndürür.
    """
    pnl, returns = _run_strategy(test_data, params["fast"], params["slow"])
    return pnl, returns

def _run_strategy(data, fast_period, slow_period):
    """Basit MA kesişim stratejisi."""
    fast_ma = pd.Series(data).rolling(fast_period).mean().values
    slow_ma = pd.Series(data).rolling(slow_period).mean().values

    position = 0
    returns = []

    for i in range(slow_period, len(data) - 1):
        if fast_ma[i] > slow_ma[i] and position <= 0:
            position = 1
        elif fast_ma[i] < slow_ma[i] and position >= 0:
            position = -1

        daily_ret = (data[i + 1] - data[i]) / data[i]
        returns.append(position * daily_ret)

    total_pnl = np.sum(returns)
    return total_pnl, np.array(returns)

wfo = WalkForwardOptimizer(
    data=prices,
    optimize_fn=my_optimize,
    evaluate_fn=my_evaluate,
    mode="rolling",
    train_size=180,    # 6 ay
    test_size=60,      # 2 ay
    step_size=60,      # adım = test
)

result = wfo.run()

print(f"Pencereler: {result.n_windows}")
print(f"OOS PnL: {result.aggregate_oos_pnl:.4f}")
print(f"IS PnL:  {result.aggregate_is_pnl:.4f}")
print(f"WFER:    {result.wfer:.3f}")
print(f"OOS Sharpe: {result.oos_sharpe:.2f}")
print(f"OOS MaxDD:  {result.oos_max_dd:.2%}")
print(f"Bozulma: {result.degradation_rate:.5f}")
print(f"Geçti:   {result.passed}")

for w in result.windows:
    print(f"  Pencere {w.window_id}: IS={w.is_pnl:.4f} OOS={w.oos_pnl:.4f} "
          f"WFER={w.wfer:.2f} params={w.best_params}")

Sonuçları Yorumlamak: Ne Zaman Güvenilir, Ne Zaman Reddedilir

Strateji WFO'yu Geçti

Tüm pencerelerde WFER >= 0.5, OOS PnL pozitif ve kararlıysa:

Pencere 0: IS=0.0812  OOS=0.0531  WFER=0.65  params={'fast': 10, 'slow': 50}
Pencere 1: IS=0.0744  OOS=0.0489  WFER=0.66  params={'fast': 10, 'slow': 50}
Pencere 2: IS=0.0698  OOS=0.0401  WFER=0.57  params={'fast': 15, 'slow': 50}
Pencere 3: IS=0.0823  OOS=0.0512  WFER=0.62  params={'fast': 10, 'slow': 60}
Pencere 4: IS=0.0756  OOS=0.0478  WFER=0.63  params={'fast': 10, 'slow': 50}
→ Toplam WFER: 0.63, tüm pencereler > 0.5, parametreler kararlı

İyi işaretler:

  • WFER pencerelerde kararlı (keskin sıçramalar yok)
  • Parametreler pencereler arasında benzer (fast = 10-15, slow = 50-60)
  • Çoğu pencerede OOS PnL pozitif
  • Bozulma hızı sıfıra yakın

Strateji WFO'yu Geçemedi

Pencere 0: IS=0.2341  OOS=-0.0312  WFER=-0.13  params={'fast': 5, 'slow': 95}
Pencere 1: IS=0.1987  OOS=0.0089   WFER=0.04   params={'fast': 25, 'slow': 30}
Pencere 2: IS=0.2156  OOS=-0.0567  WFER=-0.26  params={'fast': 10, 'slow': 90}
Pencere 3: IS=0.1834  OOS=0.0234   WFER=0.13   params={'fast': 20, 'slow': 40}
→ Toplam WFER: -0.07, IS yüksek, OOS sıfıra yakın → aşırı uyum

Aşırı uyum belirtileri:

  • Yüksek IS PnL, düşük/negatif OOS PnL — klasik aşırı uyum
  • Parametreler pencereler arasında önemli ölçüde değişiyor — kararlı bir optimum yok
  • Çoğu pencerede WFER < 0.3 — parametreler aktarılmıyor
  • Bozulma hızı güçlü biçimde negatif — hızlı bozulma

Parametre kararlılığı analizi hakkında daha fazla bilgi — Plato analizi makalesinde. Optimum "keskin" ise (küçük parametre değişiklikleriyle dik biçimde düşüyorsa) — bu ek bir aşırı uyum sinyalidir.

Kripto Para Birimleri için WFO Özellikleri

Kripto para birimi WFO özellikleri görselleştirmesi

Kripto para birimleri, geleneksel piyasalarda olmayan benzersiz WFO sorunları yaratır.

Rejim Geçişleri

Kripto piyasası kökten farklı rejimler arasında geçiş yapar: boğa trendi, ayı trendi, yüksek/düşük volatiliteli yatay hareket. Bir rejimde optimal parametreler diğerinde karsız olabilir.

Çözüm: 4-6 aylık pencereyle kayan WFO (sabit başlangıçlı değil) kullanın. Bu eski rejimleri "unutmaya" izin verir. Ek olarak — volatiliteye göre veriyi kümeleyip her küme için WFO'yu ayrı çalıştırın.

Kısa Tarih

Çoğu altcoin'in 3 yıldan az işlem geçmişi vardır. Eğitim = 6 ay ve test = 2 ay ile yalnızca 4-5 pencere elde edersiniz — istatistiksel olarak zayıf bir tahmin.

Çözüm: kayan WFO yerine veya ek olarak CPCV kullanın. CPCV aynı veriden daha fazla kombinasyon üretir. 10 grup ve k=2 için: 4-5 pencere yerine 45 kombinasyon.

Yapısal Likidite Değişiklikleri

Kripto parite likiditesi durağan değildir: bir parite 6 ay likit olabilir, ardından hacimler 10 kat düşer. Likit bir piyasada optimize edilen parametreler likit olmayan birinde çalışmaz.

Çözüm: WFO pipeline'ına bir likidite filtresi ekleyin. Ortalama günlük hacmin eşiğin altında olduğu pencereleri dışarıda bırakın. Test dönemindeki likiditenin eğitim dönemiyle karşılaştırılabilir olduğunu doğrulayın.

Fonlama Oranı Etkisi

Kaldıraçlı vadeli işlem stratejileri için fonlama oranları OOS sonuçlarını temelden değiştirebilir. Bir strateji 2 ay boyunca OOS'ta +%5 gösterir, ancak 10x kaldıraçta fonlama %3,6 yer.

Fonlama etkisinin ayrıntılı analizi — Fonlama oranları kaldıracınızı öldürüyor makalemizde. WFO'da OOS PnL değerlendirirken fonlama maliyetlerini hesaba katmaya dikkat edin.

Çok Parametreli Stratejiler: 12+ Parametreyle WFO Neden Kritik

Çok parametreli optimizasyonda boyutluluk laneti

Tek bir pariteden 25 aylık veri üzerinde 21 parametreli (12 ayrım + 9 meta) bir strateji, devasa bir arama uzayına sahip bir modeldir.

Boyutluluk Laneti

Parametre kombinasyonlarının sayısı parametre sayısıyla üstel olarak büyür:

Kombinasyonlar=i=1nPi\text{Kombinasyonlar} = \prod_{i=1}^{n} |P_i|

21 parametrenin her biri en az 10 değer alırsa:

1021=10 sekstilyon kombinasyon10^{21} = 10\ \text{sekstilyon kombinasyon}

Bayesian optimizasyonu ile bile (ayrıntılar Koordinat İnişi vs Bayesian makalesinde), uzayın ihmal edilebilir bir fraksiyonunu keşfedersiniz. Bulunan optimumun gerçek bir kalıp yerine gürültü eseri olma olasılığı parametre sayısıyla birlikte büyür.

Çoklu Karşılaştırmalar için Bonferroni Formülü

MM parametre kombinasyonu test ederseniz, yanlış "keşif" (şans eseri iyi bir sonuç bulma) olasılığı:

P(yanlıs¸ kes¸if)=1(1α)M1eαMP(\text{yanlış keşif}) = 1 - (1 - \alpha)^M \approx 1 - e^{-\alpha M}

α=0.05\alpha = 0.05 ve M=10000M = 10000 denenen kombinasyonda:

P1e5001.0P \approx 1 - e^{-500} \approx 1.0

"Çalışan" parametreleri bulmak garantilidir — aslında gürültüye uydurulmuşlardır. WFO olmadan gerçek bir avantajı istatistiksel bir eserden ayırt etmenin yolu yoktur.

Kural: OOS Veri Noktaları vs Parametre Sayısı

WFO sonuçlarına güvenmek için pratik kural:

OOS is¸lemleriParametreler>10\frac{\text{OOS işlemleri}}{\text{Parametreler}} > 10

21 parametre için en az 210 OOS işlemi gerekir. WFO'nuz daha az üretiyorsa — sonuca güvenilemez.

+%3342 PnL@ML stratejisi: 21 parametre, 25 aylık veri. Diyelim ki 60 günlük 5 OOS penceresi, günde 2 işlem — toplam 5×60×2=6005 \times 60 \times 2 = 600 OOS işlemi. 600/21=28.6600/21 = 28.6 oranı — kabul edilebilir, ancak yalnızca WFER > 0.5 ise.

WFO'yu Optuna ile Entegre Etmek

Optuna entegrasyonuyla Bayesian optimizasyon

Her WFO penceresinde parametreleri optimize etmeniz gerekir. 21 parametre için ızgara araması imkansız, koordinat inişi verimsizdir. Optimal seçim, Optuna aracılığıyla Bayesian optimizasyonudur.

import optuna
from optuna.samplers import TPESampler

def optuna_optimize(train_data: np.ndarray, n_trials: int = 500) -> tuple:
    """
    Optuna kullanarak strateji parametrelerini optimize et.
    Her WFO penceresi içinde kullanılır.
    """

    def objective(trial):
        fast = trial.suggest_int("fast_period", 3, 50)
        slow = trial.suggest_int("slow_period", 20, 200)
        atr_period = trial.suggest_int("atr_period", 5, 50)
        atr_mult = trial.suggest_float("atr_multiplier", 0.5, 4.0)
        rsi_period = trial.suggest_int("rsi_period", 5, 30)
        rsi_upper = trial.suggest_int("rsi_upper", 60, 85)
        rsi_lower = trial.suggest_int("rsi_lower", 15, 40)
        vol_window = trial.suggest_int("vol_window", 10, 100)
        position_size = trial.suggest_float("position_size", 0.1, 1.0)
        take_profit = trial.suggest_float("take_profit", 0.005, 0.05)
        stop_loss = trial.suggest_float("stop_loss", 0.003, 0.03)
        trailing_pct = trial.suggest_float("trailing_pct", 0.002, 0.02)

        if fast >= slow:
            return -1e6  # geçersiz kombinasyon

        params = {
            "fast_period": fast, "slow_period": slow,
            "atr_period": atr_period, "atr_multiplier": atr_mult,
            "rsi_period": rsi_period, "rsi_upper": rsi_upper,
            "rsi_lower": rsi_lower, "vol_window": vol_window,
            "position_size": position_size,
            "take_profit": take_profit, "stop_loss": stop_loss,
            "trailing_pct": trailing_pct,
        }

        pnl, _ = run_strategy(train_data, params)

        _, returns = run_strategy(train_data, params)
        if len(returns) < 30 or np.std(returns) == 0:
            return -1e6
        sharpe = np.mean(returns) / np.std(returns) * np.sqrt(252)
        return sharpe

    optuna.logging.set_verbosity(optuna.logging.WARNING)

    study = optuna.create_study(
        direction="maximize",
        sampler=TPESampler(seed=42),
    )
    study.optimize(objective, n_trials=n_trials, show_progress_bar=False)

    best_params = study.best_params
    best_pnl, _ = run_strategy(train_data, best_params)

    return best_params, best_pnl

wfo = WalkForwardOptimizer(
    data=prices,
    optimize_fn=optuna_optimize,     # ızgara araması yerine Optuna
    evaluate_fn=my_evaluate,
    mode="rolling",
    train_size=180,
    test_size=60,
    step_size=60,
)

result = wfo.run()

Önemli: WFO içinde Sharpe'ı optimize edin, PnL'yi değil. PnL optimizasyonu belirli bir işlem dizisinde kârı maksimize eden parametreleri bulur. Sharpe optimizasyonu en iyi getiri/risk oranına sahip parametreleri bulur — bunlar OOS'ta daha sağlamdır.

Optuna TPE ile koordinat inişinin ayrıntılı karşılaştırması — Koordinat İnişi vs Bayesian makalesinde.

WFO Sonuçlarını Görselleştirme

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

def plot_wfo_results(result: WFOResult, data: np.ndarray):
    """Walk-Forward Optimizasyon sonuçlarını görselleştir."""
    fig, axes = plt.subplots(3, 1, figsize=(16, 14))

    ax = axes[0]
    ax.plot(result.oos_equity, color='#4FC3F7', linewidth=1.5)
    ax.axhline(1.0, color='#FF5252', linestyle='--', alpha=0.5, label='Başabaş')
    ax.set_title(f'OOS Öz Sermaye Eğrisi (WFER={result.wfer:.2f}, Sharpe={result.oos_sharpe:.2f})')
    ax.set_ylabel('Öz Sermaye')
    ax.legend()
    ax.grid(True, alpha=0.3)

    ax = axes[1]
    wfers = [w.wfer for w in result.windows]
    colors = ['#69F0AE' if w >= 0.5 else '#FFAB40' if w >= 0.3 else '#FF5252'
              for w in wfers]
    ax.bar(range(len(wfers)), wfers, color=colors, edgecolor='#1A237E', alpha=0.8)
    ax.axhline(0.5, color='#E040FB', linestyle='--', label='Eşik (0.5)')
    ax.axhline(0, color='gray', linestyle='-', alpha=0.3)
    ax.set_title('Pencere Başına Walk-Forward Verimlilik Oranı')
    ax.set_xlabel('Pencere')
    ax.set_ylabel('WFER')
    ax.legend()

    ax = axes[2]
    x = np.arange(len(result.windows))
    width = 0.35
    ax.bar(x - width/2, [w.is_pnl for w in result.windows],
           width, label='IS PnL', color='#7C4DFF', alpha=0.7)
    ax.bar(x + width/2, [w.oos_pnl for w in result.windows],
           width, label='OOS PnL', color='#4FC3F7', alpha=0.7)
    ax.set_title('Örnek İçi vs Örnek Dışı PnL')
    ax.set_xlabel('Pencere')
    ax.set_ylabel('PnL')
    ax.legend()
    ax.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig('wfo_results.png', dpi=150)
    plt.show()

Pratik Öneriler

Stratejiyi Üretime Almadan Önce Kontrol Listesi

1. WFO'yu çalıştırın (kayan + sabit başlangıçlı)

Her iki modun sonuçlarını karşılaştırın. Kayan WFO başarısız olurken sabit başlangıçlı geçiyorsa — strateji büyük olasılıkla yalnızca erken verilerde çalışıyor.

2. Her pencere için WFER'ı kontrol edin

Yalnızca toplam WFER değil, her pencereyi ayrı ayrı. 6 pencerenin 2'sinde WFER < 0 ise — bu bir sorundur, toplam > 0.5 olsa bile.

3. Pencereler arasında parametreleri karşılaştırın

Optimal parametreler pencereden pencereye "atlıyorsa" — kararlı bir avantaj yok. Optimum kararlılığını doğrulamak için Plato analizi kullanın.

4. Bozulma hızını kontrol edin

Güçlü biçimde negatif bozulma hızı = parametreler hızla etkinlik kaybediyor. Daha sık yeniden optimizasyon veya strateji revizyonu gerekir.

5. OOS sonuçlarına Monte Carlo bootstrap uygulayın

Toplam OOS PnL de tek noktalı bir tahmindir. Güven aralıkları elde etmek için OOS getiriler dizisine Monte Carlo bootstrap uygulayın.

6. Maliyetleri hesaba katın

OOS PnL komisyonları, kayma ve fonlama oranlarını içermelidir. Maliyetsiz güzel bir OOS PnL bir yanılsamadır. Daha fazla ayrıntı — Fonlama oranları kaldıracınızı öldürüyor.

Minimum Veri Gereksinimleri

Parametre sayısı Min OOS işlemleri Min WFO pencereleri Min veri (günde 2 işlem)
2-5 50 3 ~6 ay
6-10 100 4 ~12 ay
11-15 150 5 ~18 ay
16-21 210 6 ~24 ay
22+ 300+ 8+ ~36+ ay

21 Parametreli ve 25 Aylık Veriye Sahip Strateji

Makalenin başındaki soruya dönelim: tek bir pariteden 25 aylık veri üzerinde optimize edilmiş 21 parametre. PnL@ML = +%3342. Nasıl doğrulanır?

Adım 1. Kayan WFO: eğitim = 8 ay, test = 2 ay, adım = 2 ay. ~8 pencere elde ederiz.

Adım 2. Sabit başlangıçlı WFO: ilk eğitim = 8 ay, test = 2 ay. ~8 pencere elde ederiz.

Adım 3. CPCV: ~2,5 aylık 10 grup, k = 2. 45 kombinasyon elde ederiz.

Adım 4. Her yöntem için doğrulayın:

  • WFER >= 0.5?
  • Parametreler pencereler arasında kararlı mı?
  • Bozulma hızı kabul edilebilir mi?
  • OOS işlemleri / Parametreler >= 10?

Adım 5. Toplam OOS getirileri üzerinde Monte Carlo bootstrap. 5. yüzdelik dilim PnL > 0?

Bu testlerden herhangi biri başarısız olursa — +%3342 stratejisi büyük olasılıkla aşırı uyumludur. Tek bir pariteden 25 ay üzerinde 21 parametre — bu son derece yüksek bir parametre/veri oranıdır. WFO'yu geçmeden güven olamaz.

Strateji verimliliğini aktif zamana göre PnL hesabıyla da kontrol etmenizi ek olarak öneririz — bu +%3342'nin ne kadarının pozisyonda geçirilen süreye, ne kadarının gerçek avantaja bağlı olduğunu ortaya çıkarır.

Sonuç

Walk-Forward Optimizasyonu isteğe bağlı değil — bir zorunluluktur. Parametrelerin yeni veriye aktarılabilirliğini sistematik olarak doğrulayan tek yöntemdir. Tek bir eğitim/test bölümü bir piyangodur. Tüm veriler üzerinde tam backtest kendini kandırmaktır.

Temel çıkarımlar:

  1. WFER < 0.5 = aşırı uyum. Örnek dışı PnL örnek içinin yarısından azsa — parametreler uydurulmuştur.

  2. Parametre kararlılığı maksimumdan daha önemlidir. Her pencerede +%15 veren parametreler, birinde +%40 diğerinde -%10 verenlerden daha iyidir.

  3. Kripto için kayan WFO. Rejim geçişleri sabit başlangıçlı WFO'yu daha az güvenilir kılar. 4-6 aylık kayan pencere optimal dengedir.

  4. Daha fazla parametre — daha sıkı gereksinimler. 21 parametre en az 210 OOS işlemi ve 6+ WFO penceresi gerektirir. Bunu olmadan sonuç doğrulanamaz.

  5. WFO + Monte Carlo bootstrap + Plato analizi — aşırı uyuma karşı üç katmanlı koruma. Her katman diğerlerinin kaçırdığını yakalar.

Tüm pencereler boyunca WFER > 0.5, kararlı parametreler ve pozitif 5. yüzdelik dilim bootstrap ile WFO'yu geçen bir strateji — gerçek parayla güvenebileceğiniz bir stratejidir. Geri kalan her şey, güzel bir öz sermaye eğrisiyle eğri uydurma işlemidir.


Faydalı Bağlantılar

  1. Pardo, R. — The Evaluation and Optimization of Trading Strategies (Wiley)
  2. Lopez de Prado, M. — Advances in Financial Machine Learning, Chapter 12: Backtesting
  3. Bailey, D.H. et al. — The Probability of Backtest Overfitting
  4. Lopez de Prado, M. — The Combinatorial Purged Cross-Validation (CPCV)
  5. Aronson, D.R. — Evidence-Based Technical Analysis
  6. Optuna: A Next-generation Hyperparameter Optimization Framework
  7. Kevin Davey — Building Winning Algorithmic Trading Systems: Walk-Forward Analysis
  8. White, H. — A Reality Check for Data Snooping (2000)
  9. Harvey, C.R. & Liu, Y. — Backtesting (2015)
  10. NumPy — numpy.cumprod

Atıf

@article{soloviov2026walkforwardoptimization,
  author = {Soloviov, Eugen},
  title = {Walk-Forward Optimizasyonu: Tek Dürüst Strateji Testi},
  year = {2026},
  url = {https://marketmaker.cc/tr/blog/post/walk-forward-optimization},
  version = {0.1.0},
  description = {Tek bir eğitim/test bölümünün aşırı uyuma karşı neden koruma sağlamadığı, walk-forward optimizasyonunun parametre sağlamlığını nasıl sistematik olarak doğruladığı ve 21 parametreyle +\%3342 PnL@ML gösteren bir stratejinin WFO olmadan neden zamanlı bir bomba olduğu.}
}
Sorumluluk Reddi: Bu makalede sağlanan bilgiler yalnızca eğitim ve bilgilendirme amaçlıdır ve finansal, yatırım veya ticaret tavsiyesi niteliği taşımaz. Kripto para ticareti önemli bir kayıp riski içerir.

Yazarlar

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Piyasanın Önünde Olun

Özel yapay zeka ticaret içgörüleri, piyasa analizi ve platform güncellemeleri için bültenimize abone olun.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.