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

マルチシンボルバリデーション:全ペアで戦略をテストする

マルチシンボルバリデーション:全ペアで戦略をテストする
#algotrading
#backtest
#validation
#multi-symbol
#diversification
#crypto

「幻想なきバックテスト」シリーズの記事

ETHUSDTで戦略を最適化した。25ヶ月のデータ、12以上のパラメータ。バックテストはPnL +55%、500トレード、MaxDD -0.9%、ポジション保有時間15%を示す。エクイティカーブは滑らかに上昇。パラメータはプラトー分析を通過 — 最適値は広く見える。ウォークフォワードはWFER > 0.6を達成。モンテカルロブートストラップは正の第5パーセンタイルを示す。

すべて完璧。一つのことを除いて:戦略を単一の銘柄でしかテストしていない。

同じアルゴリズムを同じパラメータでBTCUSDTに適用すると — PnL +8%。SOLUSDTでは — PnL -12%。DOGEUSDTでは — PnL -34%。ETHですべてのチェックを通過した戦略が、他のほとんどのペアでは損失を出すことが判明する。

これはバグではない。これはシングルシンボルの罠 — アルゴトレーディングにおける最も一般的で巧妙なオーバーフィッティングの形態の一つだ。

単一銘柄の罠

シングルシンボルの罠:1つの輝くエクイティカーブの周りに他のアセットで失敗する戦略群

単一シンボルで戦略を最適化することは、本質的に特定のアセットの価格ダイナミクスにフィッティングすることだ。ウォークフォワードを実行しても、ブートストラップが広い信頼区間を示しても — これらのチェックはすべて単一の時系列内で行われた。

ウォークフォワードは時間方向の堅牢性をチェックする:パラメータが同じ銘柄の将来のデータで機能するか。モンテカルロはトレード順序方向の堅牢性をチェックする:戦略が異なるシーケンスに耐えられるか。しかし、いずれの方法も銘柄方向の堅牢性はチェックしない:戦略が異なる特性を持つ他のアセットで機能するか。

戦略がETHUSDTでのみ利益を出す場合 — 市場の非効率性ではなく、ETHの価格系列の特定の構造を捉えている:

  • ETH固有の特徴的なローソク足パターン
  • 閾値がチューニングされた特定のボラティリティレベル
  • この特定のペアの流動性とマイクロストラクチャーの特性
  • 特定の期間に特徴的なBTCとの相関

これはエッジではない。銘柄レベルのカーブフィッティングだ。

暗号通貨市場のシンボルグループ(ティア)

シンボルティア特性マトリックス

すべての暗号通貨が等しいわけではない。意味のあるマルチシンボルバリデーションには、銘柄が根本的に異なる特性を持つグループに分かれることを理解する必要がある。

ティア1:ブルーチップ(BTC、ETH)

高い流動性、比較的低いボラティリティ、機関投資家のフロー。マクロとの相関(S&P 500、DXY、FRB金利)。深いオーダーブック、タイトなスプレッド、安定したファンディングレート。典型的な日次ボラティリティ:2-4%。

ティア2:ラージキャップ(SOL、BNB、ADA、XRP、AVAX)

中程度の流動性、高めのボラティリティ。動きはセクターダイナミクス(L1 vs L2、DeFi vs infra)に左右されることが多い。ファンディングレートはより変動的。スプレッドはより広い。典型的な日次ボラティリティ:4-6%。

ティア3:ミッドキャップ(DOGE、SHIB、PEPE、ARB、OP)

ミームコインとナラティブトークン。高いボラティリティ、ファンダメンタルファクターとの低い相関。動きはソーシャルメディア、上場、ナラティブによって決定される。一部の取引所では薄いオーダーブック。典型的な日次ボラティリティ:6-10%。

ティア4:ローキャップ(新規上場)

極端なボラティリティ、薄いオーダーブック、操作リスク。完全なバックテストには不十分な履歴が多い。典型的な日次ボラティリティ:10-20%以上。

特性サマリーテーブル

特性 ティア1 ティア2 ティア3 ティア4
日次ボラティリティ 2-4% 4-6% 6-10% 10-20%+
平均スプレッド(パーペチュアル) 0.01-0.02% 0.02-0.05% 0.05-0.15% 0.1-0.5%+
オーダーブック深度(トップ5 bps) $5-50M $1-10M $100K-2M $10K-200K
ファンディングレート(平均絶対値) 0.005-0.01% 0.01-0.03% 0.02-0.08% 0.05-0.2%+
BTCとの相関 0.85-0.95 0.6-0.85 0.3-0.7 0.1-0.5
最低必要履歴 5年以上 2-5年 6ヶ月-3年 6ヶ月未満

