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

Parità backtest-live: perché il tuo bot fa trading in modo diverso dal backtest

Parità backtest-live: perché il tuo bot fa trading in modo diverso dal backtest
#algotrading
#backtest
#live trading
#parità backtest-live
#esecuzione
#NautilusTrader

Hai eseguito una strategia nel backtest. Sharpe 2,1, MaxDD -8%, PnL +67%. Hai lanciato il bot. Un mese dopo confronti i risultati: stessi segnali, stesso periodo — ma il PnL live è inferiore del 40%. Il drawdown è una volta e mezza più profondo. Due operazioni su dieci non sono state eseguite affatto.

Non è un bug. È la divergenza backtest-live — una discrepanza sistematica tra i risultati del backtest e il trading reale. Ce l'hanno tutti. L'unica domanda è se lo sai e se riesci a controllarlo.

Questo articolo fornisce una tassonomia completa delle divergenze, pattern architetturali per minimizzarle e una checklist pratica per il monitoraggio della parità in produzione.

La sindrome del "funzionava nel backtest"

Divergenza backtest vs trading live — curva di equity ideale versus risultati reali volatili

Ogni algotrader attraversa questo ciclo:

  1. Ha scritto una strategia in un Jupyter notebook
  2. Ha eseguito un backtest su dati CSV storici — i risultati sono ottimi
  3. Ha riscritto la logica come bot (spesso in un linguaggio o framework diverso)
  4. Ha lanciato — i risultati non corrispondono
  5. Ha iniziato a cercare un bug, non lo ha trovato — "il mercato è cambiato"

Il problema non è il mercato. Il problema è che il backtest e il bot sono due prodotti software diversi che modellano la stessa realtà in modo diverso. Le divergenze sono inevitabili, ma possono essere sistematizzate e minimizzate.

Tassonomia delle Divergenze

Tassonomia delle divergenze backtest-live

Tutte le fonti di divergenza rientrano in quattro categorie. Per ciascuna — un rating di gravità (da 1 a 5) e un contributo tipico alla divergenza del PnL.

1. Divergenze dei dati (gravità: 3/5)

I dati che il backtest vede e i dati che il bot vede in tempo reale non sono la stessa cosa.

Timestamp. Gli exchange consegnano le candele con regole diverse per l'assegnazione del timestamp. Un exchange marca la candela con l'inizio del periodo, un altro con la fine. Una REST API può restituire una candela con un ritardo di 1-3 secondi dopo la chiusura effettiva. Il backtest lavora con timestamp "ideali" dal file storico.

Aggregazione OHLCV. I dati storici sono spesso aggregati dal provider in modo diverso rispetto a come fa l'exchange in tempo reale. La differenza è nell'ultima cifra — ma con segnali di soglia (crossover MA, breakout di livello) questo determina se la strategia entra in posizione o meno.

Gap e dati mancanti. I dati storici sono solitamente puliti — le candele mancanti vengono riempite per interpolazione. In tempo reale, un WebSocket può cadere e il bot perde 30 secondi di dati.

Contributo tipico alla divergenza del PnL: 2-5% del PnL annuo.

2. Divergenze di esecuzione (gravità: 5/5)

Divergenze di esecuzione degli ordini — slippage dell'orderbook, latenza e visualizzazione dei fill parziali

La classe di divergenze più pericolosa. Il backtest simula l'esecuzione perfettamente — la realtà è ben lontana dall'ideale.

Slippage. Il backtest riempie l'ordine al prezzo di chiusura (o al prezzo del segnale). In realtà, un ordine di mercato viene eseguito al miglior bid/ask più lo slippage che dipende dal volume e dalla liquidità. Per una posizione da $10K su un altcoin a liquidità media, lo slippage può essere dello 0,05-0,3%.

Formula per lo slippage cumulativo su NN trade:

Slippagetotal=i=1Nsizei×si\text{Slippage}_{total} = \sum_{i=1}^{N} \text{size}_i \times s_i

dove sis_i è lo slippage dell'ii-esimo trade, dipendente dalla profondità dell'orderbook:

sisizeiLiquidity(ti)×ks_i \approx \frac{\text{size}_i}{\text{Liquidity}(t_i)} \times k

Latenza. Dal momento in cui viene generato un segnale all'esecuzione dell'ordine trascorre del tempo: calcolo del segnale (1-50 ms), trasmissione della richiesta (10-200 ms), matching sull'exchange (1-10 ms). Nel backtest, la latenza = 0. In live — il prezzo può muoversi.

Fill parziali. Il backtest assume che il 100% dell'ordine venga riempito istantaneamente. In realtà, un ordine limite potrebbe essere parzialmente riempito — o non riempito affatto se il prezzo si inverte. Per un ordine di mercato su un mercato illiquido, l'ordine "scivola" attraverso più livelli dell'orderbook.

Priorità di coda. Un ordine limite piazzato al prezzo del miglior bid non verrà riempito immediatamente — fa la coda dietro tutti gli ordini precedentemente piazzati a quel livello. Un backtest che considera "il prezzo è stato toccato = ordine riempito" sopravvaluta sistematicamente il tasso di riempimento.

Contributo tipico alla divergenza del PnL: 10-30% del PnL annuo.

3. Divergenze di logica (gravità: 4/5)

Queste sono divergenze nel codice della strategia stessa tra il backtest e il bot.

Codebase separate. L'anti-pattern classico: backtests/strategy_a.py e bot/strategy_a.py — due file separati che "fanno la stessa cosa". Dopo tre mesi di modifiche, inevitabilmente divergono. Qualcuno ha aggiunto un filtro nel backtest e si è dimenticato di replicarlo nel bot. O il contrario — un bug è stato corretto nel bot ma è rimasto nel backtest.

Framework diversi. Backtest su pandas con operazioni vettorializzate, bot su asyncio con logica event-driven. Anche con una strategia identica, i casi limite vengono gestiti diversamente: arrotondamento, ordine dei controlli delle condizioni, gestione dei NaN.

Gestione dello stato. Il backtest è solitamente stateless — itera su un array di dati. Il bot è stateful — memorizza posizioni, saldi, storico degli ordini. Riavvio del bot, perdita di stato, desincronizzazione con l'exchange — tutte queste sono fonti di divergenza.

Contributo tipico alla divergenza del PnL: 5-20% del PnL annuo.

4. Divergenze dei costi (gravità: 3/5)

Divergenze nella modellazione dei costi di trading.

Tassi di funding. La maggior parte dei backtest sui futures perpetui non tiene affatto conto dei tassi di funding. Con una leva 10x e un tasso medio dello 0,01% ogni 8 ore, questo fa 0.01%×3×365×10=109.5%0.01\% \times 3 \times 365 \times 10 = 109.5\% all'anno — più del PnL della maggior parte delle strategie. Un'analisi dettagliata è nell'articolo I tassi di funding distruggono la tua leva.

Commissioni. Le commissioni maker/taker sono solitamente modellate ma spesso con il tasso sbagliato. Livelli VIP, sconti BNB, rebate — tutto questo influisce sul risultato finale.

Spread. Un backtest basato su candele non vede lo spread bid-ask. Su una candela a 1 minuto, close = 3000, ma in realtà bid = 2999,5 e ask = 3000,5. Ogni trade "costa" metà dello spread.

Contributo tipico alla divergenza del PnL: 5-15% del PnL annuo.

Effetto Cumulativo

Tutte e quattro le categorie agiscono simultaneamente e, di regola, nella stessa direzione — contro il trader:

PnLlivePnLbacktestΔdataΔexecutionΔlogicΔcosts\text{PnL}_{live} \approx \text{PnL}_{backtest} - \Delta_{data} - \Delta_{execution} - \Delta_{logic} - \Delta_{costs}

Una divergenza totale del 20-50% dal PnL del backtest è normale per un sistema non raffinato. Con la leva, l'effetto si moltiplica.

Pattern Architetturali per la Parità

Pattern 1: Shared Core (estrazione di un core comune)

Architettura Shared Core — un singolo modulo di strategia che alimenta sia il backtest che il motore di trading live

