Simulare un Mercato Finanziario in Python: Agent-Based Model vs S&P 500

Cerca:

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
Simulare i Mercati Finanziari con Python Agent-Based Models vs S&P 500

I mercati finanziari non seguono le regole dei manuali di statistica. Se lo facessero, le crisi sistemiche non esisterebbero e i modelli di rischio delle grandi banche non fallirebbero con cadenza decennale. Eppure, gran parte della finanza tradizionale continua a usare la classica curva a campana per mappare un oceano perennemente in tempesta.

La realtà è diversa: i crolli sono violenti, la volatilità chiama altra volatilità e i prezzi non sono entità astratte calate dall’alto, ma il risultato caotico di migliaia di esseri umani (e algoritmi) che comprano e vendono presi dal panico o dall’euforia.

In questo articolo non useremo le solite formule top-down. Costruiremo un mercato dal basso. Utilizzando Python e il framework Mesa, creeremo un modello basato su agenti (Agent-Based Model) popolato da trader virtuali. Metteremo poi i risultati della nostra simulazione “in provetta” a confronto con i dati storici spietati dell’indice S&P 500.

Vedremo dove la teoria regge, e dove la realtà la fa a pezzi.

 

Pubblicità

📉 Simulare un Mercato Finanziario con Python e Mesa: Confronto con S&P500

 I mercati finanziari reali presentano caratteristiche statistiche complesse come code pesanti, clustering della volatilità e deviazioni dalla normalità. In questo articolo costruiamo un modello Agent-Based (ABM) in Python utilizzando il framework Mesa, confrontando i risultati con i dati storici dell’indice S&P500.

1. Introduzione: Oltre la Distribuzione Normale

I modelli finanziari classici (come Black-Scholes) spesso assumono che i rendimenti siano normalmente distribuiti. Tuttavia, i dati reali urlano il contrario. Esistono i cosiddetti Stylized Facts:

  • Distribuzioni leptocurtiche: Le probabilità di eventi estremi (“Cigni Neri”) sono molto più alte del previsto.
  • Volatility Clustering: La calma chiama calma, la tempesta chiama tempesta.
  • Dinamiche emergenti: Il prezzo non è un dato esterno, ma emerge dalle interazioni.

2. Il Modello Matematico

L’evoluzione del prezzo nel nostro modello segue una dinamica basata sull’eccesso di domanda:

[math]P_{t+1} = P_t + \alpha D_t + \epsilon_t[/math]

Dove:

  • [math]D_t[/math]: Domanda aggregata generata dagli agenti (Fondamentalisti vs Chartisti).
  • [math]\alpha[/math]: Sensibilità del prezzo all’impatto di mercato.
  • [math]\epsilon_t[/math]: Rumore bianco gaussiano (incertezze esterne).

I rendimenti vengono analizzati in forma logaritmica per garantire la stazionarietà:

[math]r_t = \ln\left(\frac{P_t}{P_{t-1}}\right)[/math]

3. Implementazione in Python (Mesa)

Utilizziamo Mesa per definire due tipologie di agenti:

  1. Fundamentalists: Credono che il prezzo debba tornare a un valore “equo”. Comprano se il prezzo è basso, vendono se è alto.
  2. Chartists (Trend Followers): Seguono l’inerzia del mercato. Se il prezzo sale, continuano a comprare.

Gli Attori del Mercato: Fondamentalisti vs Chartisti

Per comprendere come nasca il prezzo, dobbiamo prima osservare chi lo determina. Nel nostro ecosistema artificiale, la popolazione dei trader è divisa in due fazioni contrapposte, le cui filosofie di investimento creano la tensione necessaria al movimento del mercato:

  • I Fondamentalisti (The Anchors): Questi agenti agiscono come una forza stabilizzatrice. Essi credono nell’esistenza di un “valore equo” (o Fair Value) del titolo. La loro strategia è semplice: se il prezzo di mercato scende sotto il valore intrinseco, comprano (giudicandolo un affare); se il prezzo sale troppo, vendono. Nel lungo periodo, sono loro a riportare il mercato verso la razionalità, contrastando le deviazioni eccessive.

  • I Chartisti (The Momentum Followers): A differenza dei primi, i chartisti ignorano il valore reale del titolo. La loro attenzione è rivolta esclusivamente alla “psicologia della folla” e ai trend recenti. Se il prezzo è salito nell’ultimo periodo, scommettono che continuerà a salire e comprano. Questo comportamento genera un feedback positivo: la loro stessa domanda spinge il prezzo ancora più in alto, attirando altri acquirenti e creando quelle bolle speculative (o quei crash improvvisi) che caratterizzano i mercati reali.

