← Kembali ke artikel
March 7, 2026
Bacaan 5 minit

Pariti backtest-live: mengapa bot anda berdagang berbeza daripada backtest

Pariti backtest-live: mengapa bot anda berdagang berbeza daripada backtest
#algotrading
#backtest
#dagangan langsung
#pariti backtest-live
#pelaksanaan
#NautilusTrader

Anda menjalankan strategi melalui backtest. Sharpe 2.1, MaxDD -8%, PnL +67%. Anda melancarkan bot. Sebulan kemudian anda bandingkan: isyarat yang sama, tempoh yang sama — tetapi PnL langsung 40% lebih rendah. Drawdown satu setengah kali lebih dalam. Dua daripada sepuluh dagangan langsung tidak dilaksanakan.

Ini bukan pepijat. Ini adalah perbezaan backtest-live — perbezaan sistematik antara keputusan backtest dan dagangan sebenar. Semua orang mengalaminya. Soalan sebenarnya adalah sama ada anda mengetahuinya dan sama ada anda boleh mengawalnya.

Artikel ini menyediakan taksonomi lengkap perbezaan, corak seni bina untuk meminimumkannya, dan senarai semak praktikal untuk memantau pariti dalam pengeluaran.

Sindrom "ia berjaya dalam backtest"

Perbezaan backtest vs dagangan langsung — lengkung ekuiti ideal berbanding keputusan sebenar yang tidak menentu

Setiap pedagang algo melalui kitaran ini:

  1. Menulis strategi dalam notebook Jupyter
  2. Menjalankan backtest pada CSV sejarah — hasilnya bagus
  3. Menulis semula logik sebagai bot (sering dalam bahasa atau rangka kerja yang berbeza)
  4. Dilancarkan — keputusan tidak sepadan
  5. Mula mencari pepijat, tidak menjumpainya — "pasaran telah berubah"

Masalahnya bukan pasaran. Masalahnya ialah backtest dan bot adalah dua produk perisian yang berbeza yang memodelkan realiti yang sama secara berbeza. Perbezaan tidak dapat dielakkan, tetapi ia boleh disistematikkan dan diminimumkan.

Taksonomi Perbezaan

Taksonomi perbezaan backtest-live

Semua sumber perbezaan jatuh ke dalam empat kategori. Untuk setiap satu — penilaian keterukan (dari 1 hingga 5) dan sumbangan tipikal kepada perbezaan PnL.

1. Perbezaan data (keterukan: 3/5)

Data yang dilihat backtest dan data yang dilihat bot secara masa nyata tidak sama.

Cap waktu. Bursa menyampaikan lilin dengan peraturan berbeza untuk penugasan cap waktu. Satu bursa menanda lilin dengan permulaan tempoh, yang lain dengan penghujung. REST API mungkin mengembalikan lilin dengan kelewatan 1-3 saat selepas penutupan sebenar. Backtest berfungsi dengan cap waktu "ideal" dari fail sejarah.

Pengagregatan OHLCV. Data sejarah sering diagregatkan oleh pembekal secara berbeza daripada yang dilakukan bursa dalam masa nyata. Perbezaannya ada pada digit terakhir — tetapi dengan isyarat ambang (persilangan MA, pecah paras) ini menentukan sama ada strategi memasuki posisi atau tidak.

Jurang dan data hilang. Data sejarah biasanya bersih — lilin yang hilang diisi dengan interpolasi. Dalam masa nyata, WebSocket mungkin putus, dan bot terlepas 30 saat data.

Sumbangan tipikal kepada perbezaan PnL: 2-5% daripada PnL tahunan.

2. Perbezaan pelaksanaan (keterukan: 5/5)

Perbezaan pelaksanaan pesanan — visualisasi slippage buku pesanan, kependaman, dan pengisian separa

Kelas perbezaan yang paling berbahaya. Backtest mensimulasikan pelaksanaan dengan sempurna — realiti jauh dari ideal.

Slippage. Backtest mengisi pesanan pada harga penutupan (atau harga isyarat). Dalam realiti, pesanan pasaran dilaksanakan pada bid/ask terbaik ditambah slippage yang bergantung pada volum dan kecairan. Untuk posisi $10K pada altcoin kecairan sederhana, slippage boleh menjadi 0.05-0.3%.

Formula untuk slippage kumulatif bagi NN dagangan:

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

di mana sis_i adalah slippage dagangan ke-ii, bergantung pada kedalaman buku pesanan:

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

