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

Backtest-canlı eşitliği: botunuz neden backtestten farklı işlem yapıyor

Backtest-canlı eşitliği: botunuz neden backtestten farklı işlem yapıyor
#algotrading
#backtest
#canlı işlem
#backtest-canlı eşitliği
#execution
#NautilusTrader

Bir stratejiyi backtest üzerinden çalıştırdınız. Sharpe 2,1, MaxDD -%8, PnL +%67. Botu başlattınız. Bir ay sonra karşılaştırıyorsunuz: aynı sinyaller, aynı dönem — ancak canlı PnL %40 daha düşük. Düşüş bir buçuk kat daha derin. On işlemin ikisi hiç gerçekleşmedi.

Bu bir hata değil. Bu backtest-canlı sapması — backtest sonuçları ile gerçek işlem arasındaki sistematik tutarsızlıktır. Herkesin başına gelir. Tek soru şu: bunu biliyor musunuz ve kontrol edebiliyor musunuz?

Bu makale, sapmaların tam taksonomisini, bunları en aza indirmek için mimari desenleri ve üretimde eşitliği izlemeye yönelik pratik bir kontrol listesini sunmaktadır.

"Backtestte çalışıyordu" sendromu

Backtest ile canlı işlem sapması — ideal özsermaye eğrisi ve gerçek oynak sonuçlar karşılaştırması

Her algotrader bu döngüden geçer:

  1. Jupyter notebook'ta bir strateji yazdı
  2. Tarihsel CSV üzerinde backtest çalıştırdı — sonuçlar mükemmel
  3. Mantığı bir bot olarak yeniden yazdı (çoğunlukla farklı bir dil veya çerçevede)
  4. Başlattı — sonuçlar eşleşmiyor
  5. Hata aramaya başladı, bulamadı — "piyasa değişti"

Sorun piyasa değil. Sorun şu ki backtest ve bot, aynı gerçekliği farklı biçimde modelleyen iki ayrı yazılım ürünüdür. Sapmalar kaçınılmazdır, ancak sistematik hale getirilebilir ve en aza indirilebilir.

Sapma Taksonomisi

Backtest-canlı sapma taksonomisi

Tüm sapma kaynakları dört kategoriye ayrılır. Her biri için — bir şiddet derecesi (1'den 5'e kadar) ve PnL sapmasına tipik katkı.

1. Veri sapmaları (şiddet: 3/5)

Backtestin gördüğü verilerle botun gerçek zamanlı olarak gördüğü veriler aynı şey değildir.

Zaman damgaları. Borsalar, zaman damgası atama için farklı kurallara göre mum çubuğu iletir. Bir borsa mum çubuğunu dönemin başıyla, diğeri sonu ile işaretler. REST API, gerçek kapanıştan 1-3 saniyelik gecikmeyle mum çubuğu döndürebilir. Backtest, tarihsel dosyadaki "ideal" zaman damgalarıyla çalışır.

OHLCV agregasyonu. Tarihsel veriler genellikle sağlayıcı tarafından, borsanın gerçek zamanlı yaptığından farklı biçimde toplanır. Fark son hane düzeyindedir — ancak eşik sinyalleriyle (MA kesişimi, seviye kırılımı) bu, stratejinin pozisyona girip girmeyeceğini belirler.

Boşluklar ve eksik veriler. Tarihsel veriler genellikle temizdir — eksik mum çubukları interpolasyonla doldurulur. Gerçek zamanlı olarak WebSocket düşebilir ve bot 30 saniyelik veriyi kaçırır.

PnL sapmasına tipik katkı: yıllık PnL'nin %2-5'i.

2. Execution sapmaları (şiddet: 5/5)

Emir gerçekleşme sapmaları — emir defteri kayması, gecikme ve kısmi dolgu görselleştirmesi

En tehlikeli sapma sınıfı. Backtest, gerçekleşmeyi mükemmel biçimde simüle eder — gerçeklik ise idealden çok uzaktır.