L’interazione tra queste due specie di trader trasforma il prezzo in un fenomeno emergente: un tiro alla fune costante tra la razionalità del valore e l’inerzia del trend.

Questa distinzione è interessante perché riflette la dinamica “Mean Reverting vs Trending”. Nel codice, questa differenza è gestita da una semplice sottrazione:

  • Fondamentalisti: (valore_intrinseco - prezzo_attuale)

  • Chartisti: (prezzo_attuale - prezzo_precedente)

È proprio questa contrapposizione matematica a generare la complessità statistica che andremo ad analizzare.


# Installazione delle librerie in Google Colab
!pip install mesa yfinance
import random
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import yfinance as yf
from mesa import Agent, Model
from mesa.datacollection import DataCollector
from scipy.stats import skew, kurtosis

# --- 1. DEFINIZIONE DEGLI AGENTI ---
class Trader(Agent):
    def __init__(self, unique_id, model, trader_type):
        super().__init__(model)
        self.trader_type = trader_type
        self.demand = 0

    def step(self):
        price = self.model.price
        
        if self.trader_type == "fundamentalist":
            # Il fondamentalista crede nel valore intrinseco
            value = self.model.fundamental_value
            self.demand = (value - price)
        
        elif self.trader_type == "chartist":
            # Il chartista segue il trend recente
            trend = self.model.price - self.model.prev_price
            self.demand = trend
            
        self.demand *= self.model.sensitivity

# --- 2. DEFINIZIONE DEL MODELLO DI MERCATO ---
class MarketModel(Model):
    def __init__(self, N=200, frac_fund=0.4):
        super().__init__()
        self.price = 100
        self.prev_price = 100
        self.fundamental_value = 100
        self.alpha = 0.01        # Impatto di mercato della domanda
        self.sensitivity = 0.1  # Reattività degli agenti
        
        self.traders = []
        for i in range(N):
            t_type = "fundamentalist" if random.random() < frac_fund else "chartist"
            trader = Trader(i, self, t_type)
            self.traders.append(trader)
            
        self.datacollector = DataCollector(
            model_reporters={"Price": lambda m: m.price}
        )

    def step(self):
        self.prev_price = self.price
        random.shuffle(self.traders)
        
        for trader in self.traders:
            trader.step()
            
        total_demand = sum(t.demand for t in self.traders)
        noise = np.random.normal(0, 1) # Rumore stocastico
        
        # Equazione dinamica del prezzo
        self.price += self.alpha * total_demand + noise
        self.datacollector.collect(self)

# --- 3. ESECUZIONE SIMULAZIONE ---
model = MarketModel(N=200, frac_fund=0.4)
for _ in range(500):
    model.step()

# Estrazione dati simulati
df_sim = model.datacollector.get_model_vars_dataframe()
sim_returns = np.log(df_sim["Price"] / df_sim["Price"].shift(1)).dropna()

# --- 4. DOWNLOAD DATI REALI S&P500 ---
sp500 = yf.download("^GSPC", start="2015-01-01", end="2024-12-31")
sp_prices = sp500["Close"]
sp_returns = np.log(sp_prices / sp_prices.shift(1)).dropna()

# --- 5. VISUALIZZAZIONE E CONFRONTO ---
fig, ax = plt.subplots(1, 2, figsize=(16, 6))

# Grafico Distribuzione Rendimenti
ax[0].hist(sim_returns, bins=50, density=True, alpha=0.6, label="Simulazione ABM")
ax[0].hist(sp_returns, bins=50, density=True, alpha=0.6, label="S&P500 Reale")
ax[0].set_title("Confronto Distribuzione Rendimenti")
ax[0].legend()

# Grafico Volatility Clustering (Rendimenti al quadrato)
ax[1].plot(sim_returns.values**2, alpha=0.7, label="Simulazione")
ax[1].plot(sp_returns.values**2, alpha=0.3, label="S&P500")
ax[1].set_title("Volatility Clustering (Rendimenti^2)")
ax[1].legend()