各ティアは独自のマイクロストラクチャーを持つ別の「世界」だ。ティア1にチューニングされた戦略がティア3に移行する際、異質な環境に入る。

マルチシンボルバリデーションの方法論

マルチシンボルバリデーション方法論:最適化、同ティアテスト、他ティアテスト、結果分析

ステップ1:単一シンボルで最適化

最適化のためのシンボルを選ぶ — 例えばETHUSDT。完全なパイプラインを実行:Optuna最適化プラトー分析ウォークフォワード。パラメータを固定する。

ステップ2:同ティアのシンボルでテスト

同じパラメータで同ティアの5-10シンボルで戦略を実行する。ティア1ではこれは限られるが(BTC + ETH)、ティア2とティア3では十分なシンボルがある。

ステップ3:他ティアのシンボルでテスト

各他ティアの3-5シンボルで戦略を実行する。これは最も厳しいテスト:戦略がETHUSDT(ティア1)とDOGEUSDT(ティア3)で機能すれば、カーブフィッティングの可能性は最小限だ。

ステップ4:グループ別結果分析

ティアごとにメトリクスを集計し、クロスシンボル堅牢性を評価する。

各シンボルのメトリクス

各シンボルについて以下を記録する:

  • PnL — トータルリターン
  • MaxDD — 最大ドローダウン
  • N trades — トレード数
  • Win rate — 利益トレードの割合
  • PnL/active day — アクティブ時間あたりのリターン(詳細はアクティブ時間別PnLを参照)

合格基準

戦略がマルチシンボルバリデーションに合格する条件:

  1. 同ティアのシンボルの60%以上で利益を出す
  2. グループ全体の平均PnLがプラス
  3. MaxDDが劇的に増加しない(最適化シンボルに対して2-3倍以下)
  4. 最適化シンボルでのみ利益を出す戦略 — 却下

例:3つの戦略、3つの結果

3つの戦略の比較:A(緑、部分的成功)、B(シアン、堅牢)、C(赤、オーバーフィット)

具体的な例を見てみよう。ETHUSDTで最適化された3つの戦略(戦略A、戦略B、戦略C)を、4つのティアの12シンボルでテストする。

戦略A(ETHUSDTで最適化)

パラメータ:PnL +55%、約500トレード、約15%アクティブ時間、MaxDD約0.9%。

シンボル ティア PnL MaxDD トレード数 勝率 PnL/アクティブ日
ETHUSDT* 1 +55.2% -0.9% 491 52.1% 0.48%
BTCUSDT 1 +31.4% -1.8% 478 50.8% 0.27%
SOLUSDT 2 +22.7% -3.1% 512 49.2% 0.18%
BNBUSDT 2 +18.3% -2.7% 467 48.9% 0.16%
AVAXUSDT 2 +8.1% -4.5% 498 47.6% 0.07%
ADAUSDT 2 -3.2% -6.1% 445 46.1% -0.03%
DOGEUSDT 3 -12.8% -9.4% 531 44.3% -0.10%
SHIBUSDT 3 -18.7% -12.1% 487 43.1% -0.16%
PEPEUSDT 3 -24.3% -14.8% 556 42.7% -0.18%
ARBUSDT 3 -7.4% -7.2% 419 45.8% -0.07%
OPUSDT 3 -5.1% -6.8% 402 46.2% -0.05%

* — 最適化シンボル

ティア別結果:

ティア シンボル数 利益 平均PnL 平均MaxDD
ティア1 2 2 (100%) +43.3% -1.4%
ティア2 4 3 (75%) +11.5% -4.1%
ティア3 5 0 (0%) -13.7% -10.1%

判定: 戦略Aはティア1-2で機能するが、ティア3では完全に失敗する。これは低ボラティリティ環境にチューニングされた典型的な戦略だ。ブルーチップとラージキャップのポートフォリオ用 — 許容可能。汎用的な使用 — 不可。

戦略B(ETHUSDTで最適化)

パラメータ:PnL +25%、約40トレード、約5%アクティブ時間。

