← Kembali ke artikel
March 8, 2026
Bacaan 5 minit

Strategi Cascade: Pelaksanaan Keutamaan dengan Pengisian Fallback

Strategi Cascade: Pelaksanaan Keutamaan dengan Pengisian Fallback
#algotrading
#orkestrasi
#portfolio
#cascade
#strategi
#pengurusan slot

Penutup siri "Backtest Tanpa Ilusi". Cara membina orkestrator daripada N strategi pada M pasangan, melaksanakan mod cascade dengan pelaksanaan keutamaan dan fallback, memilih dual_size, dan mengapa portfolio strategi tidak boleh di-backtest dengan hanya menjumlahkan PnL.

Mengapa Anda Memerlukan Portfolio Strategi

Portfolio strategi dengan modal terbiar Pelbagai strategi bersaing untuk modal yang terhad — kebanyakannya terbiar sementara hanya beberapa berdagang pada satu-satu masa

Anda telah menjalankan strategi melalui saluran penuh. Bootstrap Monte Carlo menunjukkan peratusan ke-5 yang boleh diterima. Walk-forward mengesahkan pulangan luar sampel. Kadar pembiayaan diambil kira, analisis plateau lulus. Strategi ini benar-benar berkesan.

Tetapi ia berdagang hanya 15% daripada masa. Selebihnya 85% modal anda terbiar.

Jalankan strategi kedua? Ketiga? Kesepuluh? Idea ini jelas. Pelaksanaannya tidak. Portfolio strategi mewujudkan masalah yang tidak wujud dengan satu bot sahaja:

  • Konflik: dua strategi ingin membuka posisi bertentangan pada pasangan yang sama.
  • Kekangan: bursa/pengurusan risiko membenarkan tidak lebih daripada KK posisi serentak.
  • Peruntukan: berapa bahagian modal untuk diberikan kepada setiap strategi?
  • Korelasi: 10 strategi pada pasangan kripto yang berkorelasi bukan 10x diversifikasi.

Strategi cascade ialah corak seni bina yang menyelesaikan masalah ini: strategi utama mendapat saiz posisi penuh, manakala strategi fallback mengisi masa terbiar dengan posisi yang dikurangkan.

Konsep Cascade: Utama + Fallback

Hamparan garis masa strategi cascade

Strategi Keyakinan Tinggi (Utama)

Strategi utama ialah strategi dengan kriteria masuk yang ketat. Contohnya, tiga kerangka waktu dengan tiga tahap pengesahan: isyarat pada harian + 4 jam + 1 jam, dengan penapisan volatiliti dan isipadu.

Ciri-ciri:

  • Sedikit dagangan (puluhan sepanjang tempoh backtest)
  • PnL tinggi setiap dagangan
  • Masa dalam posisi rendah (5-15%)
  • Keyakinan tinggi pada setiap masuk

Strategi Fallback

Fallback ialah strategi dengan kriteria yang lebih longgar. Dua kerangka waktu, lebih sedikit penapis, toleransi yang lebih luas. Ia berdagang lebih kerap, tetapi dengan kelebihan yang lebih rendah setiap dagangan.

Ciri-ciri:

  • Lebih banyak dagangan (ratusan sepanjang tempoh)
  • PnL sederhana setiap dagangan
  • Masa dalam posisi tinggi (30-50%)
  • Keyakinan sederhana — dikompensasi dengan saiz posisi yang dikurangkan

Mod Cascade

timeline:  ──────────────────────────────────────────────────
primary:   ___████___________________████████____███________
fallback:  ███____███████████████████________████___████████

capital:   [dual][ full ][ dual_size ][  full  ][ dual  ]

Apabila strategi utama membuka posisi — fallback diam (atau ditutup). Apabila strategi utama terbiar — fallback berdagang pada posisi yang dikurangkan (dual_size). Keutamaan adalah tanpa syarat: strategi utama sentiasa menggantikan fallback.

Strategi untuk Contoh