Kependaman. Dari saat isyarat dijana hingga pelaksanaan pesanan, masa berlalu: pengiraan isyarat (1-50 ms), penghantaran permintaan (10-200 ms), pemadanan di bursa (1-10 ms). Dalam backtest, kependaman = 0. Dalam keadaan langsung — harga boleh bergerak.

Pengisian separa. Backtest mengandaikan 100% pesanan diisi serta-merta. Dalam realiti, pesanan had mungkin diisi sebahagiannya — atau tidak diisi langsung jika harga berbalik. Untuk pesanan pasaran di pasaran tidak cair, pesanan "tergelincir" melalui pelbagai aras buku pesanan.

Keutamaan giliran. Pesanan had yang diletakkan pada harga bid terbaik tidak akan diisi serta-merta — ia beratur di belakang semua pesanan yang diletakkan sebelumnya pada aras tersebut. Backtest yang menganggap "harga disentuh = pesanan diisi" secara sistematik melebih-lebihkan kadar pengisian.

Sumbangan tipikal kepada perbezaan PnL: 10-30% daripada PnL tahunan.

3. Perbezaan logik (keterukan: 4/5)

Ini adalah perbezaan dalam kod strategi itu sendiri antara backtest dan bot.

Pangkalan kod berasingan. Anti-corak klasik: backtests/strategy_a.py dan bot/strategy_a.py — dua fail berasingan yang "melakukan perkara yang sama." Selepas tiga bulan suntingan, ia pasti akan berbeza. Seseorang menambah penapis dalam backtest dan terlupa untuk mereplikasinya dalam bot. Atau sebaliknya — pepijat diperbaiki dalam bot tetapi kekal dalam backtest.

Rangka kerja yang berbeza. Backtest pada pandas dengan operasi vektor, bot pada asyncio dengan logik dipacu peristiwa. Walaupun dengan strategi yang sama, kes tepi dikendalikan secara berbeza: pembundaran, susunan pemeriksaan syarat, pengendalian NaN.

Pengurusan keadaan. Backtest biasanya tanpa keadaan — ia berulang ke atas susunan data. Bot mempunyai keadaan — ia menyimpan posisi, baki, sejarah pesanan. Mulakan semula bot, kehilangan keadaan, penyahsegerakan dengan bursa — semua ini adalah sumber perbezaan.

Sumbangan tipikal kepada perbezaan PnL: 5-20% daripada PnL tahunan.

4. Perbezaan kos (keterukan: 3/5)

Perbezaan dalam pemodelan kos dagangan.

Kadar pembiayaan. Kebanyakan backtest niaga hadapan kekal tidak mengambil kira kadar pembiayaan langsung. Pada leverage 10x dan kadar purata 0.01% setiap 8 jam, ini adalah 0.01%×3×365×10=109.5%0.01\% \times 3 \times 365 \times 10 = 109.5\% setahun — lebih daripada PnL kebanyakan strategi. Analisis terperinci ada dalam artikel Kadar pembiayaan membunuh leverage anda.

Komisen. Komisen maker/taker biasanya dimodelkan tetapi sering dengan kadar yang salah. Tahap VIP, diskaun BNB, rebat — semua ini mempengaruhi keputusan akhir.

Spread. Backtest berasaskan lilin tidak melihat spread bid-ask. Pada lilin 1 minit, penutupan = 3000, tetapi dalam realiti bid = 2999.5 dan ask = 3000.5. Setiap dagangan "berharga" separuh spread.

Sumbangan tipikal kepada perbezaan PnL: 5-15% daripada PnL tahunan.

Kesan Kumulatif

Keempat-empat kategori bertindak serentak dan, secara umum, dalam satu arah — menentang pedagang:

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

Jumlah perbezaan sebanyak 20-50% daripada PnL backtest adalah normal untuk sistem yang tidak diperhalusi. Dengan leverage, kesannya berganda.

Corak Seni Bina untuk Pariti

Corak 1: Teras Bersama (mengekstrak teras bersama)

Seni bina Teras Bersama — satu modul strategi menguasai kedua-dua enjin backtest dan dagangan langsung

Ideanya: ekstrak teras strategi — logik penjanaan isyarat dan pelaksanaan — ke dalam modul berasingan yang digunakan oleh kedua-dua backtest dan bot. Hanya infrastruktur sekeliling yang berbeza: sumber data dan mekanisme penyerahan pesanan.