シンボル ティア PnL MaxDD トレード数 勝率
ETHUSDT* 1 +25.1% -2.3% 38 57.9%
BTCUSDT 1 +21.8% -2.8% 41 56.1%
SOLUSDT 2 +19.4% -3.5% 44 54.5%
BNBUSDT 2 +16.7% -3.1% 37 54.1%
AVAXUSDT 2 +12.3% -4.2% 42 52.4%
ADAUSDT 2 +8.9% -4.8% 39 51.3%
DOGEUSDT 3 +4.2% -6.7% 48 47.9%
SHIBUSDT 3 -1.3% -8.4% 45 46.7%
PEPEUSDT 3 -3.8% -9.1% 52 46.2%
ARBUSDT 3 +6.1% -5.8% 40 50.0%
OPUSDT 3 +3.7% -6.2% 38 50.0%

ティア別結果:

ティア シンボル数 利益 平均PnL 平均MaxDD
ティア1 2 2 (100%) +23.5% -2.6%
ティア2 4 4 (100%) +14.3% -3.9%
ティア3 5 3 (60%) +1.8% -7.2%

判定: 戦略Bは11シンボル中9つで利益を出す(82%)。全ティアで平均PnLがプラス。MaxDDはティアに応じて予測可能に増加する。これは真の市場エッジを持つ堅牢な戦略だ。最適化シンボルでのPnLはより控えめだが(+25% vs +55%)、戦略Bは戦略Aよりも大幅に信頼性が高い。

戦略C(ETHUSDTで最適化)

パラメータ:PnL +300%、約400トレード、約45%アクティブ時間、MaxDD約17%。

シンボル ティア PnL MaxDD トレード数 勝率
ETHUSDT* 1 +301.2% -17.1% 418 53.8%
BTCUSDT 1 +42.7% -28.4% 395 48.6%
SOLUSDT 2 -18.3% -41.2% 456 44.1%
BNBUSDT 2 +12.1% -33.7% 387 46.8%
AVAXUSDT 2 -31.4% -52.8% 471 42.3%
ADAUSDT 2 -44.7% -58.1% 412 40.5%
DOGEUSDT 3 -67.2% -74.3% 528 38.1%
PEPEUSDT 3 -72.1% -81.6% 574 37.4%

判定: 戦略Cは典型的なオーバーフィッティングだ。ETHUSDTで+301%だが、他のほとんどのペアで壊滅的な損失。ティア3のMaxDDは70%を超える — これは資本の破壊だ。戦略はETH固有のパターンを捉えたのであり、市場の非効率性ではない。却下。

戦略が他のシンボルで失敗する理由

戦略を壊す4つの要因:ボラティリティ、流動性、マイクロストラクチャー、レジーム遷移

1. ボラティリティのミスマッチ

最も一般的な理由。戦略パラメータは特定のボラティリティレベルにチューニングされている。戦略が2%のエントリー閾値を使用する場合 — 日次ボラティリティ3%のETHでは妥当なフィルターだ。日次ボラティリティ8%のDOGEでは — この閾値が頻繁にトリガーされ、大量の偽シグナルが発生する。

同様に、1%のストップロスはETHには適切だが、PEPEにとっては通常の「ノイズ」であり、ストップが1日に数十回ヒットする。

2. 流動性の違い

戦略は現在の価格で即時注文執行を前提としている。5 bps以内で50Mのオーダーブック深度を持つBTCUSDTでは—これは現実的だ。50Mのオーダーブック深度を持つBTCUSDTでは — これは現実的だ。200Kの深度を持つARBUSDTでは — $10Kの注文が価格を動かし、実際の約定が0.05-0.2%悪化する。500トレード超えると、スリッページだけで25-100%を失う。

3. 市場マイクロストラクチャー

各銘柄には独自のマイクロストラクチャーがある:

  • ファンディングレート: BTCでは強気相場でファンディングが一貫してプラス(8時間ごとに+0.01%)。ミームコインではファンディングが-0.3%から+0.5%まで跳ね上がることがある。詳細 — ファンディングレートがレバレッジを殺すを参照。
  • スプレッド: ティア1ではスプレッドが0.01%、ティア4では0.5%。スプレッドがテイクサイズを超える場合、小さなテイクプロフィットの戦略は利益を出せない。
  • 操作パターン: ウィック、スプーフィング、ウォッシュトレーディング — ティアごとに異なる形で現れる。

4. レジーム感度

アルトコインは異なる市場フェーズで異なる挙動を示す:

  • 上昇トレンドでは、アルトコインはBTCをアウトパフォームする(ベータ > 1)
  • 下降トレンドでは、アルトコインはBTCよりも大きく下落する
  • 横ばい相場では、アルトコインはBTCと相関するか、独自のナラティブで動く可能性がある