Sepanjang siri ini kami menggunakan tiga strategi. Berikut adalah parameter mereka untuk tempoh 750 hari:

Parameter Strategi A Strategi B Strategi C
PnL +55% +27% +300%
Dagangan ~500 ~40 ~400
Masa berdagang ~15% ~5% ~45%
MaxDD ~0.9% ~0.75% ~17%
PnL/hari aktif 0.49%/hari 0.72%/hari 0.89%/hari
Karakter Aktiviti sederhana Jarang, keyakinan tinggi Kerap, agresif

Seperti yang kami tunjukkan dalam PnL per Masa Aktif, penarafan mengikut PnL mentah dan mengikut PnL/hari aktif menghasilkan keputusan yang berbeza. Untuk orkestrasi cascade, metrik kedua yang penting.

dual_size Optimum

Permukaan pengoptimuman dual_size Carian grid ke atas dual_size mendedahkan puncak nisbah Sharpe — terlalu besar meningkatkan drawdown, terlalu kecil membazirkan masa terbiar

Masalah Pemilihan

dual_size ialah pecahan posisi penuh yang diterima oleh strategi fallback. Ia adalah parameter cascade utama:

  • Terlalu besar (cth., 0.5 = 50%): apabila strategi utama dan fallback aktif serentak, jumlah pendedahan = 150% sasaran. Drawdown berganda. Asimetri kerugian-keuntungan menjadikan ini sangat mahal.

  • Terlalu kecil (cth., 0.01 = 1%): fallback mengisi 85% masa terbiar tetapi hanya menghasilkan wang yang sedikit. Modal secara berkesan terbiar.

  • Optimum: fallback menyumbang PnL yang bermakna tanpa meningkatkan drawdown secara kritikal semasa operasi serentak dengan strategi utama.

Formalisasi

Biarkan:

  • PpP_p — PnL utama per unit masa
  • PfP_f — PnL fallback per unit masa
  • tpt_p — pecahan masa dalam posisi (utama)
  • tft_f — pecahan masa dalam posisi (fallback)
  • dd — dual_size (0..1)
  • toverlapt_{overlap} — pecahan masa apabila kedua-duanya dalam posisi

Jumlah PnL cascade:

PnLcascade=Pptp+dPf(tftoverlap)\text{PnL}_{cascade} = P_p \cdot t_p + d \cdot P_f \cdot (t_f - t_{overlap})

Jumlah MaxDD (kes terburuk — korelasi penuh):

DDcascadeDDp+dDDf\text{DD}_{cascade} \approx \text{DD}_p + d \cdot \text{DD}_f

Jika kita mengehadkan jumlah drawdown kepada DtargetD_{target}:

dmax=DtargetDDpDDfd_{max} = \frac{D_{target} - \text{DD}_p}{\text{DD}_f}

Carian Grid

Dalam amalan, dual_size optimum ditemui melalui carian grid pada backtest cascade:

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)

Optimum tipikal untuk strategi kripto: dual_size dalam julat 0.05-0.10 (5-10% daripada posisi penuh). Dengan Strategi B sebagai utama (MaxDD 0.75%) dan Strategi A sebagai fallback (MaxDD 0.9%):

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

Kekangan drawdown tidak mengikat — optimum ditentukan oleh Sharpe cascade. Dalam amalan, carian grid biasanya menghasilkan d0.068d \approx 0.068 (6.8%).

Peruntukan Berasaskan Skor

Penarafan strategi berasaskan skor Strategi diberi peringkat mengikut skor komposit — pelarasan keyakinan menghukum sampel kecil, kos pembiayaan mengurangkan kelebihan bersih

Apabila terdapat lebih daripada dua strategi, cascade digeneralisasikan kepada peruntukan berasaskan skor.

Penarafan mengikut PnL per Masa Aktif

Seperti yang diterangkan secara terperinci dalam PnL per Masa Aktif, skor strategi dikira dengan mengambil kira:

  1. PnL per hari aktif — kecekapan penggunaan modal
  2. Pelarasan keyakinan — penalti untuk sampel kecil (taburan-t)
  3. Kos pembiayaan — kos sebenar leverage (Kadar pembiayaan)
  4. MaxLev — penskalaan dengan pertimbangan drawdown (Asimetri kerugian-keuntungan)

