← Torna agli articoli
March 14, 2026
5 min di lettura

Correlazione dei Segnali: Quante Coppie Monitorare

Correlazione dei Segnali: Quante Coppie Monitorare
#algotrading
#correlazione
#diversificazione
#portafoglio
#orchestrazione
#crypto

Lanci una strategia su 10 coppie crypto: BTC/USDT, ETH/USDT, SOL/USDT, AVAX/USDT e altre sei alt. La logica sembra inattaccabile: se la strategia è attiva il 5% del tempo su una coppia, allora su 10 coppie almeno una dovrebbe essere attiva 10.9510=40%1 - 0.95^{10} = 40\% del tempo. Un aumento quadruplo dell'utilizzo.

In pratica, l'utilizzo risulta essere il 15-16%, non il 40%. Le tue 10 coppie si comportano come 3. Il capitale rimane fermo, la fill_efficiency cala e il rendimento effettivo del portafoglio risulta tre volte inferiore a quello previsto.

Il motivo è la correlazione dei segnali. E nelle criptovalute è catastroficamente alta.

L'Illusione della Diversificazione nel Crypto

L'illusione della diversificazione nei mercati delle criptovalute

Nella finanza tradizionale la diversificazione funziona perché le azioni Apple e un ETF petrolifero reagiscono a fattori diversi. Nel mercato delle criptovalute le cose stanno diversamente.

BTC è il fattore dominante. Quando Bitcoin cala del 5%, ETH cala del 6-8%, SOL del 8-12%, le altcoin del 10-20%. Le correlazioni dei rendimenti giornalieri nel mercato crypto sono costantemente superiori a 0.6, e durante i momenti di panico si avvicinano a 1.0.

Ma per noi — trader algoritmici — ciò che conta non è la correlazione dei prezzi, bensì la correlazione dei segnali. Se la strategia è basata sul momentum e BTC genera un segnale di entrata, è molto probabile che ETH e SOL generino un segnale analogo nello stesso minuto. Tutte le coppie entrano in long simultaneamente, tutte escono simultaneamente. Dieci posizioni — ma in sostanza una sola scommessa.

Perché 10 Coppie ≠ 10x Diversificazione

10 coppie correlate collassano in 3 coppie effettive

Formulazione Formale

Supponiamo che una strategia su ciascuna delle NN coppie sia attiva una frazione pp del tempo. Se i segnali fossero completamente indipendenti, la probabilità che almeno una coppia sia attiva:

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

Per la Strategia 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\%

Ma i segnali non sono indipendenti. Le criptovalute si muovono in sincronia — il che significa che i segnali emergono e si spengono a grappoli.

La Correlazione Riduce 10 Coppie a 3

L'intuizione è questa: se 10 coppie sono correlate, esse contengono informazioni non da 10 fonti indipendenti ma, ad esempio, da 3-4. Formalizziamo questo attraverso effective_N:

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

dove CfC_f è il fattore di correlazione, che riflette la correlazione media a coppie dei segnali. Quando Cf=1C_f = 1 le coppie sono completamente indipendenti; quando Cf=NC_f = N sono identiche.

Per le coppie crypto, un tipico Cf3C_f \approx 3. Quindi:

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\%

Non 40%, ma 15.6%. Una differenza di 2.5x. L'efficienza di riempimento cala di conseguenza, e con essa il rendimento effettivo dell'intero portafoglio (vedi PnL per Tempo Attivo).

Correlazione nei Mercati Crypto

Heatmap della matrice di correlazione dei segnali crypto

BTC come Fattore Dominante

Il mercato crypto ha una struttura fattoriale pronunciata. BTC spiega il 60-80% della varianza dei rendimenti giornalieri per la maggior parte delle altcoin. Questo è chiaramente visibile attraverso la 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:
    """
    Analisi PCA della struttura fattoriale dei rendimenti crypto.

    Args:
        returns_matrix: matrice dei rendimenti [n_giorni x n_coppie]
        pair_names: lista dei nomi delle coppie
    """
    pca = PCA()
    pca.fit(returns_matrix)

    explained = pca.explained_variance_ratio_
    cumulative = np.cumsum(explained)

    print("Struttura fattoriale:")
    for i, (var, cum) in enumerate(zip(explained[:5], cumulative[:5])):
        print(f"  PC{i+1}: {var:.1%} varianza (cumulativa: {cum:.1%})")

    loadings = pca.components_[0]
    print("\nLoadings PC1 (fattore BTC):")
    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)),
    }