特定のフェーズで特定のシンボルに最適化された戦略は、そのシンボルのBTCに対するラグ/リードに最適にチューニングされている可能性があり — このラグ/リードはレジームシフト時に変化する。

適応的パラメータスケーリング

適応的パラメータスケーリング:ボラティリティ比率ゲージとパラメータスライダーがタイトから広い閾値に変換される

すべてのシンボルで同一パラメータで戦略を実行することは正しくない。しかし、各シンボルで完全に再最適化すると、マルチシンボルバリデーションの目的自体が無意味になる(パラメータが各シンボルに「ネイティブ」になる)。

妥協案はボラティリティによるパラメータ正規化だ:

import numpy as np

def scale_params_by_volatility(
    base_params: dict,
    optimization_symbol_vol: float,
    target_symbol_vol: float,
    vol_sensitive_params: list[str],
) -> dict:
    """
    Scale strategy parameters by target symbol volatility.

    Args:
        base_params: parameters optimized on the original symbol
        optimization_symbol_vol: daily volatility of the optimization symbol
        target_symbol_vol: daily volatility of the target symbol
        vol_sensitive_params: list of volatility-sensitive parameters
    """
    vol_ratio = target_symbol_vol / optimization_symbol_vol
    adjusted = base_params.copy()

    for param in vol_sensitive_params:
        if param in adjusted:
            adjusted[param] = adjusted[param] * vol_ratio

    return adjusted

base_params = {
    "entry_threshold": 0.02,     # 2% — entry threshold
    "stop_loss": 0.01,           # 1% — stop loss
    "take_profit": 0.03,         # 3% — take profit
    "trailing_stop": 0.008,      # 0.8% — trailing stop
    "atr_multiplier": 2.5,       # ATR multiplier (not scaled)
    "rsi_period": 14,            # RSI period (not scaled)
    "ma_fast": 10,               # fast MA (not scaled)
    "ma_slow": 50,               # slow MA (not scaled)
}

vol_sensitive = ["entry_threshold", "stop_loss", "take_profit", "trailing_stop"]

eth_vol = 0.032     # 3.2%
doge_vol = 0.081    # 8.1%

doge_params = scale_params_by_volatility(
    base_params, eth_vol, doge_vol, vol_sensitive
)

print("ETH params:", {k: f"{v:.4f}" for k, v in base_params.items() if k in vol_sensitive})
print("DOGE params:", {k: f"{v:.4f}" for k, v in doge_params.items() if k in vol_sensitive})

出力:

ETH params:  {'entry_threshold': '0.0200', 'stop_loss': '0.0100', 'take_profit': '0.0300', 'trailing_stop': '0.0080'}
DOGE params: {'entry_threshold': '0.0506', 'stop_loss': '0.0253', 'take_profit': '0.0759', 'trailing_stop': '0.0203'}

ストップロスが1%から2.53%に増加した — DOGEの8.1%日次ボラティリティに適切だ。スケーリングなしでは、1%のストップは「ノイズ」で何十回もヒットする。

重要: 価格閾値(エントリー、ストップ、テイク)のみをスケーリングする。インジケーター期間(RSI、MA)と乗数(ATR乗数)は通常スケーリングしない — インジケーター自体がボラティリティで正規化されている。

2つのバリデーションモード

  1. 厳格モード(スケーリングなし): 同一パラメータで実行。絶対的な堅牢性のテスト。戦略が利益を出す場合 — エッジは強い。

  2. 適応モード(スケーリングあり): 正規化されたパラメータで実行。ボラティリティレベルが異なることを許容した上での戦略ロジックの堅牢性テスト。

両方のテストを実行することを推奨する。厳格モード — エッジの「強度」を評価するため。適応モード — 実用的な適用のため。

クロスシンボル堅牢性スコア

マルチシンボルクロス堅牢性レーダー

マルチシンボル堅牢性の定量的評価のために、複合メトリクス — Cross-Symbol Robustness Score (CSRS) を導入する。

計算式

CSRS=w1Rprofit+w2Rpnl+w3Pconsistencyw4Pvariance\text{CSRS} = w_1 \cdot R_{profit} + w_2 \cdot R_{pnl} + w_3 \cdot P_{consistency} - w_4 \cdot P_{variance}

ここで:

  • RprofitR_{profit} — 利益を出すシンボルの割合:

Rprofit=NprofitableNtotalR_{profit} = \frac{N_{profitable}}{N_{total}}

  • RpnlR_{pnl} — 正規化された流動性加重平均PnL:

Rpnl=i=1NliPnLii=1NliR_{pnl} = \frac{\sum_{i=1}^{N} l_i \cdot \text{PnL}_i}{\sum_{i=1}^{N} l_i}

ここで lil_i はシンボル ii の流動性(平均日次出来高)。

  • PconsistencyP_{consistency} — クロスティア一貫性のボーナス:

Pconsistency=Nprofitable_tiersNtotal_tiersP_{consistency} = \frac{N_{profitable\_tiers}}{N_{total\_tiers}}

  • PvarianceP_{variance} — シンボル間のPnL分散が高いことへのペナルティ:

Pvariance=σ(PnL1,,PnLN)max(PnLˉ,0.01)P_{variance} = \frac{\sigma(\text{PnL}_1, \ldots, \text{PnL}_N)}{\max(|\bar{\text{PnL}}|, 0.01)}

デフォルトウェイト

コンポーネント ウェイト 根拠
w1w_1(利益割合) 0.35 最も重要:戦略は多数で機能すべき
w2w_2(平均PnL) 0.25 絶対リターン
w3w_3(クロスティア) 0.25 汎用性へのボーナス
w4w_4(分散ペナルティ) 0.15 不安定性へのペナルティ

CSRSの解釈

CSRS 解釈
> 0.7 優れた堅牢性。戦略はほとんどの銘柄で機能する。
0.5 — 0.7 良好な堅牢性。戦略は自身のティアで機能し、部分的に他のティアでも機能する。
0.3 — 0.5 ボーダーライン。戦略は限られたシンボルセットで機能する。
< 0.3 低い堅牢性。銘柄レベルのカーブフィッティングの可能性が高い。

完全な実装:マルチシンボルバリデーションパイプライン

マルチシンボル戦略バリデーションのためのアイソメトリック3Dデータ処理パイプライン

import numpy as np
import pandas as pd
from dataclasses import dataclass, field
from typing import Callable, Optional

@dataclass
class SymbolResult:
    """Strategy result on a single symbol."""
    symbol: str
    tier: int
    pnl: float
    max_dd: float
    n_trades: int
    win_rate: float
    pnl_per_active_day: float
    avg_daily_volume: float  # liquidity

@dataclass
class TierResult:
    """Aggregated result by tier."""
    tier: int
    symbols: list[SymbolResult]
    n_symbols: int
    n_profitable: int
    profit_ratio: float
    avg_pnl: float
    avg_max_dd: float
    pnl_std: float

@dataclass
class MultiSymbolResult:
    """Full multi-symbol validation result."""
    symbol_results: list[SymbolResult]
    tier_results: list[TierResult]
    csrs: float
    passed: bool
    optimization_symbol: str
    report: str

SYMBOL_TIERS = {
    1: ["BTCUSDT", "ETHUSDT"],
    2: ["SOLUSDT", "BNBUSDT", "ADAUSDT", "XRPUSDT", "AVAXUSDT"],
    3: ["DOGEUSDT", "SHIBUSDT", "PEPEUSDT", "ARBUSDT", "OPUSDT"],
}

SYMBOL_VOLATILITY = {
    "BTCUSDT": 0.028, "ETHUSDT": 0.032,
    "SOLUSDT": 0.052, "BNBUSDT": 0.038, "ADAUSDT": 0.048,
    "XRPUSDT": 0.045, "AVAXUSDT": 0.055,
    "DOGEUSDT": 0.081, "SHIBUSDT": 0.092, "PEPEUSDT": 0.105,
    "ARBUSDT": 0.068, "OPUSDT": 0.063,
}

SYMBOL_VOLUME = {
    "BTCUSDT": 15e9, "ETHUSDT": 8e9,
    "SOLUSDT": 2e9, "BNBUSDT": 1.5e9, "ADAUSDT": 800e6,
    "XRPUSDT": 1.2e9, "AVAXUSDT": 500e6,
    "DOGEUSDT": 1e9, "SHIBUSDT": 400e6, "PEPEUSDT": 600e6,
    "ARBUSDT": 300e6, "OPUSDT": 250e6,
}