score=PnLnet/daykecekapan×365ffilltahunan×MaxLevskala×cconfkebolehpercayaan\text{score} = \underbrace{\text{PnL}_{net/day}}_{\text{kecekapan}} \times \underbrace{365 \cdot f_{fill}}_{\text{tahunan}} \times \underbrace{\text{MaxLev}}_{\text{skala}} \times \underbrace{c_{conf}}_{\text{kebolehpercayaan}}

Pelarasan Keyakinan untuk Strategi Jarang

Strategi B dengan 40 dagangan memerlukan penalti yang serius. Kami menggunakan had bawah selang keyakinan:

cconf=max(0, rˉtα/2,n1snrˉ)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)

cf_b = confidence_factor(np.random.normal(0.0067, 0.028, 40))

cf_a = confidence_factor(np.random.normal(0.0011, 0.008, 500))

Integrasi Kos Pembiayaan

Pada niaga hadapan berterusan, pembiayaan dibayar setiap 8 jam. Dengan leverage LL dan kadar purata rfr_f:

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

Untuk Strategi A dengan MaxLev = 55x dan kadar pembiayaan purata 0.01%:

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

Dengan PnL/hari aktif = 0.49%, PnL bersih adalah negatif: 0.49%1.65%=1.16%0.49\% - 1.65\% = -1.16\%/hari. Strategi ini tidak menguntungkan pada leverage penuh. Analisis terperinci dalam Kadar Pembiayaan Membunuh Leverage Anda.

Orkestrator Pelbagai Strategi

Peruntukan slot orkestrator dan baris gilir keutamaan

Seni Bina

Orkestrator menguruskan NN strategi pada MM pasangan dagangan. Jumlah bilangan posisi berpotensi: N×MN \times M. Tetapi modal terhad — tidak lebih daripada KK posisi serentak (slot) dibenarkan.

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

Pengurusan Slot

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

Penyelesaian Konflik

Tiga tahap konflik:

Tahap 1 — Pasangan yang sama, arah yang sama. Strategi dengan skor lebih tinggi menang. Jika kedua-duanya utama — skor menentukan pemenang. Jika satu utama dan satu fallback — utama menang tanpa syarat.

Tahap 2 — Pasangan yang sama, arah bertentangan. Dilarang: anda tidak boleh secara serentak berada dalam posisi panjang dan pendek pada pasangan yang sama. Strategi dengan skor tertinggi menang.

Tahap 3 — Persaingan merentas pasangan. Apabila semua slot diduduki, isyarat baru mengusir slot dengan skor terendah. Ini berfungsi sebagai baris gilir keutamaan.

Backtesting Cascade: Metodologi

Simulasi bersama strategi cascade Simulasi bersama: keluk ekuiti utama dan fallback dengan zon pertindihan dan hasil cascade gabungan

Mengapa Anda Tidak Boleh Hanya Menjumlahkan PnL

Pendekatan naif: backtest setiap strategi secara berasingan, jumlahkan PnL. Ini menghasilkan hasil yang melambung kerana tiga sebab:

  1. Pertindihan masa. Apabila strategi utama dan fallback aktif serentak, fallback tidak sepatutnya berdagang (atau berdagang pada dual_size). Penjumlahan mudah mengabaikan pertindihan ini.

  2. Kekangan modal. Jumlah posisi adalah terhad. Jika 5 strategi ingin membuka serentak tetapi hanya ada 3 slot — dua strategi tidak akan masuk. PnL mereka tidak boleh dikira.

  3. Kos transaksi. Penukaran cascade (menutup fallback, membuka utama) menghasilkan komisen tambahan yang tidak ada dalam backtest individu.

Simulasi Bersama

Backtest cascade yang betul ialah simulasi bersama semua strategi pada garis masa bersama:

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

