← Kembali ke artikel
March 7, 2026
5 menit baca

Paritas backtest-live: mengapa bot Anda berdagang berbeda dari backtest

Paritas backtest-live: mengapa bot Anda berdagang berbeda dari backtest
#algotrading
#backtest
#live trading
#paritas backtest-live
#eksekusi
#NautilusTrader

Anda menjalankan strategi melalui backtest. Sharpe 2.1, MaxDD -8%, PnL +67%. Anda meluncurkan bot. Sebulan kemudian Anda membandingkan: sinyal yang sama, periode yang sama — tetapi PnL live 40% lebih rendah. Drawdown satu setengah kali lebih dalam. Dua dari sepuluh trade sama sekali tidak dieksekusi.

Ini bukan bug. Ini adalah divergensi backtest-live — ketidaksesuaian sistematis antara hasil backtest dan trading nyata. Semua orang mengalaminya. Satu-satunya pertanyaan adalah apakah Anda mengetahuinya dan apakah Anda dapat mengendalikannya.

Artikel ini menyediakan taksonomi lengkap divergensi, pola arsitektur untuk meminimalkannya, dan daftar periksa praktis untuk memonitor paritas di produksi.

Sindrom "berhasil di backtest"

Divergensi backtest vs live trading — kurva ekuitas ideal versus hasil volatile nyata

Setiap algotrader melalui siklus ini:

  1. Menulis strategi di Jupyter notebook
  2. Menjalankan backtest pada CSV historis — hasilnya bagus
  3. Menulis ulang logika sebagai bot (seringkali dalam bahasa atau framework yang berbeda)
  4. Diluncurkan — hasilnya tidak cocok
  5. Mulai mencari bug, tidak menemukannya — "pasar berubah"

Masalahnya bukan pasar. Masalahnya adalah backtest dan bot adalah dua produk perangkat lunak yang berbeda yang memodelkan realitas yang sama secara berbeda. Divergensi tidak terhindarkan, tetapi dapat disistematisasi dan diminimalkan.

Taksonomi Divergensi

Taksonomi divergensi backtest-live

Semua sumber divergensi terbagi dalam empat kategori. Untuk setiap sumber — penilaian keparahan (dari 1 hingga 5) dan kontribusi tipikal terhadap divergensi PnL.

1. Divergensi data (keparahan: 3/5)

Data yang dilihat backtest dan data yang dilihat bot secara real time tidak sama.

Timestamp. Exchange mengirimkan candle dengan aturan penugasan timestamp yang berbeda. Satu exchange menandai candle dengan awal periode, yang lain dengan akhir. REST API mungkin mengembalikan candle dengan penundaan 1-3 detik setelah penutupan aktual. Backtest bekerja dengan timestamp "ideal" dari file historis.

Agregasi OHLCV. Data historis sering diagregasikan oleh penyedia secara berbeda dari yang dilakukan exchange secara real time. Perbedaannya ada di digit terakhir — tetapi dengan sinyal ambang batas (persilangan MA, breakout level) ini menentukan apakah strategi masuk posisi atau tidak.

Celah dan data yang hilang. Data historis biasanya bersih — candle yang hilang diisi dengan interpolasi. Secara real time, WebSocket mungkin terputus, dan bot melewatkan 30 detik data.

Kontribusi tipikal terhadap divergensi PnL: 2-5% dari PnL tahunan.

2. Divergensi eksekusi (keparahan: 5/5)

Divergensi eksekusi order — visualisasi slippage orderbook, latensi, dan partial fill

Kelas divergensi yang paling berbahaya. Backtest mensimulasikan eksekusi dengan sempurna — kenyataan jauh dari ideal.

Slippage. Backtest mengisi order pada harga penutupan (atau harga sinyal). Dalam kenyataan, market order dieksekusi pada bid/ask terbaik ditambah slippage yang bergantung pada volume dan likuiditas. Untuk posisi $10K pada altcoin likuiditas menengah, slippage bisa 0,05-0,3%.

Formula untuk slippage kumulatif selama NN trade:

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

di mana sis_i adalah slippage dari trade ke-ii, bergantung pada kedalaman orderbook:

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

