← Мақалаларға оралу
March 7, 2026
5 мин оқу

Бэктест-лайв паритеті: боттың бэктестен неге басқаша сауда жасайды

#algotrading
#backtest
#live trading
#backtest-live parity
#execution
#NautilusTrader
🎯
Part 8 of 9 · Collection
Backtesting Without Fooling Yourself

Сіз стратегияны бэктест арқылы өткіздіңіз. Sharpe 2.1, MaxDD -8%, PnL +67%. Ботты іске қостыңыз. Бір айдан кейін салыстырасыз: сол сигналдар, сол кезең — бірақ нақты PnL 40%-ға төмен. Просадка бір жарым есе тереңірек. Он мәмілеге екеуі мүлдем орындалмады.

Бұл баг емес. Бұл — бэктест-лайв алшақтығы, яғни бэктест нәтижелері мен нақты саудадағы жүйелі алшақтық. Бұл барлығында бар. Жалғыз сұрақ — сіз бұны білесіз бе және оны бақылай аласыз ба.

Бұл мақала алшақтықтардың толық таксономиясын, оларды азайтуға арналған архитектуралық үлгілерді және өндірісте паритетті бақылауға арналған практикалық чеклистті ұсынады.

"бэктесте жұмыс істеді" синдромы

Бэктест пен нақты саудадағы алшақтық — идеалды equity қисығы мен нақты құбылмалы нәтижелер

Әрбір algotrader мына циклден өтеді:

  1. Jupyter notebook-та стратегия жазды
  2. Тарихи CSV бойынша бэктест жүргізді — нәтижелер керемет
  3. Логиканы бот түрінде қайта жазды (жиі басқа тілде немесе фреймворкта)
  4. Іске қосты — нәтижелер сәйкес келмейді
  5. Багты іздеді, таппады — "нарық өзгерді"

Мәселе нарықта емес. Мәселе — бэктест пен бот екі түрлі бағдарламалық өнім, олар бір реалдылықты әртүрлі модельдейді. Алшақтықтар сөзсіз, бірақ оларды жүйелеуге және азайтуға болады.

Алшақтықтар таксономиясы

Бэктест-лайв алшақтықтарының таксономиясы

Алшақтықтың барлық көздері төрт санатқа бөлінеді. Әрқайсысы үшін — ауырлық дәрежесі (1-ден 5-ке дейін) және PnL алшақтығына тән үлес.

1. Деректер алшақтығы (ауырлығы: 3/5)

Бэктест көретін деректер мен бот нақты уақытта көретін деректер бірдей емес.

Уақыт белгілері. Биржалар кэндлдерді уақыт белгісін тағайындаудың әртүрлі ережелерімен жеткізеді. Бір биржа кэндлді кезеңнің басымен, екіншісі соңымен белгілейді. REST API нақты жабылудан 1-3 секунд кейін кэндл қайтаруы мүмкін. Бэктест тарихи файлдан алынған "идеалды" уақыт белгілерімен жұмыс істейді.

OHLCV агрегациясы. Тарихи деректерді провайдер жиі биржа нақты уақытта жасайтын тәсілден өзгеше агрегаттайды. Айырмашылық соңғы санда — бірақ шекті сигналдарда (MA қиылысуы, деңгей бұзылуы) бұл стратегия позицияға кіру-кірмеуін анықтайды.

Олқылықтар мен жетіспейтін деректер. Тарихи деректер әдетте таза — жоқ кэндлдер интерполяциямен толтырылады. Нақты уақытта WebSocket үзілуі мүмкін, бот 30 секунд деректерін жіберіп алады.

PnL алшақтығына тән үлес: жылдық PnL-дің 2-5%.

2. Орындау алшақтығы (ауырлығы: 5/5)

Бұйрықты орындау алшақтығы — ордербук слиппеджі, кідіріс және ішінара орындаудың визуализациясы

Алшақтықтың ең қауіпті класы. Бэктест орындауды тамаша модельдейді — реалдылық идеалдан алыс.

Слиппедж. Бэктест бұйрықты жабылу бағасымен (немесе сигнал бағасымен) толтырады. Шын мәнінде, нарықтық бұйрық көлем мен өтімділікке байланысты слиппеджі бар ең жақсы bid/ask бойынша орындалады. Орташа өтімділігі бар альткоинде $10K позиция үшін слиппедж 0.05-0.3% болуы мүмкін.