Kos Transaksi pada Penukaran

Setiap penukaran cascade (fallback -> utama) memerlukan:

  1. Menutup posisi fallback: yuran taker (0.04% pada niaga hadapan Binance)
  2. Membuka posisi utama: yuran taker (0.04%)
  3. Spread: ~0.01-0.02%

Jumlah kos penukaran: ~0.06-0.10% setiap penukaran. Dengan 100 penukaran sepanjang tempoh:

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

Ini adalah jumlah yang ketara. Cascade dengan penukaran yang kerap boleh mengatasi prestasi strategi tunggal akibat kos transaksi.

Sambungan Pelbagai Pasangan: N Strategi pada M Pasangan

Rangkaian strategi pelbagai pasangan Rangkaian N strategi yang disambungkan ke M pasangan dagangan — kekuatan korelasi menentukan diversifikasi berkesan

Ruang Kombinasi

3 strategi pada 10 pasangan = 30 isyarat berpotensi. Dengan max_slots = 5, orkestrator memilih 5 teratas mengikut skor. Ini adalah masalah kombinatorial: (305)=142506\binom{30}{5} = 142\,506 portfolio yang mungkin pada setiap saat.

Dalam amalan, algoritma tamak (susun mengikut skor, isi dari atas ke bawah) menghasilkan keputusan hampir optimum dalam O(NMlogK)O(N \cdot M \cdot \log K).

Korelasi Antara Pasangan

Pasangan kripto sangat berkorelasi. BTC jatuh — ETH, SOL, AVAX jatuh bersama. Ini bermakna 5 posisi panjang pada 5 pasangan berbeza secara berkesan adalah satu posisi besar pada "pasaran kripto."

Seperti yang kami analisis secara terperinci dalam Korelasi Isyarat, bilangan berkesan posisi bebas:

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

di mana ρˉ\bar{\rho} adalah korelasi purata antara pasangan.

Dengan ρˉ=0.7\bar{\rho} = 0.7 dan 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

Lima posisi pada pasangan berkorelasi bersamaan dengan 1.3 posisi bebas. Diversifikasi hampir tiada.

Implikasi Praktikal untuk Cascade

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


Orkestrator seharusnya mengambil kira korelasi apabila mengisi slot. Dua pilihan:

  1. Bonus diversifikasi: semasa penarafan, tambah bonus kepada skor strategi pada pasangan yang tidak berkorelasi.
  2. Had korelasi: hadkan bilangan posisi dalam arah yang sama pada pasangan berkorelasi.

Saluran Pengoptimuman Cascade

Saluran pengoptimuman lapan peringkat Lapan peringkat yang bersambung dari penyediaan data melalui pengesahan hingga ke orkestrasi langsung — setiap satu dibina di atas yang sebelumnya

Saluran penuh dari data ke pengeluaran terdiri daripada 8 peringkat:

Peringkat 0: Penyediaan Data

Muatkan data sejarah, bina cache Parquet untuk akses pelbagai kerangka waktu. Tanpa caching yang cekap, peringkat berikutnya akan sangat perlahan.

Peringkat 1: TF + Panjang (Grid Hill-Climbing)

Pilih kerangka waktu asas dan panjang tetingkap penunjuk. Grid kasar: TF daripada {1m, 5m, 15m, 1h, 4h}, Panjang daripada {10, 20, 50, 100, 200}. Hill-climbing dari titik grid terbaik.

Peringkat 2: Pemisahan (Penurunan Koordinat, 12 Parameter)

Optimumkan parameter pemisahan (masuk/keluar). Penurunan koordinat ke atas 12 parameter — ambang penunjuk, penapis, stop-loss, take-profit. Penurunan koordinat lebih murah daripada Optuna untuk fungsi objektif deterministik berdimensi tinggi.

Peringkat 3: Meta-Parameter (Penurunan Koordinat)

Meta-parameter: masa pegangan maksimum, PnL minimum untuk keluar, konfigurasi trailing stop. Sekali lagi penurunan koordinat. Semak keteguhan melalui analisis plateau — jika optimum berbentuk titik, strategi terlalu dioptimumkan.