Latensi. Dari saat sinyal dihasilkan hingga eksekusi order, waktu berlalu: komputasi sinyal (1-50 ms), transmisi permintaan (10-200 ms), pencocokan di exchange (1-10 ms). Dalam backtest, latensi = 0. Secara live — harga bisa bergerak.

Partial fill. Backtest mengasumsikan 100% order terisi secara instan. Dalam kenyataan, limit order mungkin terisi sebagian — atau tidak terisi sama sekali jika harga berbalik. Untuk market order di pasar yang tidak likuid, order "tergelincir" melalui beberapa level orderbook.

Prioritas antrean. Limit order yang ditempatkan pada harga bid terbaik tidak akan langsung terisi — ia mengantri di belakang semua order yang sebelumnya ditempatkan pada level tersebut. Backtest yang menganggap "harga tersentuh = order terisi" secara sistematis melebih-lebihkan tingkat pengisian.

Kontribusi tipikal terhadap divergensi PnL: 10-30% dari PnL tahunan.

3. Divergensi logika (keparahan: 4/5)

Ini adalah divergensi dalam kode strategi itu sendiri antara backtest dan bot.

Kodebase terpisah. Anti-pola klasik: backtests/strategy_a.py dan bot/strategy_a.py — dua file terpisah yang "melakukan hal yang sama." Setelah tiga bulan pengeditan, mereka pasti menyimpang. Seseorang menambahkan filter dalam backtest dan lupa mereplikasikannya di bot. Atau sebaliknya — bug diperbaiki di bot tetapi tetap ada di backtest.

Framework berbeda. Backtest pada pandas dengan operasi tervektor, bot pada asyncio dengan logika event-driven. Bahkan dengan strategi yang identik, kasus tepi ditangani secara berbeda: pembulatan, urutan pemeriksaan kondisi, penanganan NaN.

Manajemen state. Backtest biasanya stateless — ia mengiterasi array data. Bot bersifat stateful — ia menyimpan posisi, saldo, riwayat order. Restart bot, kehilangan state, desinkronisasi dengan exchange — semua ini adalah sumber divergensi.

Kontribusi tipikal terhadap divergensi PnL: 5-20% dari PnL tahunan.

4. Divergensi biaya (keparahan: 3/5)

Divergensi dalam pemodelan biaya trading.

Funding rate. Sebagian besar backtest perpetual futures sama sekali tidak memperhitungkan funding rate. Pada leverage 10x dan rata-rata rate 0,01% per 8 jam, ini adalah 0.01%×3×365×10=109.5%0.01\% \times 3 \times 365 \times 10 = 109.5\% per tahun — lebih dari PnL sebagian besar strategi. Analisis detail ada di artikel Funding rate membunuh leverage Anda.

Komisi. Komisi maker/taker biasanya dimodelkan tetapi sering dengan rate yang salah. Tier VIP, diskon BNB, rebate — semua ini mempengaruhi hasil akhir.

Spread. Backtest berbasis candle tidak melihat spread bid-ask. Pada candle 1 menit, close = 3000, tetapi dalam kenyataan bid = 2999.5 dan ask = 3000.5. Setiap trade "berharga" setengah spread.

Kontribusi tipikal terhadap divergensi PnL: 5-15% dari PnL tahunan.

Efek Kumulatif

Keempat kategori bekerja secara bersamaan dan, sebagai aturan, dalam satu arah — melawan trader:

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

Total divergensi 20-50% dari PnL backtest adalah normal untuk sistem yang belum disempurnakan. Dengan leverage, efeknya berlipat ganda.

Pola Arsitektur untuk Paritas

Pola 1: Shared Core (mengekstrak inti bersama)

Arsitektur Shared Core — satu modul strategi yang menggerakkan mesin backtest dan live trading

Ideanya: ekstrak inti strategi — logika pembangkitan sinyal dan eksekusi — ke dalam modul terpisah yang digunakan oleh backtest dan bot. Hanya infrastruktur sekitarnya yang berbeda: sumber data dan mekanisme pengiriman order.

┌─────────────────────────────────────┐
│         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:
    """
    Strategy core. Identical code for backtest and live.
    Depends only on data, not on infrastructure.
    """
    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]:
        """
        Process a new candle. Returns an OrderRequest or None.
        This method is called identically from the backtest and the bot.
        """
        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