plt.show()
# --- 6. STATISTICHE DESCRITTIVE (Versione Corretta) ---
# Usiamo float() per assicurarci di passare un numero singolo alla formattazione f-string
print(f"{'Metrica':<20} | {'Simulazione':<15} | {'S&P500':<15}")
print("-" * 55)

try:
    # Estraiamo i valori scalari usando np.mean o convertendo a float
    s_skew = float(skew(sim_returns))
    r_skew = float(skew(sp_returns))
    s_kurt = float(kurtosis(sim_returns))
    r_kurt = float(kurtosis(sp_returns))

    print(f"{'Skewness':<20} | {s_skew:<15.4f} | {r_skew:<15.4f}")
    print(f"{'Kurtosis':<20} | {s_kurt:<15.4f} | {r_kurt:<15.4f}")
except TypeError:
    # Se sp_returns è ancora un DataFrame/Series, prendiamo il primo valore
    print(f"{'Skewness':<20} | {skew(sim_returns)[0]:<15.4f} | {skew(sp_returns)[0]:<15.4f}")
    print(f"{'Kurtosis':<20} | {kurtosis(sim_returns)[0]:<15.4f} | {kurtosis(sp_returns)[0]:<15.4f}")

Metrica              | Simulazione     | S&P500         
-------------------------------------------------------
Skewness             | -0.0803         | -0.8103        
Kurtosis             | 0.0760          | 15.7171  

Questi numeri sono estremamente rivelatori e confermano esattamente ciò che abbiamo discusso sulla natura dei mercati finanziari. Vediamo di “leggere” tra le righe di questi dati, perché raccontano una storia molto chiara.

Forse potrebbe interessarti anche:  Vettorializzazione e Broadcasting in NumPy: Guida Pratica con Esercizi

Skewness (Asimmetria): La paura dei crolli

  • Simulazione (-0.0803): È quasi zero. Significa che il  modello è molto bilanciato; le probabilità di avere una grande salita o una grande discesa sono quasi identiche.

  • S&P500 (-0.8103): Qui c’è una forte asimmetria negativa.

    • Cosa significa? Conferma che nei mercati reali i crolli sono più violenti delle salite. La psicologia umana reagisce al dolore della perdita molto più velocemente di quanto reagisca al piacere del guadagno. Il  modello attuale, essendo basato su regole simmetriche, non cattura ancora questo “panico”.

Kurtosis (Curtosi): I Cigni Neri

Qui la differenza è schiacciante:

  • Simulazione (0.0760): Questo valore è vicino allo zero (tipico di una distribuzione normale/gaussiana). Il  modello produce risultati “tranquilli”: gli eventi rari sono davvero rarissimi.

  • S&P500 (15.7171): Questo è un valore altissimo (si parla di Extra-Curtosi).

    • Cosa significa? Conferma la presenza di Fat Tails (code pesanti). Nel mercato reale, eventi che la statistica classica definirebbe “impossibili” (come il crash del 2020 o la crisi del 2008) accadono con una frequenza allarmante. La realtà è molto più selvaggia della tua simulazione attuale.

Il Giorno in cui la Gaussiana si Ruppe

E questa non è solo teoria. C’è un giorno che da solo smonta qualunque illusione di normalità statistica.

Il 12 marzo 2020, nel pieno del panico pandemico, lo S&P 500 aprì in caduta libera. Dopo pochi minuti scattò il circuit breaker: le contrattazioni vennero sospese per 15 minuti. Quando il mercato riaprì, non ci fu alcun rimbalzo. La discesa accelerò.

A fine giornata, l’indice aveva perso il 9,5%.

Per un modello gaussiano, un evento del genere ha una probabilità talmente piccola da essere considerato impossibile nell’intera vita dell’universo. Eppure è accaduto. In diretta. Sotto gli occhi di tutti.

Non fu un errore di calcolo. Fu la dimostrazione brutale che i mercati reali non seguono la curva a campana: seguono la psicologia umana, la leva finanziaria, il panico collettivo, gli algoritmi che amplificano ogni movimento.

Forse potrebbe interessarti anche:  Dalle Matrici alla Strategia: Anatomia di un Mini-Transformer per il Forecasting

