Корреляция сигналов: сколько пар нужно мониторить
Вы запускаете стратегию на 10 крипто-парах: BTC/USDT, ETH/USDT, SOL/USDT, AVAX/USDT и ещё шесть альтов. Логика кажется железной: если стратегия активна 5% времени на одной паре, то на 10 парах хотя бы одна должна быть активна времени. Четырёхкратный прирост утилизации.
На практике утилизация оказывается 15-16%, а не 40%. Ваши 10 пар ведут себя как 3. Капитал простаивает, fill_efficiency проседает, а эффективная доходность портфеля оказывается втрое ниже расчётной.
Причина — корреляция сигналов. И в криптовалютах она катастрофически высока.
Иллюзия диверсификации в крипто

В классических финансах диверсификация работает, потому что акции Apple и нефтяного ETF реагируют на разные факторы. В криптовалютном рынке всё иначе.
BTC — доминирующий фактор. Когда биткоин падает на 5%, ETH падает на 6-8%, SOL — на 8-12%, альткоины — на 10-20%. Корреляции дневных доходностей в крипто-рынке стабильно выше 0.6, а в периоды паники приближаются к 1.0.
Но для нас — алготрейдеров — важна не корреляция цен, а корреляция сигналов. Если стратегия основана на моментуме, и BTC запускает сигнал на вход — с высокой вероятностью ETH и SOL запустят аналогичный сигнал в ту же минуту. Все пары входят в лонг одновременно, все выходят одновременно. Десять позиций — но, по сути, одна ставка.
Почему 10 пар ≠ 10x диверсификация

Формальная постановка
Пусть стратегия на каждой из пар активна долю времени. Если бы сигналы были полностью независимы, вероятность того, что хотя бы одна пара активна:
Для Strategy B (, ):
Но сигналы не независимы. Криптовалюты движутся синхронно — а значит, сигналы возникают и гаснут кластерами.
Корреляция превращает 10 пар в 3
Интуиция такая: если 10 пар коррелированы, они несут информацию не 10 независимых источников, а, скажем, 3-4. Формализуем это через effective_N:
где — correlation factor, отражающий среднюю попарную корреляцию сигналов. При пары полностью независимы, при — идентичны.
Для крипто-пар типичный . Тогда:
Не 40%, а 15.6%. Разница в 2.5 раза. Fill efficiency падает соответственно, и вместе с ней — эффективная доходность всего портфеля (см. PnL по активному времени).
Корреляция в крипто-рынках

