बैकटेस्ट-लाइव समानता: आपका बॉट बैकटेस्ट से अलग तरीके से ट्रेड क्यों करता है
आपने एक स्ट्रैटेजी को बैकटेस्ट में चलाया। Sharpe 2.1, MaxDD -8%, PnL +67%। आपने बॉट लॉन्च किया। एक महीने बाद आप तुलना करते हैं: वही सिग्नल, वही अवधि — लेकिन लाइव PnL 40% कम है। ड्रॉडाउन डेढ़ गुना गहरा है। दस में से दो ट्रेड बिल्कुल भी निष्पादित नहीं हुए।
यह कोई बग नहीं है। यह बैकटेस्ट-लाइव डाइवर्जेंस है — बैकटेस्ट परिणामों और वास्तविक ट्रेडिंग के बीच एक व्यवस्थित अंतर। यह हर किसी के साथ होता है। एकमात्र सवाल यह है कि क्या आप इसके बारे में जानते हैं और क्या आप इसे नियंत्रित कर सकते हैं।
यह लेख डाइवर्जेंस का एक पूर्ण वर्गीकरण, उन्हें न्यूनतम करने के लिए आर्किटेक्चरल पैटर्न, और प्रोडक्शन में समानता की निगरानी के लिए एक व्यावहारिक चेकलिस्ट प्रस्तुत करता है।
"बैकटेस्ट में काम किया" सिंड्रोम

हर algotrader इस चक्र से गुजरता है:
- Jupyter नोटबुक में एक स्ट्रैटेजी लिखी
- ऐतिहासिक CSV पर बैकटेस्ट चलाया — परिणाम शानदार हैं
- लॉजिक को बॉट के रूप में फिर से लिखा (अक्सर किसी अलग भाषा या फ्रेमवर्क में)
- लॉन्च किया — परिणाम मेल नहीं खाते
- बग ढूंढना शुरू किया, कोई नहीं मिला — "मार्केट बदल गया"
समस्या मार्केट नहीं है। समस्या यह है कि बैकटेस्ट और बॉट दो अलग-अलग सॉफ्टवेयर प्रोडक्ट हैं जो एक ही वास्तविकता को अलग-अलग तरीके से मॉडल करते हैं। डाइवर्जेंस अपरिहार्य हैं, लेकिन उन्हें व्यवस्थित और न्यूनतम किया जा सकता है।
डाइवर्जेंस का वर्गीकरण

डाइवर्जेंस के सभी स्रोत चार श्रेणियों में आते हैं। प्रत्येक के लिए — गंभीरता रेटिंग (1 से 5 तक) और PnL डाइवर्जेंस में एक विशिष्ट योगदान।
1. डेटा डाइवर्जेंस (गंभीरता: 3/5)
बैकटेस्ट जो डेटा देखता है और बॉट रियल टाइम में जो डेटा देखता है, वे एक जैसे नहीं हैं।
टाइमस्टैम्प। एक्सचेंज टाइमस्टैम्प असाइनमेंट के लिए अलग-अलग नियमों के साथ कैंडल डिलीवर करते हैं। एक एक्सचेंज कैंडल को पीरियड की शुरुआत के साथ मार्क करता है, दूसरा अंत के साथ। एक REST API वास्तविक क्लोज के 1-3 सेकंड बाद कैंडल लौटा सकता है। बैकटेस्ट ऐतिहासिक फाइल से "आदर्श" टाइमस्टैम्प के साथ काम करता है।
OHLCV एग्रीगेशन। ऐतिहासिक डेटा को अक्सर प्रोवाइडर द्वारा उस तरीके से एग्रीगेट किया जाता है जो एक्सचेंज रियल टाइम में करने के तरीके से अलग होता है। अंतर आखिरी अंक में होता है — लेकिन थ्रेशोल्ड सिग्नलों (MA क्रॉसओवर, लेवल ब्रेकआउट) के साथ यह तय करता है कि स्ट्रैटेजी पोजीशन में एंटर करती है या नहीं।
गैप और मिसिंग डेटा। ऐतिहासिक डेटा आमतौर पर साफ होता है — मिसिंग कैंडल को इंटरपोलेशन द्वारा भरा जाता है। रियल टाइम में, एक WebSocket ड्रॉप हो सकता है, और बॉट 30 सेकंड का डेटा मिस कर देता है।
PnL डाइवर्जेंस में विशिष्ट योगदान: वार्षिक PnL का 2-5%।
2. एक्ज़ीक्यूशन डाइवर्जेंस (गंभीरता: 5/5)

