تطابق الاختبار الخلفي والتداول الحي: لماذا يتداول الروبوت الخاص بك بشكل مختلف عن الاختبار الخلفي
قمت بتشغيل استراتيجية عبر اختبار خلفي. Sharpe 2.1، MaxDD -8%، PnL +67%. أطلقت الروبوت. بعد شهر قارنت: نفس الإشارات، نفس الفترة — لكن PnL الحي أقل بنسبة 40%. التراجع أعمق بمرة ونصف. اثنان من كل عشر صفقات لم تُنفَّذ على الإطلاق.
هذا ليس خطأ برمجيًا. هذا هو التباين بين الاختبار الخلفي والتداول الحي — تباين منهجي بين نتائج الاختبار الخلفي والتداول الفعلي. الجميع لديه هذا. السؤال الوحيد هو هل تعرف عنه وهل يمكنك التحكم فيه.
تقدم هذه المقالة تصنيفًا شاملًا للتباينات، وأنماطًا معمارية لتقليلها، وقائمة مراجعة عملية لمراقبة التطابق في بيئة الإنتاج.
متلازمة "كان يعمل في الاختبار الخلفي"

كل متداول خوارزمي يمر بهذه الدورة:
- كتب استراتيجية في Jupyter notebook
- شغّل اختبارًا خلفيًا على CSV تاريخي — النتائج ممتازة
- أعاد كتابة المنطق كروبوت (غالبًا بلغة أو إطار عمل مختلف)
- أطلقه — النتائج لا تتطابق
- بدأ البحث عن خطأ، لم يجده — "السوق تغيّر"
المشكلة ليست السوق. المشكلة هي أن الاختبار الخلفي والروبوت هما منتجان برمجيان مختلفان يحاكيان نفس الواقع بطرق مختلفة. التباينات حتمية، لكن يمكن تنظيمها وتقليلها.
تصنيف التباينات

تندرج جميع مصادر التباين ضمن أربع فئات. لكل منها — تصنيف الخطورة (من 1 إلى 5) والمساهمة النموذجية في تباين PnL.
1. تباينات البيانات (الخطورة: 3/5)
البيانات التي يراها الاختبار الخلفي والبيانات التي يراها الروبوت في الوقت الفعلي ليست نفس الشيء.
الطوابع الزمنية. تسلّم البورصات الشموع بقواعد مختلفة لتعيين الطوابع الزمنية. بورصة واحدة تحدد الشمعة ببداية الفترة، وأخرى بنهايتها. قد تُرجع واجهة REST API شمعة بتأخير 1-3 ثوانٍ بعد الإغلاق الفعلي. يعمل الاختبار الخلفي بطوابع زمنية "مثالية" من الملف التاريخي.
تجميع OHLCV. غالبًا ما يتم تجميع البيانات التاريخية من قبل المزود بشكل مختلف عما تفعله البورصة في الوقت الفعلي. الفرق في الرقم الأخير — لكن مع إشارات العتبة (تقاطع MA، اختراق المستوى) هذا يحدد ما إذا كانت الاستراتيجية تدخل مركزًا أم لا.
الفجوات والبيانات المفقودة. عادةً ما تكون البيانات التاريخية نظيفة — يتم ملء الشموع المفقودة بالاستيفاء. في الوقت الفعلي، قد ينقطع WebSocket، ويفقد الروبوت 30 ثانية من البيانات.
المساهمة النموذجية في تباين PnL: 2-5% من PnL السنوي.
2. تباينات التنفيذ (الخطورة: 5/5)

