← 記事一覧に戻る
March 14, 2026
読了時間: 5分

シグナル相関:何ペア監視すべきか

シグナル相関:何ペア監視すべきか
#algotrading
#correlation
#diversification
#portfolio
#orchestration
#crypto

10個の暗号通貨ペアで戦略を立ち上げます:BTC/USDT、ETH/USDT、SOL/USDT、AVAX/USDTなど。ロジックは完璧に見えます:1ペアで戦略がアクティブな時間が5%なら、10ペアでは少なくとも1つがアクティブである時間は10.9510=40%1 - 0.95^{10} = 40\%。稼働率が4倍になるはずです。

実際には、稼働率は40%ではなく15-16%です。10ペアが3ペアのように振る舞います。資本は遊休し、fill_efficiencyは低下し、実効ポートフォリオリターンは予測の3分の1になります。

原因はシグナル相関です。そして暗号通貨では、それは壊滅的に高いのです。

暗号通貨における分散の幻想

暗号通貨市場における分散の幻想

伝統的な金融では、Apple株と石油ETFが異なる要因に反応するため、分散が機能します。暗号通貨市場では、すべてが異なります。

BTCが支配的な要因です。ビットコインが5%下落すると、ETHは6-8%下落し、SOLは8-12%下落し、アルトコインは10-20%下落します。暗号通貨市場における日次リターンの相関は一貫して0.6を超え、パニック時には1.0に近づきます。

しかし、アルゴトレーダーである私たちにとって重要なのは、価格の相関ではなくシグナルの相関です。戦略がモメンタムに基づいていて、BTCがエントリーシグナルをトリガーした場合、ETHとSOLも同じ分以内に類似のシグナルをトリガーする確率が高いです。すべてのペアが同時にロングに入り、同時に退出します。10ポジション — しかし本質的には1つの賭けです。

なぜ10ペア ≠ 10倍の分散なのか

10個の相関ペアが3個の実効ペアに縮約される

形式的な定式化

NNペアで戦略が時間のpp割合アクティブであるとします。シグナルが完全に独立であれば、少なくとも1ペアがアクティブである確率:

P(1 active)=1(1p)NP(\geq 1\ \text{active}) = 1 - (1 - p)^N

戦略B(p=0.05p = 0.05N=10N = 10)の場合:

P(1)=10.9510=10.598740.1%P(\geq 1) = 1 - 0.95^{10} = 1 - 0.5987 \approx 40.1\%

しかし、シグナルは独立ではありません。暗号通貨は同期して動きます — つまり、シグナルはクラスターで発生し消滅します。

相関が10ペアを3ペアに変える

直感はこうです:10ペアが相関している場合、10の独立したソースではなく、3-4のソースからの情報を持っています。これをeffective_Nを通じて形式化します:

Neff=NCfN_{eff} = \frac{N}{C_f}

ここでCfC_fcorrelation factorであり、平均ペアワイズシグナル相関を反映します。Cf=1C_f = 1のときペアは完全に独立、Cf=NC_f = Nのとき同一です。

暗号通貨ペアの場合、典型的なCf3C_f \approx 3。すると:

Neff=1033.3N_{eff} = \frac{10}{3} \approx 3.3

P(1)=10.953.310.844=15.6%P(\geq 1) = 1 - 0.95^{3.3} \approx 1 - 0.844 = 15.6\%

40%ではなく15.6%。2.5倍の差です。Fill efficiencyもそれに応じて低下し、ポートフォリオ全体の実効リターンも低下します(PnL per Active Timeを参照)。

暗号通貨市場における相関

暗号通貨シグナル相関行列ヒートマップ

支配的要因としてのBTC

暗号通貨市場は顕著なファクター構造を持っています。BTCはほとんどのアルトコインの日次リターン分散の60-80%を説明します。これはPCA(主成分分析)を通じて明確に見ることができます:

import numpy as np
from sklearn.decomposition import PCA