NN мәміле бойынша жинақталған слиппедж формуласы:

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

мұндағы sis_i — ордербук тереңдігіне байланысты ii-ші мәміленің слиппеджі:

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

Кідіріс. Сигнал жасалған сәттен бұйрықты орындауға дейін уақыт өтеді: сигналды есептеу (1-50 мс), сұранысты жіберу (10-200 мс), биржада сәйкестендіру (1-10 мс). Бэктесте кідіріс = 0. Лайвта баға өзгеруі мүмкін.

Ішінара орындау. Бэктест бұйрықтың 100%-ы бірден толтырылады деп есептейді. Шын мәнінде, лимитті бұйрық ішінара толтырылуы мүмкін — немесе баға кері бағытта жүрсе мүлдем толтырылмайды. Өтімсіз нарықтағы нарықтық бұйрық үшін ол бірнеше ордербук деңгейлерінен "сырғанайды".

Кезек басымдығы. Ең жақсы bid бағасына қойылған лимитті бұйрық бірден толтырылмайды — ол осы деңгейде бұрын қойылған барлық бұйрықтардың артынан кезекке тұрады. "Баға тиді = бұйрық толтырылды" деп есептейтін бэктест толтыру мөлшерін жүйелі түрде асыра бағалайды.

PnL алшақтығына тән үлес: жылдық PnL-дің 10-30%.

3. Логика алшақтығы (ауырлығы: 4/5)

Бұл бэктест пен бот арасындағы стратегия кодының өзіндегі алшақтықтар.

Бөлек кодтық базалар. Классикалық антиүлгі: backtests/strategy_a.py және bot/strategy_a.py — "бір нәрсе жасайтын" екі бөлек файл. Үш ай өзгертулерден кейін олар сөзсіз алшақтайды. Біреу бэктестке фильтр қосып, ботта оны қайталауды ұмытып кетті. Немесе керісінше — ботта баг түзетілді, бірақ бэктесте қалды.

Әртүрлі фреймворктар. Векторланған операциялары бар pandas-та бэктест, оқиғаға негізделген логикасы бар asyncio-да бот. Тіпті бірдей стратегияда шекаралық жағдайлар әртүрлі өңделеді: дөңгелектеу, шарттарды тексеру реті, NaN-ды өңдеу.

Күй басқару. Бэктест әдетте күйсіз — ол деректер массиві бойынша итерациялайды. Бот күйлі — ол позицияларды, баланстарды, бұйрықтар тарихын сақтайды. Ботты қайта іске қосу, күйдің жоғалуы, биржамен десинхронизация — бұлардың барлығы алшақтық көздері.

PnL алшақтығына тән үлес: жылдық PnL-дің 5-20%.

4. Шығындар алшақтығы (ауырлығы: 3/5)

Сауда шығындарын модельдеудегі алшақтықтар.

Фандинг мөлшерлемелері. Мәңгілік фьючерстердің көпшілігінің бэктестері фандинг мөлшерлемелерін мүлдем есепке алмайды. 10x левередж және 8 сағат сайын орташа 0.01% мөлшерлемесі кезінде бұл жылына 0.01%×3×365×10=109.5%0.01\% \times 3 \times 365 \times 10 = 109.5\% құрайды — стратегиялардың көпшілігінің PnL-нен көп. Толық талдау Фандинг мөлшерлемелері левереджіңізді өлтіреді мақаласында.

Комиссиялар. Maker/taker комиссиялары әдетте модельденеді, бірақ жиі қате мөлшерлемемен. VIP деңгейлер, BNB жеңілдіктері, rebate-тер — бұлардың барлығы соңғы нәтижеге әсер етеді.

Спред. Кэндлге негізделген бэктест bid-ask спредін көрмейді. 1 минуттық кэндлде close = 3000, бірақ шындығында bid = 2999.5 және ask = 3000.5. Әрбір мәміле спредтің жартысы "тұрады".

PnL алшақтығына тән үлес: жылдық PnL-дің 5-15%.

Жинақталған әсер

Барлық төрт санат бір мезгілде және әдетте бір бағытта — трейдерге қарсы әрекет етеді:

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