Kayma (Slippage). Backtest, emri kapanış fiyatından (veya sinyal fiyatından) doldurur. Gerçekte, bir piyasa emri en iyi alış/satış fiyatı artı hacme ve likiditeye bağlı kaymadan gerçekleştirilir. Orta likiditeye sahip bir altcoin'de 10.000 dolarlık pozisyon için kayma %0,05-0,3 olabilir.

NN işlemde birikimli kayma formülü:

Slippagetotal=i=1Nsizei×si\text{Slippage}_{total} = \sum_{i=1}^{N} \text{size}_i \times s_i

burada sis_i, emir defteri derinliğine bağlı olarak ii'nci işlemin kaymasıdır:

sisizeiLiquidity(ti)×ks_i \approx \frac{\text{size}_i}{\text{Liquidity}(t_i)} \times k

Gecikme. Sinyal oluşturulmasından emir gerçekleşmesine kadar zaman geçer: sinyal hesaplama (1-50 ms), istek iletimi (10-200 ms), borsada eşleştirme (1-10 ms). Backtestte gecikme = 0. Canlıda — fiyat hareket edebilir.

Kısmi dolgular. Backtest, emrin %100'ünün anında doldurulduğunu varsayar. Gerçekte, bir limit emri kısmen doldurulabilir — veya fiyat tersine dönerse hiç doldurulamayabilir. İllikid bir piyasada piyasa emri için emir, birden fazla emir defteri seviyesinden "kayar".

Kuyruk önceliği. En iyi alış fiyatına yerleştirilen bir limit emri hemen dolmaz — o seviyedeki daha önce yerleştirilen tüm emirlerin arkasında sıraya girer. "Fiyata dokunuldu = emir doldu" olarak kabul eden bir backtest, sistematik olarak dolum oranını olduğundan yüksek gösterir.

PnL sapmasına tipik katkı: yıllık PnL'nin %10-30'u.

3. Mantık sapmaları (şiddet: 4/5)

Bunlar, backtest ile bot arasındaki strateji kodundaki sapmalardır.

Ayrı kod tabanları. Klasik anti-desen: backtests/strategy_a.py ve bot/strategy_a.py — "aynı şeyi yapan" iki ayrı dosya. Üç aylık düzenlemelerin ardından kaçınılmaz olarak birbirinden ayrışırlar. Biri backteste bir filtre ekledi ve botu güncellemeyi unuttu. Ya da tam tersi — botta bir hata düzeltildi ama backtestte kaldı.

Farklı çerçeveler. Vektörleştirilmiş işlemlerle pandas'ta backtest, olay güdümlü mantıkla asyncio'da bot. Özdeş bir stratejiyle bile, uç durumlar farklı biçimde ele alınır: yuvarlama, koşul kontrol sırası, NaN işleme.

Durum yönetimi. Backtest genellikle durumsuzdur — bir veri dizisi üzerinde yineleme yapar. Bot durumludur — pozisyonları, bakiyeleri, emir geçmişini saklar. Bot yeniden başlatma, durum kaybı, borsa ile desenkronizasyon — bunların hepsi sapma kaynaklarıdır.

PnL sapmasına tipik katkı: yıllık PnL'nin %5-20'si.

4. Maliyet sapmaları (şiddet: 3/5)

İşlem maliyeti modellemesindeki sapmalar.

Fonlama oranları. Çoğu sürekli vadeli işlem backtesti, fonlama oranlarını hiç hesaba katmaz. 10x kaldıraçta ve ortalama %0,01 oranında 8 saatte bir, bu 0,01%×3×365×10=109,5%0,01\% \times 3 \times 365 \times 10 = 109,5\% yıllık — çoğu stratejinin PnL'sinden fazla. Ayrıntılı analiz Fonlama oranları kaldıracınızı öldürür makalesindedir.

Komisyonlar. Maker/taker komisyonları genellikle modellenir, ancak çoğunlukla yanlış oranla. VIP kademeleri, BNB indirimleri, iade (rebate) — bunların tümü nihai sonucu etkiler.