┌─────────────────────────────────────┐
│         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:
    """
    Teras strategi. Kod yang sama untuk backtest dan live.
    Bergantung hanya pada data, bukan pada infrastruktur.
    """
    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]:
        """
        Proses lilin baru. Mengembalikan OrderRequest atau None.
        Kaedah ini dipanggil secara sama dari backtest dan 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)

Peraturan utama: StrategyCore tidak mengetahui dari mana data datang atau ke mana pesanan dihantar. Ia menerima OHLCV dan mengembalikan OrderRequest. Selebihnya adalah tanggungjawab lapisan infrastruktur.

Corak 2: Penyatuan dipacu peristiwa (pendekatan NautilusTrader)

Seni bina dagangan dipacu peristiwa dengan saluran peristiwa berperingkat — data pasaran, isyarat, pesanan, pengisian

NautilusTrader melaksanakan pariti melalui NautilusKernel yang bersatu — enjin asli Rust dengan teras dipacu peristiwa deterministik dan resolusi nanosaat. Pelaksanaan strategi yang sama berfungsi dalam kedua-dua backtest dan dagangan langsung.

Seni bina dibina atas corak port dan penyesuai (seni bina heksagon):

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

Kelebihan:

  • Ulangan deterministik. Peristiwa diproses dalam susunan yang ditentukan dengan ketat — keputusan backtest boleh direproduksi bit demi bit.
  • FillModel tersuai. Simulasi buku pesanan L2 untuk setiap pelaksanaan — slippage disimulasikan berdasarkan kedalaman buku pesanan sebenar.
  • Prestasi. Sehingga 5 juta baris/saat, memproses data yang tidak muat dalam RAM.
  • Redis + PostgreSQL. Cache dan bas mesej melalui Redis, kegigihan melalui PostgreSQL — infrastruktur yang sama untuk backtest dan live.

Corak 3: Antara Muka Strategi (pendekatan Freqtrade)

Freqtrade menggunakan antara muka IStrategy yang bersatu: kelas strategi yang sama berfungsi dalam kedua-dua backtest dan live. Satu-satunya perbezaan adalah lapisan kegigihan.


class IStrategy:
    """Antara muka bersatu — pelaksanaan tidak tahu sama ada ini backtest atau live."""

    def populate_indicators(self, dataframe, metadata):
        """Kira penunjuk."""
        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):
        """Tentukan isyarat masuk."""
        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):
        """Tentukan isyarat keluar."""
        dataframe.loc[
            (dataframe['fast_ma'] < dataframe['slow_ma']),
            'exit_long'
        ] = 1
        return dataframe

Freqtrade turut menyediakan:

  • Hyperopt melalui Optuna — pengoptimuman parameter strategi
  • --timeframe-detail — gerudi ke jangka masa yang lebih halus untuk penghalusan pengisian (serupa dengan gerudi adaptif)

Perbandingan Corak

Teras Bersama Dipacu peristiwa (NautilusTrader) Antara Muka Strategi (Freqtrade)
Kerumitan pelaksanaan Rendah Tinggi Sederhana
Tahap pariti Sederhana Maksimum Tinggi
Simulasi pengisian FillModel berasingan Buku pesanan L2 --timeframe-detail
Bahasa teras Python Rust + Python Python
Sesuai untuk Enjin tersuai Dagangan institusi Permulaan pantas

Ketepatan Simulasi Pengisian

Tahap ketepatan simulasi pengisian

Simulasi pengisian adalah sumber utama perbezaan pelaksanaan. Tiga tahap ketepatan:

Tahap 1: Naif (pengisian pada harga penutupan)

fill_price = candle['close']

Ralat: tidak mengambil kira slippage, spread, atau pengisian separa. Secara sistematik melebih-lebihkan PnL.

Tahap 2: Model slippage

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

    if order.side == 'buy':
        return base_price + slip  # Beli pada harga lebih tinggi
    else:
        return base_price - slip  # Jual pada harga lebih rendah

Ralat: slippage tetap tidak mengambil kira kecairan dan saiz pesanan. Lebih baik daripada naif, tetapi masih model kasar.

Tahap 3: Gerudi adaptif dengan data 1s/100ms

Pilihan terbaik: gunakan data keberbutiran halus sebenar untuk penentuan tepat susunan pengisian SL/TP. Dihuraikan secara terperinci dalam artikel Gerudi adaptif: backtesting dengan keberbutiran berubah.

class RealisticFillModel:
    """
    Model pengisian gabungan: slippage + spread + impak volum.
    """
    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 impak pasaran (model Almgren-Chriss yang dipermudahkan):

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

di mana σ\sigma adalah turun naik, kk adalah pekali impak, VorderV_{order} adalah volum pesanan, dan VmarketV_{market} adalah volum pasaran untuk tempoh tersebut.

Senarai Semak Pariti Praktikal

Senarai semak pengesahan pariti holografik diatur mengikut kategori — data, pelaksanaan, masa, yuran

Sebelum melancarkan bot secara langsung, sahkan setiap item:

Kod:

  • Strategi menggunakan teras bersama (satu modul untuk backtest dan live)
  • Tiada pendua logik isyarat di dua tempat
  • Ujian unit mengesahkan output teras yang sama untuk input yang sama
  • Susunan pemeriksaan syarat adalah sama (SL sebelum TP? TP sebelum SL?)

Data:

  • Format cap waktu adalah sama (UTC, pembekal yang sama)
  • Pengagregatan OHLCV menggunakan peraturan yang sama
  • Pengendalian lilin yang hilang adalah sama
  • Tiada berat sebelah melihat ke hadapan — backtest tidak mengintip masa hadapan

Pelaksanaan:

  • Model slippage dikalibrasi pada data sebenar
  • Pengisian separa dimodelkan (atau sekurang-kurangnya dianggarkan secara pesimistik)
  • Pesanan had mempunyai model keutamaan giliran
  • Kependaman diambil kira (kelewatan 100-500 ms dari isyarat ke pengisian)

Kos:

  • Komisen maker/taker dimasukkan dengan kadar semasa
  • Kadar pembiayaan diambil kira dengan niaga hadapan kekal
  • Spread dimodelkan (sekurang-kurangnya purata)

Infrastruktur:

  • Kegigihan keadaan: bot memulihkan posisi selepas dimulakan semula
  • Logik penyambungan semula: WebSocket menyambung semula tanpa kehilangan data
  • Pengelogan: semua pesanan dan pengisian dilog untuk analisis pasca-mortem

Memantau Perbezaan dalam Pengeluaran

Pariti bukan pemeriksaan sekali sahaja tetapi proses berterusan. Selepas melancarkan bot, perbezaan mesti dijejaki dalam masa nyata.

Mod bayangan (dagangan kertas)

Mod dagangan bayangan — data pasaran langsung dan pesanan yang disimulasikan berjalan selari

Jalankan bot selari dengan backtest pada data yang sama. Bot menjana isyarat tetapi tidak menghantar pesanan — ia hanya mencatat log. Pada masa yang sama, backtest memproses data yang sama. Bandingkan:

class DivergenceMonitor:
    """
    Membandingkan isyarat backtest dan bot langsung dalam masa nyata.
    """
    def __init__(self, tolerance_pct=0.5):
        self.tolerance = tolerance_pct / 100
        self.divergences = []

    def compare_signal(self, backtest_signal, live_signal, timestamp):
        """Bandingkan isyarat backtest dan live."""
        if backtest_signal is None and live_signal is None:
            return  # Kedua-dua senyap — 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):
        """Bandingkan pelaksanaan."""
        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):
        """Laporan perbezaan mingguan."""
        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 Papan Pemuka

Metrik Formula Had Amaran
Kadar padanan isyarat matchestotal signals\frac{\text{matches}}{\text{total signals}} < 95%
Slippage purata 1Nsi\frac{1}{N}\sum s_i (bps) > 10 bps
Kadar pengisian filledsent\frac{\text{filled}}{\text{sent}} < 90%
Perbezaan PnL PnLlivePnLbtPnLbt\frac{PnL_{live} - PnL_{bt}}{PnL_{bt}} > 20%
Kependaman p99 Persentil ke-99 isyarat-ke-pengisian > 500 ms

Kalibrasi Model Slippage

Kalibrasi model slippage — kedalaman buku pesanan dengan lengkung impak harga yang menunjukkan pengisian dijangka berbanding sebenar

Selepas mengumpul data selama 2-4 minggu, anda boleh mengkalibrasi model slippage backtest pada data sebenar:

def calibrate_slippage(live_fills: list[dict]) -> dict:
    """
    Kalibrasi model slippage menggunakan pengisian sebenar.

    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