L'idea: estrarre il core della strategia — generazione dei segnali e logica di esecuzione — in un modulo separato usato sia dal backtest che dal bot. Solo l'infrastruttura circostante differisce: la fonte dei dati e il meccanismo di invio degli ordini.

┌─────────────────────────────────────┐
│         strategy_core.py            │
│  ┌─────────────┐ ┌───────────────┐  │
│  │ SignalEngine │ │ OrderManager  │  │
│  └──────┬──────┘ └──────┬────────┘  │
│         │               │           │
│    generate_signal()  create_order()│
└─────────┬───────────────┬───────────┘
          │               │
    ┌─────┴─────┐   ┌─────┴──────┐
    │ Backtest   │   │ Live       │
    │ DataFeed   │   │ DataFeed   │
    │ FillModel  │   │ Exchange   │
    └────────────┘   └────────────┘

from dataclasses import dataclass
from typing import Optional
import numpy as np

@dataclass
class Signal:
    side: str          # 'long' | 'short'
    entry_price: float
    sl_price: float
    tp_price: float
    size: float
    timestamp: int

@dataclass
class OrderRequest:
    side: str
    order_type: str    # 'market' | 'limit'
    price: float
    size: float

class StrategyCore:
    """
    Strategy core. Identical code for backtest and live.
    Depends only on data, not on infrastructure.
    """
    def __init__(self, params: dict):
        self.fast_period = params.get('fast_ma', 20)
        self.slow_period = params.get('slow_ma', 50)
        self.sl_pct = params.get('sl_pct', 0.02)
        self.tp_pct = params.get('tp_pct', 0.04)
        self.position: Optional[Signal] = None
        self._closes: list[float] = []

    def on_candle(self, timestamp: int, o: float, h: float,
                  l: float, c: float, v: float) -> Optional[OrderRequest]:
        """
        Process a new candle. Returns an OrderRequest or None.
        This method is called identically from the backtest and the bot.
        """
        self._closes.append(c)

        if len(self._closes) < self.slow_period:
            return None

        fast_ma = np.mean(self._closes[-self.fast_period:])
        slow_ma = np.mean(self._closes[-self.slow_period:])

        if self.position is not None:
            exit_order = self._check_exit(h, l, c)
            if exit_order:
                self.position = None
                return exit_order

        if self.position is None:
            if fast_ma > slow_ma and self._prev_fast_ma <= self._prev_slow_ma:
                self.position = Signal(
                    side='long', entry_price=c,
                    sl_price=c * (1 - self.sl_pct),
                    tp_price=c * (1 + self.tp_pct),
                    size=1.0, timestamp=timestamp,
                )
                return OrderRequest('buy', 'market', c, 1.0)

        self._prev_fast_ma = fast_ma
        self._prev_slow_ma = slow_ma
        return None

    def _check_exit(self, high: float, low: float,
                    close: float) -> Optional[OrderRequest]:
        pos = self.position
        if pos.side == 'long':
            if low <= pos.sl_price:
                return OrderRequest('sell', 'market', pos.sl_price, pos.size)
            if high >= pos.tp_price:
                return OrderRequest('sell', 'market', pos.tp_price, pos.size)
        return None

Ora il backtest e il bot usano lo stesso StrategyCore:


from strategy_core import StrategyCore

def run_backtest(candles, params, fill_model):
    core = StrategyCore(params)
    trades = []

    for candle in candles:
        order = core.on_candle(
            candle['timestamp'], candle['open'], candle['high'],
            candle['low'], candle['close'], candle['volume'],
        )
        if order:
            fill_price = fill_model.simulate_fill(order, candle)
            trades.append({'price': fill_price, 'side': order.side})

    return trades

from strategy_core import StrategyCore

async def run_live(exchange, symbol, params):
    core = StrategyCore(params)

    async for candle in exchange.stream_candles(symbol, '1m'):
        order = core.on_candle(
            candle['timestamp'], candle['open'], candle['high'],
            candle['low'], candle['close'], candle['volume'],
        )
        if order:
            await exchange.place_order(symbol, order.side,
                                       order.order_type, order.size)

