← Макалаларга кайтуу
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    │
   └────────────┘   └────────────┘

Артыкчылыктары:

  • Детерминисттик кайра ойнотуу. Окуялар катуу аныкталган тартипте иштелет — бэктест жыйынтыгы бит-биттеп кайра жаралат.
  • 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 соода аналитикасы, рынок талдоолору жана платформа жаңылыктары үчүн биздин жаңылыктар бюллетенине жазылыңыз.

Биз сиздин купуялыгыңызды урматтайбыз. Каалаган убакта жазылымдан чыга аласыз.