def run_multi_symbol_validation(
    strategy_fn: Callable,
    base_params: dict,
    optimization_symbol: str,
    data_loader: Callable,
    vol_sensitive_params: list[str],
    adaptive: bool = True,
    csrs_weights: tuple = (0.35, 0.25, 0.25, 0.15),
    min_profit_ratio: float = 0.6,
) -> MultiSymbolResult:
    """
    Full multi-symbol validation pipeline.

    Args:
        strategy_fn: strategy function (data, params) -> (pnl, max_dd, n_trades, win_rate, returns)
        base_params: parameters optimized on optimization_symbol
        optimization_symbol: optimization symbol
        data_loader: data loading function (symbol) -> np.ndarray
        vol_sensitive_params: parameters to scale by volatility
        adaptive: use volatility scaling
        csrs_weights: weights (w1, w2, w3, w4) for CSRS
        min_profit_ratio: minimum fraction of profitable symbols in a tier
    """
    w1, w2, w3, w4 = csrs_weights
    opt_vol = SYMBOL_VOLATILITY.get(optimization_symbol, 0.03)

    symbol_results = []

    for tier, symbols in SYMBOL_TIERS.items():
        for symbol in symbols:
            data = data_loader(symbol)
            if data is None or len(data) < 100:
                continue

            if adaptive and symbol != optimization_symbol:
                sym_vol = SYMBOL_VOLATILITY.get(symbol, 0.05)
                params = scale_params_by_volatility(
                    base_params, opt_vol, sym_vol, vol_sensitive_params
                )
            else:
                params = base_params.copy()

            pnl, max_dd, n_trades, win_rate, returns = strategy_fn(data, params)

            active_days = max(n_trades * 0.5, 1)  # rough estimate
            pnl_per_day = pnl / active_days

            symbol_results.append(SymbolResult(
                symbol=symbol,
                tier=tier,
                pnl=pnl,
                max_dd=max_dd,
                n_trades=n_trades,
                win_rate=win_rate,
                pnl_per_active_day=pnl_per_day,
                avg_daily_volume=SYMBOL_VOLUME.get(symbol, 1e6),
            ))

    tier_results = []
    tiers_present = sorted(set(r.tier for r in symbol_results))

    for tier in tiers_present:
        tier_symbols = [r for r in symbol_results if r.tier == tier]
        n_profitable = sum(1 for r in tier_symbols if r.pnl > 0)
        pnls = [r.pnl for r in tier_symbols]

        tier_results.append(TierResult(
            tier=tier,
            symbols=tier_symbols,
            n_symbols=len(tier_symbols),
            n_profitable=n_profitable,
            profit_ratio=n_profitable / len(tier_symbols) if tier_symbols else 0,
            avg_pnl=np.mean(pnls),
            avg_max_dd=np.mean([r.max_dd for r in tier_symbols]),
            pnl_std=np.std(pnls),
        ))

    all_pnls = [r.pnl for r in symbol_results]
    all_volumes = [r.avg_daily_volume for r in symbol_results]
    n_total = len(symbol_results)
    n_profitable = sum(1 for r in symbol_results if r.pnl > 0)

    r_profit = n_profitable / n_total if n_total > 0 else 0

    total_vol = sum(all_volumes)
    r_pnl_raw = sum(r.pnl * r.avg_daily_volume for r in symbol_results) / total_vol
    r_pnl = 1 / (1 + np.exp(-r_pnl_raw * 5))

    profitable_tiers = sum(1 for tr in tier_results if tr.avg_pnl > 0)
    p_consistency = profitable_tiers / len(tier_results) if tier_results else 0

    pnl_std = np.std(all_pnls) if len(all_pnls) > 1 else 0
    pnl_mean = np.mean(all_pnls) if all_pnls else 0.01
    p_variance = pnl_std / max(abs(pnl_mean), 0.01)
    p_variance = min(p_variance, 5.0)  # cap the penalty

    csrs = w1 * r_profit + w2 * r_pnl + w3 * p_consistency - w4 * (p_variance / 5.0)
    csrs = max(0, min(1, csrs))  # clamp to [0, 1]

    opt_tier = None
    for tier, symbols in SYMBOL_TIERS.items():
        if optimization_symbol in symbols:
            opt_tier = tier
            break

    same_tier_result = next((tr for tr in tier_results if tr.tier == opt_tier), None)
    passed = (
        csrs >= 0.5
        and (same_tier_result is None or same_tier_result.profit_ratio >= min_profit_ratio)
        and np.mean(all_pnls) > 0
    )

    report = _generate_report(
        symbol_results, tier_results, csrs, passed,
        optimization_symbol, adaptive
    )

    return MultiSymbolResult(
        symbol_results=symbol_results,
        tier_results=tier_results,
        csrs=csrs,
        passed=passed,
        optimization_symbol=optimization_symbol,
        report=report,
    )