Spread. Mum çubuğu tabanlı backtest, alış-satış spreadini görmez. 1 dakikalık bir mum çubuğunda kapanış = 3000, ancak gerçekte alış = 2999,5 ve satış = 3000,5'tir. Her işlem spreadın yarısına "mal olur".

PnL sapmasına tipik katkı: yıllık PnL'nin %5-15'i.

Kümülatif Etki

Dört kategorinin tümü eş zamanlı ve kural olarak tek yönde — yatırımcıya karşı — etki eder:

PnLlivePnLbacktestΔdataΔexecutionΔlogicΔcosts\text{PnL}_{live} \approx \text{PnL}_{backtest} - \Delta_{data} - \Delta_{execution} - \Delta_{logic} - \Delta_{costs}

Rafine edilmemiş bir sistem için backtest PnL'sinden %20-50 toplam sapma normaldir. Kaldıraçla birlikte etki çarpılır.

Eşitlik İçin Mimari Desenler

Desen 1: Paylaşılan Çekirdek (ortak bir çekirdek çıkarmak)

Paylaşılan Çekirdek mimarisi — hem backtest hem de canlı işlem motorlarına güç veren tek bir strateji modülü

Fikir: strateji çekirdeğini — sinyal üretimi ve gerçekleşme mantığını — hem backtest hem de bot tarafından kullanılan ayrı bir modüle çıkarmak. Yalnızca çevresindeki altyapı farklıdır: veri kaynağı ve emir gönderme mekanizması.

┌─────────────────────────────────────┐
│         strategy_core.py            │
│  ┌─────────────┐ ┌───────────────┐  │
│  │ SignalEngine │ │ OrderManager  │  │
│  └──────┬──────┘ └──────┬────────┘  │
│         │               │           │
│    generate_signal()  create_order()│
└─────────┬───────────────┬───────────┘
          │               │
    ┌─────┴─────┐   ┌─────┴──────┐
    │ Backtest   │   │ Live       │
    │ DataFeed   │   │ DataFeed   │
    │ FillModel  │   │ Exchange   │
    └────────────┘   └────────────┘

from dataclasses import dataclass
from typing import Optional
import numpy as np

@dataclass
class Signal:
    side: str          # 'long' | 'short'
    entry_price: float
    sl_price: float
    tp_price: float
    size: float
    timestamp: int

@dataclass
class OrderRequest:
    side: str
    order_type: str    # 'market' | 'limit'
    price: float
    size: float

class StrategyCore:
    """
    Strateji çekirdeği. Backtest ve canlı için özdeş kod.
    Yalnızca veriye bağlıdır, altyapıya değil.
    """
    def __init__(self, params: dict):
        self.fast_period = params.get('fast_ma', 20)
        self.slow_period = params.get('slow_ma', 50)
        self.sl_pct = params.get('sl_pct', 0.02)
        self.tp_pct = params.get('tp_pct', 0.04)
        self.position: Optional[Signal] = None
        self._closes: list[float] = []

    def on_candle(self, timestamp: int, o: float, h: float,
                  l: float, c: float, v: float) -> Optional[OrderRequest]:
        """
        Yeni bir mum çubuğunu işle. OrderRequest veya None döndürür.
        Bu metot, backtest ve bottan özdeş biçimde çağrılır.
        """
        self._closes.append(c)

        if len(self._closes) < self.slow_period:
            return None

        fast_ma = np.mean(self._closes[-self.fast_period:])
        slow_ma = np.mean(self._closes[-self.slow_period:])

        if self.position is not None:
            exit_order = self._check_exit(h, l, c)
            if exit_order:
                self.position = None
                return exit_order

        if self.position is None:
            if fast_ma > slow_ma and self._prev_fast_ma <= self._prev_slow_ma:
                self.position = Signal(
                    side='long', entry_price=c,
                    sl_price=c * (1 - self.sl_pct),
                    tp_price=c * (1 + self.tp_pct),
                    size=1.0, timestamp=timestamp,
                )
                return OrderRequest('buy', 'market', c, 1.0)

        self._prev_fast_ma = fast_ma
        self._prev_slow_ma = slow_ma
        return None

    def _check_exit(self, high: float, low: float,
                    close: float) -> Optional[OrderRequest]:
        pos = self.position
        if pos.side == 'long':
            if low <= pos.sl_price:
                return OrderRequest('sell', 'market', pos.sl_price, pos.size)
            if high >= pos.tp_price:
                return OrderRequest('sell', 'market', pos.tp_price, pos.size)
        return None