def analyze_crypto_factor_structure(returns_matrix: np.ndarray, pair_names: list) -> dict:
    """
    PCA analysis of the factor structure of crypto returns.

    Args:
        returns_matrix: returns matrix [n_days x n_pairs]
        pair_names: list of 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%

4つのファクターが分散の90%を説明します。10ペアのうち、「独立」しているのは最大4つです。

シグナル相関と価格相関の違い

重要なニュアンスがあります。価格の相関とシグナルの相関は異なるものです。BTCとETHの価格相関は0.85ですが、特定の戦略のシグナル相関はエントリーロジックに応じて0.95にも0.50にもなり得ます。

例:RSIの買われすぎ/売られすぎ戦略。BTCのRSIが30を下回る(売られすぎ)— ロングでエントリー。ETHも同じ瞬間に売られすぎかもしれません(シグナル相関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:
    """
    Calculate the signal correlation matrix (binary: 0 = flat, 1 = in position).

    Args:
        signals: dictionary {pair_name: binary_signal_array}
        method: correlation 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:
    """
    Estimate correlation_factor from the signal correlation matrix.

    correlation_factor = 1 + (N-1) * mean_pairwise_correlation

    When correlation is 0 → C_f = 1 (all independent).
    When correlation is 1 → C_f = N (all identical).
    """
    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. パニック時

時間的相関レジーム:平常市場 vs. パニック

相関は静的ではありません。平常期間中、BTCとアルトコインは乖離する可能性があります — ETHはイーサリアムのニュースで上昇し、SOLはソラナのニュースで上昇します。危機時には、すべてが単一のファクターに崩壊します:リスクオン/リスクオフ。

def rolling_correlation_factor(
    signals: dict,
    window_days: int = 30,
    step_days: int = 7,
) -> list:
    """
    Rolling correlation_factor to detect regime changes.
    """
    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個の暗号通貨ペアの典型的な様相:

市場レジーム 平均シグナル相関 CfC_f NeffN_{eff}
レンジ相場(低ボラティリティ) 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
パニック(暴落) 0.60-0.90 6.4-9.1 1.1-1.6

パニック時には、10ペアが1-2の実効ペアに圧縮されます。分散が最も必要なときに、それが消失するのです。これは古典的な「危機時には相関が1に向かう」の暗号通貨版です。

effective_N:核心概念

Effective N:相関ペアからの次元削減

公式と導出

effective_Nのアイデアは統計学から借用したもので、有効標本サイズが観測値の自己相関を考慮するものです。我々の目的では:

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

ここでρˉ\bar{\rho}は平均ペアワイズシグナル相関です。簡略表記:

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

性質:

  • ρˉ=0\bar{\rho} = 0のとき:Cf=1C_f = 1Neff=NN_{eff} = N — 完全独立
  • ρˉ=1\bar{\rho} = 1のとき:Cf=NC_f = NNeff=1N_{eff} = 1 — すべてのペアが同一
  • ρˉ=0.25\bar{\rho} = 0.25かつN=10N = 10のとき:Cf=3.25C_f = 3.25Neff=3.08N_{eff} = 3.08

データからcorrelation_factorを推定する方法

実践では3つのアプローチがあります:

1. シグナル相関行列から(正確)。

すべてのペアで戦略を実行し、バイナリシグナル(各分ごとの0/1)を取得し、相関行列を構築し、上記の公式を使ってCfC_fを計算します。

2. 価格リターンのPCAから(近似)。

シグナルが価格ダイナミクス(モメンタム、平均回帰)に強く依存する場合、NeffN_{eff}を分散の90%を説明するPCAコンポーネント数として推定できます。

3. 資産クラスのヒューリスティクスから(概算)。

資産クラス 典型的なCfC_f
暗号通貨(トップ10) 2.5-4.0
暗号通貨(DeFi/ミームコイン含む) 2.0-3.0
外国為替(メジャー) 1.5-2.5
株式(単一セクター) 2.0-3.5
株式(クロスセクター) 1.2-1.8