def _generate_report(
    symbol_results, tier_results, csrs, passed,
    opt_symbol, adaptive
) -> str:
    """Generate text report."""
    lines = []
    lines.append("=" * 60)
    lines.append("MULTI-SYMBOL VALIDATION REPORT")
    lines.append(f"Optimization symbol: {opt_symbol}")
    lines.append(f"Mode: {'adaptive' if adaptive else 'strict'}")
    lines.append(f"CSRS: {csrs:.3f}")
    lines.append(f"Passed: {'YES' if passed else 'NO'}")
    lines.append("=" * 60)

    for tr in tier_results:
        lines.append(f"\n--- Tier {tr.tier} ---")
        lines.append(f"  Symbols: {tr.n_symbols}, Profitable: {tr.n_profitable} "
                      f"({tr.profit_ratio:.0%})")
        lines.append(f"  Avg PnL: {tr.avg_pnl:.2%}, Avg MaxDD: {tr.avg_max_dd:.2%}")
        lines.append(f"  PnL StdDev: {tr.pnl_std:.2%}")

        for sr in tr.symbols:
            marker = "*" if sr.symbol == opt_symbol else " "
            status = "+" if sr.pnl > 0 else "-"
            lines.append(
                f"  {marker} [{status}] {sr.symbol:12s} "
                f"PnL={sr.pnl:+.2%}  MaxDD={sr.max_dd:.2%}  "
                f"Trades={sr.n_trades:4d}  WR={sr.win_rate:.1%}"
            )

    lines.append("\n" + "=" * 60)
    return "\n".join(lines)

パイプライン使用例

def my_strategy(data, params):
    """Your strategy. Returns (pnl, max_dd, n_trades, win_rate, returns)."""
    pass

def load_ohlcv(symbol):
    """Load OHLCV data for a symbol."""
    pass

base_params = {
    "entry_threshold": 0.02,
    "stop_loss": 0.01,
    "take_profit": 0.03,
    "trailing_stop": 0.008,
    "atr_multiplier": 2.5,
    "rsi_period": 14,
    "ma_fast": 10,
    "ma_slow": 50,
}

result = run_multi_symbol_validation(
    strategy_fn=my_strategy,
    base_params=base_params,
    optimization_symbol="ETHUSDT",
    data_loader=load_ohlcv,
    vol_sensitive_params=["entry_threshold", "stop_loss", "take_profit", "trailing_stop"],
    adaptive=True,
)

print(result.report)
print(f"\nCSRS: {result.csrs:.3f}")
print(f"Passed: {result.passed}")

シングルシンボルバリデーションが許容される場合

例外:マーケットメイキングオーダーブック、クロスエクスチェンジアービトラージ、ユニークなアセット固有の相関パターン

すべての戦略が複数の銘柄で機能する必要はない。シングルシンボルが正常なアプローチとなる正当なケースがある:

特定のオーダーブックでのマーケットメイキング

マーケットメイキング戦略(例えばAvellaneda-Stoikovモデルを使用)は、定義上特定のオーダーブックに紐づいている。パラメータは特定のマイクロストラクチャー(深度、スプレッド、キューポジション、約定率)に依存する。別のシンボルでのテストは無意味 — 別のオーダーブックだからだ。

特定ペア間のアービトラージ

ファンディングレートアービトラージクロスエクスチェンジアービトラージは、定義上特定の銘柄ペアに紐づいている。ここでのバリデーションは、異なるシンボルではなく、同じペアを持つ他の取引所で行う。

固有のアセット特性を明示的に使用する戦略

戦略が特定のアセットの固有特性(例:BTCとハッシュレートの相関、ETHとガス手数料の相関)に基づいている場合、マルチシンボルバリデーションは適用できない。ただし、そのような戦略は稀だ。

それ以外のすべてのケース — 戦略が「汎用的な」シグナル(MAクロスオーバー、RSI、モメンタム、平均回帰)に基づいている場合 — マルチシンボルバリデーションは必須だ。汎用的な戦略が1つのシンボルでのみ機能する場合、それはエッジではなくオーバーフィッティングだ。

他のバリデーション手法との関係

3つの直交するバリデーション軸:時間(ウォークフォワード)、銘柄(マルチシンボル)、トレード順序(モンテカルロ)

マルチシンボルバリデーションは、アウトオブサンプルテストの3つの直交する手法の1つだ:

手法 バリデーション軸 検出対象
ウォークフォワード 時間 特定期間へのオーバーフィッティング
マルチシンボル 銘柄 特定アセットへのオーバーフィッティング
モンテカルロブートストラップ トレード順序 特定シーケンスへの依存

