استراتيجيات التتابع: التنفيذ ذو الأولوية مع التعبئة الاحتياطية
الحلقة الأخيرة من سلسلة "اختبارات رجعية بلا أوهام". كيفية بناء منسق من N استراتيجية على M زوج تداول، وتنفيذ وضع التتابع مع الأولوية والتنفيذ الاحتياطي، واختيار dual_size، ولماذا لا يمكن اختبار محافظ الاستراتيجيات رجعياً بمجرد جمع PnL.
لماذا تحتاج محفظة استراتيجيات
تتنافس استراتيجيات متعددة على رأس مال محدود — معظمها في وضع الخمول بينما يتداول عدد قليل فقط في أي وقت
لقد اجتازت استراتيجيتك كامل خط الإنتاج. أظهر مونت كارلو بوتستراب نسبة مئوية خامسة مقبولة. أكد الاختبار المتقدم عوائد خارج العينة. تم احتساب معدلات التمويل، واجتاز تحليل الهضبة. الاستراتيجية تعمل فعلاً.
لكنها تتداول 15% من الوقت فقط. أما الـ 85% المتبقية فرأس مالك خامل.
هل تشغّل استراتيجية ثانية؟ ثالثة؟ عاشرة؟ الفكرة واضحة. التنفيذ ليس كذلك. تُنشئ محفظة الاستراتيجيات مشاكل لا وجود لها مع بوت واحد:
- التعارضات: استراتيجيتان تريدان فتح مراكز متعاكسة على نفس الزوج.
- القيود: البورصة/إدارة المخاطر لا تسمح بأكثر من مركز متزامن.
- التخصيص: ما نسبة رأس المال التي تُعطى لكل استراتيجية؟
- الارتباط: 10 استراتيجيات على أزواج عملات مشفرة مترابطة ليست تنويعاً بـ 10 أضعاف.
استراتيجية التتابع هي نمط معماري يحل هذه المشاكل: تحصل الاستراتيجية الأساسية على حجم المركز الكامل، بينما تملأ الاستراتيجية الاحتياطية وقت الخمول بمركز مخفّض.
مفهوم التتابع: أساسي + احتياطي