La regola chiave: StrategyCore non sa da dove provengono i dati o dove vengono inviati gli ordini. Riceve OHLCV e restituisce un OrderRequest. Tutto il resto è responsabilità del livello infrastrutturale.

Pattern 2: Unificazione event-driven (approccio NautilusTrader)

Architettura di trading event-driven con pipeline di eventi a cascata — dati di mercato, segnali, ordini, fill

NautilusTrader implementa la parità attraverso un NautilusKernel unificato — un motore Rust-native con un core event-driven deterministico e risoluzione al nanosecondo. La stessa implementazione della strategia funziona sia nel backtest che nel trading live.

L'architettura è costruita sul pattern ports and adapters (architettura esagonale):

┌──────────────────────────────────┐
│        NautilusKernel            │
│  ┌───────────┐  ┌─────────────┐  │
│  │ Strategy   │  │ RiskEngine  │  │
│  │ (Python)   │  │ (Rust)      │  │
│  └─────┬─────┘  └──────┬──────┘  │
│        │               │         │
│  ┌─────┴───────────────┴──────┐  │
│  │      Message Bus (Rust)    │  │
│  └─────┬───────────────┬──────┘  │
└────────┼───────────────┼─────────┘
         │               │
   ┌─────┴─────┐   ┌─────┴──────┐
   │ Backtest   │   │ Live       │
   │ Adapter    │   │ Adapter    │
   │ FillModel  │   │ Exchange   │
   │ (L2 book)  │   │ Gateway    │
   └────────────┘   └────────────┘

Vantaggi:

  • Replay deterministico. Gli eventi vengono elaborati in un ordine rigorosamente definito — il risultato del backtest è bit-riproducibile.
  • FillModel personalizzato. Simulazione dell'orderbook L2 per ogni esecuzione — lo slippage è simulato in base alla reale profondità dell'orderbook.
  • Performance. Fino a 5 milioni di righe/sec, elaborando dati che non entrano in RAM.
  • Redis + PostgreSQL. Cache e message bus tramite Redis, persistenza tramite PostgreSQL — infrastruttura identica per backtest e live.

Pattern 3: Strategy Interface (approccio Freqtrade)

Freqtrade utilizza un'interfaccia IStrategy unificata: la stessa classe di strategia funziona sia nel backtest che in live. L'unica differenza è il livello di persistenza.


class IStrategy:
    """Unified interface — the implementation does not know if this is a backtest or live."""

    def populate_indicators(self, dataframe, metadata):
        """Compute indicators."""
        dataframe['fast_ma'] = dataframe['close'].rolling(20).mean()
        dataframe['slow_ma'] = dataframe['close'].rolling(50).mean()
        return dataframe

    def populate_entry_trend(self, dataframe, metadata):
        """Determine entry signals."""
        dataframe.loc[
            (dataframe['fast_ma'] > dataframe['slow_ma']) &
            (dataframe['fast_ma'].shift(1) <= dataframe['slow_ma'].shift(1)),
            'enter_long'
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe, metadata):
        """Determine exit signals."""
        dataframe.loc[
            (dataframe['fast_ma'] < dataframe['slow_ma']),
            'exit_long'
        ] = 1
        return dataframe

Freqtrade fornisce inoltre:

  • Hyperopt tramite Optuna — ottimizzazione dei parametri della strategia
  • --timeframe-detail — drill-down a un timeframe più fine per il raffinamento dei fill (simile al drill-down adattivo)

Confronto dei Pattern

Shared Core Event-driven (NautilusTrader) Strategy Interface (Freqtrade)
Complessità di implementazione Bassa Alta Media
Livello di parità Medio Massimo Alto
Simulazione dei fill FillModel separato Orderbook L2 --timeframe-detail
Linguaggio core Python Rust + Python Python
Adatto per Motori personalizzati Trading istituzionale Avvio rapido

Accuratezza della Simulazione dei Fill

Livelli di accuratezza della simulazione dei fill

La simulazione dei fill è la principale fonte di divergenza di esecuzione. Tre livelli di accuratezza:

Livello 1: Naive (fill al prezzo di chiusura)

fill_price = candle['close']

Errore: non tiene conto di slippage, spread o fill parziali. Sopravvaluta sistematicamente il PnL.

Livello 2: Modello di slippage

def simulate_fill(order, candle, slippage_bps=5):
    """Fill con slippage."""
    base_price = candle['close']
    slip = base_price * slippage_bps / 10000

    if order.side == 'buy':
        return base_price + slip  # Acquisto a un prezzo più alto
    else:
        return base_price - slip  # Vendita a un prezzo più basso

Errore: lo slippage fisso non tiene conto della liquidità e della dimensione dell'ordine. Meglio del naive, ma comunque un modello grezzo.

Livello 3: Drill-down adattivo con dati 1s/100ms

L'opzione migliore: utilizzare dati reali a granularità fine per una determinazione precisa dell'ordine di fill SL/TP. Descritto in dettaglio nell'articolo Drill-down adattivo: backtest con granularità variabile.

class RealisticFillModel:
    """
    Combined fill model: slippage + spread + volume impact.
    """
    def __init__(self, avg_spread_bps=3, impact_coeff=0.1):
        self.avg_spread_bps = avg_spread_bps
        self.impact_coeff = impact_coeff

    def simulate_fill(self, order, candle, order_size_usd):
        base_price = candle['close']

        spread_cost = base_price * self.avg_spread_bps / 20000

        candle_volume_usd = candle['volume'] * candle['close']
        participation_rate = order_size_usd / max(candle_volume_usd, 1)
        impact = base_price * self.impact_coeff * np.sqrt(participation_rate)

        if order.side == 'buy':
            return base_price + spread_cost + impact
        else:
            return base_price - spread_cost - impact

Formula dell'impatto di mercato (modello Almgren-Chriss semplificato):

Δp=σkVorderVmarket\Delta p = \sigma \cdot k \cdot \sqrt{\frac{V_{order}}{V_{market}}}

dove σ\sigma è la volatilità, kk è il coefficiente di impatto, VorderV_{order} è il volume dell'ordine e VmarketV_{market} è il volume di mercato per il periodo.

Checklist Pratica per la Parità

Checklist olografica di validazione della parità organizzata per categoria — dati, esecuzione, timing, commissioni

Prima di lanciare il bot in live, verifica ogni elemento:

Codice:

  • La strategia utilizza un core condiviso (un modulo per backtest e live)
  • Nessuna duplicazione della logica dei segnali in due posti
  • I test unitari verificano output identici del core per input identici
  • L'ordine dei controlli delle condizioni è identico (SL prima di TP? TP prima di SL?)

Dati:

  • Il formato del timestamp è identico (UTC, stesso provider)
  • L'aggregazione OHLCV usa le stesse regole
  • La gestione delle candele mancanti è identica
  • Nessun look-ahead bias — il backtest non guarda nel futuro

Esecuzione:

  • Il modello di slippage è calibrato su dati reali
  • I fill parziali sono modellati (o almeno stimati pessimisticamente)
  • Gli ordini limite hanno un modello di priorità di coda
  • La latenza è considerata (ritardo di 100-500 ms dal segnale al fill)

Costi:

  • Le commissioni maker/taker sono incluse con il tasso corrente
  • I tassi di funding sono considerati con i futures perpetui
  • Lo spread è modellato (almeno la media)

Infrastruttura:

  • Persistenza dello stato: il bot recupera le posizioni dopo il riavvio
  • Logica di riconnessione: il WebSocket si riconnette senza perdita di dati
  • Logging: tutti gli ordini e i fill sono loggati per l'analisi post-mortem

Monitoraggio della Divergenza in Produzione

La parità non è un controllo una-tantum ma un processo continuo. Dopo il lancio del bot, le divergenze devono essere monitorate in tempo reale.

Modalità shadow (paper trading)

Modalità di shadow trading — dati di mercato live e ordini simulati in esecuzione parallela