Kini backtest dan bot menggunakan StrategyCore yang sama:


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)

Aturan utama: StrategyCore tidak tahu dari mana data berasal atau ke mana order dikirim. Ia menerima OHLCV dan mengembalikan OrderRequest. Segala sesuatu yang lain adalah tanggung jawab lapisan infrastruktur.

Pola 2: Unifikasi event-driven (pendekatan NautilusTrader)

Arsitektur trading event-driven dengan pipeline event bertingkat — data pasar, sinyal, order, fill

NautilusTrader mengimplementasikan paritas melalui NautilusKernel terpadu — mesin native Rust dengan inti event-driven deterministik dan resolusi nanodetik. Implementasi strategi yang sama bekerja di backtest dan live trading.

Arsitektur dibangun di atas pola ports and adapters (arsitektur heksagonal):

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

Keunggulan:

  • Pemutaran ulang deterministik. Event diproses dalam urutan yang ditentukan secara ketat — hasil backtest dapat direproduksi secara bit.
  • FillModel kustom. Simulasi orderbook L2 untuk setiap eksekusi — slippage disimulasikan berdasarkan kedalaman orderbook nyata.
  • Performa. Hingga 5 juta baris/dtk, memproses data yang tidak muat di RAM.
  • Redis + PostgreSQL. Cache dan message bus melalui Redis, persistensi melalui PostgreSQL — infrastruktur identik untuk backtest dan live.

Pola 3: Strategy Interface (pendekatan Freqtrade)

Freqtrade menggunakan antarmuka IStrategy terpadu: kelas strategi yang sama bekerja di backtest dan live. Satu-satunya perbedaan adalah lapisan persistensi.


class IStrategy:
    """Unified interface — the implementation does not know if this is a backtest or live."""

    def populate_indicators(self, dataframe, metadata):
        """Compute indicators."""
        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):
        """Determine entry signals."""
        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):
        """Determine exit signals."""
        dataframe.loc[
            (dataframe['fast_ma'] < dataframe['slow_ma']),
            'exit_long'
        ] = 1
        return dataframe

Freqtrade juga menyediakan:

  • Hyperopt via Optuna — optimasi parameter strategi
  • --timeframe-detail — drill-down ke timeframe yang lebih halus untuk penyempurnaan fill (mirip dengan adaptive drill-down)

Perbandingan Pola

Shared Core Event-driven (NautilusTrader) Strategy Interface (Freqtrade)
Kompleksitas implementasi Rendah Tinggi Menengah
Tingkat paritas Menengah Maksimum Tinggi
Simulasi fill FillModel terpisah Orderbook L2 --timeframe-detail
Bahasa inti Python Rust + Python Python
Cocok untuk Mesin kustom Trading institusional Memulai cepat

Akurasi Simulasi Fill

Tingkat akurasi simulasi fill

Simulasi fill adalah sumber utama divergensi eksekusi. Tiga tingkat akurasi:

Tingkat 1: Naif (fill pada harga penutupan)

fill_price = candle['close']

Kesalahan: tidak memperhitungkan slippage, spread, atau partial fill. Secara sistematis melebih-lebihkan PnL.

Tingkat 2: Model slippage

def simulate_fill(order, candle, slippage_bps=5):
    """Fill with slippage."""
    base_price = candle['close']
    slip = base_price * slippage_bps / 10000

    if order.side == 'buy':
        return base_price + slip  # Buy at a higher price
    else:
        return base_price - slip  # Sell at a lower price

Kesalahan: slippage tetap tidak memperhitungkan likuiditas dan ukuran order. Lebih baik dari naif, tetapi masih model yang kasar.

Tingkat 3: Adaptive drill-down dengan data 1d/100ms

Opsi terbaik: gunakan data granularitas halus nyata untuk penentuan urutan fill SL/TP yang tepat. Dijelaskan secara detail dalam artikel Adaptive drill-down: backtesting dengan granularitas variabel.

class RealisticFillModel:
    """
    Combined fill model: slippage + spread + volume impact.
    """
    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