استراتيجية عالية الثقة (أساسية)
الأساسية هي استراتيجية بمعايير دخول صارمة. مثلاً، إطار زمني ثلاثي بثلاثة مستويات تأكيد: إشارة على اليومي + 4 ساعات + الساعة، مع تصفية التقلب والحجم.
الخصائص:
- صفقات قليلة (عشرات خلال فترة الاختبار الرجعي)
- PnL مرتفع لكل صفقة
- وقت منخفض في المركز (5-15%)
- ثقة عالية في كل دخول
الاستراتيجية الاحتياطية
الاحتياطية هي استراتيجية بمعايير مخففة. إطار زمني مزدوج، مرشحات أقل، تفاوتات أوسع. تتداول بشكل أكثر تكراراً لكن بميزة أقل لكل صفقة.
الخصائص:
- صفقات أكثر (مئات خلال الفترة)
- PnL معتدل لكل صفقة
- وقت مرتفع في المركز (30-50%)
- ثقة معتدلة — يُعوَّض عنها بتخفيض حجم المركز
وضع التتابع
timeline: ──────────────────────────────────────────────────
primary: ___████___________________████████____███________
fallback: ███____███████████████████________████___████████
capital: [dual][ full ][ dual_size ][ full ][ dual ]
عندما تفتح الأساسية مركزاً — تتوقف الاحتياطية (أو تُغلق). عندما تكون الأساسية خاملة — تتداول الاحتياطية بمركز مخفّض (dual_size). الأولوية غير مشروطة: الأساسية تُزيح الاحتياطية دائماً.
الاستراتيجيات المستخدمة في الأمثلة
استخدمنا ثلاث استراتيجيات عبر السلسلة. إليك معاملاتها لفترة 750 يوماً:
| المعامل | الاستراتيجية A | الاستراتيجية B | الاستراتيجية C |
|---|---|---|---|
| PnL | +55% | +27% | +300% |
| الصفقات | ~500 | ~40 | ~400 |
| وقت التداول | ~15% | ~5% | ~45% |
| MaxDD | ~0.9% | ~0.75% | ~17% |
| PnL/يوم نشط | 0.49%/يوم | 0.72%/يوم | 0.89%/يوم |
| الطابع | نشاط متوسط | نادر، ثقة عالية | متكرر، عدواني |
كما أوضحنا في PnL لكل وقت نشط، يُنتج الترتيب حسب PnL الخام وحسب PnL/يوم نشط نتائج مختلفة. في تنسيق التتابع، المقياس الثاني هو المهم.
dual_size الأمثل
البحث الشبكي عبر dual_size يكشف عن ذروة نسبة شارب — كبير جداً يزيد التراجع، صغير جداً يهدر وقت الخمول
مشكلة الاختيار
dual_size هو نسبة المركز الكامل الذي تتلقاه الاستراتيجية الاحتياطية. وهو المعامل الرئيسي للتتابع:
-
كبير جداً (مثلاً 0.5 = 50%): عندما تكون الأساسية والاحتياطية نشطتين معاً، إجمالي التعرض = 150% من الهدف. يتضاعف التراجع. عدم تماثل الخسارة والربح يجعل هذا مكلفاً بشكل غير متناسب.
-
صغير جداً (مثلاً 0.01 = 1%): الاحتياطية تملأ 85% من وقت الخمول لكنها تربح فتاتاً. رأس المال خامل فعلياً.
-
الأمثل: الاحتياطية تُساهم بـ PnL ذي معنى دون زيادة التراجع بشكل حرج أثناء التشغيل المتزامن مع الأساسية.
الصياغة الرياضية
لنعرّف:
- — PnL الأساسية لكل وحدة زمنية
- — PnL الاحتياطية لكل وحدة زمنية
- — نسبة الوقت في المركز (أساسية)
- — نسبة الوقت في المركز (احتياطية)
- — dual_size (0..1)
- — نسبة الوقت عندما يكون كلاهما في مركز
إجمالي PnL التتابع:
إجمالي MaxDD (أسوأ حالة — ارتباط كامل):
إذا قيّدنا إجمالي التراجع بـ :
البحث الشبكي
عملياً، يُوجد dual_size الأمثل عبر البحث الشبكي على الاختبار الرجعي للتتابع:
import numpy as np
from dataclasses import dataclass
@dataclass
class CascadeResult:
dual_size: float
total_pnl: float
max_dd: float
sharpe: float
pnl_per_active_day: float
def grid_search_dual_size(
primary_equity: np.ndarray, # equity curve primary (minute bars)
fallback_equity: np.ndarray, # equity curve fallback (minute bars)
primary_positions: np.ndarray, # 1 = in position, 0 = flat
fallback_positions: np.ndarray,
grid: np.ndarray = np.arange(0.01, 0.30, 0.005),
) -> list[CascadeResult]:
"""
Grid search for dual_size.
primary_equity and fallback_equity are log-returns, minute bars.
"""
results = []
for d in grid:
fallback_active = fallback_positions & ~primary_positions
cascade_returns = (
primary_equity * primary_positions
+ d * fallback_equity * fallback_active
)
equity_curve = np.cumprod(1 + cascade_returns)
peak = np.maximum.accumulate(equity_curve)
drawdown = (equity_curve - peak) / peak
max_dd = drawdown.min()
total_pnl = equity_curve[-1] - 1
sharpe = (
np.mean(cascade_returns) / np.std(cascade_returns)
* np.sqrt(525_600) # minutes per year
) if np.std(cascade_returns) > 0 else 0
active_minutes = np.sum(primary_positions | fallback_active)
active_days = active_minutes / (24 * 60)
pnl_per_day = total_pnl / active_days if active_days > 0 else 0
results.append(CascadeResult(
dual_size=d,
total_pnl=total_pnl,
max_dd=max_dd,
sharpe=sharpe,
pnl_per_active_day=pnl_per_day,
))
return sorted(results, key=lambda r: r.sharpe, reverse=True)
القيمة المثلى النموذجية لاستراتيجيات العملات المشفرة: dual_size في نطاق 0.05-0.10 (5-10% من المركز الكامل). مع الاستراتيجية B كأساسية (MaxDD 0.75%) والاستراتيجية A كاحتياطية (MaxDD 0.9%):
قيد التراجع ليس مُلزِماً — تُحدَّد القيمة المثلى بنسبة شارب للتتابع. عملياً، يُعطي البحث الشبكي عادةً (6.8%).
التخصيص القائم على النقاط
ترتيب الاستراتيجيات حسب النقاط المركبة — تعديل الثقة يُعاقب العينات الصغيرة، وتكاليف التمويل تُقلل الميزة الصافية
عندما يكون هناك أكثر من استراتيجيتين، يتعمم التتابع إلى تخصيص قائم على النقاط.
الترتيب حسب PnL لكل وقت نشط
كما وُصف بالتفصيل في PnL لكل وقت نشط، تُحسب نقاط الاستراتيجية مع مراعاة:
- PnL لكل يوم نشط — كفاءة استخدام رأس المال
- تعديل الثقة — عقوبة للعينات الصغيرة (توزيع t)
- تكاليف التمويل — التكلفة الفعلية للرافعة المالية (معدلات التمويل)
- MaxLev — القياس مع مراعاة التراجع (عدم تماثل الخسارة والربح)
تعديل الثقة للاستراتيجيات النادرة
الاستراتيجية B بـ 40 صفقة تتطلب عقوبة جدية. نستخدم الحد الأدنى لفترة الثقة:
import scipy.stats as st
import numpy as np
def confidence_factor(trade_returns: np.ndarray, confidence: float = 0.95) -> float:
"""Confidence factor: 0..1, penalty for small samples."""
n = len(trade_returns)
if n < 10:
return 0.0
mean_r = np.mean(trade_returns)
if mean_r <= 0:
return 0.0
se = np.std(trade_returns, ddof=1) / np.sqrt(n)
t_crit = st.t.ppf(1 - (1 - confidence) / 2, df=n - 1)
ci_lower = mean_r - t_crit * se
return max(0.0, ci_lower / mean_r)
cf_b = confidence_factor(np.random.normal(0.0067, 0.028, 40))
cf_a = confidence_factor(np.random.normal(0.0011, 0.008, 500))
دمج تكلفة التمويل
في العقود الآجلة الدائمة، يُدفع التمويل كل 8 ساعات. مع الرافعة المالية ومتوسط المعدل :
للاستراتيجية A مع MaxLev = 55x ومتوسط معدل تمويل 0.01%:
مع PnL/يوم نشط = 0.49%، صافي PnL سالب: /يوم. الاستراتيجية غير مربحة عند الرافعة المالية الكاملة. تحليل مفصل في معدلات التمويل تقتل الرافعة المالية.
المنسق متعدد الاستراتيجيات