Тазаланбаған жүйе үшін бэктест PnL-нен 20-50% жалпы алшақтық қалыпты жағдай. Левереджбен әсер еселенеді.

Паритет үшін архитектуралық үлгілер

1-үлгі: Shared Core (ортақ ядроны бөлектеу)

Shared Core архитектурасы — бір стратегия модулі бэктест те, нақты сауда қозғалтқыштарын да басқарады

Идея: стратегия ядросын — сигнал генерациясы мен орындау логикасын — бэктест те, бот та пайдаланатын бөлек модульге бөлектеу. Тек айналасындағы инфрақұрылым өзгеше: деректер көзі және бұйрықтарды жіберу механизмі.

┌─────────────────────────────────────┐
│         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

Енді бэктест пен бот бір StrategyCore-ды пайдаланады:


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)

Негізгі ереже: StrategyCore деректер қайдан келетінін немесе бұйрықтар қайда жіберілетінін білмейді. Ол OHLCV алады және OrderRequest қайтарады. Қалғанының бәрі инфрақұрылым қабатының жауапкершілігі.

2-үлгі: Оқиғаға негізделген біріздендіру (NautilusTrader тәсілі)

Каскадты оқиғалар конвейерімен оқиғаға негізделген сауда архитектурасы — нарық деректері, сигналдар, бұйрықтар, толтырулар

NautilusTrader паритетті біріздендірілген NautilusKernel арқылы жүзеге асырады — детерминистік оқиғаға негізделген ядросы және наносекундтық ажыратымдылығы бар Rust-native қозғалтқыш. Бір стратегия іске асыруы бэктесте де, нақты саудада да жұмыс істейді.

Архитектура порттар мен адаптерлер үлгісіне (гексагональды архитектура) негізделген:

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

Артықшылықтары:

  • Детерминистік replay. Оқиғалар қатаң анықталған ретпен өңделеді — бэктест нәтижесі бит-биттіклей қайта жаңғыртылады.
  • Custom FillModel. Әрбір орындау үшін L2 ордербук симуляциясы — слиппедж нақты ордербук тереңдігі негізінде модельденеді.
  • Өнімділік. Секундына 5 миллионға дейін жол, RAM-ға сыймайтын деректерді өңдеу.
  • Redis + PostgreSQL. Redis арқылы кэш пен хабарлар шинасы, PostgreSQL арқылы персистенттілік — бэктест пен лайв үшін бірдей инфрақұрылым.

3-үлгі: Strategy Interface (Freqtrade тәсілі)

Freqtrade біріздендірілген IStrategy интерфейсін пайдаланады: бір стратегия класы бэктесте де, лайвта да жұмыс істейді. Жалғыз айырмашылық — персистенттілік қабаты.


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 қосымша ұсынады:

  • Optuna арқылы Hyperopt — стратегия параметрлерін оңтайландыру
  • --timeframe-detail — толтыруды нақтылау үшін неғұрлым нәзік уақыт кезеңіне тереңдету (адаптивті drill-down-ге ұқсас)

Үлгілерді салыстыру

Shared Core Оқиғаға негізделген (NautilusTrader) Strategy Interface (Freqtrade)
Іске асыру күрделілігі Төмен Жоғары Орташа
Паритет деңгейі Орташа Максималды Жоғары
Толтыру симуляциясы Бөлек FillModel L2 ордербук --timeframe-detail
Ядро тілі Python Rust + Python Python
Қолайлы Custom қозғалтқыштар Институционалды сауда Жылдам бастау

Толтыру симуляциясының дәлдігі

Толтыру симуляциясының дәлдік деңгейлері

Толтыру симуляциясы орындау алшақтығының негізгі көзі. Дәлдіктің үш деңгейі:

1-деңгей: Наивті (жабылу бағасымен толтыру)

fill_price = candle['close']

Қате: слиппеджді, спредті немесе ішінара толтыруды есепке алмайды. PnL-ды жүйелі түрде асыра бағалайды.

2-деңгей: Слиппедж моделі

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

Қате: тұрақты слиппедж өтімділік пен бұйрық көлемін есепке алмайды. Наивтіден жақсырақ, бірақ бәрібір дөрекі модель.

3-деңгей: 1с/100мс деректерімен адаптивті drill-down