Artık backtest ve bot aynı StrategyCore'u kullanır:


from strategy_core import StrategyCore

def run_backtest(candles, params, fill_model):
    core = StrategyCore(params)
    trades = []

    for candle in candles:
        order = core.on_candle(
            candle['timestamp'], candle['open'], candle['high'],
            candle['low'], candle['close'], candle['volume'],
        )
        if order:
            fill_price = fill_model.simulate_fill(order, candle)
            trades.append({'price': fill_price, 'side': order.side})

    return trades

from strategy_core import StrategyCore

async def run_live(exchange, symbol, params):
    core = StrategyCore(params)

    async for candle in exchange.stream_candles(symbol, '1m'):
        order = core.on_candle(
            candle['timestamp'], candle['open'], candle['high'],
            candle['low'], candle['close'], candle['volume'],
        )
        if order:
            await exchange.place_order(symbol, order.side,
                                       order.order_type, order.size)

Temel kural: StrategyCore, verinin nereden geldiğini veya emirlerin nereye gönderildiğini bilmez. OHLCV alır ve OrderRequest döndürür. Geri kalanı altyapı katmanının sorumluluğundadır.

Desen 2: Olay güdümlü birleşim (NautilusTrader yaklaşımı)

Olay güdümlü işlem mimarisi — piyasa verisi, sinyaller, emirler, dolgular için basamaklı olay hattı

NautilusTrader, birleşik bir NautilusKernel aracılığıyla eşitlik sağlar — deterministik olay güdümlü çekirdek ve nanosaniye çözünürlüklü Rust tabanlı bir motor. Aynı strateji uygulaması hem backtest hem de canlı işlemde çalışır.

Mimari, portlar ve adaptörler desenine (altıgen mimari) dayanmaktadır:

┌──────────────────────────────────┐
│        NautilusKernel            │
│  ┌───────────┐  ┌─────────────┐  │
│  │ Strategy   │  │ RiskEngine  │  │
│  │ (Python)   │  │ (Rust)      │  │
│  └─────┬─────┘  └──────┬──────┘  │
│        │               │         │
│  ┌─────┴───────────────┴──────┐  │
│  │      Message Bus (Rust)    │  │
│  └─────┬───────────────┬──────┘  │
└────────┼───────────────┼─────────┘
         │               │
   ┌─────┴─────┐   ┌─────┴──────┐
   │ Backtest   │   │ Live       │
   │ Adapter    │   │ Adapter    │
   │ FillModel  │   │ Exchange   │
   │ (L2 book)  │   │ Gateway    │
   └────────────┘   └────────────┘

Avantajlar:

  • Deterministik tekrar. Olaylar kesin bir sırayla işlenir — backtest sonucu bit düzeyinde yeniden üretilebilir.
  • Özel FillModel. Her gerçekleşme için L2 emir defteri simülasyonu — kayma, gerçek emir defteri derinliğine göre simüle edilir.
  • Performans. Saniyede 5 milyona kadar satır, RAM'e sığmayan verilerin işlenmesi.
  • Redis + PostgreSQL. Redis aracılığıyla önbellek ve mesaj veri yolu, PostgreSQL aracılığıyla kalıcılık — backtest ve canlı için özdeş altyapı.

Desen 3: Strateji Arayüzü (Freqtrade yaklaşımı)