Esegui il bot in parallelo con il backtest sugli stessi dati. Il bot genera segnali ma non invia ordini — fa solo logging. Contemporaneamente, il backtest elabora gli stessi dati. Confronta:

class DivergenceMonitor:
    """
    Compares backtest and live bot signals in real time.
    """
    def __init__(self, tolerance_pct=0.5):
        self.tolerance = tolerance_pct / 100
        self.divergences = []

    def compare_signal(self, backtest_signal, live_signal, timestamp):
        """Compare backtest and live signals."""
        if backtest_signal is None and live_signal is None:
            return  # Both silent — OK

        if (backtest_signal is None) != (live_signal is None):
            self.divergences.append({
                'timestamp': timestamp,
                'type': 'signal_mismatch',
                'backtest': backtest_signal,
                'live': live_signal,
                'severity': 'HIGH',
            })
            return

        price_diff = abs(
            backtest_signal.entry_price - live_signal.entry_price
        ) / backtest_signal.entry_price

        if price_diff > self.tolerance:
            self.divergences.append({
                'timestamp': timestamp,
                'type': 'price_divergence',
                'diff_pct': price_diff * 100,
                'severity': 'MEDIUM',
            })

    def compare_fill(self, backtest_fill, live_fill, timestamp):
        """Compare execution."""
        if backtest_fill and live_fill:
            slippage = (live_fill['price'] - backtest_fill['price']
                        ) / backtest_fill['price']
            self.divergences.append({
                'timestamp': timestamp,
                'type': 'fill_divergence',
                'slippage_bps': slippage * 10000,
                'severity': 'LOW' if abs(slippage) < 0.001 else 'MEDIUM',
            })

    def report(self):
        """Weekly divergence report."""
        from collections import Counter
        severity_counts = Counter(d['severity'] for d in self.divergences)
        return {
            'total_divergences': len(self.divergences),
            'by_severity': dict(severity_counts),
            'avg_slippage_bps': np.mean([
                d['slippage_bps'] for d in self.divergences
                if d['type'] == 'fill_divergence'
            ]) if any(d['type'] == 'fill_divergence'
                      for d in self.divergences) else 0,
        }

Metriche del Dashboard

Metrica Formula Soglia di allerta
Tasso di corrispondenza segnali matchestotal signals\frac{\text{matches}}{\text{total signals}} < 95%
Slippage medio 1Nsi\frac{1}{N}\sum s_i (bps) > 10 bps
Tasso di fill filledsent\frac{\text{filled}}{\text{sent}} < 90%
Divergenza PnL PnLlivePnLbtPnLbt\frac{PnL_{live} - PnL_{bt}}{PnL_{bt}} > 20%
Latenza p99 99° percentile segnale-to-fill > 500 ms

Calibrazione del Modello di Slippage

Calibrazione del modello di slippage — profondità dell'order book con curva di impatto del prezzo che mostra fill attesi vs reali

Dopo aver accumulato dati per 2-4 settimane, puoi calibrare il modello di slippage del backtest sui dati reali:

def calibrate_slippage(live_fills: list[dict]) -> dict:
    """
    Calibrate slippage model using real fills.

    live_fills: [{'expected_price': ..., 'actual_price': ..., 'size_usd': ..., 'volume_usd': ...}]
    """
    slippages = []
    participation_rates = []

    for fill in live_fills:
        slip = abs(fill['actual_price'] - fill['expected_price']
                   ) / fill['expected_price']
        part = fill['size_usd'] / max(fill['volume_usd'], 1)
        slippages.append(slip)
        participation_rates.append(part)

    slippages = np.array(slippages)
    participation_rates = np.array(participation_rates)

    from scipy.optimize import curve_fit

    def model(x, k, base):
        return k * np.sqrt(x) + base

    popt, _ = curve_fit(model, participation_rates, slippages,
                        p0=[0.1, 0.0001])

    return {
        'impact_coeff': popt[0],
        'base_slippage': popt[1],
        'mean_slippage_bps': np.mean(slippages) * 10000,
        'p95_slippage_bps': np.percentile(slippages, 95) * 10000,
    }

Connessioni con Altri Strumenti

