Каскадты стратегиялар: Резервпен толтыру арқылы басымдықты орындау
«Иллюзиясыз бэктесттер» сериясының финалы. N стратегия мен M жұп негізінде оркестраторды қалай құру керек, басымдық пен резервті орындауы бар каскадты режимді іске асыру, dual_size таңдау және неге стратегиялар портфелін жай ғана PnL қосу арқылы бэктестілеуге болмайтыны туралы.
Неге стратегиялар портфелі керек
Бірнеше стратегия шектеулі капитал үшін бәсекелеседі — олардың көбі бос тұрады, әр сәтте тек біреуі ғана сауда жасайды
Сіз стратегияны толық конвейерден өткіздіңіз. Монте-Карло бутстрап қолайлы 5-ші процентильді көрсетті. Walk-forward үлгіден тыс кірістерді растады. Фандинг мөлшерлемелері есептелді, плато талдауы өтті. Стратегия шынымен жұмыс істейді.
Бірақ ол уақыттың 15%-ында ғана сауда жасайды. Қалған 85% уақытта сіздің капиталыңыз бос тұрады.
Екінші стратегияны іске қосу керек пе? Үшінші? Оныншы? Идея айқын. Іске асыру — жоқ. Стратегиялар портфелі бір боттың өзінде жоқ мәселелерді тудырады:
- Қайшылықтар: екі стратегия бір жұп бойынша қарама-қарсы позицияларды ашқысы келеді.
- Шектеулер: биржа/тәуекел-менеджмент бір уақытта -дан аспайтын позицияларға ғана рұқсат етеді.
- Бөлу: әр стратегияға капиталдың қандай үлесін беру керек?
- Корреляция: корреляцияланған крипто жұптарында 10 стратегия — бұл 10 есе диверсификация емес.
Каскадты стратегия — бұл осы мәселелерді шешетін архитектуралық үлгі: негізгі стратегия толық позиция мөлшерін алады, ал резервті стратегия бос уақытты азайтылған позициямен толтырады.
Каскад тұжырымдамасы: Негізгі + Резерв

