ความสัมพันธ์ของสัญญาณ: ต้องติดตามกี่คู่เหรียญ
คุณเปิดกลยุทธ์บน 10 คู่คริปโต: BTC/USDT, ETH/USDT, SOL/USDT, AVAX/USDT และอีกหกคู่ alt. ตรรกะดูแน่นหนา: หากกลยุทธ์ active 5% ของเวลาในคู่เดียว แล้วใน 10 คู่อย่างน้อยหนึ่งคู่ควร active ของเวลา การใช้งานเพิ่มขึ้นสี่เท่า
ในทางปฏิบัติ การใช้งานกลับเป็น 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 เท่า

การนิยามอย่างเป็นทางการ
สมมติว่ากลยุทธ์ในแต่ละคู่จาก คู่ active เป็น ส่วนของเวลา หากสัญญาณ อิสระอย่างสมบูรณ์ ความน่าจะเป็นที่อย่างน้อยหนึ่งคู่ active:
สำหรับ Strategy B (, ):
แต่สัญญาณไม่ได้อิสระจากกัน คริปโตเคลื่อนไหวพร้อมกัน — หมายความว่าสัญญาณเกิดและดับเป็นกลุ่ม
ความสัมพันธ์เปลี่ยน 10 คู่ให้เป็น 3
ความเข้าใจโดยสัญชาตญาณคือ: หาก 10 คู่มีความสัมพันธ์กัน พวกมันส่งข้อมูลจากไม่ใช่ 10 แหล่งอิสระ แต่อาจแค่ 3-4 แหล่ง เราทำให้เป็นทางการผ่าน effective_N:
โดยที่ คือ ปัจจัยความสัมพันธ์ (correlation factor) ที่สะท้อนความสัมพันธ์เฉลี่ยของสัญญาณแบบคู่ต่อคู่ เมื่อ คู่จะอิสระอย่างสมบูรณ์; เมื่อ คู่ทั้งหมดเหมือนกัน
สำหรับคู่คริปโต เป็นเรื่องปกติ ดังนั้น:
ไม่ใช่ 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. ช่วงตื่นตระหนก

ความสัมพันธ์ไม่คงที่ ในช่วงสงบ 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 คู่คริปโต:
| ระบอบตลาด | ความสัมพันธ์สัญญาณเฉลี่ย | ||
|---|---|---|---|
| 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 sample size คำนึงถึง autocorrelation ของการสังเกต สำหรับวัตถุประสงค์ของเรา:
โดยที่ คือความสัมพันธ์สัญญาณเฉลี่ยแบบคู่ต่อคู่ สัญกรณ์แบบย่อ:
คุณสมบัติ:
- เมื่อ : , — อิสระอย่างสมบูรณ์
- เมื่อ : , — ทุกคู่เหมือนกัน
- เมื่อ และ : ,
วิธีประมาณค่า correlation_factor จากข้อมูล
ในทางปฏิบัติ มีสามวิธี:
1. จากเมทริกซ์ความสัมพันธ์สัญญาณ (แม่นยำ)
รันกลยุทธ์บนทุกคู่ รับสัญญาณไบนารี (0/1 สำหรับแต่ละนาที) สร้างเมทริกซ์ความสัมพันธ์ คำนวณ โดยใช้สูตรข้างต้น
2. จาก PCA ของผลตอบแทนราคา (ประมาณ)
หากสัญญาณขึ้นอยู่กับพลวัตราคาอย่างมาก (momentum, mean-reversion) คุณสามารถประมาณ เป็นจำนวนคอมโพเนนต์ PCA ที่อธิบาย 90% ของความแปรปรวน
3. จากฮิวริสติกส์ประเภทสินทรัพย์ (คร่าวๆ)
| ประเภทสินทรัพย์ | ทั่วไป |
|---|---|
| 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 การประมาณที่ปลอดภัยคือ
การสร้างแบบจำลองการใช้งาน Slot

