कैस्केड रणनीतियाँ: फॉलबैक फिलिंग के साथ प्राथमिकता निष्पादन

'बैकटेस्ट्स विदाउट इल्यूजन्स' सीरीज़ का समापन। N रणनीतियों x M पेयर्स से एक ऑर्केस्ट्रेटर कैसे बनाएं, प्राथमिकता और फॉलबैक निष्पादन के साथ कैस्केड मोड कैसे लागू करें, dual_size कैसे चुनें, और स्ट्रैटेजी पोर्टफोलियो को केवल PnL जोड़कर बैकटेस्ट क्यों नहीं किया जा सकता।

"बैकटेस्ट्स विदाउट इल्यूजन्स" सीरीज़ का समापन। M पेयर्स पर N रणनीतियों से एक ऑर्केस्ट्रेटर कैसे बनाएं, प्राथमिकता और फॉलबैक निष्पादन के साथ कैस्केड मोड कैसे लागू करें, dual_size कैसे चुनें, और स्ट्रैटेजी पोर्टफोलियो को केवल PnL जोड़कर बैकटेस्ट क्यों नहीं किया जा सकता।

स्ट्रैटेजी पोर्टफोलियो की आवश्यकता क्यों है

निष्क्रिय पूंजी वाला स्ट्रैटेजी पोर्टफोलियो कई रणनीतियाँ सीमित पूंजी के लिए प्रतिस्पर्धा करती हैं — अधिकांश निष्क्रिय रहती हैं जबकि किसी भी समय केवल कुछ ही ट्रेड करती हैं

आपने एक रणनीति को पूरे पाइपलाइन से गुज़ारा है। मोंटे कार्लो बूटस्ट्रैप ने एक स्वीकार्य 5वां पर्सेंटाइल दिखाया। वॉक-फॉरवर्ड ने आउट-ऑफ-सैंपल रिटर्न की पुष्टि की। फंडिंग रेट्स का हिसाब लगाया गया है, पठार विश्लेषण पास हो गया है। रणनीति वास्तव में काम करती है।

लेकिन यह 15% समय ट्रेड करती है। बाकी 85% समय आपकी पूंजी निष्क्रिय पड़ी रहती है।

दूसरी रणनीति चलाएं? तीसरी? दसवीं? यह विचार स्पष्ट है। कार्यान्वयन नहीं है। एक स्ट्रैटेजी पोर्टफोलियो ऐसी समस्याएं पैदा करता है जो एक ही बॉट के साथ मौजूद नहीं होतीं:

  • संघर्ष: दो रणनीतियाँ एक ही पेयर पर विपरीत पोज़िशन खोलना चाहती हैं।
  • बाधाएं: एक्सचेंज/रिस्क मैनेजमेंट KK से अधिक एक साथ पोज़िशन की अनुमति नहीं देता।
  • आवंटन: प्रत्येक रणनीति को पूंजी का कितना हिस्सा दिया जाए?
  • सहसंबंध: सहसंबंधित क्रिप्टो पेयर्स पर 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%/d0.72%/d0.89%/d
प्रकृतिमध्यम गतिविधिदुर्लभ, उच्च विश्वासबार-बार, आक्रामक

जैसा कि हमने PnL प्रति सक्रिय समय में दिखाया, कच्चे PnL और PnL/सक्रिय दिन के आधार पर रैंकिंग अलग-अलग परिणाम देती है। कैस्केड ऑर्केस्ट्रेशन के लिए, दूसरा मीट्रिक महत्वपूर्ण है।

इष्टतम dual_size

dual_size ऑप्टिमाइज़ेशन सरफेस dual_size पर ग्रिड सर्च शार्प रेशियो पीक दिखाती है — बहुत बड़ा ड्रॉडाउन बढ़ाता है, बहुत छोटा निष्क्रिय समय बर्बाद करता है

चयन समस्या

dual_size पूरी पोज़िशन का वह अंश है जो फॉलबैक रणनीति को मिलता है। यह मुख्य कैस्केड पैरामीटर है:

  • बहुत बड़ा (जैसे, 0.5 = 50%): जब प्राइमरी और फॉलबैक एक साथ सक्रिय होते हैं, कुल एक्सपोज़र = लक्ष्य का 150%। ड्रॉडाउन दोगुना हो जाता है। लॉस-प्रॉफिट असममिति इसे असमान रूप से महंगा बनाती है।

  • बहुत छोटा (जैसे, 0.01 = 1%): फॉलबैक 85% निष्क्रिय समय भरता है लेकिन मामूली कमाई करता है। पूंजी प्रभावी रूप से निष्क्रिय पड़ी रहती है।

  • इष्टतम: फॉलबैक प्राइमरी के साथ एक साथ संचालन के दौरान ड्रॉडाउन को गंभीर रूप से बढ़ाए बिना सार्थक PnL का योगदान देता है।

औपचारीकरण

मान लीजिए:

  • PpP_p — प्रति इकाई समय प्राइमरी PnL
  • PfP_f — प्रति इकाई समय फॉलबैक PnL
  • tpt_p — पोज़िशन में समय का अंश (प्राइमरी)
  • tft_f — पोज़िशन में समय का अंश (फॉलबैक)
  • dd — dual_size (0..1)
  • toverlapt_{overlap} — समय का अंश जब दोनों पोज़िशन में हों

कुल कैस्केड PnL:

PnLcascade=Pp⋅tp+d⋅Pf⋅(tf−toverlap)\text{PnL}_{cascade} = P_p \cdot t_p + d \cdot P_f \cdot (t_f - t_{overlap})