BTC как доминирующий фактор
Крипто-рынок имеет ярко выраженную факторную структуру. BTC объясняет 60-80% дисперсии дневных доходностей большинства альткоинов. Это хорошо видно через PCA (Principal Component Analysis):
import numpy as np
from sklearn.decomposition import PCA
def analyze_crypto_factor_structure(returns_matrix: np.ndarray, pair_names: list) -> dict:
"""
PCA-анализ факторной структуры крипто-доходностей.
Args:
returns_matrix: матрица доходностей [n_days x n_pairs]
pair_names: список названий пар
"""
pca = PCA()
pca.fit(returns_matrix)
explained = pca.explained_variance_ratio_
cumulative = np.cumsum(explained)
print("Factor structure:")
for i, (var, cum) in enumerate(zip(explained[:5], cumulative[:5])):
print(f" PC{i+1}: {var:.1%} variance (cumulative: {cum:.1%})")
loadings = pca.components_[0]
print("\nPC1 loadings (BTC factor):")
for name, load in sorted(zip(pair_names, loadings), key=lambda x: -abs(x[1])):
print(f" {name}: {load:.3f}")
return {
"explained_variance": explained,
"n_effective_factors": int(np.searchsorted(cumulative, 0.90)) + 1,
"pc1_loadings": dict(zip(pair_names, loadings)),
}
Типичный результат для портфеля из 10 крипто-пар:
| Компонент | Объяснённая дисперсия | Кумулятивная |
|---|---|---|
| PC1 (BTC) | 65% | 65% |
| PC2 | 12% | 77% |
| PC3 | 8% | 85% |
| PC4 | 5% | 90% |
| PC5-PC10 | 10% | 100% |
Четыре фактора объясняют 90% дисперсии. Из 10 пар «независимых» — не больше 4.
Корреляция сигналов vs. корреляция цен
Здесь важный нюанс. Корреляция цен и корреляция сигналов — разные вещи. Цены BTC и ETH коррелированы на 0.85, но сигналы конкретной стратегии могут быть коррелированы на 0.95 или на 0.50 — в зависимости от логики входа.
Пример: стратегия на основе RSI overbought/oversold. RSI на BTC пересекает 30 (oversold) — вход в лонг. ETH в тот же момент тоже может быть oversold (корреляция сигналов ~0.90). А может не быть, если ETH падал медленнее (корреляция сигналов ~0.40).
Правильный подход — измерять корреляцию именно сигналов, а не ценовых рядов:
import numpy as np
from itertools import combinations
def signal_correlation_matrix(
signals: dict, # {pair: np.array of 0/1 per minute}
method: str = "pearson",
) -> np.ndarray:
"""
Расчёт матрицы корреляции сигналов (бинарных: 0 = вне позиции, 1 = в позиции).
Args:
signals: словарь {pair_name: binary_signal_array}
method: метод корреляции ("pearson", "jaccard")
"""
pairs = sorted(signals.keys())
n = len(pairs)
corr_matrix = np.ones((n, n))
for i, j in combinations(range(n), 2):
s_i = signals[pairs[i]]
s_j = signals[pairs[j]]
if method == "pearson":
corr = np.corrcoef(s_i, s_j)[0, 1]
elif method == "jaccard":
intersection = np.sum(s_i & s_j)
union = np.sum(s_i | s_j)
corr = intersection / union if union > 0 else 0
else:
raise ValueError(f"Unknown method: {method}")
corr_matrix[i, j] = corr
corr_matrix[j, i] = corr
return corr_matrix, pairs
def estimate_correlation_factor(corr_matrix: np.ndarray) -> float:
"""
Оценка correlation_factor из матрицы корреляции сигналов.
correlation_factor = 1 + (N-1) * mean_pairwise_correlation
При корреляции 0 → C_f = 1 (все независимы).
При корреляции 1 → C_f = N (все идентичны).
"""
n = corr_matrix.shape[0]
upper_triangle = corr_matrix[np.triu_indices(n, k=1)]
mean_corr = np.mean(upper_triangle)
correlation_factor = 1 + (n - 1) * mean_corr
return correlation_factor
Временная корреляция: спокойствие vs. паника

Корреляция не статична. В спокойные периоды BTC и альты могут расходиться — ETH растёт на новостях Ethereum, SOL — на новостях Solana. В кризис всё коллапсирует в один фактор: risk-on/risk-off.
def rolling_correlation_factor(
signals: dict,
window_days: int = 30,
step_days: int = 7,
) -> list:
"""
Скользящий correlation_factor для обнаружения смены режимов.
"""
pairs = sorted(signals.keys())
minutes_per_day = 1440
window = window_days * minutes_per_day
step = step_days * minutes_per_day
total_minutes = len(signals[pairs[0]])
results = []
for start in range(0, total_minutes - window, step):
end = start + window
window_signals = {p: signals[p][start:end] for p in pairs}
corr_matrix, _ = signal_correlation_matrix(window_signals)
cf = estimate_correlation_factor(corr_matrix)
results.append({
"start_minute": start,
"end_minute": end,
"correlation_factor": cf,
"effective_n": len(pairs) / cf,
})
return results
Типичная картина для 10 крипто-пар:
| Режим рынка | Средняя корреляция сигналов | ||
|---|---|---|---|
| Боковик (low vol) | 0.15-0.25 | 2.4-3.3 | 3.0-4.2 |
| Тренд вверх | 0.25-0.40 | 3.3-4.6 | 2.2-3.0 |
| Тренд вниз | 0.30-0.50 | 3.7-5.5 | 1.8-2.7 |
| Паника (crash) | 0.60-0.90 | 6.4-9.1 | 1.1-1.6 |
В панике 10 пар сжимаются до 1-2 эффективных. Именно тогда, когда диверсификация нужнее всего, она исчезает. Это криптовалютный аналог классического «correlations go to 1 in a crisis».
effective_N: ключевая концепция