डाइवर्जेंस की सबसे खतरनाक श्रेणी। बैकटेस्ट एक्ज़ीक्यूशन को पूरी तरह से सिम्युलेट करता है — वास्तविकता आदर्श से बहुत दूर है।
स्लिपेज। बैकटेस्ट ऑर्डर को क्लोज़ प्राइस (या सिग्नल प्राइस) पर फिल करता है। वास्तविकता में, एक मार्केट ऑर्डर सबसे अच्छे बिड/आस्क पर प्लस स्लिपेज पर निष्पादित होता है, जो वॉल्यूम और लिक्विडिटी पर निर्भर करता है। मध्यम लिक्विडिटी वाले altcoin पर $10K पोजीशन के लिए, स्लिपेज 0.05-0.3% हो सकता है।
ट्रेडों पर संचयी स्लिपेज के लिए फॉर्मूला:
जहां -वें ट्रेड का स्लिपेज है, जो ऑर्डरबुक की गहराई पर निर्भर करता है:
लेटेंसी। सिग्नल जनरेट होने के क्षण से लेकर ऑर्डर एक्ज़ीक्यूशन तक समय बीतता है: सिग्नल कैलकुलेशन (1-50 ms), रिक्वेस्ट ट्रांसमिशन (10-200 ms), एक्सचेंज पर मैचिंग (1-10 ms)। बैकटेस्ट में, लेटेंसी = 0। लाइव में — प्राइस मूव कर सकती है।
आंशिक फिल। बैकटेस्ट मानता है कि ऑर्डर का 100% तुरंत फिल हो जाता है। वास्तविकता में, एक लिमिट ऑर्डर आंशिक रूप से फिल हो सकता है — या अगर प्राइस रिवर्स होता है तो बिल्कुल फिल नहीं होता। एक अलिक्विड मार्केट में मार्केट ऑर्डर के लिए, ऑर्डर कई ऑर्डरबुक लेवल के माध्यम से "स्लिप" हो जाता है।
क्यू प्रायोरिटी। सबसे अच्छे बिड प्राइस पर रखा गया एक लिमिट ऑर्डर तुरंत फिल नहीं होगा — यह उस लेवल पर पहले से रखे गए सभी ऑर्डर के पीछे कतार में लगता है। एक बैकटेस्ट जो "प्राइस टच = ऑर्डर फिल" मानता है, वह व्यवस्थित रूप से फिल रेट को अधिक आंकता है।
PnL डाइवर्जेंस में विशिष्ट योगदान: वार्षिक PnL का 10-30%।
3. लॉजिक डाइवर्जेंस (गंभीरता: 4/5)
ये बैकटेस्ट और बॉट के बीच स्ट्रैटेजी कोड में ही डाइवर्जेंस हैं।
अलग कोडबेस। क्लासिक एंटी-पैटर्न: backtests/strategy_a.py और bot/strategy_a.py — दो अलग-अलग फाइलें जो "एक ही काम करती हैं"। तीन महीने के एडिट के बाद, वे अनिवार्य रूप से डाइवर्ज हो जाती हैं। किसी ने बैकटेस्ट में एक फिल्टर जोड़ा और बॉट में इसे रेप्लिकेट करना भूल गया। या इसके विपरीत — बॉट में एक बग ठीक किया गया लेकिन बैकटेस्ट में रह गया।
अलग फ्रेमवर्क। वेक्टराइज़्ड ऑपरेशंस के साथ pandas पर बैकटेस्ट, इवेंट-ड्रिवन लॉजिक के साथ asyncio पर बॉट। समान स्ट्रैटेजी के साथ भी, edge cases को अलग तरीके से हैंडल किया जाता है: राउंडिंग, कंडीशन चेक का क्रम, NaN हैंडलिंग।
स्टेट मैनेजमेंट। बैकटेस्ट आमतौर पर स्टेटलेस होता है — यह एक डेटा एरे पर इटरेट करता है। बॉट स्टेटफुल होता है — यह पोजीशन, बैलेंस, ऑर्डर हिस्ट्री स्टोर करता है। बॉट रीस्टार्ट, स्टेट लॉस, एक्सचेंज के साथ डीसिंक्रोनाइज़ेशन — ये सभी डाइवर्जेंस के स्रोत हैं।
PnL डाइवर्जेंस में विशिष्ट योगदान: वार्षिक PnL का 5-20%।
4. कॉस्ट डाइवर्जेंस (गंभीरता: 3/5)
ट्रेडिंग कॉस्ट मॉडलिंग में डाइवर्जेंस।
फंडिंग रेट्स। ज्यादातर पर्पेचुअल फ्यूचर्स बैकटेस्ट फंडिंग रेट्स को बिल्कुल भी ध्यान में नहीं रखते। 10x लीवरेज और हर 8 घंटे में औसतन 0.01% की दर पर, यह प्रति वर्ष है — ज्यादातर स्ट्रैटेजियों के PnL से अधिक। एक विस्तृत विश्लेषण लेख में है फंडिंग रेट्स आपके लीवरेज को मार देती हैं।
कमीशन। Maker/taker कमीशन आमतौर पर मॉडल किए जाते हैं लेकिन अक्सर गलत दर के साथ। VIP टियर, BNB डिस्काउंट, रिबेट्स — ये सभी अंतिम परिणाम को प्रभावित करते हैं।
स्प्रेड। एक कैंडल-आधारित बैकटेस्ट बिड-आस्क स्प्रेड नहीं देखता। 1-मिनट की कैंडल पर, क्लोज = 3000, लेकिन वास्तविकता में bid = 2999.5 और ask = 3000.5। हर ट्रेड स्प्रेड के आधे हिस्से की "कीमत" चुकाता है।
PnL डाइवर्जेंस में विशिष्ट योगदान: वार्षिक PnL का 5-15%।
संचयी प्रभाव
सभी चार श्रेणियां एक साथ और आमतौर पर एक ही दिशा में काम करती हैं — ट्रेडर के खिलाफ:
एक अपरिष्कृत सिस्टम के लिए बैकटेस्ट PnL से 20-50% की कुल डाइवर्जेंस सामान्य है। लीवरेज के साथ, प्रभाव कई गुना बढ़ जाता है।
समानता के लिए आर्किटेक्चरल पैटर्न
पैटर्न 1: 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 │
└────────────┘ └────────────┘
फायदे:
- डिटरमिनिस्टिक रीप्ले। इवेंट्स को एक सख्ती से परिभाषित क्रम में प्रोसेस किया जाता है — बैकटेस्ट परिणाम बिट-रिप्रोड्यूसिबल है।
- कस्टम FillModel। हर एक्ज़ीक्यूशन के लिए L2 ऑर्डरबुक सिमुलेशन — स्लिपेज को वास्तविक ऑर्डरबुक गहराई के आधार पर सिम्युलेट किया जाता है।
- परफॉर्मेंस। प्रति सेकंड 50 लाख तक रो, उस डेटा को प्रोसेस करना जो 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— फिल रिफाइनमेंट के लिए एक बारीक टाइमफ्रेम पर ड्रिल-डाउन (एडेप्टिव ड्रिल-डाउन के समान)
पैटर्न तुलना
| Shared Core | इवेंट-ड्रिवन (NautilusTrader) | Strategy Interface (Freqtrade) | |
|---|---|---|---|
| इम्प्लीमेंटेशन जटिलता | कम | उच्च | मध्यम |
| समानता स्तर | मध्यम | अधिकतम | उच्च |
| फिल सिमुलेशन | अलग FillModel | L2 ऑर्डरबुक | --timeframe-detail |
| कोर भाषा | Python | Rust + Python | Python |
| उपयुक्त | कस्टम इंजन | इंस्टीट्यूशनल ट्रेडिंग | त्वरित शुरुआत |
फिल सिमुलेशन की सटीकता