कुल MaxDD (सबसे खराब स्थिति — पूर्ण सहसंबंध):

DDcascade≈DDp+d⋅DDf\text{DD}_{cascade} \approx \text{DD}_p + d \cdot \text{DD}_f

यदि हम कुल ड्रॉडाउन को DtargetD_{target} तक सीमित करते हैं:

dmax=Dtarget−DDpDDfd_{max} = \frac{D_{target} - \text{DD}_p}{\text{DD}_f}

ग्रिड सर्च

व्यवहार में, इष्टतम 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:
        # Cascade equity: primary always full, fallback only when primary is flat
        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 (annualized, minute bars)
        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 time
        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%) मानते हुए:

dmax=2%−0.75%0.9%=1.39d_{max} = \frac{2\% - 0.75\%}{0.9\%} = 1.39

ड्रॉडाउन बाधा बाध्यकारी नहीं है — इष्टतम कैस्केड शार्प द्वारा निर्धारित होता है। व्यवहार में, ग्रिड सर्च आमतौर पर d≈0.068d \approx 0.068 (6.8%) देता है।

स्कोर-आधारित आवंटन

स्कोर-आधारित रणनीति रैंकिंग कंपोज़िट स्कोर द्वारा रैंक की गई रणनीतियाँ — कॉन्फिडेंस एडजस्टमेंट छोटे सैंपल्स को दंडित करता है, फंडिंग लागत नेट एज को कम करती है

जब दो से अधिक रणनीतियाँ होती हैं, कैस्केड स्कोर-आधारित आवंटन के लिए सामान्यीकृत हो जाता है।

सक्रिय समय प्रति PnL द्वारा रैंकिंग

जैसा कि PnL प्रति सक्रिय समय में विस्तार से वर्णित है, रणनीति स्कोर की गणना निम्नलिखित को ध्यान में रखते हुए की जाती है:

  1. प्रति सक्रिय दिन PnL — पूंजी उपयोग दक्षता
  2. कॉन्फिडेंस एडजस्टमेंट — छोटे सैंपल्स के लिए दंड (t-वितरण)
  3. फंडिंग लागत — लीवरेज की वास्तविक लागत (फंडिंग रेट्स)
  4. MaxLev — ड्रॉडाउन को ध्यान में रखते हुए स्केलिंग (लॉस-प्रॉफिट असममिति)

score=PnLnet/day⏟दक्षता×365⋅ffill⏟वार्षिकीकरण×MaxLev⏟स्केल×cconf⏟विश्वसनीयता\text{score} = \underbrace{\text{PnL}_{net/day}}_{\text{दक्षता}} \times \underbrace{365 \cdot f_{fill}}_{\text{वार्षिकीकरण}} \times \underbrace{\text{MaxLev}}_{\text{स्केल}} \times \underbrace{c_{conf}}_{\text{विश्वसनीयता}}

दुर्लभ रणनीतियों के लिए कॉन्फिडेंस एडजस्टमेंट

40 ट्रेड वाली रणनीति B को गंभीर दंड की आवश्यकता है। हम कॉन्फिडेंस इंटरवल की निचली सीमा का उपयोग करते हैं:

cconf=max⁡(0, rˉ−tα/2,n−1⋅snrˉ)c_{conf} = \max\left(0,\ \frac{\bar{r} - t_{\alpha/2, n-1} \cdot \frac{s}{\sqrt{n}}}{\bar{r}}\right)

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)

# Strategy B: 40 trades, mean_ret=0.67%
cf_b = confidence_factor(np.random.normal(0.0067, 0.028, 40))
# ~0.15-0.25

# Strategy A: 500 trades, mean_ret=0.11%
cf_a = confidence_factor(np.random.normal(0.0011, 0.008, 500))
# ~0.60-0.75

फंडिंग लागत का एकीकरण

पर्पेचुअल फ्यूचर्स पर, फंडिंग हर 8 घंटे में भुगतान की जाती है। लीवरेज LL और औसत दर rfr_f के साथ:

Fundingdaily=3⋅rf⋅L\text{Funding}_{daily} = 3 \cdot r_f \cdot L

रणनीति A के लिए MaxLev = 55x और औसत फंडिंग दर 0.01% के साथ:

Fundingdaily=3×0.0001×55=0.0165=1.65%/दिन\text{Funding}_{daily} = 3 \times 0.0001 \times 55 = 0.0165 = 1.65\%/\text{दिन}

PnL/सक्रिय दिन = 0.49% के साथ, नेट PnL नेगेटिव है: 0.49%−1.65%=−1.16%0.49\% - 1.65\% = -1.16\%/दिन। रणनीति पूर्ण लीवरेज पर लाभदायक नहीं है। विस्तृत विश्लेषण फंडिंग रेट्स आपके लीवरेज को खत्म कर देती हैं में।

मल्टी-स्ट्रैटेजी ऑर्केस्ट्रेटर

ऑर्केस्ट्रेटर स्लॉट आवंटन और प्राथमिकता कतार

आर्किटेक्चर