Freqtrade, birleşik bir IStrategy arayüzü kullanır: aynı strateji sınıfı hem backtest hem de canlıda çalışır. Tek fark kalıcılık katmanıdır.


class IStrategy:
    """Birleşik arayüz — uygulama bunun backtest mi yoksa canlı mı olduğunu bilmez."""

    def populate_indicators(self, dataframe, metadata):
        """Göstergeleri hesapla."""
        dataframe['fast_ma'] = dataframe['close'].rolling(20).mean()
        dataframe['slow_ma'] = dataframe['close'].rolling(50).mean()
        return dataframe

    def populate_entry_trend(self, dataframe, metadata):
        """Giriş sinyallerini belirle."""
        dataframe.loc[
            (dataframe['fast_ma'] > dataframe['slow_ma']) &
            (dataframe['fast_ma'].shift(1) <= dataframe['slow_ma'].shift(1)),
            'enter_long'
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe, metadata):
        """Çıkış sinyallerini belirle."""
        dataframe.loc[
            (dataframe['fast_ma'] < dataframe['slow_ma']),
            'exit_long'
        ] = 1
        return dataframe

Freqtrade ayrıca şunları sağlar:

  • Optuna aracılığıyla Hyperopt — strateji parametre optimizasyonu
  • --timeframe-detail — dolgu iyileştirmesi için daha ince bir zaman çerçevesine inme (uyarlamalı sondaj'a benzer)

Desen Karşılaştırması

Paylaşılan Çekirdek Olay güdümlü (NautilusTrader) Strateji Arayüzü (Freqtrade)
Uygulama karmaşıklığı Düşük Yüksek Orta
Eşitlik düzeyi Orta Maksimum Yüksek
Dolgu simülasyonu Ayrı FillModel L2 emir defteri --timeframe-detail
Çekirdek dili Python Rust + Python Python
Uygun olduğu alan Özel motorlar Kurumsal işlem Hızlı başlangıç

Dolgu Simülasyonu Doğruluğu

Dolgu simülasyonu doğruluk seviyeleri

Dolgu simülasyonu, gerçekleşme sapmasının ana kaynağıdır. Üç doğruluk düzeyi:

Düzey 1: Naif (kapanış fiyatından dolgu)

fill_price = candle['close']

Hata: kaymayı, spreadi veya kısmi dolguları hesaba katmaz. Sistematik olarak PnL'yi olduğundan yüksek gösterir.

Düzey 2: Kayma modeli

def simulate_fill(order, candle, slippage_bps=5):
    """Kaymalı dolgu."""
    base_price = candle['close']
    slip = base_price * slippage_bps / 10000

    if order.side == 'buy':
        return base_price + slip  # Daha yüksek fiyattan al
    else:
        return base_price - slip  # Daha düşük fiyattan sat

Hata: sabit kayma, likiditeyi ve emir boyutunu hesaba katmaz. Naif'ten iyidir, ancak yine de kaba bir modeldir.

Düzey 3: 1s/100ms veriyle uyarlamalı sondaj

En iyi seçenek: SL/TP dolgu sırasını doğru belirlemek için gerçek ince tanecikli veriler kullanmak. Uyarlamalı sondaj: değişken granülariteli backtesting makalesinde ayrıntılı olarak açıklanmıştır.

class RealisticFillModel:
    """
    Birleşik dolgu modeli: kayma + spread + hacim etkisi.
    """
    def __init__(self, avg_spread_bps=3, impact_coeff=0.1):
        self.avg_spread_bps = avg_spread_bps
        self.impact_coeff = impact_coeff

    def simulate_fill(self, order, candle, order_size_usd):
        base_price = candle['close']

        spread_cost = base_price * self.avg_spread_bps / 20000

        candle_volume_usd = candle['volume'] * candle['close']
        participation_rate = order_size_usd / max(candle_volume_usd, 1)
        impact = base_price * self.impact_coeff * np.sqrt(participation_rate)

        if order.side == 'buy':
            return base_price + spread_cost + impact
        else:
            return base_price - spread_cost - impact

Piyasa etkisi formülü (basitleştirilmiş Almgren-Chriss modeli):

Δp=σkVorderVmarket\Delta p = \sigma \cdot k \cdot \sqrt{\frac{V_{order}}{V_{market}}}

burada σ\sigma oynaklık, kk etki katsayısı, VorderV_{order} emir hacmi ve VmarketV_{market} dönem için piyasa hacmidir.

Pratik Eşitlik Kontrol Listesi

Kategoriye göre düzenlenmiş holografik eşitlik doğrulama kontrol listesi — veri, gerçekleşme, zamanlama, ücretler

Botu canlıya almadan önce her öğeyi doğrulayın:

Kod:

  • Strateji paylaşılan bir çekirdek kullanır (backtest ve canlı için tek modül)
  • Sinyal mantığının iki yerde kopyası yok
  • Birim testler, aynı girdiler için özdeş çekirdek çıktılarını doğrular
  • Koşul kontrol sırası özdeş (SL, TP'den önce mi? TP, SL'den önce mi?)

Veri:

  • Zaman damgası biçimi özdeş (UTC, aynı sağlayıcı)
  • OHLCV agregasyonu aynı kuralları kullanır
  • Eksik mum çubuğu işleme özdeş
  • İleriye dönük önyargı yok — backtest geleceğe gözetlemiyor

Gerçekleşme:

  • Kayma modeli gerçek verilerle kalibre edilmiş
  • Kısmi dolgular modellenmiş (veya en azından kötümser tahmin yapılmış)
  • Limit emirlerin kuyruk önceliği modeli var
  • Gecikme hesaba katılmış (sinyalden dolguya 100-500 ms gecikme)

Maliyetler:

  • Maker/taker komisyonları güncel oranla dahil
  • Sürekli vadeli işlemlerde fonlama oranları hesaba katılmış
  • Spread modellenmiş (en azından ortalama)

Altyapı:

  • Durum kalıcılığı: bot yeniden başlatma sonrası pozisyonları kurtarır
  • Yeniden bağlanma mantığı: WebSocket, veri kaybı olmadan yeniden bağlanır
  • Günlükleme: tüm emirler ve dolgular post-mortem analiz için günlükleniyor

Üretimdeki Sapmaları İzleme

Eşitlik, tek seferlik bir kontrol değil, sürekli bir süreçtir. Botu başlattıktan sonra sapmalar gerçek zamanlı olarak izlenmelidir.

Gölge modu (kağıt işlem)

Gölge işlem modu — canlı piyasa verisi ve simüle edilmiş emirler paralel çalışıyor

Botu, aynı veriler üzerinde backtest ile paralel çalıştırın. Bot sinyaller üretir ancak emir göndermez — yalnızca günlükler. Aynı zamanda backtest aynı verileri işler. Karşılaştırın:

class DivergenceMonitor:
    """
    Backtest ve canlı bot sinyallerini gerçek zamanlı olarak karşılaştırır.
    """
    def __init__(self, tolerance_pct=0.5):
        self.tolerance = tolerance_pct / 100
        self.divergences = []

    def compare_signal(self, backtest_signal, live_signal, timestamp):
        """Backtest ve canlı sinyalleri karşılaştır."""
        if backtest_signal is None and live_signal is None:
            return  # İkisi de sessiz — Tamam

        if (backtest_signal is None) != (live_signal is None):
            self.divergences.append({
                'timestamp': timestamp,
                'type': 'signal_mismatch',
                'backtest': backtest_signal,
                'live': live_signal,
                'severity': 'HIGH',
            })
            return

        price_diff = abs(
            backtest_signal.entry_price - live_signal.entry_price
        ) / backtest_signal.entry_price

        if price_diff > self.tolerance:
            self.divergences.append({
                'timestamp': timestamp,
                'type': 'price_divergence',
                'diff_pct': price_diff * 100,
                'severity': 'MEDIUM',
            })

    def compare_fill(self, backtest_fill, live_fill, timestamp):
        """Gerçekleşmeyi karşılaştır."""
        if backtest_fill and live_fill:
            slippage = (live_fill['price'] - backtest_fill['price']
                        ) / backtest_fill['price']
            self.divergences.append({
                'timestamp': timestamp,
                'type': 'fill_divergence',
                'slippage_bps': slippage * 10000,
                'severity': 'LOW' if abs(slippage) < 0.001 else 'MEDIUM',
            })

    def report(self):
        """Haftalık sapma raporu."""
        from collections import Counter
        severity_counts = Counter(d['severity'] for d in self.divergences)
        return {
            'total_divergences': len(self.divergences),
            'by_severity': dict(severity_counts),
            'avg_slippage_bps': np.mean([
                d['slippage_bps'] for d in self.divergences
                if d['type'] == 'fill_divergence'
            ]) if any(d['type'] == 'fill_divergence'
                      for d in self.divergences) else 0,
        }

Dashboard Metrikleri

Metrik Formül Uyarı Eşiği
Sinyal eşleşme oranı matchestotal signals\frac{\text{matches}}{\text{total signals}} < %95
Ortalama kayma 1Nsi\frac{1}{N}\sum s_i (bps) > 10 bps
Dolgu oranı filledsent\frac{\text{filled}}{\text{sent}} < %90
PnL sapması PnLlivePnLbtPnLbt\frac{PnL_{live} - PnL_{bt}}{PnL_{bt}} > %20
Gecikme p99 99. yüzdelik sinyal-dolgu süresi > 500 ms

Kayma Modeli Kalibrasyonu

Kayma modeli kalibrasyonu — beklenen ve gerçek dolgular için fiyat etki eğrisiyle emir defteri derinliği

2-4 haftalık veri biriktirdikten sonra, backtest kayma modelini gerçek verilerle kalibre edebilirsiniz:

def calibrate_slippage(live_fills: list[dict]) -> dict:
    """
    Gerçek dolgular kullanılarak kayma modelini kalibre et.

    live_fills: [{'expected_price': ..., 'actual_price': ..., 'size_usd': ..., 'volume_usd': ...}]
    """
    slippages = []
    participation_rates = []

    for fill in live_fills:
        slip = abs(fill['actual_price'] - fill['expected_price']
                   ) / fill['expected_price']
        part = fill['size_usd'] / max(fill['volume_usd'], 1)
        slippages.append(slip)
        participation_rates.append(part)

    slippages = np.array(slippages)
    participation_rates = np.array(participation_rates)

    from scipy.optimize import curve_fit

    def model(x, k, base):
        return k * np.sqrt(x) + base

    popt, _ = curve_fit(model, participation_rates, slippages,
                        p0=[0.1, 0.0001])

    return {
        'impact_coeff': popt[0],
        'base_slippage': popt[1],
        'mean_slippage_bps': np.mean(slippages) * 10000,
        'p95_slippage_bps': np.percentile(slippages, 95) * 10000,
    }

Diğer Araçlarla Bağlantılar

Backtest-canlı eşitliği izole bir görev değildir. "Yanılsamasız Backtestler" serisinin diğer araçlarıyla kesişir:

  • Uyarlamalı sondaj — dolgu simülasyonu doğruluğunu artırır, gerçekleşme eşitliğinin temel bileşenidir.
  • Fonlama oranları — backtest fonlamayı modellemiyorsa, kaldıraç > 3x'te eşitlik imkansızdır.
  • Parquet önbellek — önceden hesaplanmış zaman çerçeveleri ve göstergeler, backtestin botla aynı verileri görmesini sağlar. RunningCandleBuffer öykünmesi = gerçek zamanlı güncelleme.
  • Polars vs Pandas — pandas'tan (backtest) Polars'a (canlı) geçerken sayısal sonuçların eşleştiğinden emin olmanız gerekir.
  • Walk-Forward — örnek dışı verilerle walk-forward, stratejinin nasıl bozulduğunu gösterir — bu, örnek içi backtesten canlıya daha yakındır.

Öneriler

  1. Paylaşılan çekirdek zorunludur. Sinyal üretimi için tek bir kod tabanı, eşitliğin asgari gereksinimidir. Özdeş mantıkla iki dosya, bir ay içinde sapmanın garantisidir.

  2. Dolgu modelini kalibre edin. Sabit 5 bps kayma, hiç yoktan iyidir. Gerçek verilerle kalibre edilmiş bir kayma modeli çok daha iyidir.

  3. İlk 2-4 hafta için gölge modunu kullanın. Sinyal eşleşme oranı %95+'e ulaşana kadar gerçek parayla işlem yapmayın.

  4. Fonlama oranlarını modelleyin. Sürekli vadeli işlemler için bu isteğe bağlı değil — zorunludur. Fonlama, kaldıraç > 5x'te tüm PnL'yi tüketebilir.

  5. Her şeyi günlükleyin. Her sinyal, her emir, her dolgu — zaman damgalarıyla. Günlükler olmadan post-mortem analiz imkansızdır.

  6. Karşılaştırmayı otomatikleştirin. Haftalık DivergenceMonitor raporu otomatik olarak gelmelidir. PnL negatife dönene kadar beklemeyin.

  7. Varsayılan olarak kötümser backtest. Backtestte beklentileri düşük tutup canlıda hoş sürprizlerle karşılaşmak, tersinin yaşanmasından çok daha iyidir. Kayma modeli tutucu olmalıdır.

Sonuç

İşlem sistemi olgunluk seviyeleri — temel backtestingden tam üretime kadar

Backtest-canlı eşitliği, bir sistemin özelliği değil bir süreçtir. Mükemmel eşitlik yoktur: backtest, tanımı gereği gerçekliğin bir modelidir ve model her zaman basitleştirir. Ancak "model %5 farklıdır" ile "model %50 farklıdır" arasındaki fark mimari tarafından belirlenir.

Üç olgunluk düzeyi:

  1. Temel. Paylaşılan çekirdek, sabit kayma, komisyonlar. Sapma: %10-20.
  2. Gelişmiş. Olay güdümlü mimari, uyarlamalı sondaj, fonlama modeli, gölge modu. Sapma: %5-10.
  3. Kurumsal. L2 emir defteri simülasyonu, kalibre edilmiş etki modeli, gerçek zamanlı sapma izleme. Sapma: %2-5.

Göreviniz, hangi düzeyde olduğunuzu belirlemek ve pozisyon büyüklüğünüz ve kaldıracınız için hangi sapmayı kabul edilebilir gördüğünüzü anlamaktır.


Yararlı Bağlantılar

  1. NautilusTrader — Yüksek Performanslı Algoritmik İşlem Platformu
  2. Freqtrade — Ücretsiz, açık kaynaklı kripto işlem botu
  3. Almgren, R., Chriss, N. — Optimal Execution of Portfolio Transactions (2001)
  4. Lopez de Prado — Advances in Financial Machine Learning, Chapter 12: Backtesting
  5. Ernest Chan — Quantitative Trading: How to Build Your Own Algorithmic Trading Business
  6. Hexagonal Architecture (Ports and Adapters) — Alistair Cockburn
  7. Optuna — Hyperparameter Optimization Framework

Alıntı

@article{soloviov2026backtestliveparity,
  author = {Soloviov, Eugen},
  title = {Backtest-canlı eşitliği: botunuz neden backtestten farklı işlem yapıyor},
  year = {2026},
  url = {https://marketmaker.cc/tr/blog/post/backtest-live-parity},
  description = {Backtesting ile canlı işlem arasındaki sapmaların tam taksonomisi: kayma ve kısmi dolgulardan kod tabanı desenkronizasyonuna kadar. Eşitlik sağlamak için mimari desenler ve üretim izleme kontrol listesi.}
}
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.