สูตรสำหรับ
สูตรพื้นฐานที่คำนึงถึงความสัมพันธ์:
ตารางสำหรับกลยุทธ์ต่างๆ และจำนวนคู่ ():
| กลยุทธ์ | (เวลาเทรด) | 5 คู่ () | 10 คู่ () | 20 คู่ () | 50 คู่ () |
|---|---|---|---|---|---|
| Strategy B | 5% | 8.2% | 15.6% | 29.1% | 58.0% |
| Strategy A | 15% | 23.6% | 41.8% | 65.9% | 92.8% |
| Strategy C | 45% | 67.1% | 89.0% | 98.8% | ~100% |
สำหรับ Strategy B ที่มี activity 5% คุณต้องการ 50 คู่เพื่อให้มีอย่างน้อยหนึ่ง active position ครึ่งหนึ่งของเวลา และนั่นยังไม่คำนึงถึงว่า 50 คู่คริปโตมีความสัมพันธ์กันมากกว่า 10 คู่
Orchestrator หลาย Slot
Orchestrator จริงจัดการหลาย slot พร้อมกัน หากคุณมี 5 slot และ 10 คู่ การใช้งานคำนวณต่างออกไป:
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 แล้วก็เงียบพร้อมกัน สร้างช่องว่าง
ต้องติดตามกี่คู่? การวิเคราะห์ผลตอบแทนที่ลดลง

คำถามสำคัญ: ที่ เท่าไหร่การเพิ่มคู่ใหม่จึงหยุดเพิ่ม 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 (, ):
| คู่ | ผลตอบแทนส่วนเพิ่ม | ||
|---|---|---|---|
| 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 (, ):
| คู่ | ผลตอบแทนส่วนเพิ่ม | ||
|---|---|---|---|
| 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 (, ):
| คู่ | ||
|---|---|---|
| 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 กลยุทธ์ที่พัฒนาและปรับแต่งบน 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 และการเสื่อมของ edge เป็นเมตริกเดียว — PnL พอร์ตโฟลิโอที่คาดหวังต่อวัน:
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)
การกระจายความเสี่ยงข้ามคู่: กลยุทธ์เพื่อลดความสัมพันธ์