Formula dampak pasar (model Almgren-Chriss yang disederhanakan):

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

di mana σ\sigma adalah volatilitas, kk adalah koefisien dampak, VorderV_{order} adalah volume order, dan VmarketV_{market} adalah volume pasar untuk periode tersebut.

Daftar Periksa Paritas Praktis

Daftar periksa validasi paritas holografis yang diorganisir berdasarkan kategori — data, eksekusi, timing, biaya

Sebelum meluncurkan bot secara live, verifikasi setiap item:

Kode:

  • Strategi menggunakan inti bersama (satu modul untuk backtest dan live)
  • Tidak ada duplikasi logika sinyal di dua tempat
  • Unit test memverifikasi output inti yang identik untuk input yang identik
  • Urutan pemeriksaan kondisi identik (SL sebelum TP? TP sebelum SL?)

Data:

  • Format timestamp identik (UTC, penyedia yang sama)
  • Agregasi OHLCV menggunakan aturan yang sama
  • Penanganan candle yang hilang identik
  • Tidak ada look-ahead bias — backtest tidak mengintip ke masa depan

Eksekusi:

  • Model slippage dikalibrasi pada data nyata
  • Partial fill dimodelkan (atau setidaknya diperkirakan secara pesimis)
  • Limit order memiliki model prioritas antrean
  • Latensi diperhitungkan (penundaan 100-500 ms dari sinyal ke fill)

Biaya:

  • Komisi maker/taker disertakan dengan rate saat ini
  • Funding rate diperhitungkan untuk perpetual futures
  • Spread dimodelkan (setidaknya rata-rata)

Infrastruktur:

  • Persistensi state: bot memulihkan posisi setelah restart
  • Logika reconnection: WebSocket menyambung kembali tanpa kehilangan data
  • Logging: semua order dan fill dicatat untuk analisis post-mortem

Memantau Divergensi di Produksi

Paritas bukan pemeriksaan satu kali tetapi proses berkelanjutan. Setelah meluncurkan bot, divergensi harus dilacak secara real time.

Mode shadow (paper trading)

Mode trading shadow — data pasar live dan order yang disimulasikan berjalan secara paralel

Jalankan bot secara paralel dengan backtest pada data yang sama. Bot menghasilkan sinyal tetapi tidak mengirim order — hanya mencatat log. Secara bersamaan, backtest memproses data yang sama. Bandingkan:

class DivergenceMonitor:
    """
    Compares backtest and live bot signals in real time.
    """
    def __init__(self, tolerance_pct=0.5):
        self.tolerance = tolerance_pct / 100
        self.divergences = []

    def compare_signal(self, backtest_signal, live_signal, timestamp):
        """Compare backtest and live signals."""
        if backtest_signal is None and live_signal is None:
            return  # Both silent — OK

        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):
        """Compare execution."""
        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):
        """Weekly divergence report."""
        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,
        }

Metrik Dashboard

Metrik Formula Ambang Batas Peringatan
Tingkat kecocokan sinyal matchestotal signals\frac{\text{matches}}{\text{total signals}} < 95%
Rata-rata slippage 1Nsi\frac{1}{N}\sum s_i (bps) > 10 bps
Tingkat fill filledsent\frac{\text{filled}}{\text{sent}} < 90%
Divergensi PnL PnLlivePnLbtPnLbt\frac{PnL_{live} - PnL_{bt}}{PnL_{bt}} > 20%
Latensi p99 Persentil ke-99 sinyal-ke-fill > 500 ms

Kalibrasi Model Slippage

Kalibrasi model slippage — kedalaman order book dengan kurva dampak harga yang menunjukkan fill yang diharapkan vs aktual

Setelah mengumpulkan data selama 2-4 minggu, Anda dapat mengkalibrasi model slippage backtest pada data nyata:

def calibrate_slippage(live_fills: list[dict]) -> dict:
    """
    Calibrate slippage model using real fills.

    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,
    }

Hubungan dengan Alat Lain

Paritas backtest-live bukan tugas yang terisolasi. Ia bersilangan dengan alat lain dari seri "Backtest Tanpa Ilusi":

  • Adaptive drill-down — meningkatkan akurasi simulasi fill, komponen kunci dari paritas eksekusi.
  • Funding rate — jika backtest tidak memodelkan funding, paritas tidak mungkin pada leverage > 3x.
  • Cache Parquet — timeframe dan indikator yang telah dihitung sebelumnya memastikan bahwa backtest melihat data yang sama seperti bot. Emulasi RunningCandleBuffer = pembaruan real time.
  • Polars vs Pandas — saat beralih dari pandas (backtest) ke Polars (live), Anda perlu memastikan hasil numerik cocok.
  • Walk-Forward — walk-forward pada data out-of-sample menunjukkan bagaimana strategi terdegradasi — ini lebih dekat ke live daripada backtest in-sample.

Rekomendasi

  1. Shared core adalah wajib. Satu kodebase untuk pembangkitan sinyal adalah persyaratan minimum untuk paritas. Dua file dengan logika identik menjamin divergensi dalam sebulan.

  2. Kalibrasi model fill. Slippage tetap 5 bps lebih baik daripada tidak sama sekali. Model slippage yang dikalibrasi pada data nyata jauh lebih baik.

  3. Gunakan mode shadow selama 2-4 minggu pertama. Jangan berdagang dengan uang nyata sampai tingkat kecocokan sinyal mencapai 95%+.

  4. Modelkan funding rate. Untuk perpetual futures, ini bukan opsional — ini wajib. Funding dapat menghabiskan semua PnL pada leverage > 5x.

  5. Catat semua log. Setiap sinyal, setiap order, setiap fill — dengan timestamp. Tanpa log, analisis post-mortem tidak mungkin dilakukan.

  6. Otomatiskan perbandingan. Laporan DivergenceMonitor mingguan harus tiba secara otomatis. Jangan menunggu sampai PnL menjadi negatif.

  7. Backtest pesimis secara default. Lebih baik meremehkan ekspektasi dalam backtest dan mendapat kejutan menyenangkan di live daripada sebaliknya. Model slippage harus konservatif.

Kesimpulan

Tingkat kematangan sistem trading — dari backtesting dasar hingga produksi penuh

Paritas backtest-live bukan properti sistem melainkan sebuah proses. Paritas sempurna tidak ada: backtest pada dasarnya adalah model realitas, dan model selalu menyederhanakan. Tetapi perbedaan antara "model berbeda 5%" dan "model berbeda 50%" ditentukan oleh arsitektur.

Tiga tingkat kematangan:

  1. Dasar. Shared core, slippage tetap, komisi. Divergensi: 10-20%.
  2. Lanjutan. Arsitektur event-driven, adaptive drill-down, model funding, mode shadow. Divergensi: 5-10%.
  3. Institusional. Simulasi orderbook L2, model dampak yang dikalibrasi, pemantauan divergensi real time. Divergensi: 2-5%.

Tugas Anda adalah menentukan pada tingkat mana Anda berada dan memahami divergensi apa yang Anda anggap dapat diterima untuk ukuran posisi dan leverage Anda.


Tautan Berguna

  1. NautilusTrader — High-Performance Algorithmic Trading Platform
  2. Freqtrade — Free, open source crypto trading bot
  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

Kutipan

@article{soloviov2026backtestliveparity,
  author = {Soloviov, Eugen},
  title = {Backtest-live parity: why your bot trades differently from the backtest},
  year = {2026},
  url = {https://marketmaker.cc/ru/blog/post/backtest-live-parity},
  description = {Taksonomi lengkap perbedaan antara backtesting dan live trading: dari slippage dan partial fill hingga desinkronisasi kodebase. Pola arsitektur untuk mencapai paritas dan daftar periksa monitoring produksi.}
}
Penafian: Informasi yang disediakan dalam artikel ini hanya untuk tujuan edukasi dan informasi serta tidak merupakan nasihat keuangan, investasi, atau trading. Trading mata uang kripto mengandung risiko kerugian yang signifikan.

Penulis

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

Selangkah Lebih Maju dari Pasar

Berlangganan newsletter kami untuk wawasan AI trading eksklusif, analisis pasar, dan pembaruan platform.

Kami menghormati privasi Anda. Berhenti berlangganan kapan saja.