ऑर्केस्ट्रेटर MM ट्रेडिंग पेयर्स पर NN रणनीतियों का प्रबंधन करता है। संभावित पोज़िशन की कुल संख्या: N×MN \times M। लेकिन पूंजी सीमित है — KK से अधिक एक साथ पोज़िशन (स्लॉट) की अनुमति नहीं है।

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

        # Case 1: pair already has an active slot
        if pair in self.active_slots:
            existing = self.active_slots[pair]

            # Primary displaces fallback on same pair (cascade rule)
            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,
                }

            # Higher score displaces lower score (same type)
            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

        # Case 2: free slot available
        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,
            }

        # Case 3: all slots occupied — evict lowest score if new signal is better
        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 जोड़ें। यह तीन कारणों से एक फुला हुआ परिणाम उत्पन्न करता है:

  1. समय ओवरलैप। जब प्राइमरी और फॉलबैक एक साथ सक्रिय होते हैं, फॉलबैक को ट्रेड नहीं करना चाहिए (या dual_size पर ट्रेड करना चाहिए)। साधारण योग इस ओवरलैप को नज़रअंदाज़ करता है।

  2. पूंजी बाधा। कुल पोज़िशन सीमित है। यदि 5 रणनीतियाँ एक साथ खोलना चाहती हैं लेकिन केवल 3 स्लॉट हैं — दो रणनीतियाँ प्रवेश नहीं करेंगी। उनका PnL गिना नहीं जा सकता।

  3. लेनदेन लागत। कैस्केड स्विचिंग (फॉलबैक बंद करना, प्राइमरी खोलना) अतिरिक्त कमीशन उत्पन्न करती है जो व्यक्तिगत बैकटेस्ट में मौजूद नहीं होते।

संयुक्त सिमुलेशन

