← กลับไปยังบทความ
March 14, 2026
อ่าน 5 นาที

ความสัมพันธ์ของสัญญาณ: ต้องติดตามกี่คู่เหรียญ

ความสัมพันธ์ของสัญญาณ: ต้องติดตามกี่คู่เหรียญ
#algotrading
#correlation
#diversification
#portfolio
#orchestration
#crypto

คุณเปิดกลยุทธ์บน 10 คู่คริปโต: BTC/USDT, ETH/USDT, SOL/USDT, AVAX/USDT และอีกหกคู่ alt. ตรรกะดูแน่นหนา: หากกลยุทธ์ active 5% ของเวลาในคู่เดียว แล้วใน 10 คู่อย่างน้อยหนึ่งคู่ควร active 10.9510=40%1 - 0.95^{10} = 40\% ของเวลา การใช้งานเพิ่มขึ้นสี่เท่า

ในทางปฏิบัติ การใช้งานกลับเป็น 15-16% ไม่ใช่ 40% 10 คู่ของคุณทำตัวเหมือนแค่ 3 คู่ เงินทุนนั่งอยู่เฉย fill_efficiency ลดลง และผลตอบแทนพอร์ตโฟลิโอที่แท้จริงต่ำกว่าที่คาดการณ์ถึงสามเท่า

สาเหตุคือ ความสัมพันธ์ของสัญญาณ (signal correlation) และในตลาดคริปโตนั้นสูงอย่างน่าตกใจ

ภาพลวงตาของการกระจายความเสี่ยงในคริปโต

ภาพลวงตาของการกระจายความเสี่ยงในตลาดคริปโตเคอเรนซี

ในตลาดการเงินดั้งเดิม การกระจายความเสี่ยงได้ผลเพราะหุ้น Apple กับ ETF น้ำมันตอบสนองต่อปัจจัยที่แตกต่างกัน แต่ในตลาดคริปโตนั้นต่างออกไปโดยสิ้นเชิง

BTC คือปัจจัยหลัก เมื่อ Bitcoin ลด 5% ETH ลด 6-8%, SOL ลด 8-12%, altcoin ลด 10-20% ความสัมพันธ์ของผลตอบแทนรายวันในตลาดคริปโตอยู่สูงกว่า 0.6 อย่างสม่ำเสมอ และในช่วงตื่นตระหนกจะเข้าใกล้ 1.0

แต่สำหรับเรา — นักเทรด algo — สิ่งที่สำคัญไม่ใช่ความสัมพันธ์ของราคา แต่คือ ความสัมพันธ์ของสัญญาณ หาก BTC กระตุ้นสัญญาณเข้าซื้อจากกลยุทธ์ที่ใช้ momentum มีความน่าจะเป็นสูงที่ ETH และ SOL จะกระตุ้นสัญญาณคล้ายกันในนาทีเดียวกัน ทุกคู่เข้า long พร้อมกัน ออกพร้อมกัน สิบโพสิชัน — แต่จริงๆ แล้วเดิมพันเดียว

ทำไม 10 คู่ ≠ การกระจายความเสี่ยง 10 เท่า

10 คู่ที่มีความสัมพันธ์กันยุบเป็น 3 คู่ที่มีประสิทธิภาพ

การนิยามอย่างเป็นทางการ

สมมติว่ากลยุทธ์ในแต่ละคู่จาก NN คู่ active เป็น pp ส่วนของเวลา หากสัญญาณ อิสระอย่างสมบูรณ์ ความน่าจะเป็นที่อย่างน้อยหนึ่งคู่ active:

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

