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

Toplu Parquet Önbelleği: Çoklu Zaman Dilimi Backtestlerini Yüzlerce Kat Nasıl Hızlandırırsınız

Toplu Parquet Önbelleği: Çoklu Zaman Dilimi Backtestlerini Yüzlerce Kat Nasıl Hızlandırırsınız
#algotrading
#backtest
#çoklu-zaman-dilimi
#parquet
#optimizasyon
#önbellek

Çoklu zaman dilimi stratejisi birkaç zaman dilimini eş zamanlı kullanır: günlük trend yönünü belirler, saatlik giriş noktalarını tanımlar ve 5 dakikalık uygulama zamanlamasını saptar. Her zaman dilimi kendi göstergelerini gerektirir: hareketli ortalamalar, osilatörler, seviyeler.

Tek bir backtest için her şey basittir — dakikalık verilerden zaman dilimlerini yeniden hesaplayın, göstergeleri hesaplayın, stratejiyi çalıştırın. Ancak toplu optimizasyon sırasında — binlerce parametre kombinasyonunu test etmeniz gerektiğinde — her iterasyonda zaman dilimlerini ve göstergeleri yeniden hesaplamak bir darboğaz haline gelir. İki yıllık dakikalık veriler üzerindeki tek bir geçiş, bir milyondan fazla çubuğun işlenmesi anlamına gelir ve bunu bin kez tekrarlamak israftır.

Çözüm: her şeyi bir kez önceden hesaplayıp parquet dosyasında önbelleğe almak.

Sorun: Optimizasyon Sırasında Gereksiz Hesaplamalar

Tipik bir çoklu zaman dilimi backtest süreci şöyle görünür:

for params in parameter_grid:
    df_1m = load_candles("ETHUSDT", "1m", start, end)

    df_5m = resample_ohlcv(df_1m, "5m")
    df_1h = resample_ohlcv(df_1m, "1h")
    df_4h = resample_ohlcv(df_1m, "4h")
    df_1d = resample_ohlcv(df_1m, "D")

    ma_1h = compute_ma(df_1h["close"], length=params["ma_1h_len"])
    ma_4h = compute_ma(df_4h["close"], length=params["ma_4h_len"])
    ma_1d = compute_ma(df_1d["close"], length=params["ma_1d_len"])

    result = run_strategy(df_1m, ma_1h, ma_4h, ma_1d, params)

Her iterasyonda 1-3. adımlar yeniden hesaplanır, oysa veriler aynıdır. Yalnızca strateji eşik parametreleri (4. adım) değişir. Bu, sadece farklı bir duvar rengi denemek istediğinizde her seferinde tüm evi yeniden inşa etmeye benzer.

Fikir: Bir Kez Hesapla, Kaydet, Defalarca Kullan

Temel gözlem: zaman dilimleri ve göstergeler yalnızca dakikalık verilere ve gösterge parametrelerine bağlıdır, strateji parametrelerine değil. Gerekli göstergeler kümesini sabitlersek, onları bir kez hesaplayıp kaydedebiliriz.

Şema:

Adım 1 (bir kez):
  Dakikalık mumlar -> Zaman dilimi yeniden örnekleme -> Gösterge hesaplama -> Parquet dosyası

Adım 2 (defalarca):
  Parquet dosyası -> Farklı parametrelerle strateji -> Sonuç

Dakikalık Mumlardan Zaman Dilimi Emülasyonu

Gerçek zamanlı zaman dilimi emülasyon görselleştirmesi

Eksiksiz bir dakikalık mum arşivimiz var. Bundan herhangi bir daha yüksek zaman dilimini doğru şekilde yeniden üretebiliriz. Ancak bir nüans var: standart resample ile dönem başına bir satır elde ederiz (saatte bir satır, 4 saatte bir vb.). Bu, dakika dakika backtesting için işe yaramaz — her dakikada gösterge değerini bilmemiz gerekir.

Bu nedenle, botun gerçek zamanlı olarak verileri nasıl gördüğünü modelleyerek her dakikalık mum için daha yüksek zaman dilimi değerlerini emüle ederiz:

  1. Bot bir sonraki dakikalık mumu alır
  2. Daha yüksek zaman diliminin mevcut (kapanmamış) çubuğunu günceller — High, Low, Close, Volume'u yeniden hesaplar
  3. Tüm kapalı çubuklar artı mevcut kısmi çubuk üzerinden göstergeyi yeniden hesaplar
  4. Dönem sona erdiğinde — çubuk sonlandırılır ve yeni biri başlar

Bu yaklaşım, backtestin botun gerçek zamanlı olarak gördüğü verilerle birebir aynı verileri görmesini garanti eder. Geleceğe bakmak yok — her dakikalık mum, o anda mevcut olacak verilerle sıkı bir şekilde işlenir.