फिल सिमुलेशन एक्ज़ीक्यूशन डाइवर्जेंस का मुख्य स्रोत है। सटीकता के तीन स्तर:
स्तर 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: 1s/100ms डेटा के साथ एडेप्टिव ड्रिल-डाउन
सबसे अच्छा विकल्प: SL/TP फिल ऑर्डर का सटीक निर्धारण करने के लिए वास्तविक फाइन-ग्रैन्युलैरिटी डेटा का उपयोग करना। लेख में विस्तार से वर्णित है एडेप्टिव ड्रिल-डाउन: वेरिएबल ग्रैन्युलैरिटी के साथ बैकटेस्टिंग।
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 मॉडल):
जहां वोलैटिलिटी है, इम्पैक्ट कोएफिशिएंट है, ऑर्डर वॉल्यूम है, और उस अवधि के लिए मार्केट वॉल्यूम है।
प्रैक्टिकल पैरिटी चेकलिस्ट

बॉट को लाइव लॉन्च करने से पहले, प्रत्येक आइटम को वेरीफाई करें:
कोड:
- स्ट्रैटेजी एक shared core का उपयोग करती है (बैकटेस्ट और लाइव के लिए एक मॉड्यूल)
- सिग्नल लॉजिक की दो जगहों पर डुप्लिकेशन नहीं है
- यूनिट टेस्ट समान इनपुट के लिए समान कोर आउटपुट को वेरीफाई करते हैं
- कंडीशन चेक का क्रम समान है (SL पहले TP से? TP पहले SL से?)
डेटा:
- टाइमस्टैम्प फॉर्मेट समान है (UTC, वही प्रोवाइडर)
- OHLCV एग्रीगेशन समान नियमों का उपयोग करता है
- मिसिंग कैंडल हैंडलिंग समान है
- कोई लुक-अहेड बायस नहीं — बैकटेस्ट भविष्य में नहीं झांकता
एक्ज़ीक्यूशन:
- स्लिपेज मॉडल वास्तविक डेटा पर कैलिब्रेट किया गया है
- आंशिक फिल को मॉडल किया गया है (या कम से कम निराशावादी रूप से अनुमानित)
- लिमिट ऑर्डर में एक क्यू प्रायोरिटी मॉडल है
- लेटेंसी को ध्यान में रखा गया है (सिग्नल से फिल तक 100-500 ms की देरी)
कॉस्ट:
- Maker/taker कमीशन वर्तमान दर के साथ शामिल हैं
- पर्पेचुअल फ्यूचर्स के साथ फंडिंग रेट्स को ध्यान में रखा गया है
- स्प्रेड को मॉडल किया गया है (कम से कम औसत)
इन्फ्रास्ट्रक्चर:
- स्टेट पर्सिस्टेंस: बॉट रीस्टार्ट के बाद पोजीशन रिकवर करता है
- रीकनेक्शन लॉजिक: WebSocket बिना डेटा लॉस के फिर से कनेक्ट होता है
- लॉगिंग: पोस्ट-मॉर्टम एनालिसिस के लिए सभी ऑर्डर और फिल लॉग किए जाते हैं
प्रोडक्शन में डाइवर्जेंस की निगरानी
समानता एक बार की जांच नहीं बल्कि एक निरंतर प्रक्रिया है। बॉट लॉन्च करने के बाद, डाइवर्जेंस को रियल टाइम में ट्रैक किया जाना चाहिए।
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,
}
डैशबोर्ड मेट्रिक्स
| मेट्रिक | फॉर्मूला | अलर्ट थ्रेशोल्ड |
|---|---|---|
| सिग्नल मैच रेट | < 95% | |
| औसत स्लिपेज | (bps) | > 10 bps |
| फिल रेट | < 90% | |
| PnL डाइवर्जेंस | > 20% | |
| लेटेंसी p99 | 99वां पर्सेंटाइल सिग्नल-टू-फिल | > 500 ms |
स्लिपेज मॉडल कैलिब्रेशन

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" सीरीज़ के अन्य टूल्स के साथ जुड़ती है:
- एडेप्टिव ड्रिल-डाउन — फिल सिमुलेशन की सटीकता में सुधार करता है, जो एक्ज़ीक्यूशन समानता का एक मुख्य घटक है।
- फंडिंग रेट्स — अगर बैकटेस्ट फंडिंग को मॉडल नहीं करता, तो 3x से अधिक लीवरेज पर समानता असंभव है।
- Parquet कैश — पहले से कैलकुलेट किए गए टाइमफ्रेम और इंडिकेटर यह सुनिश्चित करते हैं कि बैकटेस्ट वही डेटा देखे जो बॉट देखता है। RunningCandleBuffer एम्युलेशन = रियल-टाइम अपडेटिंग।
- Polars बनाम Pandas — pandas (बैकटेस्ट) से Polars (लाइव) पर स्विच करते समय, आपको यह सुनिश्चित करना होगा कि संख्यात्मक परिणाम मेल खाते हैं।
- Walk-Forward — आउट-ऑफ-सैंपल डेटा पर walk-forward दिखाता है कि स्ट्रैटेजी कैसे डिग्रेड होती है — यह इन-सैंपल बैकटेस्ट की तुलना में लाइव के करीब है।
सिफारिशें
-
Shared core अनिवार्य है। सिग्नल जनरेशन के लिए एक एकल कोडबेस समानता की न्यूनतम आवश्यकता है। समान लॉजिक वाली दो फाइलें एक महीने के भीतर डाइवर्जेंस की गारंटी देती हैं।
-
फिल मॉडल को कैलिब्रेट करें। एक फिक्स्ड 5 bps स्लिपेज कुछ न होने से बेहतर है। वास्तविक डेटा पर कैलिब्रेट किया गया स्लिपेज मॉडल काफी बेहतर है।
-
पहले 2-4 हफ्तों के लिए shadow मोड का उपयोग करें। असली पैसे से ट्रेड न करें जब तक कि सिग्नल मैच रेट 95%+ न पहुंच जाए।
-
फंडिंग रेट्स को मॉडल करें। पर्पेचुअल फ्यूचर्स के लिए, यह वैकल्पिक नहीं है — यह अनिवार्य है। 5x से अधिक लीवरेज पर फंडिंग पूरे PnL को खा सकती है।
-
सब कुछ लॉग करें। हर सिग्नल, हर ऑर्डर, हर फिल — टाइमस्टैम्प के साथ। लॉग के बिना, पोस्ट-मॉर्टम विश्लेषण असंभव है।
-
तुलना को ऑटोमेट करें। एक साप्ताहिक DivergenceMonitor रिपोर्ट स्वचालित रूप से आनी चाहिए। PnL के नेगेटिव होने का इंतजार न करें।
-
डिफॉल्ट रूप से निराशावादी बैकटेस्ट। बैकटेस्ट में अपेक्षाओं को कम आंकना और लाइव में सुखद आश्चर्य पाना, उल्टा होने से बेहतर है। स्लिपेज मॉडल कंज़र्वेटिव होना चाहिए।
निष्कर्ष

