← 기사 목록으로
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에서 모든 검증을 통과한 전략이 대부분의 다른 페어에서는 손실을 기록한다.

이것은 버그가 아니다. 이것은 단일심볼 함정 — 알고트레이딩에서 가장 흔하고 교활한 과적합 형태 중 하나이다.

단일 종목의 함정

단일심볼 함정: 하나의 빛나는 에쿼티 커브 주변에 다른 자산에서 실패하는 전략들

단일 심볼에서 전략을 최적화하는 것은 본질적으로 특정 자산의 가격 역학에 피팅하는 것이다. 워크포워드를 실행했더라도, 부트스트랩이 넓은 신뢰 구간을 보여줬더라도 — 이 모든 검증은 단일 시계열 내에서 수행되었다.

워크포워드는 시간 방향의 견고성을 검증한다: 파라미터가 같은 종목의 미래 데이터에서 작동하는지. 몬테카를로는 거래 순서 방향의 견고성을 검증한다: 전략이 다른 시퀀스를 견딜 수 있는지. 하지만 이러한 방법 중 어느 것도 종목 방향의 견고성을 검증하지 않는다: 전략이 다른 특성을 가진 다른 자산에서 작동하는지.

전략이 ETHUSDT에서만 수익을 낸다면 — 시장의 비효율성이 아니라 ETH 가격 계열의 특정 구조를 포착한 것이다:

  • ETH 고유의 특징적인 캔들 패턴
  • 임계값이 튜닝된 특정 변동성 수준
  • 이 특정 페어의 유동성 및 미시구조 특성
  • 특정 기간에 특징적인 BTC와의 상관관계

이것은 에지가 아니다. 종목 수준의 커브 피팅이다.

암호화폐 시장의 심볼 그룹(티어)

심볼 티어 특성 매트릭스

모든 암호화폐가 동등하지는 않다. 의미 있는 멀티심볼 검증을 위해서는 종목이 근본적으로 다른 특성을 가진 그룹으로 나뉜다는 것을 이해해야 한다.

티어 1: 블루칩 (BTC, ETH)

높은 유동성, 비교적 낮은 변동성, 기관 투자자 자금 흐름. 매크로와의 상관관계(S&P 500, DXY, 연준 금리). 깊은 오더북, 타이트한 스프레드, 안정적인 펀딩 레이트. 일반적인 일간 변동성: 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. 최적화 심볼에서만 수익을 내는 전략 — 기각

예시: 세 가지 전략, 세 가지 결과

세 전략 비교: A(녹색, 부분적 성공), B(시안, 견고함), C(빨강, 과적합)

구체적인 예를 살펴보자. ETHUSDT에서 최적화된 세 가지 전략(전략 A, 전략 B, 전략 C)을 네 개의 티어에 걸쳐 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 고유의 패턴을 포착했다. 기각.

전략이 다른 심볼에서 실패하는 이유

전략을 무너뜨리는 네 가지 요인: 변동성, 유동성, 미시구조, 레짐 전환

1. 변동성 불일치

가장 흔한 원인. 전략 파라미터가 특정 변동성 수준에 맞게 튜닝되어 있다. 전략이 2% 진입 임계값을 사용하는 경우 — 일간 변동성 3%인 ETH에서는 합리적인 필터이다. 일간 변동성 8%인 DOGE에서는 — 이 임계값이 너무 자주 트리거되어 대량의 거짓 신호가 발생한다.

마찬가지로, 1% 손절매는 ETH에 적절하지만, PEPE에게는 일반적인 "노이즈"이며, 손절이 하루에 수십 번 발동된다.

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 승수)는 일반적으로 스케일링하지 않는다 — 인디케이터 자체가 이미 변동성으로 정규화되어 있다.

두 가지 검증 모드

  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. 단일 심볼에서 파라미터 최적화
  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. 워크포워드 + 멀티심볼 + 몬테카를로 — 세 가지 직교 검증 축. 각 방법은 다른 것이 놓치는 것을 포착한다. 세 가지 모두 사용한다.

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/ko/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 트레이딩 통찰력, 시장 분석 및 플랫폼 업데이트를 받아보세요.

귀하의 개인정보를 존중합니다. 언제든지 구독을 취소할 수 있습니다.