Risultato tipico per un portafoglio di 10 coppie crypto:

Componente Varianza Spiegata Cumulativa
PC1 (BTC) 65% 65%
PC2 12% 77%
PC3 8% 85%
PC4 5% 90%
PC5-PC10 10% 100%

Quattro fattori spiegano il 90% della varianza. Su 10 coppie, non più di 4 sono "indipendenti."

Correlazione dei Segnali vs. Correlazione dei Prezzi

Ecco una sfumatura importante. La correlazione dei prezzi e la correlazione dei segnali sono cose diverse. I prezzi di BTC e ETH sono correlati a 0.85, ma i segnali di una specifica strategia possono essere correlati a 0.95 o a 0.50 — a seconda della logica di entrata.

Esempio: una strategia RSI ipercomprato/ipervenduto. L'RSI su BTC supera 30 (ipervenduto) — si entra in long. ETH nello stesso momento potrebbe essere anch'esso ipervenduto (correlazione dei segnali ~0.90). Oppure no, se ETH stava calando più lentamente (correlazione dei segnali ~0.40).

L'approccio corretto è misurare la correlazione dei segnali specificamente, non delle serie di prezzi:

import numpy as np
from itertools import combinations

def signal_correlation_matrix(
    signals: dict,  # {coppia: np.array di 0/1 per minuto}
    method: str = "pearson",
) -> np.ndarray:
    """
    Calcola la matrice di correlazione dei segnali (binario: 0 = flat, 1 = in posizione).

    Args:
        signals: dizionario {nome_coppia: array_segnale_binario}
        method: metodo di correlazione ("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"Metodo sconosciuto: {method}")

        corr_matrix[i, j] = corr
        corr_matrix[j, i] = corr

    return corr_matrix, pairs


def estimate_correlation_factor(corr_matrix: np.ndarray) -> float:
    """
    Stima il correlation_factor dalla matrice di correlazione dei segnali.

    correlation_factor = 1 + (N-1) * correlazione_media_a_coppie

    Quando la correlazione è 0 → C_f = 1 (tutti indipendenti).
    Quando la correlazione è 1 → C_f = N (tutti identici).
    """
    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

Correlazione Temporale: Calma vs. Panico

Regimi di correlazione temporale: mercato calmo vs. panico

La correlazione non è statica. Nei periodi di calma, BTC e le alt possono divergere — ETH sale sulle notizie di Ethereum, SOL sulle notizie di Solana. In una crisi, tutto collassa in un unico fattore: risk-on/risk-off.

def rolling_correlation_factor(
    signals: dict,
    window_days: int = 30,
    step_days: int = 7,
) -> list:
    """
    Rolling correlation_factor per rilevare i cambiamenti di regime.
    """
    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

Quadro tipico per 10 coppie crypto:

Regime di Mercato Correlazione Media dei Segnali CfC_f NeffN_{eff}
Laterale (bassa 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
Panico (crash) 0.60-0.90 6.4-9.1 1.1-1.6

Durante il panico, 10 coppie si comprimono a 1-2 effettive. Proprio quando la diversificazione è più necessaria, svanisce. Questo è l'analogo crypto del classico "le correlazioni vanno a 1 durante una crisi."

effective_N: Il Concetto Chiave

Effective N: riduzione della dimensionalità da coppie correlate

Formula e Derivazione

L'idea di effective_N è presa in prestito dalla statistica, dove la dimensione campionaria effettiva tiene conto dell'autocorrelazione delle osservazioni. Per i nostri scopi:

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

dove ρˉ\bar{\rho} è la correlazione media a coppie dei segnali. Notazione semplificata:

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

Proprietà:

  • Quando ρˉ=0\bar{\rho} = 0: Cf=1C_f = 1, Neff=NN_{eff} = N — piena indipendenza
  • Quando ρˉ=1\bar{\rho} = 1: Cf=NC_f = N, Neff=1N_{eff} = 1 — tutte le coppie sono identiche
  • Quando ρˉ=0.25\bar{\rho} = 0.25 e N=10N = 10: Cf=3.25C_f = 3.25, Neff=3.08N_{eff} = 3.08

Come Stimare il correlation_factor dai Dati

In pratica, ci sono tre approcci:

1. Dalla matrice di correlazione dei segnali (esatto).

Esegui la strategia su tutte le coppie, ottieni i segnali binari (0/1 per ogni minuto), costruisci la matrice di correlazione, calcola CfC_f usando la formula sopra.

2. Dalla PCA dei rendimenti dei prezzi (approssimato).

Se i segnali dipendono fortemente dalla dinamica dei prezzi (momentum, mean-reversion), puoi stimare NeffN_{eff} come il numero di componenti PCA che spiegano il 90% della varianza.

3. Dalle euristiche della classe di attivi (approssimativo).

Classe di Attivo Tipico CfC_f
Crypto (top-10) 2.5-4.0
Crypto (con DeFi/memecoins) 2.0-3.0
Forex (major) 1.5-2.5
Azioni (singolo settore) 2.0-3.5
Azioni (cross-settoriale) 1.2-1.8

Per un portafoglio crypto composto da BTC, ETH, SOL, AVAX, MATIC, DOGE, DOT, LINK, UNI, ATOM, una stima prudente è Cf3C_f \approx 3.

Modellazione dell'Utilizzo degli Slot

Dashboard di utilizzo degli slot dell'orchestratore

Formula per P(1 attivo)P(\geq 1\ \text{attivo})

La formula base che tiene conto della correlazione:

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

Tabella per diverse strategie e numero di coppie (Cf=3C_f = 3):

Strategia pp (tempo di trading) 5 coppie (Neff=1.7N_{eff}=1.7) 10 coppie (Neff=3.3N_{eff}=3.3) 20 coppie (Neff=6.7N_{eff}=6.7) 50 coppie (Neff=16.7N_{eff}=16.7)
Strategia B 5% 8.2% 15.6% 29.1% 58.0%
Strategia A 15% 23.6% 41.8% 65.9% 92.8%
Strategia C 45% 67.1% 89.0% 98.8% ~100%

Per la Strategia B con attività al 5%, servono 50 coppie solo per avere almeno una posizione attiva la metà del tempo. E questo senza nemmeno considerare che 50 coppie crypto sono più fortemente correlate di 10.

Orchestratore Multi-Slot

Un orchestratore reale gestisce più slot simultaneamente. Se hai 5 slot e 10 coppie, l'utilizzo si calcola diversamente:

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

E[attivi]=NeffpE[\text{attivi}] = 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:
    """
    Stima analitica della fill efficiency per un orchestratore multi-slot.

    Args:
        trading_time_pct: frazione del tempo attivo per una strategia su una coppia
        n_pairs: numero di coppie di trading
        correlation_factor: coefficiente di correlazione dei segnali
        max_slots: numero massimo di posizioni simultanee

    Returns:
        dict con le metriche di utilizzo
    """
    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 = [
    ("Strategia B, 10 coppie, 1 slot", 0.05, 10, 3.0, 1),
    ("Strategia B, 10 coppie, 3 slot", 0.05, 10, 3.0, 3),
    ("Strategia B, 30 coppie, 1 slot", 0.05, 30, 3.0, 1),
    ("Strategia A, 10 coppie, 1 slot", 0.15, 10, 3.0, 1),
    ("Strategia C, 10 coppie, 1 slot", 0.45, 10, 3.0, 1),
    ("Strategia C, 10 coppie, 5 slot", 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 attivo) = {result['p_at_least_one']:.1%}")
    print(f"  E[attivi] = {result['expected_active']:.2f}")
    print(f"  fill_efficiency = {result['fill_efficiency']:.1%}")
    print()

Output atteso:

Strategia B, 10 coppie, 1 slot:
  N_eff = 3.3
  P(≥1 attivo) = 15.6%
  E[attivi] = 0.17
  fill_efficiency = 15.6%

Strategia B, 10 coppie, 3 slot:
  N_eff = 3.3
  P(≥1 attivo) = 15.6%
  E[attivi] = 0.17
  fill_efficiency = 5.6%

Strategia B, 30 coppie, 1 slot:
  N_eff = 10.0
  P(≥1 attivo) = 40.1%
  E[attivi] = 0.50
  fill_efficiency = 40.1%

Strategia A, 10 coppie, 1 slot:
  N_eff = 3.3
  P(≥1 attivo) = 41.8%
  E[attivi] = 0.50
  fill_efficiency = 41.8%

Strategia C, 10 coppie, 1 slot:
  N_eff = 3.3
  P(≥1 attivo) = 89.0%
  E[attivi] = 1.50
  fill_efficiency = 89.0%

Strategia C, 10 coppie, 5 slot:
  N_eff = 3.3
  P(≥1 attivo) = 89.0%
  E[attivi] = 1.50
  fill_efficiency = 30.0%

Nota: la Strategia B con 3 slot e 10 coppie mostra una fill_efficiency del 5.6%. Tre slot sono inutili quando il numero atteso di coppie attive è solo 0.17. Gli slot devono essere allocati proporzionalmente al carico atteso.

Simulazione su Dati Reali

Il modello analitico è un'approssimazione. Per una stima accurata, è necessaria una simulazione su segnali reali:

import numpy as np

def simulate_fill_efficiency(
    all_signals: dict,       # {(strategia, coppia): [(minuto_entrata, minuto_uscita), ...]}
    max_slots: int = 10,
    test_period_minutes: int = 750 * 24 * 60,  # 750 giorni
    priority_fn=None,        # funzione di priorità per la selezione delle posizioni
) -> dict:
    """
    Simula il carico reale degli slot dell'orchestratore.

    Per ogni minuto: conta quante coppie vogliono entrare in posizione,
    e quanti slot sono effettivamente occupati (tenendo conto del limite).

    Args:
        all_signals: segnali per coppie e strategie
        max_slots: numero massimo di posizioni simultanee
        test_period_minutes: durata del periodo di test in minuti
        priority_fn: se None — FIFO; altrimenti — funzione di ranking
    """
    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),
    }

La simulazione su dati reali spesso mostra un utilizzo ancora più basso rispetto alla stima analitica, perché tiene conto del raggruppamento temporale dei segnali: tutte le coppie entrano simultaneamente in un cluster, creando overflow, e poi tutte tacciono, creando un vuoto.

Quante Coppie Monitorare? Analisi dei Rendimenti Decrescenti

Effective N e curva dei rendimenti decrescenti

La domanda chiave: a quale NN l'aggiunta di un'ulteriore coppia smette di aumentare sensibilmente la 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:
    """
    Analisi dei rendimenti decrescenti dall'aggiunta di nuove coppie.
    """
    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"Strategia B: servono {analysis_b['target_n_for_utilization']} coppie per P(≥1) all'80%")

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"marginale={r['marginal_gain']:.2%}")

Risultati per la Strategia B (p=0.05p = 0.05, Cf=3C_f = 3):

Coppie NN NeffN_{eff} P(1)P(\geq 1) Guadagno marginale
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%

Per la Strategia B, raggiungere l'80% di utilizzo su un singolo slot è impossibile anche con 100 coppie (ne servirebbero ~96). Questa è una limitazione fondamentale: una strategia con il 5% di tempo di trading non è adatta per l'operatività su singolo slot — ha bisogno di un approccio portfolio con più strategie.

Per la Strategia A (p=0.15p = 0.15, Cf=3C_f = 3):

Coppie NN NeffN_{eff} P(1)P(\geq 1) Guadagno marginale
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%

La Strategia A raggiunge l'80% di utilizzo con ~30 coppie. Il guadagno marginale alla 30a coppia è solo +1.2%.

Per la Strategia C (p=0.45p = 0.45, Cf=3C_f = 3):

Coppie 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%

La Strategia C con il 45% di tempo di trading raggiunge il 90% di utilizzo con sole 10 coppie. Aggiungerne altre è inutile.

Degradazione dell'Edge Tra le Coppie

Degradazione dell'edge tra le coppie di trading

C'è un altro fattore che limita il numero di coppie: la degradazione dell'edge. Una strategia sviluppata e ottimizzata su BTC/USDT potrebbe funzionare peggio su alt meno liquide.

Cause della degradazione:

  • Liquidità: lo slippage su DOGE/USDT è diverse volte superiore a quello su BTC/USDT
  • Spread: le coppie meno liquide hanno spread bid-ask più ampi
  • Microstruttura: i pattern del book degli ordini differiscono tra le coppie
  • Manipolazione: le alt a bassa liquidità sono soggette a pump-and-dump
def edge_decay_analysis(
    strategy_results: dict,  # {coppia: {"pnl_per_day": float, "n_trades": int}}
    min_trades: int = 30,
) -> list:
    """
    Classifica le coppie per edge tenendo conto della degradazione.
    """
    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

Quadro tipico:

# coppie Ultima Aggiunta PnL/giorno ultima PnL/giorno medio
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%

Aggiungere la 10a coppia abbassa il PnL/giorno medio del portafoglio. All'8a coppia, l'edge è già la metà rispetto alla migliore. È necessario un equilibrio tra fill_efficiency (cresce con il numero di coppie) e edge medio (diminuisce).

Numero Ottimale di Coppie: Modello Unificato

Numero ottimale di coppie: intersezione tra fill efficiency e edge medio

Combiniamo fill_efficiency e decadimento dell'edge in un'unica metrica — PnL atteso del portafoglio per giorno:

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

def optimal_pairs_count(
    pair_edges: list,           # PnL/giorno in ordine decrescente: [0.89, 0.82, 0.71, ...]
    trading_time_pct: float,
    correlation_factor: float = 3.0,
    max_slots: int = 1,
) -> dict:
    """
    Trova il numero ottimale di coppie che massimizza il PnL del portafoglio.
    """
    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"Numero ottimale di coppie: {opt['optimal_n']}")
print(f"Portafoglio annualizzato: {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}%")

L'ottimo si trova tipicamente nel punto in cui il guadagno marginale di fill_efficiency dall'aggiunta di una coppia non compensa più il calo dell'edge medio. Per un tipico portafoglio crypto:

  • Strategia B (5% del tempo): ottimo a 8-12 coppie
  • Strategia A (15% del tempo): ottimo a 6-10 coppie
  • Strategia C (45% del tempo): ottimo a 4-6 coppie

Un paradosso: la strategia con il minor tempo di trading beneficia del maggior numero di coppie, eppure la fill_efficiency rimane comunque bassa. La soluzione non è più coppie, ma la combinazione con altre strategie (vedi Strategie Combo).

Diversificazione Cross-Coppia: Strategie per Ridurre la Correlazione

Rete di diversificazione cross-strategia

Se non è possibile aumentare il numero di coppie indefinitamente, si può ridurre CfC_f — ossia aumentare la diversità dei segnali.

Strategia 1: Mix di Token Liquidi e DeFi

BTC, ETH, BNB sono molto fortemente correlati. Ma UNI (DEX), AAVE (lending), CRV (stablecoin) possono avere i propri driver. L'aggiunta di token DeFi riduce la ρˉ\bar{\rho} media da 0.35 a 0.20-0.25:

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

Strategia 2: Strategie Diverse sulle Stesse Coppie

Invece di 10 coppie con una strategia — 5 coppie con due strategie diverse. Se le strategie si basano su principi diversi (momentum vs. mean-reversion), i loro segnali possono essere anti-correlati:

  • Il momentum entra in long quando il prezzo sale
  • Il mean-reversion entra in long quando il prezzo scende

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

Questo è l'unico modo per ottenere Neff>NN_{eff} > N — usare strategie con correlazione negativa dei segnali.

Strategia 3: Spot vs. Futures

L'arbitraggio sul tasso di funding e il trading spot hanno una struttura di correlazione diversa. L'aggiunta di strategie di arbitraggio al portafoglio riduce significativamente il CfC_f complessivo, perché l'arbitraggio per definizione sfrutta le divergenze, non le convergenze.

Raccomandazioni Pratiche per Tipo di Strategia

Strategie ad Alta Frequenza con Basso Tempo di Trading (tempo di trading < 10%)

Rappresentante tipico: Strategia B (5% del tempo, 38 trade in 750 giorni).

  • Numero di coppie: 10-15 (ottimo per l'equilibrio edge/fill)
  • Problema: la fill_efficiency è bassa anche con 15 coppie (~20-25%)
  • Soluzione: combinazione obbligatoria con altre strategie nell'orchestratore
  • CfC_f per crypto: 2.5-3.5
  • Monitoraggio: rolling CfC_f per adattare il numero di coppie al regime di mercato

Strategie a Tempo Medio (tempo di trading 10-30%)

Rappresentante tipico: Strategia A (15% del tempo, 418 trade in 750 giorni).

  • Numero di coppie: 6-10
  • Fill_efficiency con 10 coppie: ~40%
  • Soluzione: la combinazione di 2-3 tali strategie riempie l'80%+ del tempo
  • CfC_f per crypto: 2.5-3.5
  • Focus: selezionare le coppie con il massimo edge, non inseguire la quantità

Strategie ad Alto Tempo di Trading (tempo di trading > 30%)

Rappresentante tipico: Strategia C (45% del tempo).

  • Numero di coppie: 4-6
  • Fill_efficiency con 6 coppie: ~80%
  • Problema: overflow — più coppie attive simultaneamente, ma meno slot
  • Soluzione: aumentare max_slots o aggiungere la prioritizzazione delle coppie
  • CfC_f per crypto: 2.5-4.0 (più alto a causa della sovrapposizione delle posizioni lunghe durante le crisi)

Tabella Riassuntiva

Parametro Strategia B (5%) Strategia A (15%) Strategia C (45%)
NN raccomandato 10-15 6-10 4-6
NeffN_{eff} con 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%
Combinazione necessaria? Obbligatoria Consigliabile No
Collo di bottiglia Pochi segnali Equilibrio Overflow

Connessione con Altre Metriche della Serie

Questo articolo è l'undicesimo della serie "Backtest Senza Illusioni". La correlazione dei segnali impatta direttamente le metriche degli articoli precedenti:

  • PnL per Tempo Attivo: la fill_efficiency è il moltiplicatore chiave nella formula del rendimento effettivo. Se sovrastimi la fill_efficiency ignorando la correlazione, le previsioni di PnL del portafoglio saranno eccessivamente ottimistiche.

  • Tassi di funding: con alta correlazione, le posizioni si aprono simultaneamente — e i costi di funding crescono linearmente con il numero di slot. Overflow + funding = bruciatura accelerata del capitale.

  • Arbitraggio sui tassi di funding: le strategie di arbitraggio sono un diversificatore naturale che riduce il CfC_f del portafoglio. I loro segnali sono debolmente correlati con le strategie momentum e mean-reversion.

  • Strategie combo (prossimo articolo): come assemblare un portafoglio di strategie con diversi pp e CfC_f per raggiungere un utilizzo del 90%+. L'orchestrazione a cascata tiene conto della correlazione dei segnali nell'assegnazione delle priorità.

Conclusione

La diversificazione nel crypto non riguarda il numero di coppie. 10 coppie correlate producono l'effetto di 3-4 indipendenti. Durante il panico, ancora meno.

Quattro punti chiave:

  1. Calcola effective_N, non N. Per le coppie crypto Cf3C_f \approx 3. Dieci coppie sono ~3.3 effettive. Pianifica la fill_efficiency basandoti su NeffN_{eff}, non su NN.

  2. Misura la correlazione dei segnali, non dei prezzi. La correlazione dei prezzi è un proxy, non un sostituto. Esegui la strategia su tutte le coppie e calcola la matrice di correlazione dei segnali binari.

  3. Tieni conto della degradazione dell'edge. Più coppie significa un PnL/giorno medio inferiore. L'ottimo è nel punto in cui la fill_efficiency marginale derivante da una nuova coppia compensa ancora il calo dell'edge.

  4. Riduci CfC_f, non aumentare NN. Combinare strategie diverse sulle stesse coppie è più efficace che una strategia su più coppie. La diversificazione cross-strategia può produrre Neff>NN_{eff} > N.

Il fattore di correlazione è la variabile nascosta che determina quanto siano realistiche le previsioni di utilizzo e rendimento. Ignorarlo significa costruire un portafoglio su illusioni.


Link Utili

  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

Citazione

@article{soloviov2026signalcorrelation,
  author = {Soloviov, Eugen},
  title = {Correlazione dei Segnali: Quante Coppie Monitorare},
  year = {2026},
  url = {https://marketmaker.cc/it/blog/post/signal-correlation-pairs},
  version = {0.1.0},
  description = {Perché 10 coppie crypto non offrono una diversificazione 10x, come calcolare effective\_N tramite correlation\_factor, e quante coppie è davvero necessario monitorare per un utilizzo degli slot dell'orchestratore dell'80-90\%.}
}
Disclaimer: le informazioni fornite in questo articolo hanno solo scopo didattico e informativo e non costituiscono consulenza finanziaria, di investimento o di trading. Il trading di criptovalute comporta un rischio significativo di perdita.

Autori

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

Resta un Passo Avanti al Mercato

Iscriviti alla nostra newsletter per approfondimenti esclusivi sul trading con IA, analisi di mercato e aggiornamenti sulla piattaforma.

Rispettiamo la tua privacy. Annulla l'iscrizione in qualsiasi momento.