Жоғары сенімділікті стратегия (Негізгі)
Негізгі — қатаң кіру критерийлері бар стратегия. Мысалы, үш растайтын деңгейі бар үштік уақыт кадры: тәуліктік + 4-сағаттық + сағаттық сигнал, волатильдік пен көлемді сүзу арқылы.
Сипаттамалары:
- Аз мәмілелер (бэктест кезеңінде ондаған)
- Мәмілеге шаққандағы жоғары PnL
- Позицияда болу уақыты аз (5-15%)
- Әр кіруге деген жоғары сенімділік
Резервті стратегия
Резерв — жеңілдетілген критерийлері бар стратегия. Қосарлы уақыт кадры, аз сүзгілер, кеңірек шектер. Ол жиірек сауда жасайды, бірақ мәмілеге шаққандағы артықшылығы төмен.
Сипаттамалары:
- Көбірек мәмілелер (кезеңде жүздеген)
- Орташа PnL мәмілеге шаққанда
- Позицияда болу уақыты жоғары (30-50%)
- Орташа сенімділік — азайтылған позиция мөлшерімен өтеледі
Каскадты режим
timeline: ──────────────────────────────────────────────────
primary: ___████___________________████████____███________
fallback: ███____███████████████████________████___████████
capital: [dual][ full ][ dual_size ][ full ][ dual ]
Негізгі стратегия позиция ашқанда — резерв үнсіз қалады (немесе жабылады). Негізгі бос тұрғанда — резерв азайтылған позициямен (dual_size) сауда жасайды. Басымдық сөзсіз: негізгі әрқашан резервті ығыстырады.
Мысалдарға арналған стратегиялар
Серия бойы біз үш стратегияны қолдандық. Міне олардың 750 күндік кезеңге арналған параметрлері:
| Параметр | Стратегия A | Стратегия B | Стратегия C |
|---|---|---|---|
| PnL | +55% | +27% | +300% |
| Мәмілелер | ~500 | ~40 | ~400 |
| Сауда уақыты | ~15% | ~5% | ~45% |
| MaxDD | ~0.9% | ~0.75% | ~17% |
| Белсенді күнге PnL | 0.49%/к | 0.72%/к | 0.89%/к |
| Сипаты | Орташа белсенділік | Сирек, жоғары сенімділік | Жиі, агрессивті |
Белсенді уақытқа шаққандағы PnL мақаласында көрсеткеніміздей, шикі PnL бойынша және белсенді күнге шаққандағы PnL бойынша реттеу әртүрлі нәтижелер береді. Каскадты оркестрация үшін маңыздысы — екінші көрсеткіш.
Оңтайлы dual_size
dual_size бойынша тор іздеу Шарп коэффициентінің шыңын көрсетеді — тым үлкені просадканы арттырады, тым кішкентайы бос уақытты жоғалтады
Таңдау мәселесі
dual_size — резервті стратегия алатын толық позицияның үлесі. Бұл негізгі каскад параметрі:
-
Тым үлкен (мысалы, 0.5 = 50%): негізгі мен резерв бір уақытта белсенді болғанда, жалпы экспозиция = мақсаттың 150%. Просадка екі есе артады. Шығын-пайда асимметриясы мұны шектен тыс қымбат етеді.
-
Тым кіші (мысалы, 0.01 = 1%): резерв бос уақыттың 85%-ын толтырады, бірақ тиынды ғана табады. Капитал іс жүзінде бос тұрады.
-
Оңтайлы: резерв негізгімен бір уақытта жұмыс істеу кезінде просадканы сыни түрде арттырмай, мағыналы PnL қосады.
Формализация
Белгілейік:
- — негізгі стратегияның уақыт бірлігіне PnL
- — резервтің уақыт бірлігіне PnL
- — позицияда болу уақытының үлесі (негізгі)
- — позицияда болу уақытының үлесі (резерв)
- — dual_size (0..1)
- — екеуі де позицияда болатын уақыт үлесі
Жалпы каскадты PnL:
Жалпы MaxDD (ең нашар жағдай — толық корреляция):
Егер жалпы просадканы шамасымен шектесек:
Тор іздеу
Іс жүзінде оңтайлы dual_size каскадты бэктестте тор іздеу арқылы табылады:
import numpy as np
from dataclasses import dataclass
@dataclass
class CascadeResult:
dual_size: float
total_pnl: float
max_dd: float
sharpe: float
pnl_per_active_day: float
def grid_search_dual_size(
primary_equity: np.ndarray, # equity curve primary (minute bars)
fallback_equity: np.ndarray, # equity curve fallback (minute bars)
primary_positions: np.ndarray, # 1 = in position, 0 = flat
fallback_positions: np.ndarray,
grid: np.ndarray = np.arange(0.01, 0.30, 0.005),
) -> list[CascadeResult]:
"""
Grid search for dual_size.
primary_equity and fallback_equity are log-returns, minute bars.
"""
results = []
for d in grid:
fallback_active = fallback_positions & ~primary_positions
cascade_returns = (
primary_equity * primary_positions
+ d * fallback_equity * fallback_active
)
equity_curve = np.cumprod(1 + cascade_returns)
peak = np.maximum.accumulate(equity_curve)
drawdown = (equity_curve - peak) / peak
max_dd = drawdown.min()
total_pnl = equity_curve[-1] - 1
sharpe = (
np.mean(cascade_returns) / np.std(cascade_returns)
* np.sqrt(525_600) # minutes per year
) if np.std(cascade_returns) > 0 else 0
active_minutes = np.sum(primary_positions | fallback_active)
active_days = active_minutes / (24 * 60)
pnl_per_day = total_pnl / active_days if active_days > 0 else 0
results.append(CascadeResult(
dual_size=d,
total_pnl=total_pnl,
max_dd=max_dd,
sharpe=sharpe,
pnl_per_active_day=pnl_per_day,
))
return sorted(results, key=lambda r: r.sharpe, reverse=True)
Крипто стратегияларына арналған типтік оптимум: dual_size 0.05-0.10 диапазонында (толық позицияның 5-10%). Стратегия B негізгі ретінде (MaxDD 0.75%) және Стратегия A резерв ретінде (MaxDD 0.9%):
Просадка шектеуі байланыстырушы емес — оңтайлы мән каскадты Шарп коэффициентімен анықталады. Іс жүзінде тор іздеу әдетте (6.8%) береді.
Балл негізінде бөлу
Композиттік балл бойынша реттелген стратегиялар — сенімділікті түзету аз үлгілерді жазалайды, фандинг шығындары таза артықшылықты азайтады
Екіден көп стратегия болғанда, каскад балл негізінде бөлуге дейін жалпыланады.
Белсенді уақытқа шаққандағы PnL бойынша реттеу
Белсенді уақытқа шаққандағы PnL мақаласында толық сипатталғандай, стратегия баллы келесіні ескере отырып есептеледі:
- Белсенді күнге PnL — капиталды пайдалану тиімділігі
- Сенімділікті түзету — аз үлгілерге арналған айыппұл (t-үлестіру)
- Фандинг шығындары — левередждің нақты құны (Фандинг мөлшерлемелері)
- MaxLev — просадканы ескере отырып масштабтау (Шығын-пайда асимметриясы)
Сирек стратегияларға арналған сенімділікті түзету
40 мәмілесі бар Стратегия B елеулі айыппұл талап етеді. Біз сенімділік интервалының төменгі шегін қолданамыз:
import scipy.stats as st
import numpy as np
def confidence_factor(trade_returns: np.ndarray, confidence: float = 0.95) -> float:
"""Confidence factor: 0..1, penalty for small samples."""
n = len(trade_returns)
if n < 10:
return 0.0
mean_r = np.mean(trade_returns)
if mean_r <= 0:
return 0.0
se = np.std(trade_returns, ddof=1) / np.sqrt(n)
t_crit = st.t.ppf(1 - (1 - confidence) / 2, df=n - 1)
ci_lower = mean_r - t_crit * se
return max(0.0, ci_lower / mean_r)
cf_b = confidence_factor(np.random.normal(0.0067, 0.028, 40))
cf_a = confidence_factor(np.random.normal(0.0011, 0.008, 500))
Фандинг шығынын біріктіру
Мәңгілік фьючерстерде фандинг әрбір 8 сағат сайын төленеді. левереджі және орташа мөлшерлеме кезінде:
MaxLev = 55x және орташа фандинг мөлшерлемесі 0.01% болатын Стратегия A үшін:
Белсенді күнге PnL = 0.49% болғанда, таза PnL теріс: /күн. Стратегия толық левереджде тиімсіз. Толық талдау Фандинг мөлшерлемелері левереджіңізді өлтіреді мақаласында.
Көп стратегиялы оркестратор