Формула и вывод
Идея effective_N заимствована из статистики, где effective sample size учитывает автокорреляцию наблюдений. Для наших целей:
где — средняя попарная корреляция сигналов. Упрощённая запись:
Свойства:
- При : , — полная независимость
- При : , — все пары идентичны
- При и : ,
Как оценить correlation_factor из данных
На практике есть три подхода:
1. Из матрицы корреляции сигналов (точный).
Прогоняете стратегию на всех парах, получаете бинарные сигналы (0/1 для каждой минуты), строите матрицу корреляции, вычисляете по формуле выше.
2. Из PCA ценовых доходностей (приближённый).
Если сигналы сильно зависят от ценовой динамики (моментум, mean-reversion), можно оценить как число компонент PCA, объясняющих 90% дисперсии.
3. Из эвристики по классам активов (грубый).
| Класс активов | Типичный |
|---|---|
| Крипто (топ-10) | 2.5-4.0 |
| Крипто (с DeFi/мемкоинами) | 2.0-3.0 |
| Форекс (majors) | 1.5-2.5 |
| Акции (один сектор) | 2.0-3.5 |
| Акции (кросс-секторные) | 1.2-1.8 |
Для крипто-портфеля из BTC, ETH, SOL, AVAX, MATIC, DOGE, DOT, LINK, UNI, ATOM безопасная оценка — .
Моделирование утилизации слотов

Формула
Базовая формула с учётом корреляции:
Таблица для разных стратегий и числа пар ():
| Strategy | (trading time) | 5 пар () | 10 пар () | 20 пар () | 50 пар () |
|---|---|---|---|---|---|
| Strategy B | 5% | 8.2% | 15.6% | 29.1% | 58.0% |
| Strategy A | 15% | 23.6% | 41.8% | 65.9% | 92.8% |
| Strategy C | 45% | 67.1% | 89.0% | 98.8% | ~100% |
Для Strategy B с 5% активности нужно 50 пар, чтобы хотя бы половину времени иметь хоть одну активную позицию. А ведь это ещё без учёта того, что 50 крипто-пар коррелированы сильнее, чем 10.
Мульти-слотовый оркестратор
Реальный оркестратор управляет несколькими слотами одновременно. Если у вас 5 слотов и 10 пар, утилизация считается иначе:
def estimate_fill_efficiency(
trading_time_pct: float,
n_pairs: int,
correlation_factor: float = 3.0,
max_slots: int = 1,
) -> dict:
"""
Аналитическая оценка fill efficiency для мульти-слотового оркестратора.
Args:
trading_time_pct: доля активного времени одной стратегии на одной паре
n_pairs: количество торговых пар
correlation_factor: коэффициент корреляции сигналов
max_slots: максимальное число одновременных позиций
Returns:
dict с метриками утилизации
"""
effective_n = n_pairs / correlation_factor
p_at_least_one = 1 - (1 - trading_time_pct) ** effective_n
expected_active = effective_n * trading_time_pct
utilization = min(expected_active, max_slots) / max_slots
fill_efficiency = min(p_at_least_one, utilization)
return {
"effective_n": effective_n,
"p_at_least_one": p_at_least_one,
"expected_active": expected_active,
"utilization": utilization,
"fill_efficiency": fill_efficiency,
}
configs = [
("Strategy B, 10 пар, 1 слот", 0.05, 10, 3.0, 1),
("Strategy B, 10 пар, 3 слота", 0.05, 10, 3.0, 3),
("Strategy B, 30 пар, 1 слот", 0.05, 30, 3.0, 1),
("Strategy A, 10 пар, 1 слот", 0.15, 10, 3.0, 1),
("Strategy C, 10 пар, 1 слот", 0.45, 10, 3.0, 1),
("Strategy C, 10 пар, 5 слотов", 0.45, 10, 3.0, 5),
]
for name, p, n, cf, slots in configs:
result = estimate_fill_efficiency(p, n, cf, slots)
print(f"{name}:")
print(f" N_eff = {result['effective_n']:.1f}")
print(f" P(≥1 active) = {result['p_at_least_one']:.1%}")
print(f" E[active] = {result['expected_active']:.2f}")
print(f" fill_efficiency = {result['fill_efficiency']:.1%}")
print()
Ожидаемый вывод:
Strategy B, 10 пар, 1 слот:
N_eff = 3.3
P(≥1 active) = 15.6%
E[active] = 0.17
fill_efficiency = 15.6%
Strategy B, 10 пар, 3 слота:
N_eff = 3.3
P(≥1 active) = 15.6%
E[active] = 0.17
fill_efficiency = 5.6%
Strategy B, 30 пар, 1 слот:
N_eff = 10.0
P(≥1 active) = 40.1%
E[active] = 0.50
fill_efficiency = 40.1%
Strategy A, 10 пар, 1 слот:
N_eff = 3.3
P(≥1 active) = 41.8%
E[active] = 0.50
fill_efficiency = 41.8%
Strategy C, 10 пар, 1 слот:
N_eff = 3.3
P(≥1 active) = 89.0%
E[active] = 1.50
fill_efficiency = 89.0%
Strategy C, 10 пар, 5 слотов:
N_eff = 3.3
P(≥1 active) = 89.0%
E[active] = 1.50
fill_efficiency = 30.0%
Обратите внимание: Strategy B с 3 слотами и 10 парами показывает fill_efficiency 5.6%. Три слота бессмысленны, если ожидаемое число активных пар всего 0.17. Слоты следует назначать пропорционально ожидаемой загрузке.
Симуляция из реальных данных
Аналитическая модель — приближение. Для точной оценки нужна симуляция на реальных сигналах:
import numpy as np
def simulate_fill_efficiency(
all_signals: dict, # {(strategy, pair): [(entry_min, exit_min), ...]}
max_slots: int = 10,
test_period_minutes: int = 750 * 24 * 60, # 750 дней
priority_fn=None, # функция приоритета для выбора позиций
) -> dict:
"""
Симуляция реальной загрузки слотов оркестратора.
Для каждой минуты: считаем, сколько пар хотят войти в позицию,
и сколько слотов реально заняты (с учётом лимита).
Args:
all_signals: сигналы по парам и стратегиям
max_slots: максимальное число одновременных позиций
test_period_minutes: длина тестового периода в минутах
priority_fn: если None — FIFO; иначе — функция ранжирования
"""
demand_timeline = np.zeros(test_period_minutes, dtype=np.int32)
capped_timeline = np.zeros(test_period_minutes, dtype=np.int32)
for signals in all_signals.values():
for entry_min, exit_min in signals:
if entry_min < test_period_minutes:
end = min(exit_min, test_period_minutes)
demand_timeline[entry_min:end] += 1
capped_timeline = np.minimum(demand_timeline, max_slots)
total_demand = np.sum(demand_timeline)
total_filled = np.sum(capped_timeline)
time_with_any_active = np.sum(demand_timeline > 0)
fill_efficiency = np.mean(capped_timeline) / max_slots
demand_fill_ratio = total_filled / total_demand if total_demand > 0 else 0
time_utilization = time_with_any_active / test_period_minutes
slot_distribution = {}
for s in range(max_slots + 1):
slot_distribution[s] = np.mean(capped_timeline == s)
return {
"fill_efficiency": fill_efficiency,
"demand_fill_ratio": demand_fill_ratio,
"time_utilization": time_utilization,
"avg_demand": np.mean(demand_timeline),
"avg_filled": np.mean(capped_timeline),
"slot_distribution": slot_distribution,
"overflow_pct": np.mean(demand_timeline > max_slots),
}
Симуляция на реальных данных часто показывает ещё более низкую утилизацию, чем аналитическая оценка, потому что учитывает кластеризацию сигналов во времени: все пары входят одновременно в кластере, создавая overflow, а затем все замолкают, создавая пустоту.
Сколько пар мониторить? Анализ убывающей отдачи