La parità backtest-live non è un compito isolato. Si interseca con altri strumenti della serie "Backtest Senza Illusioni":

  • Drill-down adattivo — migliora l'accuratezza della simulazione dei fill, un componente chiave della parità di esecuzione.
  • Tassi di funding — se il backtest non modella il funding, la parità è impossibile a leva > 3x.
  • Cache Parquet — i timeframe e gli indicatori precalcolati assicurano che il backtest veda gli stessi dati del bot. L'emulazione RunningCandleBuffer = aggiornamento in tempo reale.
  • Polars vs Pandas — quando si passa da pandas (backtest) a Polars (live), è necessario garantire che i risultati numerici corrispondano.
  • Walk-Forward — il walk-forward su dati out-of-sample mostra come la strategia degrada — questo è più vicino al live rispetto a un backtest in-sample.

Raccomandazioni

  1. Il core condiviso è obbligatorio. Una singola codebase per la generazione dei segnali è il requisito minimo per la parità. Due file con logica identica garantiscono la divergenza entro un mese.

  2. Calibra il modello di fill. Uno slippage fisso di 5 bps è meglio di niente. Un modello di slippage calibrato su dati reali è significativamente migliore.

  3. Usa la modalità shadow per le prime 2-4 settimane. Non fare trading con denaro reale finché il tasso di corrispondenza dei segnali non raggiunge il 95%+.

  4. Modella i tassi di funding. Per i futures perpetui, questo non è opzionale — è obbligatorio. Il funding può consumare tutto il PnL a leva > 5x.

  5. Logga tutto. Ogni segnale, ogni ordine, ogni fill — con timestamp. Senza log, l'analisi post-mortem è impossibile.

  6. Automatizza il confronto. Un report settimanale di DivergenceMonitor dovrebbe arrivare automaticamente. Non aspettare finché il PnL diventa negativo.

  7. Backtest pessimistico per default. È meglio sottostimare le aspettative nel backtest ed essere piacevolmente sorpresi in live che il contrario. Il modello di slippage dovrebbe essere conservativo.

Conclusione

Livelli di maturità del sistema di trading — dal backtest di base alla produzione completa

La parità backtest-live non è una proprietà di un sistema ma un processo. La parità perfetta non esiste: un backtest è per definizione un modello della realtà, e un modello semplifica sempre. Ma la differenza tra "il modello differisce del 5%" e "il modello differisce del 50%" è determinata dall'architettura.

Tre livelli di maturità:

  1. Base. Core condiviso, slippage fisso, commissioni. Divergenza: 10-20%.
  2. Avanzato. Architettura event-driven, drill-down adattivo, modello di funding, modalità shadow. Divergenza: 5-10%.
  3. Istituzionale. Simulazione orderbook L2, modello di impatto calibrato, monitoraggio della divergenza in tempo reale. Divergenza: 2-5%.

Il tuo compito è determinare a che livello sei e capire quale divergenza ritieni accettabile per la tua dimensione di posizione e leva.


Link Utili

  1. NautilusTrader — High-Performance Algorithmic Trading Platform
  2. Freqtrade — Free, open source crypto trading bot
  3. Almgren, R., Chriss, N. — Optimal Execution of Portfolio Transactions (2001)
  4. Lopez de Prado — Advances in Financial Machine Learning, Chapter 12: Backtesting
  5. Ernest Chan — Quantitative Trading: How to Build Your Own Algorithmic Trading Business
  6. Hexagonal Architecture (Ports and Adapters) — Alistair Cockburn
  7. Optuna — Hyperparameter Optimization Framework

Citazione

@article{soloviov2026backtestliveparity,
  author = {Soloviov, Eugen},
  title = {Parità backtest-live: perché il tuo bot fa trading in modo diverso dal backtest},
  year = {2026},
  url = {https://marketmaker.cc/it/blog/post/backtest-live-parity},
  description = {Tassonomia completa delle divergenze tra backtest e trading live: dallo slippage e i fill parziali alla desincronizzazione del codice. Pattern architetturali per ottenere la parità e una checklist di monitoraggio in produzione.}
}
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.