各手法は独自の軸に沿って堅牢性をチェックする。戦略はウォークフォワードを通過してもマルチシンボルで失敗する可能性がある(銘柄にカーブフィット)。マルチシンボルを通過してもモンテカルロで失敗する可能性がある(幸運なトレード順序に依存)。

最大のオーバーフィッティング防御:3つの手法すべてを使用する。

完全なバリデーションパイプライン:

  1. 単一シンボルでのパラメータ最適化
  2. プラトー分析 — 最適値の安定性チェック
  3. ウォークフォワード — 時間軸バリデーション(WFER > 0.5)
  4. マルチシンボル — 銘柄軸バリデーション(CSRS > 0.5)
  5. モンテカルロブートストラップ — 信頼区間(第5パーセンタイル > 0)
  6. ファンディングレート損失の非対称性を考慮

6つのチェックすべてを通過した戦略は、オーバーフィッティングの産物である確率が最小限だ。

拡張:シンボル相関とカスケード戦略

暗号通貨の相関ネットワークグラフとカスケード戦略配分フロー

マルチシンボルバリデーションは、もう一つの側面を明らかにする:シンボル間の相関。戦略がBTCとETHで利益を出すが、すべてのアルトコインで損失を出す場合 — これはエッジがBTC-ETHの高い相関に結びついているという情報だ。相関構造の詳細な分析はシグナル相関とペアトレーディングの記事で。

戦略ポートフォリオの場合、マルチシンボルの結果はどの銘柄で戦略を稼働させるべきかを決定する。上記の例の戦略A — ティア1-2のみ。戦略B — ティア1-3。これはカスケードオーケストレーションへの入力データであり、堅牢性プロファイルに応じて異なる銘柄に異なる戦略が展開される。

結論

マルチシンボルバリデーションはオプションではなく — 一般化された市場エッジを主張するすべての戦略にとって必須のステップだ。重要なポイント:

  1. 単一シンボルでのみ機能する戦略は、そのシンボルの特性にオーバーフィットしている可能性が高い。 例外:マーケットメイキング、アービトラージ、固有のアセット特性に基づく戦略。

  2. ティアグルーピングは必須。 ボラティリティ、流動性、マイクロストラクチャーの違いを理解せずにBTC(ティア1)の結果とPEPE(ティア3)の結果を比較することはできない。

  3. 適応的パラメータスケーリング — ボラティリティによる閾値の正規化 — はマルチシンボルテストの現実性を大幅に向上させる。

  4. CSRS > 0.5 — 妥当な最低閾値。戦略は同ティアのシンボルの60%以上で利益を出し、全シンボルの平均PnLがプラスであるべきだ。

  5. ウォークフォワード + マルチシンボル + モンテカルロ — 3つの直交するバリデーション軸。各手法は他が見逃すものを捕捉する。3つすべてを使用する。

PnL +25%でCSRS 0.72の戦略は、PnL +300%でCSRS 0.18の戦略よりも信頼性が高い。前者は市場の非効率性から利益を得る。後者は単一の価格系列の記憶から利益を得る。


参考リンク

  1. Lopez de Prado, M. — Advances in Financial Machine Learning (Wiley)
  2. Pardo, R. — The Evaluation and Optimization of Trading Strategies (Wiley)
  3. Bailey, D.H. et al. — The Probability of Backtest Overfitting
  4. Aronson, D.R. — Evidence-Based Technical Analysis
  5. Kevin Davey — Building Winning Algorithmic Trading Systems (Wiley)
  6. Harvey, C.R. & Liu, Y. — Backtesting (2015)
  7. Chan, E. — Algorithmic Trading: Winning Strategies and Their Rationale (Wiley)
  8. Binance Research — Cryptocurrency Correlation Analysis
  9. NumPy — numpy.random.choice
  10. Pandas — DataFrame

Citation

@article{soloviov2026multisymbolvalidation,
  author = {Soloviov, Eugen},
  title = {Multi-Symbol Validation: Test Your Strategy on All Pairs},
  year = {2026},
  url = {https://marketmaker.cc/ja/blog/post/multi-symbol-validation},
  version = {0.1.0},
  description = {Why a strategy optimized on ETHUSDT may fail on altcoins. How to properly test across pair groups (blue chips, large caps, shitcoins) and what cross-symbol robustness score to consider sufficient.}
}
blog.disclaimer

MarketMaker.cc Team

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

Telegramで議論する
Newsletter

市場の先を行く

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

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