Ключевой вопрос: при какой добавление ещё одной пары перестаёт заметно увеличивать fill_efficiency?
import numpy as np
def diminishing_returns_analysis(
trading_time_pct: float,
correlation_factor: float = 3.0,
max_pairs: int = 100,
target_utilization: float = 0.80,
) -> dict:
"""
Анализ убывающей отдачи от добавления новых пар.
"""
results = []
target_n = None
for n in range(1, max_pairs + 1):
n_eff = n / correlation_factor
p_active = 1 - (1 - trading_time_pct) ** n_eff
marginal = 0
if n > 1:
prev_eff = (n - 1) / correlation_factor
prev_p = 1 - (1 - trading_time_pct) ** prev_eff
marginal = p_active - prev_p
results.append({
"n_pairs": n,
"n_effective": n_eff,
"p_at_least_one": p_active,
"marginal_gain": marginal,
})
if target_n is None and p_active >= target_utilization:
target_n = n
return {
"results": results,
"target_n_for_utilization": target_n,
}
analysis_b = diminishing_returns_analysis(0.05, correlation_factor=3.0, target_utilization=0.80)
print(f"Strategy B: нужно {analysis_b['target_n_for_utilization']} пар для 80% P(≥1)")
for r in analysis_b["results"]:
if r["n_pairs"] in [1, 3, 5, 10, 20, 30, 50, 80]:
print(f" N={r['n_pairs']:3d}: N_eff={r['n_effective']:.1f}, "
f"P(≥1)={r['p_at_least_one']:.1%}, "
f"marginal={r['marginal_gain']:.2%}")
Результат для Strategy B (, ):
| пар | Marginal gain | ||
|---|---|---|---|
| 1 | 0.3 | 1.7% | — |
| 3 | 1.0 | 5.0% | +1.7% |
| 5 | 1.7 | 8.2% | +1.6% |
| 10 | 3.3 | 15.6% | +1.4% |
| 20 | 6.7 | 29.1% | +1.1% |
| 30 | 10.0 | 40.1% | +0.9% |
| 50 | 16.7 | 58.0% | +0.6% |
| 80 | 26.7 | 74.5% | +0.4% |
Для Strategy B достичь 80% утилизации одним слотом невозможно даже при 100 парах (нужно ~96 пар). Это фундаментальное ограничение: стратегия с 5% trading time не подходит для работы на одном слоте — ей нужен портфельный подход с несколькими стратегиями.
Для Strategy A (, ):
| пар | Marginal gain | ||
|---|---|---|---|
| 5 | 1.7 | 23.6% | — |
| 10 | 3.3 | 41.8% | +3.3% |
| 20 | 6.7 | 65.9% | +2.1% |
| 30 | 10.0 | 80.3% | +1.2% |
Strategy A достигает 80% утилизации при ~30 парах. Marginal gain на 30-й паре — лишь +1.2%.
Для Strategy C (, ):
| пар | ||
|---|---|---|
| 3 | 1.0 | 45.0% |
| 5 | 1.7 | 67.1% |
| 10 | 3.3 | 89.0% |
| 15 | 5.0 | 95.0% |
Strategy C с 45% trading time достигает 90% утилизации уже при 10 парах. Добавлять больше — смысла нет.
Деградация edge на разных парах