หากคุณไม่สามารถเพิ่มจำนวนคู่ได้ไม่จำกัด คุณสามารถลด — นั่นคือเพิ่มความหลากหลายของสัญญาณ
กลยุทธ์ 1: ผสม Liquid Token กับ DeFi Token
BTC, ETH, BNB มีความสัมพันธ์กันอย่างแน่นแฟ้น แต่ UNI (DEX), AAVE (lending), CRV (stablecoins) อาจมีตัวขับเคลื่อนของตัวเอง การเพิ่ม DeFi token ลด เฉลี่ยจาก 0.35 เป็น 0.20-0.25:
กลยุทธ์ 2: กลยุทธ์ต่างกันบนคู่เดียวกัน
แทนที่ 10 คู่กับกลยุทธ์เดียว — 5 คู่กับสองกลยุทธ์ต่างกัน หากกลยุทธ์ตั้งอยู่บนหลักการต่างกัน (momentum vs. mean-reversion) สัญญาณของพวกมันอาจ anti-correlated:
- Momentum เข้า long เมื่อราคาขึ้น
- Mean-reversion เข้า long เมื่อราคาลง
นี่คือวิธีเดียวที่จะได้ — ใช้กลยุทธ์ที่มีความสัมพันธ์สัญญาณเชิงลบ
กลยุทธ์ 3: Spot vs. Futures
การ arbitrage Funding rate และการเทรด spot มีโครงสร้างความสัมพันธ์ต่างกัน การเพิ่ม กลยุทธ์ arbitrage ในพอร์ตโฟลิโอลด โดยรวมอย่างมีนัยสำคัญ เพราะ arbitrage โดยนิยามใช้ประโยชน์จากการแยกทาง ไม่ใช่การบรรจบ
คำแนะนำปฏิบัติตามประเภทกลยุทธ์
กลยุทธ์ความถี่สูงที่ใช้เวลาต่ำ (เวลาเทรด < 10%)
ตัวแทนทั่วไป: Strategy B (5% เวลา, 38 การเทรดใน 750 วัน)
- จำนวนคู่: 10-15 (ค่าที่เหมาะสมสำหรับความสมดุล edge/fill)
- ปัญหา: fill_efficiency ต่ำแม้มี 15 คู่ (~20-25%)
- ทางออก: จำเป็นต้องรวมกับกลยุทธ์อื่นใน orchestrator
- สำหรับคริปโต: 2.5-3.5
- การติดตาม: rolling เพื่อปรับจำนวนคู่ตามระบอบตลาด
กลยุทธ์ระยะกลาง (เวลาเทรด 10-30%)
ตัวแทนทั่วไป: Strategy A (15% เวลา, 418 การเทรดใน 750 วัน)
- จำนวนคู่: 6-10
- Fill_efficiency ที่ 10 คู่: ~40%
- ทางออก: การรวม 2-3 กลยุทธ์ดังกล่าวเติมเต็ม 80%+ ของเวลา
- สำหรับคริปโต: 2.5-3.5
- เน้น: เลือกคู่ที่มี edge สูงสุด อย่าไล่ตามปริมาณ
กลยุทธ์ที่ใช้เวลาสูง (เวลาเทรด > 30%)
ตัวแทนทั่วไป: Strategy C (45% เวลา)
- จำนวนคู่: 4-6
- Fill_efficiency ที่ 6 คู่: ~80%
- ปัญหา: overflow — หลายคู่ active พร้อมกัน แต่มี slot น้อยกว่า
- ทางออก: เพิ่ม max_slots หรือเพิ่มการจัดลำดับความสำคัญของคู่
- สำหรับคริปโต: 2.5-4.0 (สูงกว่าเนื่องจาก long position ทับซ้อนวิกฤต)
ตารางสรุป
| พารามิเตอร์ | Strategy B (5%) | Strategy A (15%) | Strategy C (45%) |
|---|---|---|---|
| แนะนำ | 10-15 | 6-10 | 4-6 |
| ที่ | 3.3-5.0 | 2.0-3.3 | 1.3-2.0 |
| Fill eff. (1 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 ธรรมชาติที่ลด ของพอร์ตโฟลิโอ สัญญาณของพวกมันมีความสัมพันธ์ต่ำกับกลยุทธ์ momentum และ mean-reversion
-
กลยุทธ์ Combo (บทความถัดไป): วิธีประกอบพอร์ตโฟลิโอกลยุทธ์ที่มี และ ต่างกันเพื่อให้ถึง 90%+ utilization Cascade orchestration คำนึงถึงความสัมพันธ์สัญญาณเมื่อกำหนดลำดับความสำคัญ
บทสรุป
การกระจายความเสี่ยงในคริปโตไม่ใช่เรื่องของจำนวนคู่ 10 คู่ที่มีความสัมพันธ์กันให้ผลเหมือน 3-4 คู่ที่อิสระ ในช่วงตื่นตระหนก ยิ่งน้อยลงไปอีก
สี่บทเรียนสำคัญ:
-
คำนวณ effective_N ไม่ใช่ N สำหรับคู่คริปโต สิบคู่คือ ~3.3 คู่ที่มีประสิทธิภาพ วาง fill_efficiency บน ไม่ใช่
-
วัดความสัมพันธ์สัญญาณ ไม่ใช่ความสัมพันธ์ราคา ความสัมพันธ์ราคาเป็นตัวแทน ไม่ใช่สิ่งทดแทน รันกลยุทธ์บนทุกคู่และคำนวณเมทริกซ์ความสัมพันธ์สัญญาณไบนารี
-
คำนึงถึงการเสื่อมของ edge คู่มากขึ้นหมายถึง PnL/วันเฉลี่ยต่ำลง ค่าที่เหมาะสมอยู่ที่จุดที่ fill_efficiency ส่วนเพิ่มจากคู่ใหม่ยังชดเชยการลดลงของ edge ได้
-
ลด อย่าเพิ่ม การรวมกลยุทธ์ต่างกันบนคู่เดียวกันมีประสิทธิภาพมากกว่ากลยุทธ์เดียวบนคู่มากขึ้น การกระจายความเสี่ยงข้ามกลยุทธ์สามารถให้
Correlation factor คือตัวแปรซ่อนเร้นที่กำหนดความสมจริงของการคาดการณ์ utilization และผลตอบแทนของคุณ การเพิกเฉยต่อมันหมายถึงการสร้างพอร์ตโฟลิโอบนภาพลวงตา
ลิงก์ที่เป็นประโยชน์
- Markowitz, H. — Portfolio Selection (1952)
- López de Prado — Advances in Financial Machine Learning: Denoising and Detoning
- Ledoit, O. & Wolf, M. — Honey, I Shrunk the Sample Covariance Matrix (2004)
- Laloux, L. et al. — Noise Dressing of Financial Correlation Matrices (1999)
- Cont, R. — Empirical Properties of Asset Returns: Stylized Facts and Statistical Issues
- Ernest Chan — Algorithmic Trading: Portfolio Construction and Risk Management
- Rebonato, R. & Jäckel, P. — The Most General Methodology for Creating a Valid Correlation Matrix
การอ้างอิง
@article{soloviov2026signalcorrelation,
author = {Soloviov, Eugen},
title = {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\%}
}
ผู้เขียน
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.