← Torna agli articoli
May 17, 2025
5 min di lettura

Costruire un Algoritmo di Market Making per Coppie Crypto con il Modello Avellaneda-Stoikov

Costruire un Algoritmo di Market Making per Coppie Crypto con il Modello Avellaneda-Stoikov
#market making
#criptovaluta
#Avellaneda-Stoikov
#trading algoritmico
#apprendimento per rinforzo
#PPO
#DeFi

Ciao, amici! Oggi vi mostrerò come costruire un algoritmo di market making per le coppie crypto USD+/wETH e USD+/cbbtc. Utilizzeremo il modello Avellaneda-Stoikov (A-S) e lo miglioreremo con un algoritmo di Reinforcement Learning (PPO) per l'ottimizzazione dinamica dello spread. Sembra complicato? Non preoccupatevi, suddividerò tutto in passaggi chiari così anche uno sviluppatore alle prime armi potrà seguire.

Visualizzazione del Market Making Visualizzare l'essenza del market making: bilanciare continuamente gli ordini di acquisto e vendita attorno a un prezzo equo per sintetizzare una liquidità di mercato ottimale.

Cos'è il Market Making e Perché ne Abbiamo Bisogno?

Il market making è una strategia in cui un trader piazza simultaneamente ordini di acquisto e vendita per un asset, guadagnando sullo spread (la differenza tra i prezzi). Nello spazio DeFi, i market maker svolgono un ruolo chiave fornendo liquidità e riducendo lo slippage per gli altri partecipanti al mercato.

Immaginate di essere un venditore al mercato, sempre pronto ad acquistare un prodotto leggermente sotto il prezzo di mercato e a rivenderlo leggermente sopra. Il vostro profitto è la differenza tra il prezzo di acquisto e quello di vendita. Ma c'è un problema: se il prezzo si muove improvvisamente in una direzione, potreste accumulare troppo inventario o, al contrario, non avere nulla da vendere.

Il Modello Avellaneda-Stoikov: La Matematica al Servizio del Trading

Il modello A-S è un approccio matematico per determinare i prezzi ottimali per il market making. Il suo principale vantaggio è che tiene conto non solo del prezzo di mercato corrente, ma anche della dimensione della vostra posizione (inventario), della volatilità del mercato e della propensione al rischio.

Le formule principali del modello:

δ_a = S_t + (1/γ) * ln(1 + γ/k) + q_t * σ² * T
δ_b = S_t - (1/γ) * ln(1 + γ/k) - q_t * σ² * T

dove:

  • δ_a e δ_b sono i prezzi ask e bid
  • S_t è il prezzo di mercato corrente
  • γ è il parametro di rischio (più è alto, più ampio è lo spread)
  • k è il tasso di arrivo degli ordini
  • q_t è l'inventario corrente
  • σ è la volatilità
  • T è l'orizzonte temporale

Caratteristiche del Trading Onchain

Quando spostiamo l'algoritmo onchain, emergono ulteriori sfide:

  1. Latenza – le transazioni sulla blockchain non sono istantanee e il prezzo può cambiare prima che l'ordine venga eseguito
  2. Costi del gas – ogni transazione richiede una commissione di rete
  3. Specificità di AMM/PMM – la meccanica del liquidity pool differisce dalle exchange tradizionali

Vediamo come tenere conto di questi fattori nel nostro algoritmo.

Passo 1: Configurare l'Ambiente e Raccogliere i Dati

Prima di tutto, dobbiamo configurare un ambiente per ottenere i dati di mercato. Utilizzeremo l'API di Binance per ottenere i prezzi correnti e la profondità del book degli ordini.

std::tuple MarketMaker::get_binance_data(const std::string& pair) {
    // Nel codice reale, questa sarebbe una richiesta all'API di Binance
    // Restituisce: mid_price, bid, ask, bid_volume, ask_volume
    double mid_price = 2000.0;
    double bid = mid_price - 1.0;
    double ask = mid_price + 1.0;
    double bid_volume = 10.0;
    double ask_volume = 8.0;
    return {mid_price, bid, ask, bid_volume, ask_volume};
}

Avremo anche bisogno di metriche onchain come il costo del gas e la latenza di rete:

std::pair MarketMaker::get_onchain_metrics() {
    // Nel codice reale, questa sarebbe una richiesta a un nodo Ethereum
    // Restituisce: gas_price (wei), latency (secondi)
    return {50e9, 12.0};
}

Passo 2: Implementare il Modello A-S di Base

Ora implementiamo il calcolo dello spread usando il modello A-S:

std::pair MarketMaker::calculate_spreads(double S_t, double sigma, double k, double q_t) {
    // Formula di Avellaneda-Stoikov
    double spread_term = (1.0 / gamma_) * log(1.0 + gamma_ / k);
    double inventory_term = q_t * sigma * sigma * T_;
    
    double delta_a = S_t + spread_term + inventory_term;  // Prezzo Ask
    double delta_b = S_t - spread_term - inventory_term;  // Prezzo Bid
    
    return {delta_a, delta_b};
}

Notate l'inventory_term. Se avete un inventario positivo (molto dell'asset), il prezzo ask diminuisce e il prezzo bid diminuisce ulteriormente per incoraggiare la vendita e limitare l'acquisto. E viceversa per l'inventario negativo.

Passo 3: Adattare il Modello per il Trading Onchain

Ora dobbiamo tenere conto delle specificità della blockchain. Iniziamo con la latenza:

double MarketMaker::adjust_price_with_latency(double S_t, double sigma, double latency) {
    // Simula una variazione casuale del prezzo dovuta alla latenza
    double latency_adjustment = utils::normal_dist(0.0, sigma * std::sqrt(latency));
    return S_t + latency_adjustment;
}

Qui usiamo un modello di random walk: maggiore è la volatilità e più lunga è la latenza, più il prezzo può cambiare prima che l'ordine venga eseguito.

Ora teniamo conto del costo del gas:

double MarketMaker::calculate_gas_cost(double gas_price, double trade_size) {
    const double GAS_LIMIT_PER_ORDER = 100000;  // Valore approssimativo per ordine
    return (gas_price * GAS_LIMIT_PER_ORDER * trade_size) / 1e18;  // Converti wei in ETH
}

Infine, adattiamo gli spread alle specificità del pool PMM:

std::pair MarketMaker::adjust_spreads_for_pmm(double S_t, double delta_a, double delta_b, double pool_depth) {
    // Modello PMM semplificato: adatta gli spread in base alla profondità del pool
    const double MIN_POOL_DEPTH = 10.0;
    double depth_factor = std::max(pool_depth, MIN_POOL_DEPTH) / MIN_POOL_DEPTH;
    
    // Riduce gli spread con maggiore profondità del pool
    double spread_reduction = 1.0 / std::sqrt(depth_factor);
    double mid_price = (delta_a + delta_b) / 2;
    double new_delta_a = mid_price + (delta_a - mid_price) * spread_reduction;
    double new_delta_b = mid_price - (mid_price - delta_b) * spread_reduction;
    
    return {new_delta_a, new_delta_b};
}

Passo 4: Gestione dell'Inventario

Per tracciare e gestire l'inventario, creiamo una semplice classe:

class InventoryManager {
public:
    InventoryManager() : inventory_(0.0) {}
    
    void update_inventory(double size, bool is_buy) {
        inventory_ += is_buy ? size : -size;
    }
    
    double get_inventory() const {
        return inventory_;
    }
    
private:
    double inventory_;
};

Gestione dell'inventario e visualizzazione del rischio Visualizzazione del Rischio di Inventario: Monitoraggio delle dimensioni delle posizioni per evitare un'eccessiva esposizione (lunga o corta) ai movimenti di mercato unidirezionali.

Passo 5: Combinare Tutto in un Unico Algoritmo

Ora combiniamo tutti i componenti in un unico algoritmo di market making:

void MarketMaker::step(double S_t, double sigma, double k, double latency, double gas_cost, double trade_size) {
    // Ottieni l'inventario corrente
    double current_inventory = inventory_.get_inventory();
    
    // Calcola gli spread in base alle condizioni di mercato correnti e all'inventario
    auto [delta_a, delta_b] = calculate_spreads(S_t, sigma, k, current_inventory);
    auto [adjusted_delta_a, adjusted_delta_b] = adjust_spreads_for_onchain(S_t, delta_a, delta_b, latency, sigma, gas_cost, trade_size);
    
    // Genera un prezzo di mercato indipendente
    double market_price = S_t + utils::normal_dist(0.0, sigma);
    
    // Determina se devono avvenire scambi in base al prezzo di mercato e agli spread
    bool is_buy = (market_price = adjusted_delta_a);
    
    // Esegui gli scambi e aggiorna l'inventario
    if (is_buy) {
        inventory_.update_inventory(trade_size, true);
        std::cout  reset();
    
    // Esegui un'azione e ottieni il nuovo stato, la ricompensa e il flag done
    std::tuple, double, bool> step(const std::array& action);
    
private:
    // Ottieni lo stato corrente dell'ambiente
    std::vector get_state() const;
    
    MarketMaker& mm_;
    double current_inventory_;
    double current_profit_;
    int current_step_;
    int max_steps_;
    
    // Parametri di mercato correnti
    double mid_price_;
    double sigma_;
    double latency_;
    double pool_depth_;
    
    std::mt19937 rng_;
};

Lo stato dell'ambiente è un vettore del prezzo corrente, inventario, volatilità, latenza di rete e profondità del pool. L'azione è un vettore di spread e dimensioni di acquisto/vendita.

Ora implementiamo la funzione di ricompensa:

double reward = profit_term - inventory_risk - gas_cost;

Dove:

  • profit_term è il profitto dalle transazioni
  • inventory_risk è una penalità per un inventario elevato (rischio)
  • gas_cost è il gas speso

Infine, addestriamo l'agente PPO:

void PPOTrainer::train(int episodes) {
    for (int ep = 0; ep  states;
        std::vector actions;
        std::vector rewards;
        
        while (true) {
            // Ottieni l'azione dalla policy
            auto action_probs = policy_net_->forward(torch::tensor(state));
            auto action = action_probs.multinomial(1);
            
            // Esegui un passo nell'ambiente
            auto [next_state, reward, done] = env_.step(action);
            
            // Salva la transizione
            states.push_back(torch::tensor(state));
            actions.push_back(action);
            rewards.push_back(reward);
            
            if (done) break;
            state = next_state;
        }
        
        // Aggiorna la policy PPO
        update_policy(states, actions, rewards);
    }
}

Agente di Reinforcement Learning PPO per l'ottimizzazione dello spread Il Reinforcement Learning in azione: l'agente PPO elabora stati di mercato complessi per ottimizzare dinamicamente gli spread di acquisto/vendita per la massima ricompensa attesa.

Passo 7: Testing e Visualizzazione

Per testare il nostro algoritmo, creiamo una semplice simulazione:

int main() {
    // Usa T = 300 secondi come specificato nel compito
    MarketMaker mm(0.1, 300.0);

    // Simula dati storici per la volatilità
    std::vector prices = {2000.0};
    double S_t = 2000.0;
    double trade_size = 1.0;
    double initial_sigma = 0.05;  // 5% di volatilità

    for (int i = 0; i < 300; ++i) {
        std::cout << "Step " << i + 1 << ": ";

        // Ottieni i dati (stub)
        auto [mid_price, bid_ask] = mm.get_binance_data("USD+/wETH");
        auto [gas_cost, latency] = mm.get_onchain_metrics();
        
        // Aggiungi un movimento di prezzo casuale per simulare un mercato reale
        S_t = mid_price + utils::normal_dist(0.0, mid_price * 0.01);

        // Calcola la volatilità
        double sigma = mm.calculate_volatility(prices, 5);
        if (sigma < 0.01) sigma = initial_sigma;

        // Tasso di arrivo degli ordini (stub)
        double k = 5.0;

        mm.step(S_t, sigma, k, latency, gas_cost, trade_size);

        // Aggiorna il prezzo per il prossimo step
        S_t += utils::normal_dist(0.0, S_t * 0.02);
        prices.push_back(S_t);
    }

    return 0;
}

Cosa Fare Dopo?

Il nostro algoritmo di market making è pronto, ma ci sono molti modi per migliorarlo:

  1. Connettere le API reali: sostituire gli stub con richieste reali all'API di Binance e al nodo Ethereum
  2. Migliorare il modello di volatilità: usare GARCH o altri modelli avanzati
  3. Espandere il PPO: aggiungere più parametri allo stato e all'azione
  4. Ottimizzare il gas: strategie per minimizzare i costi del gas
  5. Strategia multi-asset: espandere a più coppie contemporaneamente

Conclusione

Abbiamo costruito un algoritmo di market making che tiene conto delle caratteristiche del trading onchain e utilizza sia il classico modello A-S che i moderni metodi RL. Questo approccio consente di adattarsi alle mutevoli condizioni di mercato e massimizzare il profitto controllando il rischio.

Naturalmente, nel trading reale ci sono molti altri fattori da considerare, ma il nostro algoritmo fornisce una solida base per ulteriori sviluppi. Ricordate: nel trading algoritmico non è importante solo la matematica, ma anche test approfonditi, monitoraggio e ottimizzazione costante.

Spero che questo articolo vi abbia aiutato a comprendere meglio i principi del market making e vi abbia ispirato a creare i vostri algoritmi. Buona fortuna nel trading!

Citazione

@software{soloviov2025marketmakingavellanedastoikov,
  author = {Soloviov, Eugen},
  title = {Building a Market Making Algorithm for Crypto Pairs Using the Avellaneda-Stoikov Model},
  year = {2025},
  url = {https://marketmaker.cc/it/blog/post/market-making-avellaneda-stoikov},
  version = {0.1.0},
  description = {Una guida passo-passo per costruire un algoritmo di market making per le coppie USD+/wETH e USD+/cbbtc usando il modello Avellaneda-Stoikov e PPO. Caratteristiche del trading onchain, gestione dell'inventario, addestramento RL.}
}
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.