Pariti backtest-live bukan tugas yang terpencil. Ia bersilang dengan alat lain dari siri "Backtest Tanpa Ilusi":

  • Gerudi adaptif — meningkatkan ketepatan simulasi pengisian, komponen utama pariti pelaksanaan.
  • Kadar pembiayaan — jika backtest tidak memodelkan pembiayaan, pariti mustahil pada leverage > 3x.
  • Cache Parquet — jangka masa dan penunjuk yang dikira terlebih dahulu memastikan backtest melihat data yang sama seperti bot. Emulasi RunningCandleBuffer = kemas kini masa nyata.
  • Polars vs Pandas — apabila beralih dari pandas (backtest) ke Polars (live), anda perlu memastikan keputusan berangka sepadan.
  • Walk-Forward — walk-forward pada data luar sampel menunjukkan bagaimana strategi merosot — ini lebih hampir kepada live daripada backtest dalam sampel.

Cadangan

  1. Teras bersama adalah wajib. Pangkalan kod tunggal untuk penjanaan isyarat adalah keperluan minimum untuk pariti. Dua fail dengan logik yang sama menjamin perbezaan dalam masa sebulan.

  2. Kalibrasi model pengisian. Slippage tetap 5 bps lebih baik daripada tiada. Model slippage yang dikalibrasi pada data sebenar jauh lebih baik.

  3. Gunakan mod bayangan selama 2-4 minggu pertama. Jangan berdagang dengan wang sebenar sehingga kadar padanan isyarat mencapai 95%+.

  4. Modelkan kadar pembiayaan. Untuk niaga hadapan kekal, ini bukan pilihan — ia wajib. Pembiayaan boleh menghabiskan semua PnL pada leverage > 5x.

  5. Log semua perkara. Setiap isyarat, setiap pesanan, setiap pengisian — dengan cap waktu. Tanpa log, analisis pasca-mortem mustahil.

  6. Automatikkan perbandingan. Laporan DivergenceMonitor mingguan harus tiba secara automatik. Jangan tunggu sehingga PnL menjadi negatif.

  7. Backtest pesimistik secara lalai. Lebih baik merendah-rendahkan jangkaan dalam backtest dan terkejut senang dalam live daripada sebaliknya. Model slippage hendaklah konservatif.