बैकटेस्ट-लाइव समानता किसी सिस्टम की प्रॉपर्टी नहीं बल्कि एक प्रक्रिया है। परफेक्ट समानता मौजूद नहीं होती: एक बैकटेस्ट परिभाषा के अनुसार वास्तविकता का एक मॉडल है, और एक मॉडल हमेशा सरल बनाता है। लेकिन "मॉडल 5% अलग है" और "मॉडल 50% अलग है" के बीच का अंतर आर्किटेक्चर द्वारा निर्धारित होता है।
मैच्योरिटी के तीन स्तर:
- बेसिक। Shared core, फिक्स्ड स्लिपेज, कमीशन। डाइवर्जेंस: 10-20%।
- एडवांस्ड। इवेंट-ड्रिवन आर्किटेक्चर, एडेप्टिव ड्रिल-डाउन, फंडिंग मॉडल, shadow मोड। डाइवर्जेंस: 5-10%।
- इंस्टीट्यूशनल। L2 ऑर्डरबुक सिमुलेशन, कैलिब्रेटेड इम्पैक्ट मॉडल, रियल-टाइम डाइवर्जेंस मॉनिटरिंग। डाइवर्जेंस: 2-5%।
आपका काम यह निर्धारित करना है कि आप किस स्तर पर हैं और यह समझना है कि आपके पोजीशन साइज़ और लीवरेज के लिए आप कौन सी डाइवर्जेंस स्वीकार्य मानते हैं।
उपयोगी लिंक
- NautilusTrader — High-Performance Algorithmic Trading Platform
- Freqtrade — Free, open source crypto trading bot
- Almgren, R., Chriss, N. — Optimal Execution of Portfolio Transactions (2001)
- Lopez de Prado — Advances in Financial Machine Learning, Chapter 12: Backtesting
- Ernest Chan — Quantitative Trading: How to Build Your Own Algorithmic Trading Business
- Hexagonal Architecture (Ports and Adapters) — Alistair Cockburn
- 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.}
}
Authors
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.