È esattamente questo tipo di dinamica estrema che la tua simulazione, per ora, non cattura. Ed è proprio qui che nasce la differenza abissale tra la curtosi quasi nulla del modello e la curtosi mostruosa dell’S&P500.

Anatomia del Codice: Come nasce un Mercato Digitale

Per capire perché la nostra simulazione ha prodotto quei numeri (e perché differisce dal S&P500), dobbiamo guardare sotto il cofano. Il codice si divide in tre motori logici.

1. La Classe Trader: Il Cervello degli Agenti

Questa classe definisce la “psicologia” di ogni partecipante. Ogni agente non sa cosa farà il mercato, guarda solo i propri parametri.

Il Motore Decisionale (step):

  • Fondamentalisti: Creano una forza di mean-reversion. Se il valore reale è 105 e il prezzo è 100, la domanda è [math]+5[/math]. Comprano perché è sottovalutato.
  • Chartisti (Trend Followers): Creano inerzia e bolle. Se il prezzo è salito di 2 punti nell’ultimo turno, comprano sperando che la salita continui.

2. La Classe MarketModel: L’Ecosistema

Il modello gestisce il tempo e aggrega le spinte individuali in un unico prezzo di mercato.

L’Equazione del Prezzo:

[math]P_{t+1} = P_t + \alpha \cdot \text{Total Demand} + \text{noise}[/math]

Qui avviene la magia: se la somma delle domande è positiva (più compratori che venditori), il prezzo sale. Il parametro [math]\alpha[/math] rappresenta la liquidità: più è piccolo, più il mercato è “pesante” e difficile da spostare.

3. Analisi dei Dati: Skewness e Kurtosis

Perché abbiamo usato i Log-Rendimenti? Nel trading quantitativo, calcolare [math]r_t = \ln(P_t / P_{t-1})[/math] è fondamentale per rendere i dati simmetrici e confrontabili su scale temporali diverse.

Statistica Cosa misura nel mercato
Skewness L’asimmetria. Se negativa, i crolli sono più veloci delle risalite (Paura > Avidità).
Kurtosis La “grassezza” delle code. Indica quanto sono probabili i crolli catastrofici (Cigni Neri).

Nota tecnica: La Curtosi alta che abbiamo visto nel S&P500 reale ([math]\approx 15[/math]) rispetto alla nostra simulazione ([math]\approx 0.07[/math]) suggerisce che nel mondo reale gli agenti non sono indipendenti, ma tendono a copiarsi ferocemente durante i momenti di crisi, creando code statistiche enormi.

Come migliorare il modello?

Se volessimo far saltare la curtosi del  modello da 0 a 15 per renderlo simile al S&P500, dovremmo introdurre dei feedback positivi non lineari.

Ad esempio:

  1. Herding Effect (Effetto Gregge): Più il prezzo scende, più i “Chartisti” diventano aggressivi nel vendere, creando un effetto valanga.

  2. Leva Finanziaria (Margin Calls): Se il prezzo scende oltre una soglia, alcuni agenti sono costretti a vendere tutto per coprire i debiti, accelerando il crollo.

  3. Circuit Breakers: Introdurre dei limiti che fermano le negoziazioni se la volatilità esplode troppo velocemente.

La Realtà è più Selvaggia della Simulazione

Il nostro esperimento dimostra una verità affascinante: bastano poche righe di codice per generare una dinamica di mercato che, a occhio nudo, sembra assolutamente autentica.

Il modello, infatti, riesce a replicare con successo il clustering della volatilità.

Osserviamo chiaramente come i momenti di instabilità innescati dai Chartisti tendano ad auto-alimentarsi, creando ondate di turbolenza che perdurano finché i Fondamentalisti non intervengono per riportare i prezzi verso il loro valore di equilibrio.

Tuttavia, grattando sotto la superficie visiva, i dati statistici rivelano un quadro ben diverso e ci mettono in guardia: il modello sottovaluta pesantemente il rischio reale.

Confrontando la simulazione con l’S&P 500 attraverso la lente della Skewness e della Kurtosis, emergono due grandi assenti:

  • La violenza dei crolli (Asimmetria): La nostra simulazione è troppo bilanciata. Nei mercati veri, guidati dalla paura umana, le discese sono sempre molto più rapide, profonde e violente delle lente fasi di salita.

  • Il peso dei Cigni Neri (Code Pesanti): Il nostro ecosistema artificiale non genera eventi estremi con la frequenza allarmante che si registra nella realtà (che vanta una curtosi di ben 15).