class RunningCandleBuffer:
    """
    1 dakikalık mumları kullanarak daha yüksek zaman dilimi
    çubuğunun gerçek zamanlı güncellemelerini emüle eder.
    """
    def __init__(self, period_seconds: int):
        self.period = period_seconds  # Günlük için 86400, 1h için 3600
        self.closed_bars = []
        self.current_bar = None

    def update(self, timestamp, open_, high, low, close, volume):
        bar_start = self._align_to_period(timestamp)

        if self.current_bar is None or bar_start != self.current_bar['start']:
            if self.current_bar is not None:
                self.closed_bars.append(self.current_bar)
            self.current_bar = {
                'start': bar_start,
                'open': open_, 'high': high,
                'low': low, 'close': close,
                'volume': volume,
            }
        else:
            self.current_bar['high'] = max(self.current_bar['high'], high)
            self.current_bar['low'] = min(self.current_bar['low'], low)
            self.current_bar['close'] = close
            self.current_bar['volume'] += volume

        return self.closed_bars + [self.current_bar]

Her daha yüksek zaman dilimi için ayrı bir RunningCandleBuffer oluşturulur. Her dakikalık mumda tüm tamponlar güncellenir ve bize her zaman diliminin mevcut durumunu verir — sanki bot gerçek zamanlı çalışıyormuş gibi.

Parquet Önbellek Yapısı

Ön hesaplamanın sonucu, her satırın bir dakikalık muma karşılık geldiği ve sütunların şunları içerdiği tek bir parquet dosyasıdır:

timestamp              — dakikalık mum zaman damgası
open, high, low,       — dakikalık mum OHLCV
close, volume

close_5m               — bu andaki emüle edilmiş 5 dakikalık mumun Close değeri
close_1h               — emüle edilmiş 1 saatlik mumun Close değeri
close_4h               — emüle edilmiş 4 saatlik mumun Close değeri
close_1d               — emüle edilmiş günlük mumun Close değeri

ma_20_1h               — 1 saatlikte MA(20), bu dakikada yeniden hesaplanmış
ma_50_1h               — 1 saatlikte MA(50)
ma_20_4h               — 4 saatlikte MA(20)
ma_50_4h               — 4 saatlikte MA(50)
ma_6_1d                — Günlükte MA(6)
ma_12_1d               — Günlükte MA(12)

cross_ma_1h            — 1 saatlikte MA kesişim sinyali ('buy'/'sell'/None)
cross_ma_4h            — 4 saatlikte MA kesişim sinyali
cross_ma_1d            — Günlükte MA kesişim sinyali

separation_1h          — 1 saatlikte % cinsinden MA sapması
separation_4h          — 4 saatlikte % cinsinden MA sapması
separation_1d          — Günlükte % cinsinden MA sapması

Her değer, ilgili dakikalık mumun anındaki göstergenin gerçek durumunu yansıtır — daha yüksek zaman dilimlerinin kapanmamış çubukları dahil.

Ön Hesaplama: Önbelleği Oluşturma

def precompute_cache(
    df_1m: pd.DataFrame,
    timeframes: dict[str, int],   # {"5m": 300, "1h": 3600, "4h": 14400, "D": 86400}
    indicators: dict,              # {"ma_20": 20, "ma_50": 50}
) -> pd.DataFrame:
    """
    Tüm dakikalık mumlar üzerinde tek geçiş.
    Emüle edilmiş zaman dilimleri ve göstergelerle bir DataFrame döndürür.
    """
    buffers = {tf: RunningCandleBuffer(secs) for tf, secs in timeframes.items()}

    n = len(df_1m)
    result = {}

    for tf_name, buf in buffers.items():
        closes = np.zeros(n)
        ma_values = {name: np.full(n, np.nan) for name in indicators}

        for i in range(n):
            row = df_1m.iloc[i]
            bars = buf.update(
                df_1m.index[i],
                row['open'], row['high'], row['low'], row['close'], row['volume']
            )

            all_closes = [b['close'] for b in bars]
            closes[i] = all_closes[-1]

            for ind_name, length in indicators.items():
                if len(all_closes) >= length:
                    ma_values[ind_name][i] = np.mean(all_closes[-length:])

        result[f'close_{tf_name}'] = closes
        for ind_name in indicators:
            result[f'{ind_name}_{tf_name}'] = ma_values[ind_name]

    cache_df = pd.DataFrame(result, index=df_1m.index)
    cache_df = pd.concat([df_1m[['open', 'high', 'low', 'close', 'volume']], cache_df], axis=1)

    return cache_df
cache = precompute_cache(
    df_1m,
    timeframes={"5m": 300, "1h": 3600, "4h": 14400, "D": 86400},
    indicators={"ma_20": 20, "ma_50": 50, "ma_6": 6, "ma_12": 12},
)

cache.to_parquet("cache_ETHUSDT_2024_2026.parquet")