Есть ещё один фактор, который ограничивает число пар: деградация edge. Стратегия, разработанная и оптимизированная на BTC/USDT, может работать хуже на менее ликвидных альтах.
Причины деградации:
- Ликвидность: проскальзывание на DOGE/USDT в разы выше, чем на BTC/USDT
- Спреды: менее ликвидные пары имеют более широкий bid-ask спред
- Микроструктура: паттерны стакана различаются между парами
- Манипуляции: малоликвидные альты подвержены pump-and-dump
def edge_decay_analysis(
strategy_results: dict, # {pair: {"pnl_per_day": float, "n_trades": int}}
min_trades: int = 30,
) -> list:
"""
Ранжирование пар по edge с учётом деградации.
"""
ranked = []
for pair, metrics in strategy_results.items():
if metrics["n_trades"] < min_trades:
continue
ranked.append({
"pair": pair,
"pnl_per_day": metrics["pnl_per_day"],
"n_trades": metrics["n_trades"],
"sharpe": metrics.get("sharpe", 0),
})
ranked.sort(key=lambda x: x["pnl_per_day"], reverse=True)
cumulative_pnl = []
running_sum = 0
for i, r in enumerate(ranked):
running_sum += r["pnl_per_day"]
avg = running_sum / (i + 1)
cumulative_pnl.append({
"n_pairs": i + 1,
"last_added": r["pair"],
"last_pnl_per_day": r["pnl_per_day"],
"avg_pnl_per_day": avg,
})
return cumulative_pnl
Типичная картина:
| # пар | Последняя добавленная | PnL/day последней | Средний PnL/day |
|---|---|---|---|
| 1 | BTC/USDT | 0.89% | 0.89% |
| 2 | ETH/USDT | 0.82% | 0.86% |
| 3 | SOL/USDT | 0.71% | 0.81% |
| 5 | AVAX/USDT | 0.55% | 0.73% |
| 8 | DOT/USDT | 0.31% | 0.61% |
| 10 | DOGE/USDT | 0.12% | 0.53% |
Добавление 10-й пары снижает средний PnL/day портфеля. На 8-й паре edge уже вдвое ниже, чем на лучшей. Нужен баланс между fill_efficiency (растёт с числом пар) и средним edge (падает).
Оптимальное число пар: объединённая модель