Архитектура
Оркестратор сауда жұптарында стратегияны басқарады. Ықтимал позициялардың жалпы саны: . Бірақ капитал шектеулі — бір уақытта -дан аспайтын позицияларға (слоттарға) рұқсат етіледі.
┌─────────────────────────────────────────────┐
│ ORCHESTRATOR │
│ │
│ Signal Queue (sorted by score): │
│ ┌──────────────────────────────────────┐ │
│ │ 1. Strategy C × ETHUSDT score=223 │ │
│ │ 2. Strategy B × BTCUSDT score=142 │ │
│ │ 3. Strategy A × SOLUSDT score=100 │ │
│ │ 4. Strategy C × BTCUSDT score=89 │ │
│ │ 5. Strategy A × ETHUSDT score=76 │ │
│ └──────────────────────────────────────┘ │
│ │
│ Active Slots (max_parallel = 3): │
│ ┌──────────────────────────────────────┐ │
│ │ Slot 1: Strategy C × ETHUSDT [FULL] │ │
│ │ Slot 2: Strategy B × BTCUSDT [FULL] │ │
│ │ Slot 3: Strategy A × SOLUSDT [DUAL] │ │
│ └──────────────────────────────────────┘ │
│ │
│ Conflict Rules: │
│ - One position per pair │
│ - Primary displaces fallback on same pair │
│ - Higher score wins for cross-pair slots │
└─────────────────────────────────────────────┘
Слоттарды басқару
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
import heapq
import time
class SlotType(Enum):
FULL = "full" # primary strategy, 100% position
DUAL = "dual" # fallback strategy, dual_size position
@dataclass
class Signal:
strategy_id: str
pair: str
direction: str # "long" | "short"
score: float
is_primary: bool # primary or fallback
timestamp: float
@dataclass(order=True)
class Slot:
"""A single orchestrator slot."""
priority: float = field(compare=True) # negative score for min-heap
strategy_id: str = field(compare=False)
pair: str = field(compare=False)
slot_type: SlotType = field(compare=False)
entry_time: float = field(compare=False)
class Orchestrator:
"""
Multi-strategy orchestrator with cascade mode.
Manages N strategies x M pairs within max_parallel_positions slots.
Primary strategies have unconditional priority over fallback.
"""
def __init__(
self,
max_parallel_positions: int = 10,
dual_size: float = 0.068,
min_score: float = 0,
):
self.max_parallel = max_parallel_positions
self.dual_size = dual_size
self.min_score = min_score
self.active_slots: dict[str, Slot] = {} # pair -> Slot
self.pending_signals: list[Signal] = []
def on_signal(self, signal: Signal) -> Optional[dict]:
"""
Process a new signal. Returns an action or None.
Actions:
- {"action": "open", "pair": ..., "size": ..., "slot_type": ...}
- {"action": "replace", "pair": ..., "close_strategy": ..., "open_strategy": ...}
- None (signal rejected)
"""
if signal.score < self.min_score:
return None
pair = signal.pair
if pair in self.active_slots:
existing = self.active_slots[pair]
if signal.is_primary and existing.slot_type == SlotType.DUAL:
self.active_slots[pair] = Slot(
priority=-signal.score,
strategy_id=signal.strategy_id,
pair=pair,
slot_type=SlotType.FULL,
entry_time=signal.timestamp,
)
return {
"action": "replace",
"pair": pair,
"close_strategy": existing.strategy_id,
"open_strategy": signal.strategy_id,
"size": 1.0,
}
if signal.score > -existing.priority:
slot_type = SlotType.FULL if signal.is_primary else SlotType.DUAL
size = 1.0 if signal.is_primary else self.dual_size
self.active_slots[pair] = Slot(
priority=-signal.score,
strategy_id=signal.strategy_id,
pair=pair,
slot_type=slot_type,
entry_time=signal.timestamp,
)
return {
"action": "replace",
"pair": pair,
"close_strategy": existing.strategy_id,
"open_strategy": signal.strategy_id,
"size": size,
}
return None # existing has higher priority
if len(self.active_slots) < self.max_parallel:
slot_type = SlotType.FULL if signal.is_primary else SlotType.DUAL
size = 1.0 if signal.is_primary else self.dual_size
self.active_slots[pair] = Slot(
priority=-signal.score,
strategy_id=signal.strategy_id,
pair=pair,
slot_type=slot_type,
entry_time=signal.timestamp,
)
return {
"action": "open",
"pair": pair,
"strategy": signal.strategy_id,
"size": size,
"slot_type": slot_type,
}
worst_pair = min(
self.active_slots,
key=lambda p: -self.active_slots[p].priority,
)
worst_slot = self.active_slots[worst_pair]
if signal.score > -worst_slot.priority:
del self.active_slots[worst_pair]
slot_type = SlotType.FULL if signal.is_primary else SlotType.DUAL
size = 1.0 if signal.is_primary else self.dual_size
self.active_slots[pair] = Slot(
priority=-signal.score,
strategy_id=signal.strategy_id,
pair=pair,
slot_type=slot_type,
entry_time=signal.timestamp,
)
return {
"action": "replace",
"pair": pair,
"close_strategy": worst_slot.strategy_id,
"close_pair": worst_pair,
"open_strategy": signal.strategy_id,
"size": size,
}
return None # all active slots have higher scores
def on_exit(self, pair: str) -> None:
"""Strategy closed a position."""
if pair in self.active_slots:
del self.active_slots[pair]
def utilization(self) -> float:
"""Current slot utilization."""
return len(self.active_slots) / self.max_parallel
def fill_efficiency_snapshot(self) -> float:
"""Weighted utilization: FULL=1.0, DUAL=dual_size."""
total = sum(
1.0 if s.slot_type == SlotType.FULL else self.dual_size
for s in self.active_slots.values()
)
return total / self.max_parallel
Қайшылықтарды шешу
Үш деңгейлі қайшылық:
1-деңгей — Бір жұп, бір бағыт. Жоғары баллы бар стратегия жеңеді. Егер екеуі де негізгі болса — баллы жеңімпазды анықтайды. Егер біреуі негізгі, ал екіншісі резерв болса — негізгі сөзсіз жеңеді.
2-деңгей — Бір жұп, қарама-қарсы бағыт. Тыйым салынған: бір жұпта бір уақытта лонг та, шорт та бола алмайсыз. Ең жоғары баллы бар стратегия жеңеді.
3-деңгей — Жұп аралық бәсекелестік. Барлық слоттар бос болмағанда, жаңа сигнал ең төмен баллы бар слотты ығыстырады. Бұл басымдық кезегі ретінде жұмыс істейді.
Каскадты бэктестілеу: Әдістеме
Бірлескен симуляция: қабаттасу аймақтары бар негізгі және резервті equity қисықтары және біріктірілген каскадты нәтиже
Неге жай ғана PnL қосуға болмайды
Қарапайым тәсіл: әр стратегияны бөлек бэктестілеу, PnL қосу. Бұл үш себеп бойынша асыра көрсетілген нәтиже береді:
-
Уақыттың қабаттасуы. Негізгі мен резерв бір уақытта белсенді болғанда, резерв сауда жасамауы керек (немесе dual_size-бен сауда жасайды). Қарапайым қосу бұл қабаттасуды елемейді.
-
Капитал шектеуі. Жалпы позиция шектеулі. Егер 5 стратегия бір уақытта ашылғысы келсе, бірақ тек 3 слот болса — екі стратегия кірмейді. Олардың PnL есептелмейді.
-
Транзакция шығындары. Каскадты ауысу (резервті жабу, негізгіні ашу) жеке бэктестерде жоқ қосымша комиссиялар тудырады.
Бірлескен симуляция
Дұрыс каскадты бэктест — бұл барлық стратегиялардың ортақ уақыт кестесінде бірлескен симуляциясы:
import numpy as np
from typing import NamedTuple
class Trade(NamedTuple):
strategy: str
pair: str
entry_time: int # minute index
exit_time: int # minute index
pnl_per_minute: float # log-return per minute
is_primary: bool
score: float
def backtest_cascade(
all_trades: list[Trade],
total_minutes: int,
max_slots: int = 10,
dual_size: float = 0.068,
switch_cost: float = 0.0006, # 0.06% round-trip
) -> dict:
"""
Joint simulation of cascade portfolio.
Walk through each minute, apply orchestrator rules,
calculate PnL accounting for overlap and slot constraints.
"""
entries = {}
exits = {}
active_trades = {} # trade_id -> Trade
for i, trade in enumerate(all_trades):
entries.setdefault(trade.entry_time, []).append((i, trade))
exits.setdefault(trade.exit_time, []).append((i, trade))
active_slots = {} # pair -> (trade_id, SlotType)
equity = np.ones(total_minutes)
switch_costs_total = 0.0
for t in range(1, total_minutes):
for trade_id, trade in exits.get(t, []):
if trade.pair in active_slots:
slot_id, _ = active_slots[trade.pair]
if slot_id == trade_id:
del active_slots[trade.pair]
new_signals = sorted(
entries.get(t, []),
key=lambda x: x[1].score,
reverse=True,
)
for trade_id, trade in new_signals:
pair = trade.pair
if pair in active_slots:
existing_id, existing_type = active_slots[pair]
existing_trade = all_trades[existing_id]
if trade.is_primary and existing_type == SlotType.DUAL:
active_slots[pair] = (trade_id, SlotType.FULL)
switch_costs_total += switch_cost
continue
if trade.score > existing_trade.score:
slot_type = SlotType.FULL if trade.is_primary else SlotType.DUAL
active_slots[pair] = (trade_id, slot_type)
switch_costs_total += switch_cost
elif len(active_slots) < max_slots:
slot_type = SlotType.FULL if trade.is_primary else SlotType.DUAL
active_slots[pair] = (trade_id, slot_type)
minute_return = 0.0
for pair, (trade_id, slot_type) in active_slots.items():
trade = all_trades[trade_id]
size = 1.0 if slot_type == SlotType.FULL else dual_size
minute_return += trade.pnl_per_minute * size
equity[t] = equity[t - 1] * (1 + minute_return)
peak = np.maximum.accumulate(equity)
max_dd = ((equity - peak) / peak).min()
total_pnl = equity[-1] - 1 - switch_costs_total
return {
"total_pnl": total_pnl,
"max_dd": max_dd,
"switch_costs": switch_costs_total,
"equity_curve": equity,
}
Ауысу кезіндегі транзакция шығыны
Әр каскадты ауысу (резервтен -> негізгіге) талап етеді:
- Резерв позициясын жабу: taker комиссиясы (Binance фьючерстерінде 0.04%)
- Негізгі позицияны ашу: taker комиссиясы (0.04%)
- Спред: ~0.01-0.02%
Жалпы ауысу шығыны: ауысу үшін ~0.06-0.10%. Кезең ішінде 100 ауысу болғанда:
Бұл елеулі сома. Жиі ауысатын каскад транзакция шығындарынан жалғыз стратегиядан төмен нәтиже көрсетуі мүмкін.
Көп жұпты кеңейту: M жұпта N стратегия
N стратегияны M сауда жұбымен байланыстыратын желі — корреляция күші тиімді диверсификацияны анықтайды
Комбинация кеңістігі
10 жұпта 3 стратегия = 30 ықтимал сигнал. max_slots = 5 болғанда, оркестратор балл бойынша ең жоғары 5-ті таңдайды. Бұл комбинаторлық мәселе: әр сәтте ықтимал портфель.
Іс жүзінде жалпақ алгоритм (балл бойынша реттеу, жоғарыдан төмен қарай толтыру) уақытында оңтайлыға жақын нәтижелер береді.
Жұптар арасындағы корреляция
Крипто жұптары қатты корреляцияланған. BTC құлайды — ETH, SOL, AVAX бірге құлайды. Бұл 5 түрлі жұпта 5 лонг позиция іс жүзінде "крипто нарығындағы" бір үлкен позиция дегенді білдіреді.
Сигналдар корреляциясы мақаласында толық талдағанымыздай, тәуелсіз позициялардың тиімді саны:
мұндағы — жұптар арасындағы орташа корреляция.
және болғанда:
Корреляцияланған жұптардағы бес позиция 1.3 тәуелсіз позицияға тең. Диверсификация іс жүзінде жоқ.
Каскад үшін практикалық салдары
def effective_diversification(
positions: list[dict], # [{"pair": "BTCUSDT", "direction": "long"}, ...]
correlation_matrix: np.ndarray,
pair_index: dict[str, int],
) -> float:
"""
Calculate effective diversification of open positions.
Returns:
N_eff / N — diversification coefficient (0..1)
"""
n = len(positions)
if n <= 1:
return 1.0
total_corr = 0.0
pairs_count = 0
for i in range(n):
for j in range(i + 1, n):
idx_i = pair_index[positions[i]["pair"]]
idx_j = pair_index[positions[j]["pair"]]
rho = correlation_matrix[idx_i, idx_j]
if positions[i]["direction"] != positions[j]["direction"]:
rho = -rho
total_corr += rho
pairs_count += 1
avg_rho = total_corr / pairs_count if pairs_count > 0 else 0
n_eff = n / (1 + (n - 1) * max(0, avg_rho))
return n_eff / n
Оркестратор слоттарды толтыру кезінде корреляцияны ескеруі керек. Екі нұсқа бар:
- Диверсификация бонусы: реттеу кезінде корреляцияланбаған жұптардағы стратегиялардың баллына бонус қосу.
- Корреляция шегі: корреляцияланған жұптардағы бір бағыттағы позициялар санын шектеу.
Каскадты оңтайландыру конвейері
Деректерді дайындаудан бастап растау мен тірі оркестрацияға дейінгі сегіз байланысқан кезең — әрқайсысы алдыңғысына негізделеді
Деректерден өндіріске дейінгі толық конвейер 8 кезеңнен тұрады:
0-кезең: Деректерді дайындау
Тарихи деректерді жүктеу, көп уақыт кадрлы қатынас үшін Parquet кэшін құру. Тиімді кэштеусіз кейінгі кезеңдер қолайсыз баяу болады.
1-кезең: TF + Ұзындық (Тау-шыңына өрмелеу торы)
Базалық уақыт кадрын және индикатор терезелерінің ұзындығын таңдау. Дөрекі тор: TF {1m, 5m, 15m, 1h, 4h} жиынынан, Length {10, 20, 50, 100, 200} жиынынан. Ең жақсы тор нүктесінен бастап тау-шыңына өрмелеу.
2-кезең: Бөліну (Координаталық түсу, 12 параметр)
Бөліну параметрлерін (кірулер/шығулар) оңтайландыру. 12 параметр бойынша координаталық түсу — индикатор шектері, сүзгілер, стоп-лосстар, тейк-профиттер. Жоғары өлшемді детерминистік мақсат функциялары үшін координаталық түсу Optuna-дан арзанырақ.
3-кезең: Мета-параметрлер (Координаталық түсу)
Мета-параметрлер: максималды ұстау уақыты, шығу үшін минималды PnL, трейлинг-стоп конфигурациясы. Тағы да координаталық түсу. Плато талдауы арқылы тұрақтылықты тексеру — егер оптимум нүктелі болса, стратегия шамадан тыс оңтайландырылған.
4-кезең: Комбо оңтайландыру
Жұптар (Негізгі, Резерв) бойынша тор іздеу. Әр комбинация үшін: dual_size таңдау, бірлескен симуляция арқылы каскадты PnL есептеу.
5-кезең: Растау
Көп деңгейлі растау:
- Көп символды: стратегия тек оңтайландыру жұбында ғана емес, 10+ жұпта тексеріледі
- Walk-forward: жылжымалы IS/OOS терезесі
- Параметр тұрақтылығы: әр кезеңде плато талдауы
- Монте-Карло бутстрап: каскадты PnL үшін сенімділік интервалдары
- Бэктест-тірі паритеті: бэктестті paper trading-пен салыстыру
6-кезең: Реттеу және таңдау
Каскадты комбинацияларды балл бойынша реттеу. Топ-K комбинациялар 7-кезеңге өтеді. Балл сенімділікті түзетуді, фандинг шығындарын және fill_efficiency-ді ескереді.
7-кезең: Оркестрация
Соңғы кезең: оркестраторды каскадты режимде стратегия мен жұпта іске қосу. Слоттарды басқару, басымдық кезегі, қайшылықтарды шешу — жоғарыда сипатталғанның бәрі.
Өнімділік талдауы: Каскад пен жеке стратегиялар
Қатар қатар салыстыру: каскадты портфель бос уақытты пайдалану арқылы жеке стратегиялардан асып түседі
Теориялық каскад артықшылығы
Негізгі уақыттың -ында PnL/күн = 0.49% пен сауда жасайды деп есептейік. Резерв уақытта PnL/күн = 0.89% пен сауда жасайды. Қабаттасу = (тәуелсіздікті болжай отырып).
Жалғыз негізгі (Стратегия A):
Каскад (A негізгі + C резерв):
Каскад пайдасы: резервтен PnL-ге +31%, ал просадка минималды артады ( MaxDD-ге қосылады).
Каскад көмектеспейтін жағдайлар
Каскад мына жағдайларда тиімсіз:
- Негізгі уақыттың >80%-ында белсенді. Бос уақыт аз — резервке орналасатын орын жоқ.
- Стратегиялар қатты корреляцияланған. Негізгі мен резерв бір уақытта сигнал береді — қабаттасу жоғары, ал резерв дәл негізгі бос тұрғанда бос тұрады.
- Ауысу шығындары резерв PnL-нен асып түседі. Жиі ауысу кезінде каскад комиссиялары резерв пайдасын жейді.
- dual_size тым кіші. кезінде резерв өз әлеуетінің 1%-ын ғана табады — комиссиялардан төмен.
Салыстыру кестесі
| Конфигурация | Жылдық PnL | MaxDD | Sharpe | Ауысу шығындары |
|---|---|---|---|---|
| Жалғыз Стратегия A | 26.8% | 0.9% | 1.42 | 0 |
| Жалғыз Стратегия C | 146.1% | 17% | 1.15 | 0 |
| Каскад A+C (d=0.068) | 35.2% | 2.06% | 1.58 | ~1.2% |
| Каскад B+A (d=0.068) | 19.4% | 1.36% | 1.71 | ~0.3% |
| 3 стратегиялы оркестратор | 48.7% | 3.1% | 1.63 | ~2.1% |
Каскад A+C: негізгі A резерв C-дан +8.4% алады. Sharpe бос уақытты пайдалану арқылы өседі. MaxDD орташа өседі ().
Уақыттық диверсификация математикасы
Жоғарыдағы каскад артықшылығы — бос уақытты толтыру — стратегиялар кітабын диверсификациялаудың екі түбегейлі әртүрлі жолының біреуі, және олар әртүрлі масштабталады. Айырмашылықты дұрыс түсіну каскадтың нақты не сатып ала алатынын және сатып ала алмайтынын көрсетеді.
Базалық стратегия белсенді кезең бойынша орта мәні және волатильдігі бар i.i.d. кірістерді табады делік, сондықтан оның кезеңдік Шарп коэффициенті . Төмендегі әр Шарп коэффициенті -мен жылдандырылған; жұмыс мысалында кезеңіне (жылдандырылғаны ) қолданылады, ал әр сан жабық формамен екі ондық белгіге дейін сәйкес келетін 2,000,000-кезеңдік симуляциядан алынған.
Уақыттық диверсификация: Бос уақытты толтыру
Уақыттың тек бөлігінде нарықта болатын — қалғанында тегіс (нөлдік кіріс, бірақ нөлдік тәуекел де) — стратегияның толық уақыт кестесіндегі Шарп коэффициенті
Бос уақыт орта мәнді -ге сұйылтады, бірақ стандартты ауытқуды тек -ге сұйылтады, сондықтан қатынас ретінде төмендейді: кезінде жылдандырылған Шарп -дан -ға дейін түседі. Сондықтан уақыттың -ында сауда жасайтын жоғары сенімділікті негізгі стратегия толық equity қисығында орташа болып көрінеді.
Каскад бұл бос уақытты толтырады. уақыт бойынша қиылыспайтын стратегияларды солай орналастырыңыз, олар бірге бүкіл уақыт кестесін жабады — әрбір кезеңде дәл біреуі белсенді — және біріктірілген ағын қайтадан болады:
жабылумен бір стратегиядан толық жабылумен стратегияға өту Шарпты есе көтереді — расталған: , , — бірақ ол -те қаныққанда, яғни жалғыз стратегияның кезеңдік Шарп коэффициентінде. Уақыттық диверсификация кітаптың тәуекелге түзетілген кірісін бір стратегия сауда жасаған кезде тапқаннан жоғары көтере алмайды, себебі әр сәтте сіз дәл бір стратегия ұстайсыз: орташалау жоқ, дисперсияны азайту жоқ. Ол сатып алатыны — капиталды қайта пайдалану: бір бірлік капитал барлық стратегияны кезекпен жүргізеді. Оларды бір мезгілде жүргізу үшін сізге бірлік (немесе есе левередж) керек болар еді; каскад барлық артықшылықты бір бірлікте жинайды. Бұл — тәуекел жеңісі емес, капитал тиімділігінің жеңісі.
Корреляциялық диверсификация: Каскад бас тартатын √N
Екінші түрі стратегияны бір мезгілде жүргізеді, капиталды бөледі және оларды әрбір кезеңде орташалайды. Жұп бойынша корреляциясы бар теңкорреляцияланған стратегиялар үшін,
бұл корреляцияланбаған кезде () болады және болған сайын -ке қарай жиырылады. Расталған: 8 корреляцияланбаған стратегия жылдандырылған -ке жетеді (яғни ), бірақ кезінде сол сегіз тек -ке жетеді — жалғыз стратегиядан сәл жақсы ғана. Бұл дәл көп жұпты бөлімдегі тиімді диверсификация шегерімі: корреляция — -ге салынған салық.
Kelly бөлуі — корреляциялық оптимум. Орта мәндер векторы және ковариация болғанда, өсуге оңтайлы салмақтар , ал олар қол жеткізетін Шарп
бұл масштабтауын дәл қайталайды (симуляция мен жабық форма сәйкес келеді: 8 корреляцияланбаған үшін , кезінде ). — "әр сәтте орташалау" жазбаша қалай көрінетіні.
Каскад іс жүзінде не сатып алады және оның шегі
| Өлшем | Уақыттық (каскад) | Корреляциялық (параллель) |
|---|---|---|
| Стратегиялар қашан жұмыс істейді | уақыт бойынша қиылыспайды | бір мезгілде |
| Капитал | қайта пайдаланылады (1 бірлік бәрін жүргізеді) | бөлінеді / левереджделеді ( бірлік) |
| Шарп масштабталуы | -те қанығады (бос орынды толтырады, толық жабуға ) | |
| Тәуекелді азайту | жоқ (әр сәтте бір стратегия) | , корреляциямен шектелген |
| Не сатып алады | капитал тиімділігі, нарықта болу уақыты | тәуекелге түзетілген кіріс |
Бұл екеуі көбейеді. Шынайы оркестратор әр сәтте (әлсіз корреляцияланған) стратегияны жүргізеді — сәттік -ды сатып алады — және бұл терезелерді уақыт бойынша орналастырады, капиталды қайта пайдаланады. Жалпы Шарп сәттік құрылыммен анықталады,
кезінде расталған (жылдандырылған , , ), ал уақыттық орналастыру капитал тиімділігін көбейтеді, Шарпты емес.
Шек қайда. Таза каскад — бір мезгілде бір стратегия белсенді — жалпы Шарпы дәл -ке тең, кезекте қанша стратегия күтіп тұрғанына қарамастан. Ол нарықта болу уақыты мен капитал тиімділігін максимизирлейді, бірақ оның тәуекелге түзетілген кірісі бір стратегияныкімен шектелген. -тен асу үшін сәт ішінде диверсификациялау керек — бірнеше әлсіз корреляцияланған стратегияны бір мезгілде жүргізу — және бұл пайда -мен шектеледі және корреляциямен жойылады. Дизайн ережесі осыдан шығады: бос уақытты бір бірлік капиталда арзан қайтару үшін каскадты қолданыңыз, ал сирек кездесетін, қымбат бір мезгілдегі диверсификацияны шынымен корреляцияланбаған стратегияларға ғана жұмсаңыз. Корреляцияланған стратегияларды жинақтау — уақыт бойынша немесе параллель түрде — іс жүзінде ешнәрсе бермейді.
Оркестрация: Іс жүзіндегі fill_efficiency
~78% толтыру тиімділігі: жылу картасы стратегиялар мен жұптар бойынша уақытты пайдалануды көрсетеді, жарқын ұяшықтар белсенді сауданы білдіреді
fill_efficiency параметрі оркестратордың бос уақыттың қандай бөлігін нақты пайдаланатынын анықтайды. Белсенді уақытқа шаққандағы PnL мақаласында көрсетілгендей, оны үш жолмен бағалауға болады:
- Тұрақты константа (0.80) — дөрекі, бірақ әмбебап
- Аналитикалық баға арқылы — корреляцияны ескереді
- Деректерден симуляция — ең дәл
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_times = [s["trading_time"] for s in strategies if s["is_primary"]]
p_primary = 1 - np.prod([(1 - t) ** n_eff for t in primary_times])
fallback_times = [s["trading_time"] for s in strategies if not s["is_primary"]]
p_fallback = 1 - np.prod([(1 - t) ** n_eff for t in fallback_times])
fill = p_primary + (1 - p_primary) * p_fallback
return min(fill, 1.0)
strategies = [
{"trading_time": 0.05, "is_primary": True}, # Strategy B
{"trading_time": 0.15, "is_primary": True}, # Strategy A
{"trading_time": 0.45, "is_primary": False}, # Strategy C as fallback
]
eff = cascade_fill_efficiency(strategies, n_pairs=10, correlation_factor=3.0)
Практикалық ұсыныстар
Каскадты орналастыруға арналған алты негізгі ұсыныс — кішкентайдан бастаудан бейімделгіш қайта калибрлеуге дейін
1. Екі стратегиядан бастаңыз
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-500мс ішінде қозғалуы мүмкін. Слиппедж бюджетін жоспарлаңыз.
5. fill_efficiency-ді бақылаңыз
Өндірістегі нақты fill_efficiency-ді бақылаңыз. Егер ол бэктестелгеннен елеулі төмен болса — оркестратор күтілгендей бос уақытты пайдаланбайды. Себептері: API кідірістері, қабылданбаған тапсырыстар, маржа шектеулері.
6. Бейімделгіш оңтайландыруды қолданыңыз
Каскад параметрлері (dual_size, балл салмақтары, слот шектері) статикалық болмауы керек. Жаңа деректер бойынша мерзімді қайта калибрлеу үшін бейімделгіш терең түсу қолданыңыз. Нарық өзгереді — каскад параметрлері де соған сай өзгеруі керек.
«Иллюзиясыз бэктесттер» сериясы: Қорытынды
Толық жүйе архитектурасы: математикадан растауға дейін және тірі оркестрацияға дейінгі 13 өзара байланысқан модуль
Бұл мақала 13+ мақаладан тұратын серияның финалы. Әр мақала бэктесттен өндіріске дейінгі жолдағы бір нақты мәселені шешті. Олардың қалай байланысатыны мынада:
Негіз: Кіріс математикасы
Шығын-пайда асимметриясы — кірістердің мультипликативті табиғаты, волатильдік ажырауы, Kelly критерийі. Бұл — келесінің бәрі үшін математикалық негіз: неге MaxDD левереджді анықтайды, неге Sharpe шикі PnL-ден маңыздырақ, неге симметриялы R:R-мен 50% жеңіс жылдамдығы тиімсіз.
Растау: Сенімділік интервалдары мен беріктік
Монте-Карло бутстрап — жалғыз нүктелік бағаны сенімділік интервалдары бар үлестіруге айналдыру. Кез келген көрсеткіш (PnL, MaxDD, Sharpe) тек сенімділік интервалымен мағыналы.
Walk-Forward оңтайландыру — үлгіден тыс растау. Тарихи деректердегі бэктест — IS нәтижесі; WFO стратегияның жаңа деректерде қалай жұмыс істейтінін көрсетеді.
Плато талдауы — параметр беріктігін тексеру. Егер оптимум нүктелі болса, стратегия шамадан тыс оңтайландырылған.
Бэктест-тірі паритеті — бэктестті нақты нәтижелермен салыстыру. Масштабтау алдындағы соңғы тексеру.
Шынайы шығындар: Фандинг пен левередж
Фандинг мөлшерлемелері левереджіңізді өлтіреді — мәңгілік фьючерстердегі левереждің жасырын құны. Фандингті ескермей, әдемі бэктест шығынға айналады.
Фандинг мөлшерлемесінің арбитражы — фандингті биржалар аралық стратегиялар арқылы шығыннан кіріс көзіне қалай айналдыруға болады.
Көрсеткіштер мен реттеу
Белсенді уақытқа шаққандағы PnL — портфельдегі стратегияларды реттеуге арналған көрсеткіш. Шикі PnL масштабталмайды; PnL/белсенді күн масштабталады.
Сигналдар корреляциясы — корреляцияланған жұптар портфеліндегі тиімді диверсификация.
Инфрақұрылым мен оңтайландыру
Көп уақыт кадрлы бэктестерге арналған Parquet кэші — жылдам итерацияларға арналған деректер инфрақұрылымы.
Бейімделгіш терең түсу — бейімделгіш оңтайландыру: дөрекі тор -> перспективалы аймақтарда нақтылау.
Optuna мен координаталық түсу — оптимизатор таңдау: шулы мақсат функциялары бар төмен өлшемдерге Optuna, тегіс мақсат функциялары бар жоғары өлшемдерге координаталық түсу.
Polars пен Pandas — бэктестілеуге арналған DataFrame операцияларының өнімділігі.
Оркестрация (Осы мақала)
Каскадты стратегиялар — алдыңғы барлық компоненттерді жұмыс істейтін жүйеге біріктіру. Балл негізіндегі бөлу белсенді уақытқа PnL-ді, сенімділікті түзетуді, фандинг шығындарын қолданады. Каскадты режим бос уақытты толтырады. Бірлескен симуляция портфельді растайды. Монте-Карло бутстрап каскадты PnL үшін сенімділік интервалдарын береді.
Әр мақала — тәуелсіз модуль. Бірге олар деректерді жүктеуден стратегиялар портфелінің тірі оркестрациясына дейінгі толық конвейерді құрайды.
Қорытынды
Каскад — стратегиялар портфеліне жалғыз тәсіл емес. Бірақ ол ең қарапайым және практикалық тәсілдердің бірі: негізгі стратегия толық қуатта сауда жасайды, резерв бос уақытты азайтылған позициямен толтырады. Екі негізгі параметр (dual_size және max_slots) көптеген конфигурациялар үшін жеткілікті икемділік береді.
Үш маңызды қорытынды:
-
Каскад тек бірлескен симуляция арқылы бэктестелуі керек. Жеке PnL-ді қосу нәтижелерді асыра көрсетеді. Ауысу шығындары, қабаттасу, слот шектеулері — мұның бәрі тек бірлескен симуляцияда түсіріледі.
-
dual_size PnL мен просадка арасындағы тепе-теңдікті анықтайды. Типтік оптимум — 5-10%. Sharpe бойынша тор іздеу — сенімді таңдау әдісі.
-
Оркестратор — балл негізіндегі басымдық кезегі. Барлығы әр сигнал үшін бір санға (баллға) азаяды. Score = f(PnL/белсенді күн, MaxLev, сенімділік, фандинг). Ең жоғары баллы бар стратегиялар слот алады. Қалғандары күтеді.
«Иллюзиясыз бэктесттер» сериясы бір нәрсені көрсетеді: әдемі бэктест пен нақты пайда арасында ондаған тұзақ жатыр. Әр мақала біреуін жояды. Каскадты оркестрация — соңғы қадам: расталған стратегиялар жиынтығын жұмыс істейтін портфельге айналдыру.
Пайдалы сілтемелер
- López de Prado — Advances in Financial Machine Learning: Portfolio Construction
- Pardo, R. — The Evaluation and Optimization of Trading Strategies
- Ernest Chan — Algorithmic Trading: Winning Strategies and Their Rationale
- Perry Kaufman — Trading Systems and Methods, Chapter on Portfolio Allocation
- Tomasini, Jaekle — Trading Systems: A New Approach to System Development and Portfolio Optimisation
- Bailey, D.H. & López de Prado — The Deflated Sharpe Ratio
- Markowitz, H. — Portfolio Selection (1952)
- Kelly, J.L. — A New Interpretation of Information Rate (1956)
Дәйексөз
@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.}
}
Authors
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.