Ең жақсы нұсқа: SL/TP толтыру ретін дәл анықтау үшін нақты нәзік грануляциялы деректерді пайдалану. Толығырақ Адаптивті drill-down: айнымалы грануляциямен бэктестинг мақаласында сипатталған.

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

Нарықтық әсер формуласы (жеңілдетілген Almgren-Chriss моделі):

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

мұндағы σ\sigma — құбылмалылық, kk — әсер коэффициенті, VorderV_{order} — бұйрық көлемі, ал VmarketV_{market} — кезең үшін нарық көлемі.

Практикалық паритет чеклисті

Санат бойынша ұйымдастырылған голографиялық паритетті валидациялау чеклисті — деректер, орындау, уақыт, комиссиялар

Ботты лайвқа іске қоспас бұрын, әр тармақты тексеріңіз:

Код:

  • Стратегия shared core пайдаланады (бэктест пен лайв үшін бір модуль)
  • Сигнал логикасы екі жерде қайталанбайды
  • Юнит-тесттер бірдей кірістер үшін ядроның бірдей шығуын тексереді
  • Шарттарды тексеру реті бірдей (SL TP-ден бұрын ба? TP SL-ден бұрын ба?)

Деректер:

  • Уақыт белгісінің форматы бірдей (UTC, бірдей провайдер)
  • OHLCV агрегациясы бірдей ережелерді қолданады
  • Жоқ кэндлдерді өңдеу бірдей
  • Look-ahead bias жоқ — бэктест болашаққа үңілмейді

Орындау:

  • Слиппедж моделі нақты деректерде калибрленген
  • Ішінара толтырулар модельденген (немесе кем дегенде пессимистік бағаланған)
  • Лимитті бұйрықтарда кезек басымдық моделі бар
  • Кідіріс есепке алынған (сигналдан толтыруға дейін 100-500 мс кідіріс)

Шығындар:

  • Maker/taker комиссиялары ағымдағы мөлшерлемемен қосылған
  • Мәңгілік фьючерстермен фандинг мөлшерлемелері есепке алынған
  • Спред модельденген (кем дегенде орташа)

Инфрақұрылым:

  • Күй персистенттілігі: бот қайта іске қосудан кейін позицияларды қалпына келтіреді
  • Қайта қосылу логикасы: WebSocket деректер жоғалтусыз қайта қосылады
  • Логтау: пост-мортем талдау үшін барлық бұйрықтар мен толтырулар логталады

Өндірісте алшақтықты бақылау

Паритет — бір реттік тексеру емес, үздіксіз процесс. Ботты іске қосқаннан кейін алшақтықтарды нақты уақытта бақылау қажет.

Shadow режимі (paper trading)

Shadow сауда режимі — нақты нарық деректері мен модельденген бұйрықтар параллель жұмыс істейді

Ботты бэктестпен параллель, бірдей деректер бойынша жүргізіңіз. Бот сигналдар генерациялайды, бірақ бұйрықтар жібермейді — тек логтайды. Сонымен қатар бэктест бірдей деректерді өңдейді. Салыстырыңыз:

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

Дашборд метрикалары

Метрика Формула Ескерту шегі
Сигнал сәйкестік деңгейі matchestotal signals\frac{\text{matches}}{\text{total signals}} < 95%
Орташа слиппедж 1Nsi\frac{1}{N}\sum s_i (bps) > 10 bps
Толтыру деңгейі filledsent\frac{\text{filled}}{\text{sent}} < 90%
PnL алшақтығы PnLlivePnLbtPnLbt\frac{PnL_{live} - PnL_{bt}}{PnL_{bt}} > 20%
Кідіріс p99 Сигналдан толтыруға дейінгі 99-персентиль > 500 мс

Слиппедж моделін калибрлеу

Слиппедж моделін калибрлеу — күтілетін мен нақты толтыруларды көрсететін баға әсер қисығы бар ордербук тереңдігі

2-4 апта бойы деректер жинақтағаннан кейін, сіз бэктест слиппедж моделін нақты деректерге калибрлей аласыз:

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

Басқа құралдармен байланыс