Объединим fill_efficiency и edge decay в одну метрику — expected portfolio PnL per day:
def optimal_pairs_count(
pair_edges: list, # PnL/day по убыванию: [0.89, 0.82, 0.71, ...]
trading_time_pct: float,
correlation_factor: float = 3.0,
max_slots: int = 1,
) -> dict:
"""
Поиск оптимального числа пар, максимизирующего portfolio PnL.
"""
best_n = 0
best_score = 0
results = []
for n in range(1, len(pair_edges) + 1):
avg_edge = np.mean(pair_edges[:n])
n_eff = n / correlation_factor
p_active = 1 - (1 - trading_time_pct) ** n_eff
expected_active = n_eff * trading_time_pct
utilization = min(expected_active, max_slots) / max_slots
fill_eff = min(p_active, utilization)
portfolio_score = avg_edge * fill_eff * 365
results.append({
"n_pairs": n,
"avg_edge": avg_edge,
"fill_efficiency": fill_eff,
"portfolio_annualized": portfolio_score,
})
if portfolio_score > best_score:
best_score = portfolio_score
best_n = n
return {
"optimal_n": best_n,
"optimal_score": best_score,
"results": results,
}
edges = [0.89, 0.82, 0.71, 0.65, 0.55, 0.48, 0.40, 0.31, 0.22, 0.12,
0.08, 0.05, 0.02, -0.01, -0.05]
opt = optimal_pairs_count(edges, trading_time_pct=0.15, correlation_factor=3.0)
print(f"Оптимальное число пар: {opt['optimal_n']}")
print(f"Portfolio annualized: {opt['optimal_score']:.1f}%")
for r in opt["results"]:
print(f" N={r['n_pairs']:2d}: avg_edge={r['avg_edge']:.2f}%, "
f"fill_eff={r['fill_efficiency']:.1%}, "
f"portfolio={r['portfolio_annualized']:.1f}%")
Оптимум обычно находится в точке, где marginal fill_efficiency от добавления пары перестаёт компенсировать снижение среднего edge. Для типичного крипто-портфеля:
- Strategy B (5% time): оптимум при 8-12 парах
- Strategy A (15% time): оптимум при 6-10 парах
- Strategy C (45% time): оптимум при 4-6 парах
Парадокс: стратегия с наименьшим trading time получает выгоду от наибольшего числа пар, но при этом fill_efficiency всё равно остаётся низким. Решение — не больше пар, а комбинация с другими стратегиями (см. Combo-стратегии).
Кросс-парная диверсификация: стратегии снижения корреляции