Peringkat 4: Pengoptimuman Kombo

Carian grid ke atas pasangan (Utama, Fallback). Untuk setiap kombinasi: pilih dual_size, kira PnL cascade melalui simulasi bersama.

Peringkat 5: Pengesahan

Pengesahan pelbagai peringkat:

Peringkat 6: Penarafan dan Pemilihan

Beri peringkat kombinasi cascade mengikut skor. Kombinasi K teratas maju ke Peringkat 7. Skor mengambil kira pelarasan keyakinan, kos pembiayaan, dan fill_efficiency.

Peringkat 7: Orkestrasi

Peringkat akhir: lancarkan orkestrator pada NN strategi dan MM pasangan dalam mod cascade. Pengurusan slot, baris gilir keutamaan, penyelesaian konflik — semua yang diterangkan di atas.

Analisis Prestasi: Cascade vs. Individu

Prestasi cascade berbanding strategi individu Perbandingan bersebelahan: portfolio cascade mengatasi strategi individu melalui penggunaan masa terbiar

Kelebihan Cascade Teori

Andaikan strategi utama berdagang tp=15%t_p = 15\% daripada masa dengan PnL/hari = 0.49%. Fallback berdagang tf=45%t_f = 45\% dengan PnL/hari = 0.89%. Pertindihan = tp×tf=6.75%t_p \times t_f = 6.75\% (menganggap kebebasan).

Strategi utama sahaja (Strategi A):

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

Cascade (A utama + C fallback):

Annual PnL=0.49%×0.15×365+0.068×0.89%×(0.450.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\%

Keuntungan cascade: +31% kepada PnL daripada fallback, dengan peningkatan drawdown yang minima (0.068×17%=1.16%0.068 \times 17\% = 1.16\% ditambah kepada MaxDD).

Bila Cascade Tidak Membantu

Cascade tidak berkesan apabila:

  1. Strategi utama aktif >80% daripada masa. Sedikit masa terbiar — tiada ruang untuk fallback.
  2. Strategi sangat berkorelasi. Utama dan fallback menghasilkan isyarat serentak — pertindihan tinggi, dan fallback terbiar tepat apabila utama juga terbiar.
  3. Kos penukaran melebihi PnL fallback. Dengan penukaran yang kerap, komisen cascade memakan keuntungan fallback.
  4. dual_size terlalu kecil. Pada d=0.01d = 0.01, fallback hanya menghasilkan 1% daripada potensinya — di bawah komisen.

Jadual Perbandingan

Konfigurasi PnL Tahunan MaxDD Sharpe Kos penukaran
Strategi A sahaja 26.8% 0.9% 1.42 0
Strategi C sahaja 146.1% 17% 1.15 0
Cascade A+C (d=0.068) 35.2% 2.06% 1.58 ~1.2%
Cascade B+A (d=0.068) 19.4% 1.36% 1.71 ~0.3%
Orkestrator 3 strategi 48.7% 3.1% 1.63 ~2.1%

Cascade A+C: utama A mendapat +8.4% daripada fallback C. Sharpe meningkat melalui penggunaan masa terbiar. MaxDD berkembang secara sederhana (0.9%+0.068×17%2.06%0.9\% + 0.068 \times 17\% \approx 2.06\%).

Orkestrasi: fill_efficiency dalam Amalan

Pengukur dan peta haba fill efficiency Fill efficiency pada ~78%: peta haba menunjukkan penggunaan masa merentas strategi dan pasangan, sel cerah menunjukkan dagangan aktif

Parameter fill_efficiency menentukan bahagian masa terbiar yang sebenarnya digunakan oleh orkestrator. Seperti yang ditunjukkan dalam PnL per Masa Aktif, ia boleh dianggar dengan tiga cara:

  1. Pemalar tetap (0.80) — kasar tetapi universal
  2. Anggaran analitik melalui (1p)Neff(1-p)^{N_{eff}} — mengambil kira korelasi
  3. Simulasi dari data — paling tepat

Untuk cascade dengan 3 strategi pada 10 pasangan:

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)