BTC、ETH、SOL、AVAX、MATIC、DOGE、DOT、LINK、UNI、ATOMの暗号通貨ポートフォリオの場合、安全な推定値はCf3C_f \approx 3です。

スロット稼働率モデリング

オーケストレータースロット稼働率ダッシュボード

P(1 active)P(\geq 1\ \text{active})の公式

相関を考慮した基本公式:

P(1 active)=1(1p)NeffP(\geq 1\ \text{active}) = 1 - (1 - p)^{N_{eff}}

異なる戦略とペア数の表(Cf=3C_f = 3):

戦略 pp(取引時間) 5ペア(Neff=1.7N_{eff}=1.7 10ペア(Neff=3.3N_{eff}=3.3 20ペア(Neff=6.7N_{eff}=6.7 50ペア(Neff=16.7N_{eff}=16.7
戦略B 5% 8.2% 15.6% 29.1% 58.0%
戦略A 15% 23.6% 41.8% 65.9% 92.8%
戦略C 45% 67.1% 89.0% 98.8% ~100%

5%のアクティビティの戦略Bでは、少なくとも1つのアクティブポジションが半分の時間ある状態にするために50ペアが必要です。しかも、50の暗号通貨ペアは10より強く相関しているという事実を考慮していません。

マルチスロットオーケストレーター

実際のオーケストレーターは複数のスロットを同時に管理します。5スロットと10ペアがある場合、稼働率の計算は異なります:

utilization=min(E[active],max_slots)max_slots\text{utilization} = \frac{\min(E[\text{active}], \text{max\_slots})}{\text{max\_slots}}

E[active]=NeffpE[\text{active}] = N_{eff} \cdot p

def estimate_fill_efficiency(
    trading_time_pct: float,
    n_pairs: int,
    correlation_factor: float = 3.0,
    max_slots: int = 1,
) -> dict:
    """
    Analytical estimate of fill efficiency for a multi-slot orchestrator.

    Args:
        trading_time_pct: fraction of active time for one strategy on one pair
        n_pairs: number of trading pairs
        correlation_factor: signal correlation coefficient
        max_slots: maximum number of simultaneous positions

    Returns:
        dict with utilization metrics
    """
    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 pairs, 1 slot", 0.05, 10, 3.0, 1),
    ("Strategy B, 10 pairs, 3 slots", 0.05, 10, 3.0, 3),
    ("Strategy B, 30 pairs, 1 slot", 0.05, 30, 3.0, 1),
    ("Strategy A, 10 pairs, 1 slot", 0.15, 10, 3.0, 1),
    ("Strategy C, 10 pairs, 1 slot", 0.45, 10, 3.0, 1),
    ("Strategy C, 10 pairs, 5 slots", 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 pairs, 1 slot:
  N_eff = 3.3
  P(≥1 active) = 15.6%
  E[active] = 0.17
  fill_efficiency = 15.6%

Strategy B, 10 pairs, 3 slots:
  N_eff = 3.3
  P(≥1 active) = 15.6%
  E[active] = 0.17
  fill_efficiency = 5.6%

Strategy B, 30 pairs, 1 slot:
  N_eff = 10.0
  P(≥1 active) = 40.1%
  E[active] = 0.50
  fill_efficiency = 40.1%

Strategy A, 10 pairs, 1 slot:
  N_eff = 3.3
  P(≥1 active) = 41.8%
  E[active] = 0.50
  fill_efficiency = 41.8%

Strategy C, 10 pairs, 1 slot:
  N_eff = 3.3
  P(≥1 active) = 89.0%
  E[active] = 1.50
  fill_efficiency = 89.0%

Strategy C, 10 pairs, 5 slots:
  N_eff = 3.3
  P(≥1 active) = 89.0%
  E[active] = 1.50
  fill_efficiency = 30.0%

注意:戦略Bの3スロット・10ペアではfill_efficiencyが5.6%です。期待されるアクティブペア数がわずか0.17のとき、3スロットは無意味です。スロットは期待される負荷に比例して割り当てるべきです。

実データからのシミュレーション

分析モデルは近似です。正確な推定にはリアルシグナルでのシミュレーションが必要です:

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 days
    priority_fn=None,        # priority function for position selection
) -> dict:
    """
    Simulate real slot load of the orchestrator.

    For each minute: count how many pairs want to enter a position,
    and how many slots are actually occupied (accounting for the limit).

    Args:
        all_signals: signals by pairs and strategies
        max_slots: maximum number of simultaneous positions
        test_period_minutes: length of the test period in minutes
        priority_fn: if None — FIFO; otherwise — ranking function
    """
    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),
    }

リアルデータでのシミュレーションは、分析的推定よりもさらに低い稼働率を示すことが多いです。これは、シグナルの時間的クラスタリングを考慮するためです:すべてのペアがクラスターで同時にエントリーしてオーバーフローを生み出し、その後すべてが沈黙して空白を生み出します。

何ペア監視すべきか?収穫逓減分析

Effective Nと収穫逓減曲線

重要な問題:どのNNでもう1ペア追加しても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:
    """
    Diminishing returns analysis from adding new pairs.
    """
    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: need {analysis_b['target_n_for_utilization']} pairs for 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%}")

戦略B(p=0.05p = 0.05Cf=3C_f = 3)の結果:

NNペア NeffN_{eff} P(1)P(\geq 1) 限界利得
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%

戦略Bの場合、80%のシングルスロット稼働率に到達することは100ペアでも不可能です(~96ペアが必要)。これは根本的な制約です:取引時間5%の戦略はシングルスロット運用には適していません — 複数の戦略を組み合わせたポートフォリオアプローチが必要です。

戦略A(p=0.15p = 0.15Cf=3C_f = 3)の場合:

NNペア NeffN_{eff} P(1)P(\geq 1) 限界利得
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%

戦略Aは~30ペアで80%の稼働率に到達します。30番目のペアの限界利得はわずか+1.2%です。

戦略C(p=0.45p = 0.45Cf=3C_f = 3)の場合:

NNペア NeffN_{eff} P(1)P(\geq 1)
3 1.0 45.0%
5 1.7 67.1%
10 3.3 89.0%
15 5.0 95.0%

取引時間45%の戦略Cは、わずか10ペアで90%の稼働率に到達します。それ以上追加しても意味がありません。

ペア間のエッジ劣化

取引ペア間のエッジ劣化

ペア数を制限するもう一つの要因があります:エッジの劣化。BTC/USDTで開発・最適化された戦略は、流動性の低いアルトコインではパフォーマンスが低下する可能性があります。

劣化の原因:

  • 流動性:DOGE/USDTのスリッページはBTC/USDTの数倍
  • スプレッド:流動性の低いペアはビッド・アスクスプレッドが広い
  • マイクロストラクチャー:オーダーブックのパターンはペアによって異なる
  • マニピュレーション:低流動性のアルトコインはポンプ・アンド・ダンプの影響を受けやすい
def edge_decay_analysis(
    strategy_results: dict,  # {pair: {"pnl_per_day": float, "n_trades": int}}
    min_trades: int = 30,
) -> list:
    """
    Rank pairs by edge accounting for degradation.
    """
    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/日 平均PnL/日
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/日が下がります。8番目のペアの時点で、エッジはすでにベストの半分です。fill_efficiency(ペア数とともに増加)と平均エッジ(低下する)のバランスが必要です。

最適ペア数:統一モデル

最適ペア数:fill efficiencyと平均エッジの交点

fill_efficiencyとエッジ劣化を単一の指標 — 期待ポートフォリオPnL/日 — に統合します:

Portfolio PnL/day=avg_edge(N)×fill_efficiency(N)×365\text{Portfolio PnL/day} = \text{avg\_edge}(N) \times \text{fill\_efficiency}(N) \times 365

def optimal_pairs_count(
    pair_edges: list,           # PnL/day in descending order: [0.89, 0.82, 0.71, ...]
    trading_time_pct: float,
    correlation_factor: float = 3.0,
    max_slots: int = 1,
) -> dict:
    """
    Find the optimal number of pairs that maximizes 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"Optimal number of pairs: {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}%")

最適値は通常、ペア追加による限界fill_efficiencyが平均エッジの低下を補えなくなるポイントにあります。典型的な暗号通貨ポートフォリオの場合:

  • 戦略B(5%時間):最適は8-12ペア
  • 戦略A(15%時間):最適は6-10ペア
  • 戦略C(45%時間):最適は4-6ペア

パラドックス:取引時間が最も短い戦略が最も多くのペアから恩恵を受けますが、fill_efficiencyは依然として低いままです。解決策はペアを増やすことではなく、他の戦略との組み合わせです(コンボ戦略を参照)。

クロスペア分散:相関削減戦略

クロス戦略分散ネットワーク

ペア数を無限に増やせないなら、CfC_fを下げる — つまりシグナルの多様性を高めることができます。

戦略1:流動性の高いトークンとDeFiトークンのミックス

BTC、ETH、BNBは非常に強く相関しています。しかし、UNI(DEX)、AAVE(レンディング)、CRV(ステーブルコイン)は独自のドライバーを持つ可能性があります。DeFiトークンを追加すると、平均ρˉ\bar{\rho}が0.35から0.20-0.25に低下します:

Cf=1+9×0.20=2.8(ρˉ=0.25のときの3.25と比較)C_f = 1 + 9 \times 0.20 = 2.8 \quad \text{(} \bar{\rho} = 0.25\text{のときの3.25と比較)}

戦略2:同じペアに異なる戦略

1つの戦略で10ペアではなく — 2つの異なる戦略で5ペア。戦略が異なる原理に基づいている場合(モメンタム vs. 平均回帰)、シグナルは逆相関になり得ます:

  • モメンタムは価格上昇時にロングエントリー
  • 平均回帰は価格下落時にロングエントリー

ρˉcross-strategy<0    Cf<1    Neff>N\bar{\rho}_{\text{cross-strategy}} < 0 \implies C_f < 1 \implies N_{eff} > N

これはNeff>NN_{eff} > Nを達成する唯一の方法です — 負のシグナル相関を持つ戦略を使用することです。

戦略3:現物 vs. 先物

ファンディングレート裁定取引と現物取引は異なる相関構造を持っています。裁定戦略をポートフォリオに追加すると、全体のCfC_fが大幅に低下します。裁定取引は定義上、収束ではなく乖離を利用するためです。

戦略タイプ別の実践的推奨事項

高頻度・低時間戦略(取引時間 < 10%)

典型的な代表:戦略B(5%時間、750日間で38トレード)。

  • ペア数:10-15(エッジ/稼働バランスの最適値)
  • 問題:15ペアでもfill_efficiencyは低い(~20-25%)
  • 解決策:オーケストレーターで他の戦略との組み合わせが必須
  • 暗号通貨のCfC_f:2.5-3.5
  • モニタリング:ローリングCfC_fで市場レジームに応じたペア数の適応

中時間戦略(取引時間10-30%)

典型的な代表:戦略A(15%時間、750日間で418トレード)。

  • ペア数:6-10
  • 10ペアでのFill_efficiency:~40%
  • 解決策:このような戦略を2-3組み合わせると80%以上の時間を埋められる
  • 暗号通貨のCfC_f:2.5-3.5
  • 重点:最大エッジのペアを選択し、数を追わない

高時間戦略(取引時間 > 30%)

典型的な代表:戦略C(45%時間)。

  • ペア数:4-6
  • 6ペアでのFill_efficiency:~80%
  • 問題:オーバーフロー — 複数ペアが同時にアクティブだがスロットが足りない
  • 解決策:max_slotsを増やすかペア優先順位付けを追加
  • 暗号通貨のCfC_f:2.5-4.0(ロングポジションが危機と重なるため高い)

サマリーテーブル

パラメータ 戦略B(5%) 戦略A(15%) 戦略C(45%)
推奨NN 10-15 6-10 4-6
Cf=3C_f=3でのNeffN_{eff} 3.3-5.0 2.0-3.3 1.3-2.0
Fill eff.(1スロット) 15-23% 32-42% 77-89%
組み合わせ必要? 必須 望ましい 不要
ボトルネック シグナル不足 バランス オーバーフロー

シリーズの他の指標との関連

この記事は「幻想なきバックテスト」シリーズの第11回です。シグナル相関は、前回の記事の指標に直接影響します:

  • PnL per Active Time fill_efficiencyは実効リターン公式の主要な乗数です。相関を無視してfill_efficiencyを過大評価すると、ポートフォリオのPnL予測が過度に楽観的になります。

  • ファンディングレート 高相関ではポジションが同時にオープンし、ファンディングコストはスロット数に比例して増大します。オーバーフロー + ファンディング = 資本の加速的な消耗。

  • ファンディングレート裁定 裁定戦略はポートフォリオのCfC_fを低下させる自然な分散要素です。そのシグナルはモメンタムや平均回帰戦略との相関が弱いです。

  • コンボ戦略(次の記事):異なるppCfC_fを持つ戦略のポートフォリオを組み立てて90%以上の稼働率を達成する方法。カスケードオーケストレーションは優先度を割り当てる際にシグナル相関を考慮します。

結論

暗号通貨における分散はペア数の問題ではありません。10の相関ペアは3-4の独立したペアの効果しかもたらしません。パニック時にはさらに少なくなります。

4つの要点:

  1. Nではなくeffective_Nを計算せよ。 暗号通貨ペアのCf3C_f \approx 3。10ペアは~3.3の実効ペアです。fill_efficiencyはNNではなくNeffN_{eff}に基づいて計画してください。

  2. 価格相関ではなくシグナル相関を測定せよ。 価格相関はプロキシであり、代替ではありません。すべてのペアで戦略を実行し、バイナリシグナル相関行列を計算してください。

  3. エッジの劣化を考慮せよ。 ペアが増えると平均PnL/日が下がります。最適点は、新しいペアからの限界fill_efficiencyがエッジの低下を補えるポイントです。

  4. NNを増やすのではなくCfC_fを下げよ。 同じペアに異なる戦略を組み合わせる方が、1つの戦略をより多くのペアに適用するより効果的です。クロス戦略の分散はNeff>NN_{eff} > Nを達成できます。

Correlation factorは、稼働率とリターン予測がどれほど現実的かを決定する隠れた変数です。これを無視することは、幻想の上にポートフォリオを構築することを意味します。


参考リンク

  1. Markowitz, H. — Portfolio Selection (1952)
  2. López de Prado — Advances in Financial Machine Learning: Denoising and Detoning
  3. Ledoit, O. & Wolf, M. — Honey, I Shrunk the Sample Covariance Matrix (2004)
  4. Laloux, L. et al. — Noise Dressing of Financial Correlation Matrices (1999)
  5. Cont, R. — Empirical Properties of Asset Returns: Stylized Facts and Statistical Issues
  6. Ernest Chan — Algorithmic Trading: Portfolio Construction and Risk Management
  7. Rebonato, R. & Jäckel, P. — The Most General Methodology for Creating a Valid Correlation Matrix

Citation

@article{soloviov2026signalcorrelation,
  author = {Soloviov, Eugen},
  title = {Signal Correlation: How Many Pairs to Monitor},
  year = {2026},
  url = {https://marketmaker.cc/ru/blog/post/signal-correlation-pairs},
  version = {0.1.0},
  description = {Why 10 crypto pairs don't provide 10x diversification, how to calculate effective\_N via correlation\_factor, and how many pairs you really need to monitor for 80-90\% orchestrator slot utilization.}
}
blog.disclaimer

MarketMaker.cc Team

クオンツ・リサーチ&戦略

Telegramで議論する
Newsletter

市場の先を行く

ニュースレターを購読して、独占的なAI取引の洞察、市場分析、プラットフォームの更新情報を受け取りましょう。

プライバシーを尊重します。いつでも配信停止可能です。