Если нельзя увеличить число пар бесконечно, можно снизить — то есть повысить разнообразие сигналов.
Стратегия 1: микс ликвидных и DeFi-токенов
BTC, ETH, BNB коррелированы очень сильно. Но UNI (DEX), AAVE (lending), CRV (стейблкоины) могут иметь собственные драйверы. Добавление DeFi-токенов снижает средний с 0.35 до 0.20-0.25:
Стратегия 2: разные стратегии на одних парах
Вместо 10 пар с одной стратегией — 5 пар с двумя разными стратегиями. Если стратегии основаны на разных принципах (моментум vs. mean-reversion), их сигналы могут быть антикоррелированы:
- Моментум входит в лонг, когда цена растёт
- Mean-reversion входит в лонг, когда цена падает
Это единственный способ получить — использовать стратегии с отрицательной корреляцией сигналов.
Стратегия 3: включение spot vs. futures
Арбитраж funding rates и спотовая торговля имеют другую корреляционную структуру. Добавление арбитражных стратегий в портфель существенно снижает общий , потому что арбитраж по определению эксплуатирует расхождения, а не совпадения.
Практические рекомендации по типам стратегий
High-frequency low-time стратегии (trading time < 10%)
Типичный представитель: Strategy B (5% time, 38 сделок за 750 дней).
- Число пар: 10-15 (оптимум по balance edge/fill)
- Проблема: fill_efficiency низкий даже при 15 парах (~20-25%)
- Решение: обязательная комбинация с другими стратегиями в оркестраторе
- для крипто: 2.5-3.5
- Мониторинг: скользящий для адаптации числа пар к режиму рынка
Medium-time стратегии (trading time 10-30%)
Типичный представитель: Strategy A (15% time, 418 сделок за 750 дней).
- Число пар: 6-10
- Fill_efficiency при 10 парах: ~40%
- Решение: комбинация 2-3 таких стратегий заполняет 80%+ времени
- для крипто: 2.5-3.5
- Фокус: выбирать пары с максимальным edge, не гнаться за числом
High-time стратегии (trading time > 30%)
Типичный представитель: Strategy C (45% time).
- Число пар: 4-6
- Fill_efficiency при 6 парах: ~80%
- Проблема: overflow — несколько пар активны одновременно, но слотов меньше
- Решение: увеличить max_slots или добавить приоритизацию пар
- для крипто: 2.5-4.0 (выше из-за длинных позиций, перекрывающих кризисы)
Сводная таблица
| Параметр | Strategy B (5%) | Strategy A (15%) | Strategy C (45%) |
|---|---|---|---|
| Рекомендуемое | 10-15 | 6-10 | 4-6 |
| при | 3.3-5.0 | 2.0-3.3 | 1.3-2.0 |
| Fill eff. (1 слот) | 15-23% | 32-42% | 77-89% |
| Нужна комбинация? | Обязательно | Желательно | Нет |
| Bottleneck | Мало сигналов | Баланс | Overflow |
Связь с другими метриками серии
Эта статья — одиннадцатая в серии «Бэктесты без иллюзий». Корреляция сигналов напрямую влияет на метрики из предыдущих статей:
-
PnL по активному времени: fill_efficiency — ключевой множитель в формуле эффективной доходности. Если вы завысили fill_efficiency, проигнорировав корреляцию, ваш прогноз portfolio PnL будет слишком оптимистичным.
-
Funding rates: при высокой корреляции позиции открываются одновременно — и funding costs растут линейно с числом слотов. Overflow + funding = ускоренное сжигание капитала.
-
Арбитраж funding rates: арбитражные стратегии — естественный диверсификатор, снижающий портфеля. Их сигналы слабо коррелированы с моментумными и mean-reversion стратегиями.
-
Combo-стратегии (следующая статья): как собрать портфель из стратегий с разными и , чтобы достичь 90%+ утилизации. Каскадная оркестрация учитывает корреляцию сигналов при назначении приоритетов.
Заключение
Диверсификация в крипто — не о количестве пар. 10 коррелированных пар дают эффект 3-4 независимых. В панике и того меньше.
Четыре вывода:
-
Считайте effective_N, а не N. Для крипто-пар . Десять пар — это ~3.3 эффективных. Планируйте fill_efficiency исходя из , не из .
-
Измеряйте корреляцию сигналов, а не цен. Ценовая корреляция — прокси, но не замена. Прогоните стратегию на всех парах и посчитайте матрицу корреляции бинарных сигналов.
-
Учитывайте деградацию edge. Больше пар — ниже средний PnL/day. Оптимум — в точке, где marginal fill_efficiency от новой пары ещё компенсирует снижение edge.
-
Снижайте , а не увеличивайте . Комбинация разных стратегий на тех же парах эффективнее, чем одна стратегия на большем числе пар. Кросс-стратегийная диверсификация может дать .
Correlation factor — это скрытая переменная, которая определяет реалистичность ваших прогнозов утилизации и доходности. Игнорировать его — значит строить портфель на иллюзиях.
Полезные ссылки
- Markowitz, H. — Portfolio Selection (1952)
- López de Prado — Advances in Financial Machine Learning: Denoising and Detoning
- Ledoit, O. & Wolf, M. — Honey, I Shrunk the Sample Covariance Matrix (2004)
- Laloux, L. et al. — Noise Dressing of Financial Correlation Matrices (1999)
- Cont, R. — Empirical Properties of Asset Returns: Stylized Facts and Statistical Issues
- Ernest Chan — Algorithmic Trading: Portfolio Construction and Risk Management
- Rebonato, R. & Jäckel, P. — The Most General Methodology for Creating a Valid Correlation Matrix
Цитирование
@article{soloviov2026signalcorrelation,
author = {Soloviov, Eugen},
title = {Корреляция сигналов: сколько пар нужно мониторить},
year = {2026},
url = {https://marketmaker.cc/ru/blog/post/signal-correlation-pairs},
version = {0.1.0},
description = {Почему 10 крипто-пар не дают 10-кратную диверсификацию, как рассчитать effective\_N через correlation\_factor, и сколько пар действительно нужно мониторить для 80-90\% утилизации слотов оркестратора.}
}
MarketMaker.cc Team
Количественные исследования и стратегии