Optimizasyon Sırasında Önbelleği Kullanma

Önbellek tabanlı optimizasyon hız karşılaştırması

Artık optimizasyon şöyle görünür:

cache = pd.read_parquet("cache_ETHUSDT_2024_2026.parquet")

for params in parameter_grid:
    result = run_strategy(cache, params)

Strateji, önceden oluşturulmuş sütunlarla çalışır — bir milyon çubar üzerinde tekrar tekrar geçiş yok, MA yeniden hesaplama yok, zaman dilimi emülasyonu yok. Sadece bir DataFrame'den okuma ve giriş/çıkış koşullarını kontrol etme.

Neden Parquet

Parquet, bu görev için optimal bir sütunsal veri depolama formatıdır:

  • Sıkıştırma. Parquet sayısal verileri 5-10 kat sıkıştırır. 30 sütunlu 1,1 milyon satırlık bir önbellek, CSV'deki ~500 MB yerine ~50 MB kaplar.
  • Sütunsal okuma. Strateji yalnızca ma_20_4h ve ma_50_4h kullanıyorsa, parquet yalnızca o sütunları okur, geri kalanını atlar.
  • Tür koruması. Veri türleri (float64, int64, string) kayıpsız korunur — yüklemede dizeleri ayrıştırmaya gerek yoktur.
  • Okuma hızı. Parquet'i pandas'a yüklemek onlarca milisaniye sürer, CSV'den bir büyüklük sırası daha hızlıdır.

Önbelleği Genişletme: Yeni Göstergeler Ekleme

Strateji yeni bir gösterge gerektiriyorsa (RSI, MACD, Bollinger Bantları), sadece şunları yapmanız yeterlidir:

  1. Yalnızca yeni göstergeyi aynı dakikalık verilerden yeniden hesaplayın
  2. Sütunları mevcut parquet dosyasına ekleyin
  3. Daha önce hesaplanmış tüm sütunlar dokunulmadan kalır
cache = pd.read_parquet("cache_ETHUSDT_2024_2026.parquet")

rsi_cols = compute_rsi_for_timeframes(df_1m, timeframes, length=14)

cache = pd.concat([cache, rsi_cols], axis=1)
cache.to_parquet("cache_ETHUSDT_2024_2026.parquet")

Özet: Yaklaşım Karşılaştırması

Naif Yaklaşım Toplu Önbellek
Zaman dilimi yeniden örnekleme Her iterasyonda Bir kez
Gösterge hesaplama Her iterasyonda Bir kez
İterasyon başına süre Dakikalar Bir saniyeden az
1000 iterasyon Günler Dakikalar
Bellek tüketimi 1m yükle + yeniden hesapla Tek DataFrame
Backtest-canlı uyumu Uygulamaya bağlı Garantili (emülasyon = gerçek zamanlı)

Sonuç

Toplu parquet önbelleği yaklaşımı iki sorunu aynı anda çözer:

  1. Doğruluk. RunningCandleBuffer aracılığıyla dakikalık mumlardan zaman dilimi emülasyonu, backtestin gerçek zamanlı olarak botun gördüğü verilerle aynı verileri gördüğünü garanti eder — geleceğe bakmak yok ve yapay gecikmeler yok.

  2. Hız. Önceden hesaplanmış zaman dilimleri ve göstergeler, günler yerine dakikalar içinde binlerce parametre kombinasyonunun test edilmesine olanak tanır.

Fikir basittir: bir kez hesapla — defalarca kullan. Dakikalık mumlar kaynak veridir. Diğer her şey türevdir ve önceden hesaplanıp önbelleğe alınabilir. Parquet bu önbelleği kompakt, hızlı ve kullanışlı kılar.

Dakikalardan saniyelere ve milisaniyelere adaptif detaylandırma ile dolum simülasyonu doğruluğunu nasıl artıracağınız hakkında daha fazla bilgi için Adaptif detaylandırma: değişken granülariteli backtest makalesine bakın.


Faydalı Bağlantılar

  1. Apache Parquet — veri depolama formatı
  2. pandas — parquet ile çalışma
  3. Lopez de Prado — Finansal Makine Öğrenmesinde Gelişmeler
  4. Ernest Chan — Nicel Ticaret

Atıf

@article{soloviov2026parquetcache,
  author = {Soloviov, Eugen},
  title = {Aggregated Parquet Cache: How to Speed Up Multi-Timeframe Backtests by Hundreds of Times},
  year = {2026},
  url = {https://marketmaker.cc/tr/blog/post/parquet-cache-multitimeframe-backtest},
  description = {Dakikalık mumlardan zaman dilimlerini ve göstergeleri önceden hesaplama, parquet'e kaydetme ve gereksiz yeniden hesaplamalar olmadan toplu strateji testi için kullanma yöntemi.}
}
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.