Бэктест-лайв паритеті оқшауланған міндет емес. Ол "Backtests Without Illusions" сериясының басқа құралдарымен қиылысады:

  • Адаптивті drill-down — толтыру симуляциясының дәлдігін жақсартады, бұл орындау паритетінің негізгі құрамдас бөлігі.
  • Фандинг мөлшерлемелері — егер бэктест фандингті модельдемесе, 3x-тен жоғары левередж кезінде паритет мүмкін емес.
  • Parquet кэш — алдын ала есептелген уақыт кезеңдері мен индикаторлар бэктестің ботпен бірдей деректерді көруін қамтамасыз етеді. RunningCandleBuffer эмуляциясы = нақты уақыттағы жаңарту.
  • Polars vs Pandas — pandas-тан (бэктест) Polars-қа (лайв) ауысқанда, сандық нәтижелердің сәйкес келуін қамтамасыз ету қажет.
  • Walk-Forward — үлгіден тыс деректердегі walk-forward стратегияның қалай нашарлайтынын көрсетеді — бұл in-sample бэктестке қарағанда лайвқа жақынырақ.

Ұсыныстар

  1. Shared core міндетті. Сигнал генерациясы үшін жалғыз кодтық база — паритеттің минималды талабы. Бірдей логикасы бар екі файл бір ай ішінде алшақтықты кепілдейді.

  2. Толтыру моделін калибрлеңіз. Тұрақты 5 bps слиппедж ештеңеден жақсы. Нақты деректерге калибрленген слиппедж моделі айтарлықтай жақсырақ.

  3. Алғашқы 2-4 апта бойы shadow режимін пайдаланыңыз. Сигнал сәйкестік деңгейі 95%+ жеткенше нақты ақшамен сауда жасамаңыз.

  4. Фандинг мөлшерлемелерін модельдеңіз. Мәңгілік фьючерстер үшін бұл опция емес — бұл міндетті. Фандинг 5x-тен жоғары левередж кезінде барлық PnL-ды жеп қоюы мүмкін.

  5. Барлығын логтаңыз. Әрбір сигнал, әрбір бұйрық, әрбір толтыру — уақыт белгілерімен. Логтарсыз пост-мортем талдау мүмкін емес.

  6. Салыстыруды автоматтандырыңыз. Апталық DivergenceMonitor есебі автоматты түрде келуі керек. PnL теріс болғанша күтпеңіз.

  7. Әдепкі бойынша пессимистік бэктест. Бэктесте күтуді төмен бағалап, лайвта жағымды таңғалдыру керісінше болғаннан жақсырақ. Слиппедж моделі консервативті болуы керек.

Қорытынды

Сауда жүйелерінің жетілу деңгейлері — негізгі бэктестингтен толық өндіріске дейін

Бэктест-лайв паритеті жүйенің қасиеті емес, ол — процесс. Мінсіз паритет жоқ: бэктест анықтамасы бойынша реалдылықтың моделі, ал модель әрқашан жеңілдетеді. Бірақ "модель 5%-ға өзгеше" мен "модель 50%-ға өзгеше" арасындағы айырмашылықты архитектура анықтайды.

Жетілудің үш деңгейі:

  1. Негізгі. Shared core, тұрақты слиппедж, комиссиялар. Алшақтық: 10-20%.
  2. Жетілдірілген. Оқиғаға негізделген архитектура, адаптивті drill-down, фандинг моделі, shadow режимі. Алшақтық: 5-10%.
  3. Институционалды. L2 ордербук симуляциясы, калибрленген әсер моделі, нақты уақыттағы алшақтық мониторингі. Алшақтық: 2-5%.

Сіздің міндетіңіз — қай деңгейде екеніңізді анықтау және позиция көлеміңіз бен левереджіңіз үшін қандай алшақтықты қолайлы деп санайтыныңызды түсіну.


Пайдалы сілтемелер

  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

Дәйексөз

@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 = {Complete taxonomy of divergences between backtesting and live trading: from slippage and partial fills to codebase desynchronization. Architectural patterns for achieving parity and a production monitoring checklist.}
}
blog.disclaimer

Authors

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

Нарықтан бір қадам алда болыңыз

AI сауда талдаулары, нарық аналитикасы және платформа жаңалықтары үшін біздің ақпараттық бюллетеньге жазылыңыз.

Біз сіздің жекелігіңізді құрметтейміз. Кез келген уақытта жазылымнан шығуға болады.