Costruire un Algoritmo di Market Making per Coppie Crypto con il Modello Avellaneda-Stoikov
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.
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:
δ_aeδ_bsono i prezzi ask e bidS_tè il prezzo di mercato correnteγè il parametro di rischio (più è alto, più ampio è lo spread)kè il tasso di arrivo degli ordiniq_tè l'inventario correnteσè la volatilitàTè l'orizzonte temporale
Caratteristiche del Trading Onchain
Quando spostiamo l'algoritmo onchain, emergono ulteriori sfide:
- Latenza – le transazioni sulla blockchain non sono istantanee e il prezzo può cambiare prima che l'ordine venga eseguito
- Costi del gas – ogni transazione richiede una commissione di rete
- 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_;
};
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 transazioniinventory_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);
}
}
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:
- Connettere le API reali: sostituire gli stub con richieste reali all'API di Binance e al nodo Ethereum
- Migliorare il modello di volatilità: usare GARCH o altri modelli avanzati
- Espandere il PPO: aggiungere più parametri allo stato e all'azione
- Ottimizzare il gas: strategie per minimizzare i costi del gas
- 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.}
}
Autori
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.