Cadangan Praktikal

Senarai semak kejuruteraan praktikal Enam cadangan utama untuk penggunaan cascade — daripada bermula kecil hingga penentukuran semula adaptif

1. Mulakan dengan Dua Strategi

Jangan lancarkan 10 strategi pada 20 pasangan sekaligus. Mulakan dengan satu utama + satu fallback pada 3-5 pasangan. Pastikan simulasi bersama sepadan dengan tingkah laku sebenar. Pariti backtest-langsung adalah kritikal: jika backtest cascade menyimpang dari langsung sebanyak 5-10% — terdapat ralat dalam logik orkestrator.

2. dual_size dari Carian Grid, Bukan Intuisi

dual_size optimum bergantung pada pasangan strategi tertentu. 6.8% adalah panduan, bukan pemalar universal. Jalankan carian grid dari 1% hingga 30% dengan langkah 0.5% dan pilih maksimum Sharpe.

3. Had Slot Mentakrifkan Seni Bina

Dengan max_slots = 1, cascade merosot kepada penukaran strategi mudah. Dengan max_slots = 50, kekangan tidak mengikat dan masalah berkurangan kepada portfolio bebas. Zon menarik: max_slots = 3-10, di mana pengurusan slot benar-benar mempengaruhi keputusan.

4. Ambil Kira Kependaman

Dalam dagangan langsung, penukaran cascade bukan serta-merta. Menutup posisi fallback + membuka utama = 2 panggilan API + kependaman rangkaian + padanan bursa. Pada pasaran yang volatil, harga boleh bergerak dalam 200-500ms. Bina belanjawan slippage.

5. Pantau fill_efficiency

Jejak fill_efficiency sebenar dalam pengeluaran. Jika ia jauh lebih rendah daripada backtested — orkestrator tidak menggunakan masa terbiar seperti yang dijangkakan. Punca: kelewatan API, pesanan ditolak, kekangan margin.

6. Gunakan Pengoptimuman Adaptif

Parameter cascade (dual_size, pemberat skor, had slot) tidak seharusnya statik. Gunakan penggerudian turun adaptif untuk penentukuran semula berkala pada data segar. Pasaran berubah — parameter cascade seharusnya mengikuti.

Siri "Backtest Tanpa Ilusi": Ringkasan

Peta pengetahuan siri Seni bina sistem lengkap: 13 modul yang saling berkaitan dari matematik melalui pengesahan hingga orkestrasi langsung

Artikel ini adalah penutup siri 13+ artikel. Setiap artikel menangani satu masalah khusus pada laluan dari backtest ke pengeluaran. Berikut adalah cara mereka berhubung:

Asas: Matematik Pulangan

Asimetri Kerugian-Keuntungan — sifat pendarab pulangan, seret volatiliti, kriteria Kelly. Ini adalah asas matematik untuk semua yang berikut: mengapa MaxDD menentukan leverage, mengapa Sharpe lebih penting daripada PnL mentah, mengapa kadar kemenangan 50% dengan R:R simetri tidak menguntungkan.

Pengesahan: Selang Keyakinan dan Keteguhan

Bootstrap Monte Carlo — menukar anggaran satu titik kepada taburan dengan selang keyakinan. Sebarang metrik (PnL, MaxDD, Sharpe) hanya bermakna dengan selang keyakinan.

Pengoptimuman Walk-Forward — pengesahan luar sampel. Backtest pada data sejarah adalah hasil IS; WFO menunjukkan bagaimana strategi berfungsi pada data baru.

Analisis Plateau — semakan keteguhan parameter. Jika optimum berbentuk titik, strategi terlalu dioptimumkan.

Pariti Backtest-Langsung — membandingkan backtest dengan keputusan sebenar. Semakan akhir sebelum penskalaan.

Kos Realistik: Pembiayaan dan Leverage