أخطر فئة من التباينات. يحاكي الاختبار الخلفي التنفيذ بشكل مثالي — الواقع بعيد عن المثالية.
الانزلاق السعري. يملأ الاختبار الخلفي الأمر بسعر الإغلاق (أو سعر الإشارة). في الواقع، يتم تنفيذ أمر السوق بأفضل سعر bid/ask بالإضافة إلى انزلاق يعتمد على الحجم والسيولة. لمركز بقيمة 10 آلاف دولار على عملة بديلة متوسطة السيولة، يمكن أن يكون الانزلاق 0.05-0.3%.
صيغة الانزلاق التراكمي عبر صفقة:
حيث هو انزلاق الصفقة ، ويعتمد على عمق دفتر الأوامر:
التأخير. من لحظة توليد الإشارة إلى تنفيذ الأمر، يمر وقت: حساب الإشارة (1-50 مللي ثانية)، إرسال الطلب (10-200 مللي ثانية)، المطابقة في البورصة (1-10 مللي ثانية). في الاختبار الخلفي، التأخير = 0. في الحي — يمكن أن يتحرك السعر.
التنفيذ الجزئي. يفترض الاختبار الخلفي أن 100% من الأمر يتم تنفيذه فورًا. في الواقع، قد يتم تنفيذ أمر محدد جزئيًا — أو لا يتم تنفيذه على الإطلاق إذا انعكس السعر. بالنسبة لأمر سوق في سوق غير سائل، "ينزلق" الأمر عبر مستويات متعددة في دفتر الأوامر.
أولوية الطابور. لن يتم تنفيذ أمر محدد موضوع بأفضل سعر bid فورًا — فهو يصطف خلف جميع الأوامر الموضوعة سابقًا عند هذا المستوى. الاختبار الخلفي الذي يعتبر "السعر لامس = الأمر مُلئ" يبالغ بشكل منهجي في معدل التنفيذ.
المساهمة النموذجية في تباين PnL: 10-30% من PnL السنوي.
3. تباينات المنطق (الخطورة: 4/5)
هذه تباينات في كود الاستراتيجية نفسه بين الاختبار الخلفي والروبوت.
قواعد كود منفصلة. النمط المضاد الكلاسيكي: backtests/strategy_a.py و bot/strategy_a.py — ملفان منفصلان "يفعلان نفس الشيء". بعد ثلاثة أشهر من التعديلات، يتباعدان حتمًا. أحدهم أضاف فلترًا في الاختبار الخلفي ونسي تكراره في الروبوت. أو العكس — تم إصلاح خطأ في الروبوت لكنه بقي في الاختبار الخلفي.
أطر عمل مختلفة. اختبار خلفي على pandas بعمليات متجهة، روبوت على asyncio بمنطق مدفوع بالأحداث. حتى مع استراتيجية متطابقة، يتم التعامل مع الحالات الحدية بشكل مختلف: التقريب، ترتيب فحص الشروط، معالجة NaN.
إدارة الحالة. عادةً ما يكون الاختبار الخلفي عديم الحالة — يكرر عبر مصفوفة بيانات. الروبوت ذو حالة — يخزن المراكز، الأرصدة، تاريخ الأوامر. إعادة تشغيل الروبوت، فقدان الحالة، عدم التزامن مع البورصة — كل هذه مصادر للتباين.
المساهمة النموذجية في تباين PnL: 5-20% من PnL السنوي.
4. تباينات التكلفة (الخطورة: 3/5)
تباينات في نمذجة تكاليف التداول.
معدلات التمويل. معظم اختبارات العقود الدائمة الخلفية لا تأخذ معدلات التمويل في الاعتبار على الإطلاق. عند رافعة 10x ومعدل متوسط 0.01% لكل 8 ساعات، هذا يمثل سنويًا — أكثر من PnL معظم الاستراتيجيات. التحليل المفصل في مقال معدلات التمويل تقتل الرافعة المالية.
العمولات. عادةً ما يتم نمذجة عمولات Maker/taker لكن غالبًا بالمعدل الخاطئ. مستويات VIP، خصومات BNB، الحسومات — كل هذا يؤثر على النتيجة النهائية.
الفارق السعري. الاختبار الخلفي المبني على الشموع لا يرى فارق bid-ask. على شمعة الدقيقة الواحدة، الإغلاق = 3000، لكن في الواقع bid = 2999.5 و ask = 3000.5. كل صفقة "تكلف" نصف الفارق.
المساهمة النموذجية في تباين PnL: 5-15% من PnL السنوي.
التأثير التراكمي
جميع الفئات الأربع تعمل بشكل متزامن وعادةً في اتجاه واحد — ضد المتداول:
تباين إجمالي بنسبة 20-50% من PnL الاختبار الخلفي طبيعي لنظام غير مُحسَّن. مع الرافعة المالية، يتضاعف التأثير.
أنماط معمارية للتطابق
النمط 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:
"""
نواة الاستراتيجية. كود متطابق للاختبار الخلفي والتداول الحي.
يعتمد على البيانات فقط، وليس على البنية التحتية.
"""
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]:
"""
معالجة شمعة جديدة. يُرجع OrderRequest أو None.
يتم استدعاء هذه الطريقة بشكل متطابق من الاختبار الخلفي والروبوت.
"""
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 أصلي بنواة حتمية مدفوعة بالأحداث ودقة نانو ثانية. نفس تنفيذ الاستراتيجية يعمل في كل من الاختبار الخلفي والتداول الحي.
البنية مبنية على نمط المنافذ والمحولات (البنية السداسية):
┌──────────────────────────────────┐
│ NautilusKernel │
│ ┌───────────┐ ┌─────────────┐ │
│ │ Strategy │ │ RiskEngine │ │
│ │ (Python) │ │ (Rust) │ │
│ └─────┬─────┘ └──────┬──────┘ │
│ │ │ │
│ ┌─────┴───────────────┴──────┐ │
│ │ Message Bus (Rust) │ │
│ └─────┬───────────────┬──────┘ │
└────────┼───────────────┼─────────┘
│ │
┌─────┴─────┐ ┌─────┴──────┐
│ Backtest │ │ Live │
│ Adapter │ │ Adapter │
│ FillModel │ │ Exchange │
│ (L2 book) │ │ Gateway │
└────────────┘ └────────────┘
المزايا:
- إعادة تشغيل حتمية. تُعالج الأحداث بترتيب محدد بدقة — نتيجة الاختبار الخلفي قابلة للتكرار على مستوى البت.
- FillModel مخصص. محاكاة دفتر أوامر L2 لكل تنفيذ — يتم محاكاة الانزلاق بناءً على عمق دفتر الأوامر الحقيقي.
- الأداء. حتى 5 ملايين صف/ثانية، معالجة بيانات لا تتسع في RAM.
- Redis + PostgreSQL. ذاكرة مؤقتة وناقل رسائل عبر Redis، استمرارية عبر PostgreSQL — بنية تحتية متطابقة للاختبار الخلفي والتداول الحي.
النمط 3: Strategy Interface (نهج Freqtrade)
يستخدم Freqtrade واجهة IStrategy موحدة: نفس فئة الاستراتيجية تعمل في كل من الاختبار الخلفي والتداول الحي. الاختلاف الوحيد هو طبقة الاستمرارية.
class IStrategy:
"""واجهة موحدة — التنفيذ لا يعرف إن كان هذا اختبارًا خلفيًا أم تداولًا حيًا."""
def populate_indicators(self, dataframe, metadata):
"""حساب المؤشرات."""
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):
"""تحديد إشارات الدخول."""
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):
"""تحديد إشارات الخروج."""
dataframe.loc[
(dataframe['fast_ma'] < dataframe['slow_ma']),
'exit_long'
] = 1
return dataframe
يوفر Freqtrade بالإضافة إلى ذلك:
- Hyperopt عبر Optuna — تحسين معلمات الاستراتيجية
--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):
"""التنفيذ مع الانزلاق."""
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:
"""
نموذج تنفيذ مركب: انزلاق + فارق سعري + تأثير الحجم.
"""
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 المبسط):
حيث هي التقلبية، هو معامل التأثير، هو حجم الأمر، و هو حجم السوق للفترة.
قائمة مراجعة عملية للتطابق
قبل إطلاق الروبوت في التداول الحي، تحقق من كل عنصر:
الكود:
- الاستراتيجية تستخدم نواة مشتركة (وحدة واحدة للاختبار الخلفي والتداول الحي)
- لا يوجد تكرار لمنطق الإشارة في مكانين
- اختبارات الوحدة تتحقق من مخرجات نواة متطابقة لمدخلات متطابقة
- ترتيب فحص الشروط متطابق (SL قبل TP؟ TP قبل SL؟)
البيانات:
- تنسيق الطوابع الزمنية متطابق (UTC، نفس المزود)
- تجميع OHLCV يستخدم نفس القواعد
- معالجة الشموع المفقودة متطابقة
- لا يوجد look-ahead bias — الاختبار الخلفي لا يطّلع على المستقبل
التنفيذ:
- نموذج الانزلاق مُعايَر على بيانات حقيقية
- التنفيذ الجزئي مُنمذج (أو على الأقل مُقدَّر بشكل متشائم)
- أوامر الحد لديها نموذج أولوية الطابور
- التأخير مأخوذ في الاعتبار (تأخير 100-500 مللي ثانية من الإشارة إلى التنفيذ)
التكاليف:
- عمولات Maker/taker مُدرجة بالمعدل الحالي
- معدلات التمويل مأخوذة في الاعتبار مع العقود الدائمة
- الفارق السعري مُنمذج (على الأقل المتوسط)
البنية التحتية:
- استمرارية الحالة: الروبوت يستعيد المراكز بعد إعادة التشغيل
- منطق إعادة الاتصال: WebSocket يعيد الاتصال بدون فقدان بيانات
- التسجيل: جميع الأوامر والتنفيذات مسجلة للتحليل اللاحق
مراقبة التباين في بيئة الإنتاج
التطابق ليس فحصًا لمرة واحدة بل عملية مستمرة. بعد إطلاق الروبوت، يجب تتبع التباينات في الوقت الفعلي.
الوضع الظلي (التداول الورقي)
شغّل الروبوت بالتوازي مع الاختبار الخلفي على نفس البيانات. يولّد الروبوت إشارات لكنه لا يرسل أوامر — يسجّل فقط. في نفس الوقت، يعالج الاختبار الخلفي نفس البيانات. قارن:
class DivergenceMonitor:
"""
يقارن إشارات الاختبار الخلفي والروبوت الحي في الوقت الفعلي.
"""
def __init__(self, tolerance_pct=0.5):
self.tolerance = tolerance_pct / 100
self.divergences = []
def compare_signal(self, backtest_signal, live_signal, timestamp):
"""مقارنة إشارات الاختبار الخلفي والتداول الحي."""
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):
"""مقارنة التنفيذ."""
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):
"""تقرير التباين الأسبوعي."""
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:
"""
معايرة نموذج الانزلاق باستخدام تنفيذات حقيقية.
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,
}
الروابط مع الأدوات الأخرى
تطابق الاختبار الخلفي والتداول الحي ليس مهمة معزولة. يتقاطع مع أدوات أخرى من سلسلة "اختبارات خلفية بدون أوهام":
- التعمق التكيفي — يحسن دقة محاكاة التنفيذ، وهو مكون أساسي لتطابق التنفيذ.
- معدلات التمويل — إذا لم يُنمذج الاختبار الخلفي التمويل، فالتطابق مستحيل عند رافعة > 3x.
- ذاكرة Parquet المؤقتة — الأطر الزمنية والمؤشرات المحسوبة مسبقًا تضمن أن الاختبار الخلفي يرى نفس البيانات التي يراها الروبوت. محاكاة RunningCandleBuffer = تحديث في الوقت الفعلي.
- Polars مقابل Pandas — عند التبديل من pandas (اختبار خلفي) إلى Polars (تداول حي)، تحتاج للتأكد من تطابق النتائج العددية.
- التحسين المتقدم — التحسين المتقدم على بيانات خارج العينة يُظهر كيف تتدهور الاستراتيجية — وهذا أقرب للتداول الحي من اختبار خلفي داخل العينة.
التوصيات
-
النواة المشتركة إلزامية. قاعدة كود واحدة لتوليد الإشارات هي الحد الأدنى للتطابق. ملفان بمنطق متطابق يضمنان التباعد خلال شهر.
-
عايِر نموذج التنفيذ. انزلاق ثابت بمقدار 5 bps أفضل من لا شيء. نموذج انزلاق مُعايَر على بيانات حقيقية أفضل بكثير.
-
استخدم الوضع الظلي أول 2-4 أسابيع. لا تتداول بأموال حقيقية حتى يصل معدل تطابق الإشارات إلى 95%+.
-
نمذج معدلات التمويل. للعقود الدائمة، هذا ليس اختياريًا — إنه إلزامي. يمكن للتمويل أن يستهلك كل PnL عند رافعة > 5x.
-
سجّل كل شيء. كل إشارة، كل أمر، كل تنفيذ — مع طوابع زمنية. بدون سجلات، التحليل اللاحق مستحيل.
-
أتمت المقارنة. تقرير DivergenceMonitor الأسبوعي يجب أن يصل تلقائيًا. لا تنتظر حتى يصبح PnL سالبًا.
-
اختبار خلفي متشائم بشكل افتراضي. من الأفضل التقليل من التوقعات في الاختبار الخلفي والتفاجؤ بشكل إيجابي في التداول الحي من العكس. يجب أن يكون نموذج الانزلاق متحفظًا.
الخاتمة
تطابق الاختبار الخلفي والتداول الحي ليس خاصية نظام بل عملية. التطابق المثالي غير موجود: الاختبار الخلفي بحكم التعريف نموذج للواقع، والنموذج دائمًا يُبسّط. لكن الفرق بين "النموذج يختلف بنسبة 5%" و"النموذج يختلف بنسبة 50%" يحدده المعمار.
ثلاثة مستويات من النضج:
- أساسي. نواة مشتركة، انزلاق ثابت، عمولات. التباين: 10-20%.
- متقدم. بنية مدفوعة بالأحداث، تعمق تكيفي، نموذج تمويل، وضع ظلي. التباين: 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
Citation
@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.}
}
MarketMaker.cc Team
البحوث والاستراتيجيات الكمية