Il cortocircuito della finanza tradizionale Questa discrepanza non è solo un dettaglio accademico, ma il cuore del problema. È esattamente il motivo per cui i modelli di rischio delle grandi banche falliscono rovinosamente durante le crisi sistemiche: continuano ad affidarsi a simulazioni rassicuranti e troppo vicine alla distribuzione normale, ignorando del tutto quanto il mercato reale possa essere estremo e selvaggio.

Perché questo esercizio è estremamente interessante?

Questo approccio ribalta il tavolo della finanza classica. Mentre il modello di Black-Scholes o il Moto Browniano Geometrico impongono una struttura al prezzo “dall’alto” (Top-Down), l’Agent-Based Modeling (ABM) fa emergere il prezzo “dal basso” (Bottom-Up).

  • L’Emergenza come dato: La peculiarità del codice è che non esiste una funzione che dice al prezzo di essere volatile. La volatilità “nasce” spontaneamente quando il numero di Chartisti supera una certa soglia critica. È la prova software che il mercato è un sistema complesso auto-organizzato.

  • Stress Testing Realistico: A differenza dei modelli VaR (Value at Risk) che spesso falliscono nei momenti di crisi, l’ABM permette di testare scenari “What-if” comportamentali. Ad esempio: “Cosa succede se il 90% degli algoritmi decide di vendere contemporaneamente quando la volatilità sale del 2%?”.

  • Laboratorio di Policy Making: Questo codice è lo scheletro di ciò che banche centrali e autorità di vigilanza usano per simulare l’impatto dei Circuit Breakers (i blocchi automatici degli scambi) o delle nuove tasse sulle transazioni finanziarie (Tobin Tax) prima di applicarle al mondo reale.

Forse potrebbe interessarti anche:  Domanda Marshalliana e Hicksiana in Python: Guida al Pricing Causale

Conclusioni e Sviluppi Futuri: Verso un Modello “Antifragile”

Non stiamo semplicemente simulando il mercato; stiamo cercando di codificare le condizioni umane e meccaniche che lo fanno emergere come un sistema complesso auto-organizzato. Sebbene il nostro modello catturi l’essenza delle interazioni sociali, la realtà dell’S&P 500 ci ha mostrato che manca ancora quella componente di “panico sistemico” che rende i mercati finanziari così imprevedibili.

Per colmare il divario tra la simulazione “tranquilla” e la realtà “selvaggia”, i prossimi passi della ricerca si concentreranno sull’introduzione di asimmetrie comportamentali:

  • Leva Finanziaria e Margin Calls: Implementare agenti che operano a debito. Quando il prezzo scende sotto una certa soglia, questi trader sono costretti a vendere per coprire i margini, innescando una spirale discendente (feedback positivo) che farebbe esplodere la curtosi del modello.

  • Herding Effect (Effetto Gregge): Modificare la logica dei Chartisti affinché la loro aggressività aumenti proporzionalmente alla velocità del movimento del prezzo. Questo creerebbe quei “Flash Crash” tipici dei mercati moderni.

  • Circuit Breakers: Introdurre meccanismi di regolamentazione che blocchino gli scambi in caso di eccessiva volatilità, analizzando se questi strumenti calmino davvero il mercato o se, paradossalmente, aumentino il panico alla riapertura.

  • Reinforcement Learning: Evolvere gli agenti da semplici automi a “entità senzienti” capaci di imparare dalle perdite passate, adattando la propria strategia in base alle condizioni di mercato in tempo reale.

In definitiva, questo esperimento ci ricorda che in finanza la precisione non è accuratezza. Possiamo avere un modello matematicamente perfetto, ma se ignora la natura estrema dei dati reali, ci lascerà indifesi di fronte al prossimo Cigno Nero. Il vero valore dell’Agent-Based Modeling non è prevedere il prezzo di domani, ma capire quanto può diventare profonda la voragine quando il sistema decide di crollare.

Dati, mercati e decisioni: leggere oltre i numeri

Dalla statistica descrittiva ai mercati finanziari: capire i dati significa capire anche i limiti dei modelli e i bias umani.

Pubblicità