सही कैस्केड बैकटेस्ट एक साझा टाइमलाइन पर सभी रणनीतियों का संयुक्त सिमुलेशन है:

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.
    """
    # Pre-build signal timeline
    # signals_at_minute[t] = list of trades that want to enter at minute t
    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))

    # State
    active_slots = {}     # pair -> (trade_id, SlotType)
    equity = np.ones(total_minutes)
    switch_costs_total = 0.0

    for t in range(1, total_minutes):
        # Process exits
        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]

        # Process entries (sorted by score descending)
        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]

                # Primary displaces fallback
                if trade.is_primary and existing_type == SlotType.DUAL:
                    active_slots[pair] = (trade_id, SlotType.FULL)
                    switch_costs_total += switch_cost
                    continue

                # Higher score displaces lower
                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)

        # Calculate minute PnL
        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)

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

स्विचिंग पर लेनदेन लागत

प्रत्येक कैस्केड स्विच (फॉलबैक -> प्राइमरी) की आवश्यकता होती है:

  1. फॉलबैक पोज़िशन बंद करना: टेकर फीस (Binance फ्यूचर्स पर 0.04%)
  2. प्राइमरी पोज़िशन खोलना: टेकर फीस (0.04%)
  3. स्प्रेड: ~0.01-0.02%

कुल स्विच लागत: प्रति स्विच ~0.06-0.10%। अवधि में 100 स्विच के साथ:

Switch costs=100×0.0008=8%\text{Switch costs} = 100 \times 0.0008 = 8\%

यह एक महत्वपूर्ण राशि है। बार-बार स्विचिंग वाला कैस्केड लेनदेन लागत के कारण एक अकेली रणनीति से खराब प्रदर्शन कर सकता है।

मल्टी-पेयर एक्सटेंशन: M पेयर्स पर N रणनीतियाँ

मल्टी-पेयर स्ट्रैटेजी नेटवर्क M ट्रेडिंग पेयर्स से जुड़े N रणनीतियों का नेटवर्क — सहसंबंध की ताकत प्रभावी विविधीकरण निर्धारित करती है

संयोजन स्थान

10 पेयर्स पर 3 रणनीतियाँ = 30 संभावित सिग्नल। max_slots = 5 के साथ, ऑर्केस्ट्रेटर स्कोर के अनुसार शीर्ष 5 का चयन करता है। यह एक संयोजी समस्या है: प्रत्येक क्षण (305)=142 506\binom{30}{5} = 142\,506 संभावित पोर्टफोलियो।

व्यवहार में, एक ग्रीडी एल्गोरिदम (स्कोर द्वारा सॉर्ट करें, ऊपर से नीचे भरें) O(N⋅M⋅log⁡K)O(N \cdot M \cdot \log K) में लगभग-इष्टतम परिणाम देता है।

पेयर्स के बीच सहसंबंध

क्रिप्टो पेयर्स दृढ़ता से सहसंबंधित होते हैं। BTC गिरता है — ETH, SOL, AVAX साथ में गिरते हैं। इसका मतलब है कि 5 अलग-अलग पेयर्स पर 5 लॉन्ग पोज़िशन प्रभावी रूप से "क्रिप्टो मार्केट" पर एक बड़ी पोज़िशन हैं।

जैसा कि हमने सिग्नल सहसंबंध में विस्तार से विश्लेषण किया, स्वतंत्र पोज़िशन की प्रभावी संख्या है:

Neff=N1+(N−1)⋅ρˉN_{eff} = \frac{N}{1 + (N-1) \cdot \bar{\rho}}

जहां ρˉ\bar{\rho} पेयर्स के बीच औसत सहसंबंध है।

ρˉ=0.7\bar{\rho} = 0.7 और N=5N = 5 के साथ:

Neff=51+4×0.7=53.8=1.32N_{eff} = \frac{5}{1 + 4 \times 0.7} = \frac{5}{3.8} = 1.32

सहसंबंधित पेयर्स पर पांच पोज़िशन 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

    # Average pairwise correlation accounting for direction
    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 directions are opposite — correlation is inverted
            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


# Example: 5 long positions, average correlation 0.7
# effective_diversification => 1.32 / 5 = 0.26
# Only 26% of naive diversification

ऑर्केस्ट्रेटर को स्लॉट भरते समय सहसंबंध को ध्यान में रखना चाहिए। दो विकल्प:

  1. विविधीकरण बोनस: रैंकिंग करते समय, असहसंबंधित पेयर्स पर रणनीतियों के स्कोर में बोनस जोड़ें।
  2. सहसंबंध कैप: सहसंबंधित पेयर्स पर समान-दिशा पोज़िशन की संख्या सीमित करें।

कैस्केड ऑप्टिमाइज़ेशन पाइपलाइन

आठ-चरण ऑप्टिमाइज़ेशन पाइपलाइन डेटा तैयारी से लेकर वैलिडेशन और लाइव ऑर्केस्ट्रेशन तक आठ जुड़े हुए चरण — प्रत्येक पिछले पर निर्मित होता है

डेटा से प्रोडक्शन तक की पूरी पाइपलाइन में 8 चरण शामिल हैं:

चरण 0: डेटा तैयारी

ऐतिहासिक डेटा लोड करें, मल्टी-टाइमफ्रेम एक्सेस के लिए Parquet कैश बनाएं। कुशल कैशिंग के बिना, बाद के चरण अस्वीकार्य रूप से धीमे हैं।

चरण 1: TF + लेंथ (हिल-क्लाइम्बिंग ग्रिड)

बेस टाइमफ्रेम और इंडिकेटर विंडो लेंथ चुनें। मोटा ग्रिड: {1m, 5m, 15m, 1h, 4h} से TF, {10, 20, 50, 100, 200} से लेंथ। सर्वश्रेष्ठ ग्रिड बिंदु से हिल-क्लाइम्बिंग।

चरण 2: सेपरेशन (कोऑर्डिनेट डिसेंट, 12 पैरामीटर)

सेपरेशन पैरामीटर (एंट्री/एग्ज़िट) को ऑप्टिमाइज़ करें। 12 पैरामीटर पर कोऑर्डिनेट डिसेंट — इंडिकेटर थ्रेशोल्ड, फ़िल्टर, स्टॉप-लॉस, टेक-प्रॉफिट। उच्च-आयामी निर्धारक ऑब्जेक्टिव फ़ंक्शन के लिए कोऑर्डिनेट डिसेंट Optuna से सस्ता है।

चरण 3: मेटा-पैरामीटर (कोऑर्डिनेट डिसेंट)

मेटा-पैरामीटर: अधिकतम होल्ड समय, एग्ज़िट के लिए न्यूनतम PnL, ट्रेलिंग स्टॉप कॉन्फ़िगरेशन। फिर से कोऑर्डिनेट डिसेंट। पठार विश्लेषण के माध्यम से मज़बूती की जांच करें — यदि इष्टतम बिंदु-आकार का है, तो रणनीति ओवर-ऑप्टिमाइज़ है।

चरण 4: कॉम्बो ऑप्टिमाइज़ेशन

पेयर्स (प्राइमरी, फॉलबैक) पर ग्रिड सर्च। प्रत्येक संयोजन के लिए: dual_size चुनें, संयुक्त सिमुलेशन के माध्यम से कैस्केड PnL की गणना करें।

चरण 5: वैलिडेशन

बहु-स्तरीय वैलिडेशन:

चरण 6: रैंकिंग और चयन

स्कोर द्वारा कैस्केड संयोजनों को रैंक करें। शीर्ष-K संयोजन चरण 7 में आगे बढ़ते हैं। स्कोर कॉन्फिडेंस एडजस्टमेंट, फंडिंग लागत और fill_efficiency को ध्यान में रखता है।

चरण 7: ऑर्केस्ट्रेशन

अंतिम चरण: NN रणनीतियों और MM पेयर्स पर कैस्केड मोड में ऑर्केस्ट्रेटर लॉन्च करें। स्लॉट प्रबंधन, प्राथमिकता कतार, संघर्ष समाधान — ऊपर वर्णित सब कुछ।

प्रदर्शन विश्लेषण: कैस्केड बनाम व्यक्तिगत

कैस्केड बनाम व्यक्तिगत रणनीति प्रदर्शन साथ-साथ तुलना: कैस्केड पोर्टफोलियो निष्क्रिय समय के उपयोग के माध्यम से व्यक्तिगत रणनीतियों से बेहतर प्रदर्शन करता है

सैद्धांतिक कैस्केड लाभ

मान लीजिए प्राइमरी tp=15%t_p = 15\% समय ट्रेड करती है PnL/दिन = 0.49% के साथ। फॉलबैक tf=45%t_f = 45\% ट्रेड करती है PnL/दिन = 0.89% के साथ। ओवरलैप = tp×tf=6.75%t_p \times t_f = 6.75\% (स्वतंत्रता मानते हुए)।

केवल प्राइमरी (रणनीति A):

Annual PnL=0.49%×0.15×365=26.8%\text{Annual PnL} = 0.49\% \times 0.15 \times 365 = 26.8\%

कैस्केड (A प्राइमरी + C फॉलबैक):

Annual PnL=0.49%×0.15×365+0.068×0.89%×(0.45−0.0675)×365=26.8%+8.4%=35.2%\text{Annual PnL} = 0.49\% \times 0.15 \times 365 + 0.068 \times 0.89\% \times (0.45 - 0.0675) \times 365 = 26.8\% + 8.4\% = 35.2\%

कैस्केड लाभ: फॉलबैक से PnL में +31%, न्यूनतम ड्रॉडाउन वृद्धि के साथ (0.068×17%=1.16%0.068 \times 17\% = 1.16\% MaxDD में जोड़ा गया)।

कैस्केड कब मदद नहीं करता

कैस्केड तब अप्रभावी होता है जब:

  1. प्राइमरी >80% समय सक्रिय है। कम निष्क्रिय समय — फॉलबैक के लिए कोई जगह नहीं।
  2. रणनीतियाँ अत्यधिक सहसंबंधित हैं। प्राइमरी और फॉलबैक एक साथ सिग्नल उत्पन्न करते हैं — ओवरलैप अधिक है, और फॉलबैक ठीक तब निष्क्रिय होता है जब प्राइमरी भी होती है।
  3. स्विच लागत फॉलबैक PnL से अधिक है। बार-बार स्विचिंग के साथ, कैस्केड कमीशन फॉलबैक मुनाफे को खा जाते हैं।
  4. dual_size बहुत छोटा है। d=0.01d = 0.01 पर, फॉलबैक अपनी क्षमता का केवल 1% कमाता है — कमीशन से नीचे।

तुलना तालिका

कॉन्फ़िगरेशनवार्षिक PnLMaxDDशार्पस्विच लागत
केवल रणनीति A26.8%0.9%1.420
केवल रणनीति C146.1%17%1.150
कैस्केड 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 को फॉलबैक C से +8.4% मिलता है। निष्क्रिय समय के उपयोग के माध्यम से शार्प बढ़ता है। MaxDD मामूली रूप से बढ़ता है (0.9%+0.068×17%≈2.06%0.9\% + 0.068 \times 17\% \approx 2.06\%)।

टेम्पोरल विविधीकरण का गणित

ऊपर वर्णित कैस्केड लाभ — निष्क्रिय समय भरना — रणनीतियों के एक बुक को विविधीकृत करने के दो मौलिक रूप से भिन्न तरीकों में से एक है, और वे अलग-अलग तरीके से स्केल होते हैं। इस अंतर को सही ढंग से समझना बताता है कि कैस्केड क्या खरीद सकता है और क्या नहीं।

मान लीजिए एक बेस रणनीति μ\mu औसत और σ\sigma वोलैटिलिटी के साथ i.i.d. रिटर्न कमाती है प्रति सक्रिय अवधि, ताकि इसका प्रति-अवधि शार्प s=μ/σs = \mu/\sigma हो। नीचे प्रत्येक शार्प को 252\sqrt{252} द्वारा वार्षिकीकृत किया गया है; काम किया गया उदाहरण s=0.05s = 0.05 प्रति अवधि का उपयोग करता है (s252=0.79s\sqrt{252} = 0.79 वार्षिकीकृत), और प्रत्येक संख्या 2,000,000-अवधि के सिमुलेशन से आती है जो क्लोज़्ड फॉर्म से दो दशमलव तक मेल खाती है।

टेम्पोरल विविधीकरण: निष्क्रिय समय भरना

एक रणनीति जो केवल pp अंश समय मार्केट में रहती है — बाकी समय फ्लैट (शून्य रिटर्न, लेकिन शून्य जोखिम भी) — का एक पूर्ण-टाइमलाइन शार्प है

SRfull=p ⋅s.SR_{\text{full}} = \sqrt{p}\,\cdot s.

निष्क्रिय समय औसत को pp से पतला करता है लेकिन मानक विचलन को केवल p\sqrt{p} से, इसलिए अनुपात p\sqrt{p} के रूप में गिरता है: p=0.25p = 0.25 पर वार्षिकीकृत शार्प 0.790.79 से 0.390.39 तक गिर जाता है। यही कारण है कि 15% समय ट्रेड करने वाली उच्च-विश्वास प्राइमरी पूर्ण इक्विटी कर्व पर औसत दिखती है।

एक कैस्केड उस निष्क्रिय समय को भरता है। kk समय-असंयुक्त रणनीतियों को इस तरह टाइल करें कि साथ में वे पूरी टाइमलाइन को कवर करें — हर अवधि में बिल्कुल एक सक्रिय — और संयुक्त स्ट्रीम फिर से N(μ,σ)N(\mu,\sigma) बन जाती है:

SRcascade=s(k से स्वतंत्र).SR_{\text{cascade}} = s \qquad(\text{$k$ से स्वतंत्र}).

1/k1/k कवरेज पर एक रणनीति से kk रणनीतियों तक पूर्ण कवरेज पर जाना शार्प को k\sqrt{k} से बढ़ाता है — सत्यापित: k=2→1.42×k=2 \to 1.42\times, k=4→1.96×k=4 \to 1.96\times, k=8→2.93×k=8 \to 2.93\times — लेकिन यह ss पर संतृप्त हो जाता है, एकल रणनीति का प्रति-अवधि शार्प। टेम्पोरल विविधीकरण बुक के जोखिम-समायोजित रिटर्न को उससे ऊपर नहीं धकेल सकता जो एक रणनीति ट्रेडिंग के दौरान कमाती है, क्योंकि हर क्षण आप बिल्कुल एक रणनीति रखते हैं: कोई औसत नहीं, कोई वैरिएंस रिडक्शन नहीं। यह जो वास्तव में खरीदता है वह है पूंजी पुन: उपयोग — पूंजी की एक इकाई सभी kk रणनीतियों को क्रम में चलाती है। उन्हें एक साथ चलाने के लिए आपको kk इकाइयों (या kkगुना लीवरेज) की आवश्यकता होगी; कैस्केड एक इकाई पर सभी kk एज हार्वेस्ट करता है। पूंजी दक्षता में जीत, जोखिम में नहीं।

सहसंबंधात्मक विविधीकरण: वह √N जिसे कैस्केड छोड़ देता है

दूसरा प्रकार NN रणनीतियों को एक साथ चलाता है, पूंजी को विभाजित करता है, और हर क्षण उनका औसत निकालता है। जोड़ीदार सहसंबंध ρ\rho वाली इक्विकोरिलेटेड रणनीतियों के लिए,

SRparallel=N1+(N−1)ρ  ⋅s,SR_{\text{parallel}} = \sqrt{\frac{N}{1 + (N-1)\rho}}\;\cdot s,

जो असहसंबंधित होने पर (ρ=0\rho = 0) N s\sqrt{N}\,s है और ρ→1\rho \to 1 होने पर ss की ओर ढह जाता है। सत्यापित: 8 असहसंबंधित रणनीतियाँ वार्षिकीकृत 2.252.25 तक पहुंचती हैं (यह 8×0.79\sqrt{8}\times 0.79 है), लेकिन ρ=0.5\rho = 0.5 पर वही आठ केवल 1.041.04 प्रबंधित करती हैं — एक से मुश्किल से बेहतर। यह बिल्कुल मल्टी-पेयर सेक्शन से प्रभावी-विविधीकरण की कटौती है: सहसंबंध N\sqrt{N} पर लगने वाला टैक्स है।

केली आवंटन सहसंबंधात्मक इष्टतम है। औसत वेक्टर μ\boldsymbol\mu और सहप्रसरण Σ\Sigma के साथ, विकास-इष्टतम भार w∗=Σ−1μ\mathbf{w}^{*} = \Sigma^{-1}\boldsymbol\mu हैं, और वे जो शार्प प्राप्त करते हैं वह है

SRKelly=μ⊤Σ−1μ,SR_{\text{Kelly}} = \sqrt{\boldsymbol\mu^{\top} \Sigma^{-1} \boldsymbol\mu},

जो बिल्कुल N/(1+(N−1)ρ) s\sqrt{N/(1+(N-1)\rho)}\,s स्केलिंग को पुन: उत्पन्न करता है (सिमुलेशन और क्लोज़्ड फॉर्म सहमत हैं: आठ असहसंबंधित के लिए 2.252.25, ρ=0.5\rho=0.5 पर 1.001.00)। Σ−1μ\Sigma^{-1}\boldsymbol\mu वही है जो "हर क्षण औसत निकालना" लिखित रूप में दिखता है।

कैस्केड वास्तव में क्या खरीदता है, और इसकी सीमा

आयामटेम्पोरल (कैस्केड)सहसंबंधात्मक (पैरेलल)
रणनीतियाँ कब चलती हैंसमय में असंयुक्तएक साथ
पूंजीपुन: उपयोग (1 इकाई सब चलाती है)विभाजित / लीवरेज्ड (NN इकाइयां)
शार्प स्केलिंगss पर संतृप्त (निष्क्रियता भरता है, पूर्ण कवरेज तक k\sqrt{k})N/(1+(N−1)ρ) ⋅s\sqrt{N/(1+(N-1)\rho)}\,\cdot s
जोखिम कमीकोई नहीं (प्रति क्षण एक रणनीति)N\sqrt{N}, सहसंबंध द्वारा सीमित
यह क्या खरीदता हैपूंजी दक्षता, मार्केट में समयजोखिम-समायोजित रिटर्न

दोनों गुणा होते हैं। एक यथार्थवादी ऑर्केस्ट्रेटर हर क्षण mm (कमज़ोर सहसंबंधित) रणनीतियों को चलाता है — प्रति-क्षण m\sqrt{m} खरीदते हुए — और उन विंडो को समय के साथ टाइल करता है, पूंजी का पुन: उपयोग करते हुए। कुल शार्प प्रति-क्षण संरचना द्वारा निर्धारित होता है,

SRbook=m1+(m−1)ρ  ⋅s,SR_{\text{book}} = \sqrt{\frac{m}{1 + (m-1)\rho}}\;\cdot s,

m=1,2,4m = 1, 2, 4 पर सत्यापित (वार्षिकीकृत 0.790.79, 1.141.14, 1.58=m s1.58 = \sqrt{m}\,s), जबकि टेम्पोरल टाइलिंग पूंजी दक्षता को गुणा करता है, शार्प को नहीं।

सीमा कहां है। एक शुद्ध कैस्केड — एक बार में एक रणनीति सक्रिय — का कुल शार्प बिल्कुल ss है, चाहे कतार में कितनी भी रणनीतियां प्रतीक्षा करें। यह मार्केट-में-समय और पूंजी दक्षता को अधिकतम करता है, लेकिन इसका जोखिम-समायोजित रिटर्न एकल रणनीति तक सीमित है। ss को हराने के लिए आपको क्षण के भीतर विविधीकरण करना होगा — एक साथ कई कमज़ोर सहसंबंधित रणनीतियां चलानी होंगी — और वह लाभ N\sqrt{N} द्वारा सीमित है और सहसंबंध द्वारा क्षीण होता है। डिज़ाइन नियम इससे निकलता है: कैस्केड का उपयोग पूंजी की एक इकाई पर सस्ते में निष्क्रिय समय वापस पाने के लिए करें, और एक साथ विविधीकरण के दुर्लभ, महंगे N\sqrt{N} को केवल वास्तव में असहसंबंधित रणनीतियों पर खर्च करें। सहसंबंधित रणनीतियों को स्टैक करना — समय में या समानांतर में — लगभग कुछ नहीं खरीदता।

ऑर्केस्ट्रेशन: व्यवहार में fill_efficiency

fill efficiency गेज और हीटमैप ~78% पर fill efficiency: हीटमैप रणनीतियों और पेयर्स में समय उपयोग दिखाता है, चमकीले सेल सक्रिय ट्रेडिंग दर्शाते हैं

fill_efficiency पैरामीटर यह निर्धारित करता है कि ऑर्केस्ट्रेटर वास्तव में निष्क्रिय समय का कितना अंश उपयोग करता है। जैसा कि PnL प्रति सक्रिय समय में दिखाया गया, इसे तीन तरीकों से अनुमानित किया जा सकता है:

  1. निश्चित स्थिरांक (0.80) — मोटा लेकिन सार्वभौमिक
  2. विश्लेषणात्मक अनुमान (1−p)Neff(1-p)^{N_{eff}} के माध्यम से — सहसंबंध को ध्यान में रखता है
  3. डेटा से सिमुलेशन — सबसे सटीक

10 पेयर्स पर 3 रणनीतियों वाले कैस्केड के लिए:

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 fill: probability that at least one primary is active
    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 fill: during remaining time
    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])

    # Total fill
    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)
# ~0.78

व्यावहारिक सिफारिशें

व्यावहारिक इंजीनियरिंग चेकलिस्ट कैस्केड डिप्लॉयमेंट के लिए छह मुख्य सिफारिशें — छोटी शुरुआत से लेकर अनुकूली पुनर्कैलिब्रेशन तक

1. दो रणनीतियों से शुरू करें

तुरंत 20 पेयर्स पर 10 रणनीतियां लॉन्च न करें। 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. लेटेंसी का ध्यान रखें

लाइव ट्रेडिंग में, कैस्केड स्विचिंग तात्कालिक नहीं है। फॉलबैक पोज़िशन बंद करना + प्राइमरी खोलना = 2 API कॉल + नेटवर्क लेटेंसी + एक्सचेंज मैचिंग। एक अस्थिर मार्केट पर, कीमत 200-500ms में बदल सकती है। एक स्लिपेज बजट बनाएं।

5. fill_efficiency की निगरानी करें

प्रोडक्शन में वास्तविक fill_efficiency को ट्रैक करें। यदि यह बैकटेस्ट की तुलना में काफी कम है — ऑर्केस्ट्रेटर अपेक्षा के अनुसार निष्क्रिय समय का उपयोग नहीं कर रहा है। कारण: API देरी, अस्वीकृत ऑर्डर, मार्जिन बाधाएं।

6. अनुकूली ऑप्टिमाइज़ेशन का उपयोग करें

कैस्केड पैरामीटर (dual_size, स्कोर वेट्स, स्लॉट सीमाएं) स्थिर नहीं होने चाहिए। ताज़ा डेटा पर आवधिक पुनर्कैलिब्रेशन के लिए अनुकूली ड्रिल-डाउन का उपयोग करें। मार्केट बदलता है — कैस्केड पैरामीटर को इसका अनुसरण करना चाहिए।

"बैकटेस्ट्स विदाउट इल्यूजन्स" सीरीज़: सारांश

सीरीज़ नॉलेज मैप पूर्ण सिस्टम आर्किटेक्चर: गणित से वैलिडेशन से लेकर लाइव ऑर्केस्ट्रेशन तक 13 आपस में जुड़े मॉड्यूल

यह लेख 13+ लेखों की सीरीज़ का समापन है। प्रत्येक लेख ने बैकटेस्ट से प्रोडक्शन तक के रास्ते में एक विशिष्ट समस्या को संबोधित किया। यहां बताया गया है कि वे कैसे जुड़ते हैं:

आधार: रिटर्न गणित

लॉस-प्रॉफिट असममिति — रिटर्न की गुणात्मक प्रकृति, वोलैटिलिटी ड्रैग, केली क्राइटेरियन। यह इसके बाद आने वाली हर चीज़ की गणितीय नींव है: MaxDD लीवरेज क्यों निर्धारित करता है, शार्प कच्चे PnL से अधिक क्यों मायने रखता है, सममित R:R के साथ 50% जीत दर लाभदायक क्यों नहीं है।

वैलिडेशन: कॉन्फिडेंस इंटरवल और मज़बूती

मोंटे कार्लो बूटस्ट्रैप — एकल-बिंदु अनुमान को कॉन्फिडेंस इंटरवल के साथ एक वितरण में बदलना। कोई भी मीट्रिक (PnL, MaxDD, शार्प) केवल कॉन्फिडेंस इंटरवल के साथ ही समझ में आता है।

वॉक-फॉरवर्ड ऑप्टिमाइज़ेशन — आउट-ऑफ-सैंपल वैलिडेशन। ऐतिहासिक डेटा पर एक बैकटेस्ट एक IS परिणाम है; WFO दिखाता है कि रणनीति नए डेटा पर कैसा प्रदर्शन करती है।

पठार विश्लेषण — पैरामीटर मज़बूती जांच। यदि इष्टतम बिंदु-आकार का है, तो रणनीति ओवर-ऑप्टिमाइज़ है।

बैकटेस्ट-लाइव पैरिटी — वास्तविक परिणामों के साथ बैकटेस्ट की तुलना। स्केलिंग से पहले अंतिम जांच।

यथार्थवादी लागत: फंडिंग और लीवरेज

फंडिंग रेट्स लीवरेज को खत्म कर देती हैं — पर्पेचुअल फ्यूचर्स पर लीवरेज की छिपी हुई लागत। फंडिंग को ध्यान में रखे बिना, एक सुंदर बैकटेस्ट घाटे में बदल जाता है।

फंडिंग रेट आर्बिट्राज — क्रॉस-एक्सचेंज रणनीतियों के माध्यम से फंडिंग को खर्च से आय के स्रोत में कैसे बदलें।

मीट्रिक्स और रैंकिंग

PnL प्रति सक्रिय समय — पोर्टफोलियो में रणनीतियों को रैंक करने के लिए मीट्रिक। कच्चा PnL स्केल नहीं करता; PnL/सक्रिय दिन करता है।

सिग्नल सहसंबंध — सहसंबंधित पेयर्स के पोर्टफोलियो में प्रभावी विविधीकरण।

इंफ्रास्ट्रक्चर और ऑप्टिमाइज़ेशन

मल्टी-टाइमफ्रेम बैकटेस्ट के लिए Parquet कैश — तेज़ इटरेशन के लिए डेटा इंफ्रास्ट्रक्चर।

अनुकूली ड्रिल-डाउन — अनुकूली ऑप्टिमाइज़ेशन: मोटा ग्रिड -> संभावित क्षेत्रों में सूक्ष्म-ट्यूनिंग।

Optuna बनाम कोऑर्डिनेट डिसेंट — ऑप्टिमाइज़र चयन: शोरयुक्त ऑब्जेक्टिव के साथ कम आयामों के लिए Optuna, स्मूथ ऑब्जेक्टिव के साथ उच्च आयामों के लिए कोऑर्डिनेट डिसेंट।

Polars बनाम Pandas — बैकटेस्टिंग के लिए DataFrame ऑपरेशन प्रदर्शन।

ऑर्केस्ट्रेशन (यह लेख)

कैस्केड रणनीतियाँ — पिछले सभी घटकों को एक कार्यशील सिस्टम में मिलाना। स्कोर-आधारित आवंटन PnL/सक्रिय समय, कॉन्फिडेंस एडजस्टमेंट, फंडिंग लागत का उपयोग करता है। कैस्केड मोड निष्क्रिय समय भरता है। संयुक्त सिमुलेशन पोर्टफोलियो को वैलिडेट करता है। मोंटे कार्लो बूटस्ट्रैप कैस्केड PnL के लिए कॉन्फिडेंस इंटरवल प्रदान करता है।

प्रत्येक लेख एक स्वतंत्र मॉड्यूल है। साथ में वे डेटा लोडिंग से लेकर एक रणनीति पोर्टफोलियो के लाइव ऑर्केस्ट्रेशन तक की पूरी पाइपलाइन बनाते हैं।

निष्कर्ष

कैस्केड स्ट्रैटेजी पोर्टफोलियो के लिए एकमात्र दृष्टिकोण नहीं है। लेकिन यह सबसे सरल और व्यावहारिक में से एक है: प्राइमरी रणनीति पूरी क्षमता से ट्रेड करती है, फॉलबैक घटी हुई पोज़िशन के साथ निष्क्रिय समय भरता है। दो मुख्य पैरामीटर (dual_size और max_slots) अधिकांश कॉन्फ़िगरेशन के लिए पर्याप्त लचीलापन प्रदान करते हैं।

तीन मुख्य निष्कर्ष:

  1. कैस्केड को केवल संयुक्त सिमुलेशन के माध्यम से बैकटेस्ट किया जाना चाहिए। व्यक्तिगत PnL को जोड़ने से परिणाम फुल जाते हैं। स्विच लागत, ओवरलैप, स्लॉट बाधाएं — यह सब केवल संयुक्त सिमुलेशन में ही पकड़ा जाता है।

  2. dual_size PnL बनाम ड्रॉडाउन ट्रेड-ऑफ को निर्धारित करता है। विशिष्ट इष्टतम 5-10% है। शार्प पर ग्रिड सर्च एक विश्वसनीय चयन विधि है।

  3. ऑर्केस्ट्रेटर एक स्कोर-आधारित प्राथमिकता कतार है। सब कुछ प्रत्येक सिग्नल के लिए एक ही संख्या (स्कोर) में सिमट जाता है। Score = f(PnL/सक्रिय दिन, MaxLev, कॉन्फिडेंस, फंडिंग)। उच्चतम स्कोर वाली रणनीतियों को स्लॉट मिलते हैं। बाकी प्रतीक्षा करती हैं।

"बैकटेस्ट्स विदाउट इल्यूजन्स" सीरीज़ एक बात प्रदर्शित करती है: एक सुंदर बैकटेस्ट और वास्तविक मुनाफे के बीच दर्जनों नुकसान छिपे हैं। हर लेख एक को हटाता है। कैस्केड ऑर्केस्ट्रेशन अंतिम कदम है: वैलिडेटेड रणनीतियों के एक सेट को एक कार्यशील पोर्टफोलियो में बदलना।


उपयोगी लिंक

  1. López de Prado — Advances in Financial Machine Learning: Portfolio Construction
  2. Pardo, R. — The Evaluation and Optimization of Trading Strategies
  3. Ernest Chan — Algorithmic Trading: Winning Strategies and Their Rationale
  4. Perry Kaufman — Trading Systems and Methods, Chapter on Portfolio Allocation
  5. Tomasini, Jaekle — Trading Systems: A New Approach to System Development and Portfolio Optimisation
  6. Bailey, D.H. & López de Prado — The Deflated Sharpe Ratio
  7. Markowitz, H. — Portfolio Selection (1952)
  8. Kelly, J.L. — A New Interpretation of Information Rate (1956)

उद्धरण

@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.}
}