Kesimpulan

Tahap kematangan sistem dagangan — dari backtesting asas hingga pengeluaran penuh

Pariti backtest-live bukan sifat sistem tetapi proses. Pariti sempurna tidak wujud: backtest secara definisinya adalah model realiti, dan model sentiasa menyederhanakan. Tetapi perbezaan antara "model berbeza sebanyak 5%" dan "model berbeza sebanyak 50%" ditentukan oleh seni bina.

Tiga tahap kematangan:

  1. Asas. Teras bersama, slippage tetap, komisen. Perbezaan: 10-20%.
  2. Lanjutan. Seni bina dipacu peristiwa, gerudi adaptif, model pembiayaan, mod bayangan. Perbezaan: 5-10%.
  3. Institusi. Simulasi buku pesanan L2, model impak yang dikalibrasi, pemantauan perbezaan masa nyata. Perbezaan: 2-5%.

Tugas anda adalah menentukan tahap anda berada dan memahami perbezaan yang anda anggap boleh diterima untuk saiz posisi dan leverage anda.


Pautan Berguna

  1. NautilusTrader — Platform Dagangan Algoritmik Berprestasi Tinggi
  2. Freqtrade — Bot dagangan kripto percuma, sumber terbuka
  3. Almgren, R., Chriss, N. — Optimal Execution of Portfolio Transactions (2001)
  4. Lopez de Prado — Advances in Financial Machine Learning, Bab 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 — Rangka Kerja Pengoptimuman Hiperparameter

Rujukan

@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 perbezaan antara backtesting dan dagangan langsung: daripada slippage dan pengisian separa hingga penyahsegerakan pangkalan kod. Corak seni bina untuk mencapai pariti dan senarai semak pemantauan pengeluaran.}
}
Penafian: Maklumat yang disediakan dalam artikel ini adalah untuk tujuan pendidikan dan maklumat sahaja dan bukan merupakan nasihat kewangan, pelaburan, atau dagangan. Dagangan mata wang kripto melibatkan risiko kerugian yang ketara.

Pengarang

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

Kekal Mendahului Pasaran

Langgan surat berita kami untuk pandangan dagangan AI eksklusif, analisis pasaran, dan kemas kini platform.

Kami menghormati privasi anda. Berhenti melanggan pada bila-bila masa.