สำหรับ Strategy B (p=0.05p = 0.05, N=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_f คือ ปัจจัยความสัมพันธ์ (correlation 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% ของความแปรปรวนผลตอบแทนรายวันสำหรับ altcoin ส่วนใหญ่ สิ่งนี้เห็นได้ชัดเจนผ่าน 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 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 คู่คริปโต:

Component ความแปรปรวนที่อธิบาย สะสม
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) — เข้า long 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:
    """
    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 และ alt อาจแยกทาง — ETH ขึ้นตามข่าว Ethereum, SOL ขึ้นตามข่าว Solana ในช่วงวิกฤต ทุกอย่างยุบรวมเป็นปัจจัยเดียว: risk-on/risk-off

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}
Sideways (vol ต่ำ) 0.15-0.25 2.4-3.3 3.0-4.2
Uptrend 0.25-0.40 3.3-4.6 2.2-3.0
Downtrend 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 คู่ที่มีประสิทธิภาพ ตรงเมื่อต้องการการกระจายความเสี่ยงมากที่สุด มันกลับหายไป นี่คือสิ่งเปรียบเทียบคริปโตกับ "ความสัมพันธ์เข้าสู่ 1 ในช่วงวิกฤต" แบบคลาสสิก

effective_N: แนวคิดสำคัญ

Effective N: การลดมิติจากคู่ที่มีความสัมพันธ์กัน

สูตรและการอนุมาน

แนวคิดของ effective_N ยืมมาจากสถิติ ซึ่ง effective sample size คำนึงถึง autocorrelation ของการสังเกต สำหรับวัตถุประสงค์ของเรา:

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 = 1, Neff=NN_{eff} = N — อิสระอย่างสมบูรณ์
  • เมื่อ ρˉ=1\bar{\rho} = 1: Cf=NC_f = N, Neff=1N_{eff} = 1 — ทุกคู่เหมือนกัน
  • เมื่อ ρˉ=0.25\bar{\rho} = 0.25 และ N=10N = 10: Cf=3.25C_f = 3.25, Neff=3.08N_{eff} = 3.08

วิธีประมาณค่า correlation_factor จากข้อมูล

ในทางปฏิบัติ มีสามวิธี:

1. จากเมทริกซ์ความสัมพันธ์สัญญาณ (แม่นยำ)

รันกลยุทธ์บนทุกคู่ รับสัญญาณไบนารี (0/1 สำหรับแต่ละนาที) สร้างเมทริกซ์ความสัมพันธ์ คำนวณ CfC_f โดยใช้สูตรข้างต้น

2. จาก PCA ของผลตอบแทนราคา (ประมาณ)

หากสัญญาณขึ้นอยู่กับพลวัตราคาอย่างมาก (momentum, mean-reversion) คุณสามารถประมาณ NeffN_{eff} เป็นจำนวนคอมโพเนนต์ PCA ที่อธิบาย 90% ของความแปรปรวน

3. จากฮิวริสติกส์ประเภทสินทรัพย์ (คร่าวๆ)

ประเภทสินทรัพย์ CfC_f ทั่วไป
Crypto (top-10) 2.5-4.0
Crypto (รวม DeFi/memecoins) 2.0-3.0
Forex (majors) 1.5-2.5
หุ้น (sector เดียว) 2.0-3.5
หุ้น (ข้าม sector) 1.2-1.8

สำหรับพอร์ตโฟลิโอคริปโตของ BTC, ETH, SOL, AVAX, MATIC, DOGE, DOT, LINK, UNI, ATOM การประมาณที่ปลอดภัยคือ Cf3C_f \approx 3

การสร้างแบบจำลองการใช้งาน Slot

แดชบอร์ดการใช้งาน orchestrator slot

สูตรสำหรับ 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)
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 ที่มี activity 5% คุณต้องการ 50 คู่เพื่อให้มีอย่างน้อยหนึ่ง active position ครึ่งหนึ่งของเวลา และนั่นยังไม่คำนึงถึงว่า 50 คู่คริปโตมีความสัมพันธ์กันมากกว่า 10 คู่

Orchestrator หลาย Slot

Orchestrator จริงจัดการหลาย slot พร้อมกัน หากคุณมี 5 slot และ 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%

หมายเหตุ: Strategy B ที่มี 3 slot และ 10 คู่แสดง fill_efficiency ที่ 5.6% สาม slot ไม่มีประโยชน์เมื่อจำนวนคู่ที่ active ที่คาดหวังมีเพียง 0.17 ควรจัดสรร slot ตามสัดส่วนของภาระที่คาดหวัง

การจำลองจากข้อมูลจริง

แบบจำลองเชิงวิเคราะห์เป็นเพียงการประมาณ สำหรับการประมาณที่แม่นยำ คุณต้องการการจำลองบนสัญญาณจริง:

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),
    }

การจำลองบนข้อมูลจริงมักแสดงการใช้งานที่ต่ำกว่าการประมาณเชิงวิเคราะห์ เนื่องจากคำนึงถึงการรวมกลุ่มตามเวลาของสัญญาณ: ทุกคู่เข้าพร้อมกันในกลุ่ม สร้าง overflow แล้วก็เงียบพร้อมกัน สร้างช่องว่าง

ต้องติดตามกี่คู่? การวิเคราะห์ผลตอบแทนที่ลดลง

Effective N และกราฟผลตอบแทนที่ลดลง

คำถามสำคัญ: ที่ NN เท่าไหร่การเพิ่มคู่ใหม่จึงหยุดเพิ่ม 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%}")

ผลลัพธ์สำหรับ Strategy B (p=0.05p = 0.05, Cf=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%

สำหรับ Strategy B การถึง 80% single-slot utilization ไม่สามารถทำได้แม้กับ 100 คู่ (ต้องการ ~96 คู่) นี่คือข้อจำกัดพื้นฐาน: กลยุทธ์ที่มีเวลาเทรด 5% ไม่เหมาะสำหรับการดำเนินการ single-slot — ต้องการแนวทางพอร์ตโฟลิโอที่มีหลายกลยุทธ์

สำหรับ Strategy A (p=0.15p = 0.15, Cf=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%

Strategy A ถึง 80% utilization ที่ ~30 คู่ ผลตอบแทนส่วนเพิ่มที่คู่ที่ 30 คือเพียง +1.2%

สำหรับ Strategy C (p=0.45p = 0.45, Cf=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%

Strategy C ที่มีเวลาเทรด 45% ถึง 90% utilization ที่แค่ 10 คู่ การเพิ่มคู่ต่อไปไม่มีประโยชน์

การเสื่อมของ Edge ข้ามคู่เหรียญ

การเสื่อมของ edge ข้ามคู่เหรียญ

มีอีกปัจจัยที่จำกัดจำนวนคู่: การเสื่อมของ edge กลยุทธ์ที่พัฒนาและปรับแต่งบน BTC/USDT อาจทำงานได้แย่กว่าบน alt ที่มีสภาพคล่องต่ำกว่า

สาเหตุของการเสื่อม:

  • สภาพคล่อง: slippage บน DOGE/USDT สูงกว่า BTC/USDT หลายเท่า
  • Spread: คู่ที่มีสภาพคล่องต่ำมี bid-ask spread กว้างกว่า
  • Microstructure: รูปแบบ order book แตกต่างกันระหว่างคู่
  • การจัดการ: alt ที่มีสภาพคล่องต่ำเสี่ยงต่อ pump-and-dump
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 edge ลดลงเหลือครึ่งหนึ่งของคู่ที่ดีที่สุดแล้ว จำเป็นต้องมีความสมดุลระหว่าง fill_efficiency (เพิ่มขึ้นตามจำนวนคู่) และ edge เฉลี่ย (ลดลง)

จำนวนคู่ที่เหมาะสม: แบบจำลองรวม

จำนวนคู่ที่เหมาะสม: จุดตัดของ fill efficiency กับ average edge

เรารวม fill_efficiency และการเสื่อมของ edge เป็นเมตริกเดียว — 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 ส่วนเพิ่มจากการเพิ่มคู่ไม่ชดเชยการลดลงของ edge เฉลี่ยอีกต่อไป สำหรับพอร์ตโฟลิโอคริปโตทั่วไป:

  • Strategy B (5% เวลา): ค่าที่เหมาะสมที่ 8-12 คู่
  • Strategy A (15% เวลา): ค่าที่เหมาะสมที่ 6-10 คู่
  • Strategy C (45% เวลา): ค่าที่เหมาะสมที่ 4-6 คู่

ความขัดแย้ง: กลยุทธ์ที่มีเวลาเทรดต่ำสุดได้ประโยชน์จากคู่มากที่สุด แต่ fill_efficiency ยังคงต่ำ ทางออกไม่ใช่คู่มากขึ้น แต่คือ การรวมกับกลยุทธ์อื่น (ดู กลยุทธ์ Combo)

การกระจายความเสี่ยงข้ามคู่: กลยุทธ์เพื่อลดความสัมพันธ์

เครือข่ายการกระจายความเสี่ยงข้ามกลยุทธ์

หากคุณไม่สามารถเพิ่มจำนวนคู่ได้ไม่จำกัด คุณสามารถลด CfC_f — นั่นคือเพิ่มความหลากหลายของสัญญาณ

กลยุทธ์ 1: ผสม Liquid Token กับ DeFi Token

BTC, ETH, BNB มีความสัมพันธ์กันอย่างแน่นแฟ้น แต่ UNI (DEX), AAVE (lending), CRV (stablecoins) อาจมีตัวขับเคลื่อนของตัวเอง การเพิ่ม DeFi token ลด ρˉ\bar{\rho} เฉลี่ยจาก 0.35 เป็น 0.20-0.25:

Cf=1+9×0.20=2.8(vs. 3.25 at ρˉ=0.25)C_f = 1 + 9 \times 0.20 = 2.8 \quad \text{(vs. 3.25 at } \bar{\rho} = 0.25\text{)}

กลยุทธ์ 2: กลยุทธ์ต่างกันบนคู่เดียวกัน

แทนที่ 10 คู่กับกลยุทธ์เดียว — 5 คู่กับสองกลยุทธ์ต่างกัน หากกลยุทธ์ตั้งอยู่บนหลักการต่างกัน (momentum vs. mean-reversion) สัญญาณของพวกมันอาจ anti-correlated:

  • Momentum เข้า long เมื่อราคาขึ้น
  • Mean-reversion เข้า long เมื่อราคาลง

ρˉ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: Spot vs. Futures

การ arbitrage Funding rate และการเทรด spot มีโครงสร้างความสัมพันธ์ต่างกัน การเพิ่ม กลยุทธ์ arbitrage ในพอร์ตโฟลิโอลด CfC_f โดยรวมอย่างมีนัยสำคัญ เพราะ arbitrage โดยนิยามใช้ประโยชน์จากการแยกทาง ไม่ใช่การบรรจบ

คำแนะนำปฏิบัติตามประเภทกลยุทธ์

กลยุทธ์ความถี่สูงที่ใช้เวลาต่ำ (เวลาเทรด < 10%)

ตัวแทนทั่วไป: Strategy B (5% เวลา, 38 การเทรดใน 750 วัน)

  • จำนวนคู่: 10-15 (ค่าที่เหมาะสมสำหรับความสมดุล edge/fill)
  • ปัญหา: fill_efficiency ต่ำแม้มี 15 คู่ (~20-25%)
  • ทางออก: จำเป็นต้องรวมกับกลยุทธ์อื่นใน orchestrator
  • CfC_f สำหรับคริปโต: 2.5-3.5
  • การติดตาม: rolling CfC_f เพื่อปรับจำนวนคู่ตามระบอบตลาด

กลยุทธ์ระยะกลาง (เวลาเทรด 10-30%)

ตัวแทนทั่วไป: Strategy A (15% เวลา, 418 การเทรดใน 750 วัน)

  • จำนวนคู่: 6-10
  • Fill_efficiency ที่ 10 คู่: ~40%
  • ทางออก: การรวม 2-3 กลยุทธ์ดังกล่าวเติมเต็ม 80%+ ของเวลา
  • CfC_f สำหรับคริปโต: 2.5-3.5
  • เน้น: เลือกคู่ที่มี edge สูงสุด อย่าไล่ตามปริมาณ

กลยุทธ์ที่ใช้เวลาสูง (เวลาเทรด > 30%)

ตัวแทนทั่วไป: Strategy C (45% เวลา)

  • จำนวนคู่: 4-6
  • Fill_efficiency ที่ 6 คู่: ~80%
  • ปัญหา: overflow — หลายคู่ active พร้อมกัน แต่มี slot น้อยกว่า
  • ทางออก: เพิ่ม max_slots หรือเพิ่มการจัดลำดับความสำคัญของคู่
  • CfC_f สำหรับคริปโต: 2.5-4.0 (สูงกว่าเนื่องจาก long position ทับซ้อนวิกฤต)

ตารางสรุป

พารามิเตอร์ Strategy B (5%) Strategy A (15%) Strategy C (45%)
แนะนำ NN 10-15 6-10 4-6
NeffN_{eff} ที่ Cf=3C_f=3 3.3-5.0 2.0-3.3 1.3-2.0
Fill eff. (1 slot) 15-23% 32-42% 77-89%
จำเป็นต้องรวม? จำเป็น แนะนำ ไม่
คอขวด สัญญาณน้อย สมดุล Overflow

ความเชื่อมโยงกับเมตริกอื่นในซีรีส์

บทความนี้เป็นบทที่สิบเอ็ดในซีรีส์ "Backtests Without Illusions" ความสัมพันธ์สัญญาณส่งผลโดยตรงต่อเมตริกจากบทความก่อนหน้า:

  • PnL per Active Time: fill_efficiency คือตัวคูณสำคัญในสูตรผลตอบแทนที่แท้จริง หากคุณประเมิน fill_efficiency สูงเกินจริงโดยละเลยความสัมพันธ์ การคาดการณ์ PnL พอร์ตโฟลิโอของคุณจะมองโลกในแง่ดีเกินไป

  • Funding rates: เมื่อมีความสัมพันธ์สูง position เปิดพร้อมกัน — และค่า funding เพิ่มขึ้นเชิงเส้นตามจำนวน slot Overflow + funding = การเผาไหม้เงินทุนที่เร่งขึ้น

  • Funding rate arbitrage: กลยุทธ์ arbitrage เป็น diversifier ธรรมชาติที่ลด CfC_f ของพอร์ตโฟลิโอ สัญญาณของพวกมันมีความสัมพันธ์ต่ำกับกลยุทธ์ momentum และ mean-reversion

  • กลยุทธ์ Combo (บทความถัดไป): วิธีประกอบพอร์ตโฟลิโอกลยุทธ์ที่มี pp และ CfC_f ต่างกันเพื่อให้ถึง 90%+ utilization Cascade orchestration คำนึงถึงความสัมพันธ์สัญญาณเมื่อกำหนดลำดับความสำคัญ

บทสรุป

การกระจายความเสี่ยงในคริปโตไม่ใช่เรื่องของจำนวนคู่ 10 คู่ที่มีความสัมพันธ์กันให้ผลเหมือน 3-4 คู่ที่อิสระ ในช่วงตื่นตระหนก ยิ่งน้อยลงไปอีก

สี่บทเรียนสำคัญ:

  1. คำนวณ effective_N ไม่ใช่ N สำหรับคู่คริปโต Cf3C_f \approx 3 สิบคู่คือ ~3.3 คู่ที่มีประสิทธิภาพ วาง fill_efficiency บน NeffN_{eff} ไม่ใช่ NN

  2. วัดความสัมพันธ์สัญญาณ ไม่ใช่ความสัมพันธ์ราคา ความสัมพันธ์ราคาเป็นตัวแทน ไม่ใช่สิ่งทดแทน รันกลยุทธ์บนทุกคู่และคำนวณเมทริกซ์ความสัมพันธ์สัญญาณไบนารี

  3. คำนึงถึงการเสื่อมของ edge คู่มากขึ้นหมายถึง PnL/วันเฉลี่ยต่ำลง ค่าที่เหมาะสมอยู่ที่จุดที่ fill_efficiency ส่วนเพิ่มจากคู่ใหม่ยังชดเชยการลดลงของ edge ได้

  4. ลด CfC_f อย่าเพิ่ม NN การรวมกลยุทธ์ต่างกันบนคู่เดียวกันมีประสิทธิภาพมากกว่ากลยุทธ์เดียวบนคู่มากขึ้น การกระจายความเสี่ยงข้ามกลยุทธ์สามารถให้ Neff>NN_{eff} > N

Correlation factor คือตัวแปรซ่อนเร้นที่กำหนดความสมจริงของการคาดการณ์ utilization และผลตอบแทนของคุณ การเพิกเฉยต่อมันหมายถึงการสร้างพอร์ตโฟลิโอบนภาพลวงตา


ลิงก์ที่เป็นประโยชน์

  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

การอ้างอิง

@article{soloviov2026signalcorrelation,
  author = {Soloviov, Eugen},
  title = {Signal Correlation: How Many Pairs to Monitor},
  year = {2026},
  url = {https://marketmaker.cc/th/blog/post/signal-correlation-pairs},
  version = {0.1.0},
  description = {ทำไม 10 คู่คริปโตถึงไม่ให้การกระจายความเสี่ยง 10 เท่า วิธีคำนวณ effective\_N ผ่าน correlation\_factor และต้องติดตามกี่คู่เพื่อให้ orchestrator slot ถูกใช้งาน 80-90\%}
}
ข้อจำกัดความรับผิดชอบ: ข้อมูลที่ให้ไว้ในบทความนี้มีไว้เพื่อการศึกษาและให้ข้อมูลเท่านั้น และไม่ถือเป็นคำแนะนำทางการเงิน การลงทุน หรือการเทรด การเทรดสกุลเงินดิจิทัลมีความเสี่ยงสูงที่จะขาดทุน

ผู้เขียน

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

ก้าวนำหน้าตลาด

สมัครรับจดหมายข่าวของเราเพื่อรับข้อมูลเชิงลึกการเทรดด้วย AI เฉพาะ การวิเคราะห์ตลาด และการอัปเดตแพลตฟอร์ม

เราเคารพความเป็นส่วนตัวของคุณ ยกเลิกการสมัครได้ทุกเมื่อ