Strategi Cascade: Pelaksanaan Keutamaan dengan Pengisian Fallback
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
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 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

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
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:
- — PnL utama per unit masa
- — PnL fallback per unit masa
- — pecahan masa dalam posisi (utama)
- — pecahan masa dalam posisi (fallback)
- — dual_size (0..1)
- — pecahan masa apabila kedua-duanya dalam posisi
Jumlah PnL cascade:
Jumlah MaxDD (kes terburuk — korelasi penuh):
Jika kita mengehadkan jumlah drawdown kepada :
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%):
Kekangan drawdown tidak mengikat — optimum ditentukan oleh Sharpe cascade. Dalam amalan, carian grid biasanya menghasilkan (6.8%).
Peruntukan 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:
- PnL per hari aktif — kecekapan penggunaan modal
- Pelarasan keyakinan — penalti untuk sampel kecil (taburan-t)
- Kos pembiayaan — kos sebenar leverage (Kadar pembiayaan)
- MaxLev — penskalaan dengan pertimbangan drawdown (Asimetri kerugian-keuntungan)
Pelarasan Keyakinan untuk Strategi Jarang
Strategi B dengan 40 dagangan memerlukan penalti yang serius. Kami menggunakan had bawah selang keyakinan:
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 dan kadar purata :
Untuk Strategi A dengan MaxLev = 55x dan kadar pembiayaan purata 0.01%:
Dengan PnL/hari aktif = 0.49%, PnL bersih adalah negatif: /hari. Strategi ini tidak menguntungkan pada leverage penuh. Analisis terperinci dalam Kadar Pembiayaan Membunuh Leverage Anda.
Orkestrator Pelbagai Strategi

Seni Bina
Orkestrator menguruskan strategi pada pasangan dagangan. Jumlah bilangan posisi berpotensi: . Tetapi modal terhad — tidak lebih daripada 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: 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:
-
Pertindihan masa. Apabila strategi utama dan fallback aktif serentak, fallback tidak sepatutnya berdagang (atau berdagang pada dual_size). Penjumlahan mudah mengabaikan pertindihan ini.
-
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.
-
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:
- Menutup posisi fallback: yuran taker (0.04% pada niaga hadapan Binance)
- Membuka posisi utama: yuran taker (0.04%)
- Spread: ~0.01-0.02%
Jumlah kos penukaran: ~0.06-0.10% setiap penukaran. Dengan 100 penukaran sepanjang tempoh:
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 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: portfolio yang mungkin pada setiap saat.
Dalam amalan, algoritma tamak (susun mengikut skor, isi dari atas ke bawah) menghasilkan keputusan hampir optimum dalam .
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:
di mana adalah korelasi purata antara pasangan.
Dengan dan :
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:
- Bonus diversifikasi: semasa penarafan, tambah bonus kepada skor strategi pada pasangan yang tidak berkorelasi.
- Had korelasi: hadkan bilangan posisi dalam arah yang sama pada pasangan berkorelasi.
Saluran Pengoptimuman Cascade
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:
- Pelbagai simbol: strategi diuji pada 10+ pasangan, bukan hanya pasangan pengoptimuman
- Walk-forward: tetingkap IS/OOS gelongsor
- Kestabilan parameter: analisis plateau pada setiap peringkat
- Bootstrap Monte Carlo: selang keyakinan untuk PnL cascade
- Pariti backtest-langsung: perbandingan backtest dengan dagangan kertas
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 strategi dan pasangan dalam mod cascade. Pengurusan slot, baris gilir keutamaan, penyelesaian konflik — semua yang diterangkan di atas.
Analisis Prestasi: Cascade vs. Individu
Perbandingan bersebelahan: portfolio cascade mengatasi strategi individu melalui penggunaan masa terbiar
Kelebihan Cascade Teori
Andaikan strategi utama berdagang daripada masa dengan PnL/hari = 0.49%. Fallback berdagang dengan PnL/hari = 0.89%. Pertindihan = (menganggap kebebasan).
Strategi utama sahaja (Strategi A):
Cascade (A utama + C fallback):
Keuntungan cascade: +31% kepada PnL daripada fallback, dengan peningkatan drawdown yang minima ( ditambah kepada MaxDD).
Bila Cascade Tidak Membantu
Cascade tidak berkesan apabila:
- Strategi utama aktif >80% daripada masa. Sedikit masa terbiar — tiada ruang untuk fallback.
- Strategi sangat berkorelasi. Utama dan fallback menghasilkan isyarat serentak — pertindihan tinggi, dan fallback terbiar tepat apabila utama juga terbiar.
- Kos penukaran melebihi PnL fallback. Dengan penukaran yang kerap, komisen cascade memakan keuntungan fallback.
- dual_size terlalu kecil. Pada , 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 ().
Orkestrasi: fill_efficiency dalam Amalan
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:
- Pemalar tetap (0.80) — kasar tetapi universal
- Anggaran analitik melalui — mengambil kira korelasi
- 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
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
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:
-
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.
-
dual_size menentukan pertukaran PnL vs. drawdown. Optimum tipikal ialah 5-10%. Carian grid pada Sharpe adalah kaedah pemilihan yang boleh dipercayai.
-
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
- 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)
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.}
}
Pengarang
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.