Kadar Pembiayaan Membunuh Leverage — kos tersembunyi leverage pada niaga hadapan berterusan. Tanpa mengambil kira pembiayaan, backtest yang cantik bertukar menjadi kerugian.

Arbitraj Kadar Pembiayaan — cara menukar pembiayaan daripada perbelanjaan kepada sumber hasil melalui strategi merentas bursa.

Metrik dan Penarafan

PnL per Masa Aktif — metrik untuk menarafkan strategi dalam portfolio. PnL mentah tidak berskala; PnL/hari aktif berskala.

Korelasi Isyarat — diversifikasi berkesan dalam portfolio pasangan berkorelasi.

Infrastruktur dan Pengoptimuman

Cache Parquet untuk Backtest Pelbagai Kerangka Waktu — infrastruktur data untuk lelaran yang cepat.

Penggerudian Turun Adaptif — pengoptimuman adaptif: grid kasar -> penalaan halus di zon berpotensi.

Optuna vs. Penurunan Koordinat — pemilihan pengoptimum: Optuna untuk dimensi rendah dengan objektif bising, penurunan koordinat untuk dimensi tinggi dengan objektif lancar.

Polars vs Pandas — prestasi operasi DataFrame untuk backtesting.

Orkestrasi (Artikel Ini)

Strategi Cascade — menggabungkan semua komponen sebelumnya ke dalam sistem yang berfungsi. Peruntukan berasaskan skor menggunakan PnL/masa aktif, pelarasan keyakinan, kos pembiayaan. Mod cascade mengisi masa terbiar. Simulasi bersama mengesahkan portfolio. Bootstrap Monte Carlo memberikan selang keyakinan untuk PnL cascade.

Setiap artikel adalah modul bebas. Bersama-sama mereka membentuk saluran lengkap dari pemuatan data hingga orkestrasi langsung portfolio strategi.

Kesimpulan

Cascade bukan satu-satunya pendekatan untuk portfolio strategi. Tetapi ia adalah salah satu yang paling mudah dan praktikal: strategi utama berdagang pada kapasiti penuh, fallback mengisi masa terbiar pada posisi yang dikurangkan. Dua parameter utama (dual_size dan max_slots) memberikan fleksibiliti yang mencukupi untuk kebanyakan konfigurasi.

Tiga pengajaran:

  1. Cascade mesti di-backtest melalui simulasi bersama sahaja. Menjumlahkan PnL individu melambungkan keputusan. Kos penukaran, pertindihan, kekangan slot — semua ini hanya ditangkap dalam simulasi bersama.

  2. dual_size menentukan pertukaran PnL vs. drawdown. Optimum tipikal ialah 5-10%. Carian grid pada Sharpe adalah kaedah pemilihan yang boleh dipercayai.

  3. Orkestrator adalah baris gilir keutamaan berasaskan skor. Segalanya dirangkum kepada satu nombor (skor) untuk setiap isyarat. Skor = f(PnL/hari aktif, MaxLev, keyakinan, pembiayaan). Strategi dengan skor tertinggi mendapat slot. Selebihnya menunggu.

Siri "Backtest Tanpa Ilusi" menunjukkan satu perkara: antara backtest yang cantik dan keuntungan sebenar terdapat berpuluh-puluh perangkap. Setiap artikel menghapuskan satu. Orkestrasi cascade adalah langkah terakhir: menukar set strategi yang telah disahkan kepada portfolio yang berfungsi.


Pautan Berguna

  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)

Petikan

@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.}
}
Penafian: Maklumat yang disediakan dalam artikel ini adalah untuk tujuan pendidikan dan maklumat sahaja dan bukan merupakan nasihat kewangan, pelaburan, atau dagangan. Dagangan mata wang kripto melibatkan risiko kerugian yang ketara.

Pengarang

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Kekal Mendahului Pasaran

Langgan surat berita kami untuk pandangan dagangan AI eksklusif, analisis pasaran, dan kemas kini platform.

Kami menghormati privasi anda. Berhenti melanggan pada bila-bila masa.