الهندسة المعمارية
يدير المنسق استراتيجية على زوج تداول. إجمالي عدد المراكز المحتملة: . لكن رأس المال محدود — لا يُسمح بأكثر من مركز متزامن (فتحة).
┌─────────────────────────────────────────────┐
│ ORCHESTRATOR │
│ │
│ Signal Queue (sorted by score): │
│ ┌──────────────────────────────────────┐ │
│ │ 1. Strategy C × ETHUSDT score=223 │ │
│ │ 2. Strategy B × BTCUSDT score=142 │ │
│ │ 3. Strategy A × SOLUSDT score=100 │ │
│ │ 4. Strategy C × BTCUSDT score=89 │ │
│ │ 5. Strategy A × ETHUSDT score=76 │ │
│ └──────────────────────────────────────┘ │
│ │
│ Active Slots (max_parallel = 3): │
│ ┌──────────────────────────────────────┐ │
│ │ Slot 1: Strategy C × ETHUSDT [FULL] │ │
│ │ Slot 2: Strategy B × BTCUSDT [FULL] │ │
│ │ Slot 3: Strategy A × SOLUSDT [DUAL] │ │
│ └──────────────────────────────────────┘ │
│ │
│ Conflict Rules: │
│ - One position per pair │
│ - Primary displaces fallback on same pair │
│ - Higher score wins for cross-pair slots │
└─────────────────────────────────────────────┘
إدارة الفتحات
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
import heapq
import time
class SlotType(Enum):
FULL = "full" # primary strategy, 100% position
DUAL = "dual" # fallback strategy, dual_size position
@dataclass
class Signal:
strategy_id: str
pair: str
direction: str # "long" | "short"
score: float
is_primary: bool # primary or fallback
timestamp: float
@dataclass(order=True)
class Slot:
"""A single orchestrator slot."""
priority: float = field(compare=True) # negative score for min-heap
strategy_id: str = field(compare=False)
pair: str = field(compare=False)
slot_type: SlotType = field(compare=False)
entry_time: float = field(compare=False)
class Orchestrator:
"""
Multi-strategy orchestrator with cascade mode.
Manages N strategies x M pairs within max_parallel_positions slots.
Primary strategies have unconditional priority over fallback.
"""
def __init__(
self,
max_parallel_positions: int = 10,
dual_size: float = 0.068,
min_score: float = 0,
):
self.max_parallel = max_parallel_positions
self.dual_size = dual_size
self.min_score = min_score
self.active_slots: dict[str, Slot] = {} # pair -> Slot
self.pending_signals: list[Signal] = []
def on_signal(self, signal: Signal) -> Optional[dict]:
"""
Process a new signal. Returns an action or None.
Actions:
- {"action": "open", "pair": ..., "size": ..., "slot_type": ...}
- {"action": "replace", "pair": ..., "close_strategy": ..., "open_strategy": ...}
- None (signal rejected)
"""
if signal.score < self.min_score:
return None
pair = signal.pair
if pair in self.active_slots:
existing = self.active_slots[pair]
if signal.is_primary and existing.slot_type == SlotType.DUAL:
self.active_slots[pair] = Slot(
priority=-signal.score,
strategy_id=signal.strategy_id,
pair=pair,
slot_type=SlotType.FULL,
entry_time=signal.timestamp,
)
return {
"action": "replace",
"pair": pair,
"close_strategy": existing.strategy_id,
"open_strategy": signal.strategy_id,
"size": 1.0,
}
if signal.score > -existing.priority:
slot_type = SlotType.FULL if signal.is_primary else SlotType.DUAL
size = 1.0 if signal.is_primary else self.dual_size
self.active_slots[pair] = Slot(
priority=-signal.score,
strategy_id=signal.strategy_id,
pair=pair,
slot_type=slot_type,
entry_time=signal.timestamp,
)
return {
"action": "replace",
"pair": pair,
"close_strategy": existing.strategy_id,
"open_strategy": signal.strategy_id,
"size": size,
}
return None # existing has higher priority
if len(self.active_slots) < self.max_parallel:
slot_type = SlotType.FULL if signal.is_primary else SlotType.DUAL
size = 1.0 if signal.is_primary else self.dual_size
self.active_slots[pair] = Slot(
priority=-signal.score,
strategy_id=signal.strategy_id,
pair=pair,
slot_type=slot_type,
entry_time=signal.timestamp,
)
return {
"action": "open",
"pair": pair,
"strategy": signal.strategy_id,
"size": size,
"slot_type": slot_type,
}
worst_pair = min(
self.active_slots,
key=lambda p: -self.active_slots[p].priority,
)
worst_slot = self.active_slots[worst_pair]
if signal.score > -worst_slot.priority:
del self.active_slots[worst_pair]
slot_type = SlotType.FULL if signal.is_primary else SlotType.DUAL
size = 1.0 if signal.is_primary else self.dual_size
self.active_slots[pair] = Slot(
priority=-signal.score,
strategy_id=signal.strategy_id,
pair=pair,
slot_type=slot_type,
entry_time=signal.timestamp,
)
return {
"action": "replace",
"pair": pair,
"close_strategy": worst_slot.strategy_id,
"close_pair": worst_pair,
"open_strategy": signal.strategy_id,
"size": size,
}
return None # all active slots have higher scores
def on_exit(self, pair: str) -> None:
"""Strategy closed a position."""
if pair in self.active_slots:
del self.active_slots[pair]
def utilization(self) -> float:
"""Current slot utilization."""
return len(self.active_slots) / self.max_parallel
def fill_efficiency_snapshot(self) -> float:
"""Weighted utilization: FULL=1.0, DUAL=dual_size."""
total = sum(
1.0 if s.slot_type == SlotType.FULL else self.dual_size
for s in self.active_slots.values()
)
return total / self.max_parallel
حل التعارضات
ثلاثة مستويات من التعارض:
المستوى 1 — نفس الزوج، نفس الاتجاه. تفوز الاستراتيجية ذات النقاط الأعلى. إذا كانت كلتاهما أساسيتين — النقاط تحدد الفائز. إذا كانت واحدة أساسية والأخرى احتياطية — تفوز الأساسية بلا شروط.
المستوى 2 — نفس الزوج، اتجاه معاكس. محظور: لا يمكنك أن تكون في مركز شراء وبيع على نفس الزوج في وقت واحد. تفوز الاستراتيجية ذات النقاط الأعلى.
المستوى 3 — منافسة عبر الأزواج. عندما تكون جميع الفتحات مشغولة، تطرد الإشارة الجديدة الفتحة ذات أدنى النقاط. يعمل هذا كقائمة أولويات.
الاختبار الرجعي للتتابع: المنهجية
محاكاة مشتركة: منحنيات رأس المال للأساسية والاحتياطية مع مناطق التداخل والنتيجة المجمعة للتتابع
لماذا لا يمكن جمع PnL ببساطة
النهج الساذج: اختبار كل استراتيجية منفصلة رجعياً، ثم جمع PnL. ينتج هذا نتيجة مُبالَغ فيها لثلاثة أسباب:
-
تداخل الوقت. عندما تكون الأساسية والاحتياطية نشطتين في وقت واحد، يجب ألا تتداول الاحتياطية (أو تتداول بـ dual_size). الجمع البسيط يتجاهل هذا التداخل.
-
قيد رأس المال. إجمالي المراكز محدود. إذا أرادت 5 استراتيجيات الفتح في وقت واحد لكن لا يوجد سوى 3 فتحات — لن تدخل استراتيجيتان. لا يمكن احتساب PnL الخاص بهما.
-
تكاليف المعاملات. تبديل التتابع (إغلاق الاحتياطية، فتح الأساسية) يُولّد عمولات إضافية غير موجودة في الاختبارات الرجعية الفردية.
المحاكاة المشتركة
الاختبار الرجعي الصحيح للتتابع هو محاكاة مشتركة لجميع الاستراتيجيات على جدول زمني مشترك:
import numpy as np
from typing import NamedTuple
class Trade(NamedTuple):
strategy: str
pair: str
entry_time: int # minute index
exit_time: int # minute index
pnl_per_minute: float # log-return per minute
is_primary: bool
score: float
def backtest_cascade(
all_trades: list[Trade],
total_minutes: int,
max_slots: int = 10,
dual_size: float = 0.068,
switch_cost: float = 0.0006, # 0.06% round-trip
) -> dict:
"""
Joint simulation of cascade portfolio.
Walk through each minute, apply orchestrator rules,
calculate PnL accounting for overlap and slot constraints.
"""
entries = {}
exits = {}
active_trades = {} # trade_id -> Trade
for i, trade in enumerate(all_trades):
entries.setdefault(trade.entry_time, []).append((i, trade))
exits.setdefault(trade.exit_time, []).append((i, trade))
active_slots = {} # pair -> (trade_id, SlotType)
equity = np.ones(total_minutes)
switch_costs_total = 0.0
for t in range(1, total_minutes):
for trade_id, trade in exits.get(t, []):
if trade.pair in active_slots:
slot_id, _ = active_slots[trade.pair]
if slot_id == trade_id:
del active_slots[trade.pair]
new_signals = sorted(
entries.get(t, []),
key=lambda x: x[1].score,
reverse=True,
)
for trade_id, trade in new_signals:
pair = trade.pair
if pair in active_slots:
existing_id, existing_type = active_slots[pair]
existing_trade = all_trades[existing_id]
if trade.is_primary and existing_type == SlotType.DUAL:
active_slots[pair] = (trade_id, SlotType.FULL)
switch_costs_total += switch_cost
continue
if trade.score > existing_trade.score:
slot_type = SlotType.FULL if trade.is_primary else SlotType.DUAL
active_slots[pair] = (trade_id, slot_type)
switch_costs_total += switch_cost
elif len(active_slots) < max_slots:
slot_type = SlotType.FULL if trade.is_primary else SlotType.DUAL
active_slots[pair] = (trade_id, slot_type)
minute_return = 0.0
for pair, (trade_id, slot_type) in active_slots.items():
trade = all_trades[trade_id]
size = 1.0 if slot_type == SlotType.FULL else dual_size
minute_return += trade.pnl_per_minute * size
equity[t] = equity[t - 1] * (1 + minute_return)
peak = np.maximum.accumulate(equity)
max_dd = ((equity - peak) / peak).min()
total_pnl = equity[-1] - 1 - switch_costs_total
return {
"total_pnl": total_pnl,
"max_dd": max_dd,
"switch_costs": switch_costs_total,
"equity_curve": equity,
}
تكلفة المعاملات عند التبديل
يتطلب كل تبديل تتابعي (احتياطي -> أساسي):
- إغلاق مركز الاحتياطية: رسوم آخذ (0.04% على عقود Binance الآجلة)
- فتح مركز الأساسية: رسوم آخذ (0.04%)
- السبريد: ~0.01-0.02%
إجمالي تكلفة التبديل: ~0.06-0.10% لكل تبديل. مع 100 تبديل خلال الفترة:
هذا مبلغ كبير. يمكن أن يؤدي التتابع مع التبديل المتكرر إلى أداء أسوأ من استراتيجية واحدة بسبب تكاليف المعاملات.
التوسع متعدد الأزواج: N استراتيجية على M زوج
شبكة N استراتيجية متصلة بـ M زوج تداول — قوة الارتباط تحدد التنويع الفعال
مساحة التوليفات
3 استراتيجيات على 10 أزواج = 30 إشارة محتملة. مع max_slots = 5، يختار المنسق أعلى 5 حسب النقاط. هذه مسألة توليفات: محفظة ممكنة في كل لحظة.
عملياً، تُنتج الخوارزمية الجشعة (الترتيب حسب النقاط، الملء من أعلى إلى أسفل) نتائج شبه مثلى في .
الارتباط بين الأزواج
أزواج العملات المشفرة مترابطة بشدة. ينخفض BTC — ينخفض ETH وSOL وAVAX معاً. هذا يعني أن 5 مراكز شراء على 5 أزواج مختلفة هي فعلياً مركز واحد كبير على "سوق العملات المشفرة".
كما حللنا بالتفصيل في ارتباط الإشارات، العدد الفعال للمراكز المستقلة:
حيث هو متوسط الارتباط بين الأزواج.
مع و :
خمسة مراكز على أزواج مترابطة تعادل 1.3 مركز مستقل. التنويع شبه معدوم.
الآثار العملية على التتابع
def effective_diversification(
positions: list[dict], # [{"pair": "BTCUSDT", "direction": "long"}, ...]
correlation_matrix: np.ndarray,
pair_index: dict[str, int],
) -> float:
"""
Calculate effective diversification of open positions.
Returns:
N_eff / N — diversification coefficient (0..1)
"""
n = len(positions)
if n <= 1:
return 1.0
total_corr = 0.0
pairs_count = 0
for i in range(n):
for j in range(i + 1, n):
idx_i = pair_index[positions[i]["pair"]]
idx_j = pair_index[positions[j]["pair"]]
rho = correlation_matrix[idx_i, idx_j]
if positions[i]["direction"] != positions[j]["direction"]:
rho = -rho
total_corr += rho
pairs_count += 1
avg_rho = total_corr / pairs_count if pairs_count > 0 else 0
n_eff = n / (1 + (n - 1) * max(0, avg_rho))
return n_eff / n
يجب أن يأخذ المنسق في الاعتبار الارتباط عند ملء الفتحات. خياران:
- مكافأة التنويع: عند الترتيب، إضافة مكافأة لنقاط الاستراتيجيات على أزواج غير مترابطة.
- سقف الارتباط: تقييد عدد المراكز في نفس الاتجاه على أزواج مترابطة.
خط أنابيب تحسين التتابع
ثماني مراحل متصلة من إعداد البيانات عبر التحقق إلى التنسيق المباشر — كل مرحلة تبني على السابقة
يتكون خط الأنابيب الكامل من البيانات إلى الإنتاج من 8 مراحل:
المرحلة 0: إعداد البيانات
تحميل البيانات التاريخية، بناء ذاكرة Parquet المؤقتة للوصول متعدد الأطر الزمنية. بدون التخزين المؤقت الفعال، تكون المراحل اللاحقة بطيئة بشكل غير مقبول.
المرحلة 1: TF + Length (شبكة Hill-Climbing)
اختيار الإطار الزمني الأساسي وأطوال نوافذ المؤشرات. شبكة خشنة: TF من {1m, 5m, 15m, 1h, 4h}، Length من {10, 20, 50, 100, 200}. Hill-Climbing من أفضل نقطة شبكية.
المرحلة 2: الفصل (النزول الإحداثي، 12 معاملاً)
تحسين معاملات الفصل (الدخول/الخروج). النزول الإحداثي عبر 12 معاملاً — عتبات المؤشرات، المرشحات، وقف الخسارة، جني الأرباح. النزول الإحداثي أقل تكلفة من Optuna لدوال الهدف الحتمية عالية الأبعاد.
المرحلة 3: المعاملات الفوقية (النزول الإحداثي)
المعاملات الفوقية: أقصى وقت للاحتفاظ، أدنى PnL للخروج، تكوين وقف الخسارة المتحرك. مرة أخرى النزول الإحداثي. التحقق من المتانة عبر تحليل الهضبة — إذا كانت القيمة المثلى نقطية، فالاستراتيجية مُحسَّنة بشكل مفرط.
المرحلة 4: تحسين التوليفات
بحث شبكي عبر الأزواج (أساسي، احتياطي). لكل توليفة: اختيار dual_size، حساب PnL التتابع عبر المحاكاة المشتركة.
المرحلة 5: التحقق
تحقق متعدد المستويات:
- متعدد الرموز: اختبار الاستراتيجية على 10+ أزواج، ليس فقط زوج التحسين
- الاختبار المتقدم: نافذة IS/OOS منزلقة
- استقرار المعاملات: تحليل الهضبة في كل مرحلة
- مونت كارلو بوتستراب: فترات الثقة لـ PnL التتابع
- تكافؤ الاختبار الرجعي والتداول الحي: مقارنة الاختبار الرجعي بالتداول التجريبي
المرحلة 6: الترتيب والاختيار
ترتيب توليفات التتابع حسب النقاط. أفضل K توليفة تتقدم إلى المرحلة 7. تراعي النقاط تعديل الثقة وتكاليف التمويل وfill_efficiency.
المرحلة 7: التنسيق
المرحلة النهائية: إطلاق المنسق على استراتيجية و زوج في وضع التتابع. إدارة الفتحات، قائمة الأولويات، حل التعارضات — كل ما وُصف أعلاه.
تحليل الأداء: التتابع مقابل الفردي
مقارنة جنباً إلى جنب: محفظة التتابع تتفوق على الاستراتيجيات الفردية من خلال استغلال وقت الخمول
الميزة النظرية للتتابع
لنفترض أن الأساسية تتداول من الوقت بـ PnL/يوم = 0.49%. الاحتياطية تتداول بـ PnL/يوم = 0.89%. التداخل = (بافتراض الاستقلالية).
الأساسية وحدها (الاستراتيجية A):
التتابع (A أساسي + C احتياطي):
مكسب التتابع: +31% في PnL من الاحتياطية، مع زيادة طفيفة في التراجع ( مُضافة إلى MaxDD).
متى لا يساعد التتابع
التتابع غير فعال عندما:
- الأساسية نشطة >80% من الوقت. وقت خمول قليل — لا مكان للاحتياطية.
- الاستراتيجيات مترابطة بشدة. الأساسية والاحتياطية تُولّدان إشارات في وقت واحد — التداخل عالٍ، والاحتياطية خاملة تحديداً عندما تكون الأساسية خاملة أيضاً.
- تكاليف التبديل تتجاوز PnL الاحتياطية. مع التبديل المتكرر، تأكل عمولات التتابع أرباح الاحتياطية.
- dual_size صغير جداً. عند ، تكسب الاحتياطية 1% من إمكانياتها — أقل من العمولات.
جدول المقارنة
| التكوين | PnL السنوي | MaxDD | شارب | تكاليف التبديل |
|---|---|---|---|---|
| الاستراتيجية A وحدها | 26.8% | 0.9% | 1.42 | 0 |
| الاستراتيجية C وحدها | 146.1% | 17% | 1.15 | 0 |
| تتابع A+C (d=0.068) | 35.2% | 2.06% | 1.58 | ~1.2% |
| تتابع B+A (d=0.068) | 19.4% | 1.36% | 1.71 | ~0.3% |
| منسق 3 استراتيجيات | 48.7% | 3.1% | 1.63 | ~2.1% |
تتابع A+C: الأساسية A تكسب +8.4% من الاحتياطية C. يرتفع شارب من خلال استغلال وقت الخمول. يزداد MaxDD بشكل معتدل ().
التنسيق: fill_efficiency في الممارسة
Fill efficiency عند ~78%: الخريطة الحرارية تُظهر معدل استخدام الوقت عبر الاستراتيجيات والأزواج، الخلايا المضيئة تشير إلى تداول نشط
يحدد معامل fill_efficiency النسبة الفعلية من وقت الخمول التي يستغلها المنسق. كما أُظهر في PnL لكل وقت نشط، يمكن تقديره بثلاث طرق:
- ثابت ثابت (0.80) — تقريبي لكن عام
- تقدير تحليلي عبر — يراعي الارتباط
- محاكاة من البيانات — الأكثر دقة
لتتابع مع 3 استراتيجيات على 10 أزواج:
def cascade_fill_efficiency(
strategies: list[dict], # [{"trading_time": 0.15, "is_primary": True}, ...]
n_pairs: int = 10,
correlation_factor: float = 3.0,
) -> float:
"""Estimate fill_efficiency for a cascade portfolio."""
n_eff = n_pairs / correlation_factor
primary_times = [s["trading_time"] for s in strategies if s["is_primary"]]
p_primary = 1 - np.prod([(1 - t) ** n_eff for t in primary_times])
fallback_times = [s["trading_time"] for s in strategies if not s["is_primary"]]
p_fallback = 1 - np.prod([(1 - t) ** n_eff for t in fallback_times])
fill = p_primary + (1 - p_primary) * p_fallback
return min(fill, 1.0)
strategies = [
{"trading_time": 0.05, "is_primary": True}, # Strategy B
{"trading_time": 0.15, "is_primary": True}, # Strategy A
{"trading_time": 0.45, "is_primary": False}, # Strategy C as fallback
]
eff = cascade_fill_efficiency(strategies, n_pairs=10, correlation_factor=3.0)
توصيات عملية
ست توصيات رئيسية لنشر التتابع — من البداية الصغيرة إلى إعادة المعايرة التكيفية
1. ابدأ باستراتيجيتين
لا تُطلق 10 استراتيجيات على 20 زوجاً فوراً. ابدأ بأساسية واحدة + احتياطية واحدة على 3-5 أزواج. تأكد من أن المحاكاة المشتركة تتطابق مع السلوك الفعلي. تكافؤ الاختبار الرجعي والتداول الحي حاسم: إذا انحرف الاختبار الرجعي للتتابع عن الحي بنسبة 5-10% فقط — هناك خطأ في منطق المنسق.
2. dual_size من البحث الشبكي لا من الحدس
dual_size الأمثل يعتمد على الزوج المحدد من الاستراتيجيات. 6.8% هو دليل إرشادي وليس ثابتاً عالمياً. شغّل بحثاً شبكياً من 1% إلى 30% بخطوات 0.5% واختر أقصى شارب.
3. حد الفتحات يحدد الهندسة المعمارية
مع max_slots = 1، يتحلل التتابع إلى تبديل بسيط بين الاستراتيجيات. مع max_slots = 50، لا يكون القيد مُلزِماً وتتحول المسألة إلى محفظة مستقلة. المنطقة المثيرة: max_slots = 3-10، حيث تؤثر إدارة الفتحات فعلياً على النتائج.
4. احسب حساب زمن الاستجابة
في التداول الحي، تبديل التتابع ليس فورياً. إغلاق مركز الاحتياطية + فتح الأساسية = استدعاءان لـ API + زمن استجابة الشبكة + مطابقة البورصة. في سوق متقلب، يمكن أن يتحرك السعر في 200-500 ملي ثانية. ضمّن ميزانية انزلاق.
5. راقب fill_efficiency
تتبع fill_efficiency الفعلي في الإنتاج. إذا كان أقل بكثير من المختبَر رجعياً — فإن المنسق لا يستغل وقت الخمول كما هو متوقع. الأسباب: تأخيرات API، أوامر مرفوضة، قيود الهامش.
6. استخدم التحسين التكيفي
معاملات التتابع (dual_size، أوزان النقاط، حدود الفتحات) يجب ألا تكون ثابتة. استخدم التعمق التكيفي لإعادة المعايرة الدورية على بيانات جديدة. السوق يتغير — يجب أن تتبعه معاملات التتابع.
سلسلة "اختبارات رجعية بلا أوهام": ملخص
هندسة النظام الكاملة: 13 وحدة مترابطة من الرياضيات عبر التحقق إلى التنسيق المباشر
هذه المقالة هي الحلقة الأخيرة من سلسلة تضم 13+ مقالة. كل مقالة عالجت مشكلة محددة واحدة في الطريق من الاختبار الرجعي إلى الإنتاج. إليك كيف تترابط:
الأساس: رياضيات العوائد
عدم تماثل الخسارة والربح — الطبيعة الضربية للعوائد، سحب التقلب، معيار كيلي. هذا هو الأساس الرياضي لكل ما يلي: لماذا يحدد MaxDD الرافعة المالية، لماذا شارب أهم من PnL الخام، لماذا معدل فوز 50% مع R:R متماثل غير مربح.
التحقق: فترات الثقة والمتانة
مونت كارلو بوتستراب — تحويل تقدير نقطة واحدة إلى توزيع بفترات ثقة. أي مقياس (PnL، MaxDD، شارب) له معنى فقط مع فترة ثقة.
تحسين الاختبار المتقدم — التحقق خارج العينة. الاختبار الرجعي على البيانات التاريخية هو نتيجة IS؛ WFO يُظهر كيف تعمل الاستراتيجية على بيانات جديدة.
تحليل الهضبة — فحص متانة المعاملات. إذا كانت القيمة المثلى نقطية، فالاستراتيجية مُحسَّنة بشكل مفرط.
تكافؤ الاختبار الرجعي والتداول الحي — مقارنة الاختبار الرجعي بالنتائج الفعلية. الفحص النهائي قبل التوسع.
التكاليف الواقعية: التمويل والرافعة المالية
معدلات التمويل تقتل الرافعة المالية — التكلفة الخفية للرافعة المالية في العقود الآجلة الدائمة. بدون احتساب التمويل، يتحول الاختبار الرجعي الجميل إلى خسارة.
تحكيم معدل التمويل — كيفية تحويل التمويل من تكلفة إلى مصدر دخل من خلال استراتيجيات عبر البورصات.
المقاييس والترتيب
PnL لكل وقت نشط — مقياس ترتيب الاستراتيجيات في المحفظة. PnL الخام لا يتوسع؛ PnL/يوم نشط يتوسع.
ارتباط الإشارات — التنويع الفعال في محفظة أزواج مترابطة.
البنية التحتية والتحسين
ذاكرة Parquet المؤقتة للاختبارات الرجعية متعددة الأطر الزمنية — بنية البيانات التحتية للتكرارات السريعة.
التعمق التكيفي — التحسين التكيفي: شبكة خشنة -> ضبط دقيق في المناطق الواعدة.
Optuna مقابل النزول الإحداثي — اختيار المُحسِّن: Optuna للأبعاد المنخفضة مع دوال هدف ضوضائية، النزول الإحداثي للأبعاد العالية مع دوال هدف ناعمة.
Polars مقابل Pandas — أداء عمليات DataFrame للاختبار الرجعي.
التنسيق (هذه المقالة)
استراتيجيات التتابع — دمج جميع المكونات السابقة في نظام عامل. التخصيص القائم على النقاط يستخدم PnL/وقت نشط، تعديل الثقة، تكاليف التمويل. وضع التتابع يملأ وقت الخمول. المحاكاة المشتركة تتحقق من المحفظة. مونت كارلو بوتستراب يوفر فترات ثقة لـ PnL التتابع.
كل مقالة هي وحدة مستقلة. معاً تشكل خط أنابيب كاملاً من تحميل البيانات إلى التنسيق المباشر لمحفظة الاستراتيجيات.
الخلاصة
التتابع ليس النهج الوحيد لمحافظ الاستراتيجيات. لكنه واحد من أبسط وأكثرها عملية: تتداول الاستراتيجية الأساسية بكامل طاقتها، وتملأ الاحتياطية وقت الخمول بمركز مخفّض. معاملان رئيسيان (dual_size وmax_slots) يوفران مرونة كافية لمعظم التكوينات.
ثلاث نقاط رئيسية:
-
يجب اختبار التتابع رجعياً عبر المحاكاة المشتركة فقط. جمع PnL الفردي يُبالغ في النتائج. تكاليف التبديل، التداخل، قيود الفتحات — كل هذا لا يُلتقط إلا في المحاكاة المشتركة.
-
dual_size يحدد المفاضلة بين PnL والتراجع. القيمة المثلى النموذجية 5-10%. البحث الشبكي على شارب هو طريقة اختيار موثوقة.
-
المنسق هو قائمة أولويات قائمة على النقاط. كل شيء يُختزل إلى رقم واحد (النقاط) لكل إشارة. النقاط = f(PnL/يوم نشط، MaxLev، الثقة، التمويل). الاستراتيجيات ذات أعلى النقاط تحصل على الفتحات. الباقي ينتظر.
سلسلة "اختبارات رجعية بلا أوهام" تُثبت شيئاً واحداً: بين الاختبار الرجعي الجميل والربح الفعلي تقع عشرات المزالق. كل مقالة تُزيل واحدة منها. تنسيق التتابع هو الخطوة الأخيرة: تحويل مجموعة من الاستراتيجيات المُتحقق منها إلى محفظة عاملة.
روابط مفيدة
- López de Prado — Advances in Financial Machine Learning: Portfolio Construction
- Pardo, R. — The Evaluation and Optimization of Trading Strategies
- Ernest Chan — Algorithmic Trading: Winning Strategies and Their Rationale
- Perry Kaufman — Trading Systems and Methods, Chapter on Portfolio Allocation
- Tomasini, Jaekle — Trading Systems: A New Approach to System Development and Portfolio Optimisation
- Bailey, D.H. & López de Prado — The Deflated Sharpe Ratio
- Markowitz, H. — Portfolio Selection (1952)
- Kelly, J.L. — A New Interpretation of Information Rate (1956)
Citation
@article{soloviov2026cascadestrategies,
author = {Soloviov, Eugen},
title = {Cascade Strategies: Priority Execution with Fallback Filling},
year = {2026},
url = {https://marketmaker.cc/ru/blog/post/cascade-strategies-orchestration},
version = {0.1.0},
description = {Finale of the "Backtests Without Illusions" series. How to build an orchestrator from N strategies x M pairs, implement cascade mode with priority and fallback filling, choose dual\_size, and why strategy portfolios cannot be backtested by summing PnL.}
}
MarketMaker.cc Team
البحوث والاستراتيجيات الكمية