Prima volta a Monte Carlo
Il mio primo incontro con il metodo Monte Carlo lo devo a “Fooled by Randomness” di Nassim Taleb, matematico libanese esperto nella teoria delle probabilità, il quale esprime brillantemente un concetto semplice quanto spaventoso: la casualità può simulare il talento.
Attraverso simulazioni di mercati caratterizzati da dinamiche casuali, Taleb mostra come, anche in assenza di una strategia di trading sistematicamente vincente, alcune strategie possano ottenere risultati eccezionali in determinati periodi e fallire in altri. Nessuna strategia, infatti, garantisce il successo: ciò che osserviamo a posteriori come talento o abilità può essere, almeno in parte, semplicemente uno dei possibili esiti di un processo stocastico. La casualità, quindi, può avere sui mercati e sulle nostre vite un’influenza molto maggiore di quanto siamo disposti ad ammettere. Come dice Nassim:
“Le persone sopravvalutano la loro conoscenza quanto sottostimano la loro probabilità di sbagliarsi” (Taleb, N. N. (2007). Fooled by Randomness. Penguin Books.).
L’applicazione di questa teoria mi è sembrata più chiaramente espressa in “Processi sociali virtuali” di Sabrina Moretti: per stimare gli ingressi in un negozio non servono complesse equazioni differenziali, basta convertire i dati reali in probabilità e far “entrare” i clienti tramite numeri casuali.
É qui che ho compreso la vera natura del metodo. Una simulazione Monte Carlo non è una dimostrazione matematica: non offre una prova formale o una soluzione analitica. Sfrutta semplicemente la Legge dei Grandi Numeri per campionare lo spazio degli stati. Quando un problema combinatorio diventa intrattabile, anziché forzare l’algebra sottostante, ci si limita a simulare milioni di scenari e a contarne gli esiti.
Da allora ho ritrovato questa logica ovunque. Non immaginavo, però, che l’avrei usata per risolvere una faida d’orgoglio su una spiaggia.
La scusa
Durante una giornata al mare con un amico, ho scoperto un gioco di carte di cui ignoravo il nome: una variante a due giocatori di Nomination (Oh Hell!). Le regole sono semplici: osservata la propria mano, si dichiara a priori il numero esatto di prese che si pensa di riuscire a fare. Ogni errore di stima penalizza il punteggio.
Dopo averlo insegnato ad un’amica, mi sono ritrovato regolarmente sconfitto dalle sue spiccate intuizioni nel prevedere gli esiti delle partite. Per colmare il divario, ho abbandonato l’intuito viscerale alla ricerca di una regola decisionale sistematica.
È bastato formulare un’euristica quantitativa per annullare lo svantaggio e riportare l’equilibrio tra vittorie e sconfitte. Restava solo da capire una cosa: quanto fosse matematicamente solida la mia strategia.
Le regole
Sebbene ricordi per meccanica i giochi di previsione come Oh Hell!, la variante presentata possiede caratteristiche peculiari che ne alterano profondamente la struttura probabilistica.
1. Ordinamento e gerarchia univoca
A differenza dei giochi tradizionali, ogni carta possiede un valore unico e assoluto all’interno del mazzo. La gerarchia è definita in modo rigido da due parametri in ordine di priorità:
- Valore nominale: Re > Cavallo > Fante > 7 > … > Asso (o l’equivalente per mazzi francesi).
- Gerarchia dei semi: Denari > Coppe > Spade > Bastoni (o l’ordine da poker Cuori > Quadri > Fiori > Picche).
Un mazzo da 40 carte risulta quindi ordinato in una scala univoca e sequenziale dal valore 1 al valore 40, in cui il Re di Denari/Cuori vale 40 e l’Asso di Bastoni/Picche vale 1.
2. Svolgimento della partita
I giocatori partono con un certo numero di punti (o vite), scelti arbitrariamente dai giocatori stessi. La struttura del gioco segue un ciclo a fisarmonica:
- Dichiarazione: Ricevute le carte (es. 5 nella prima mano), il giocatore di turno dichiara le prese previste; l’avversario dichiara a sua volta con un solo vincolo: la somma delle due dichiarazioni non può eguagliare il totale delle carte in mano (garantendo che almeno un giocatore sbagli la stima).
- Gioco: Le carte vengono calate a turni alterni. Vince la presa chi gioca la carta di valore assoluto superiore.
- Punteggio: A fine mano si sottraggono dai punti iniziali le penalità pari all’errore assoluto commesso (|dichiarate — realizzate|).
- Ciclo delle mani: Si scala da 5 carte fino ad arrivare a una singola carta (giocata a carte scoperte), per poi risalire fino a 5 carte. Il gioco termina quando uno dei due giocatori azzera i propri punti.
Più della meta
La ricerca di una strategia si è focalizzata sulla fase centrale della partita: la mano con una sola carta in gioco.
Get Francis’s stories in your inbox
Join Medium for free to get updates from this writer.
Trattandosi di una fase a carte scoperte e con valori univoci dal valore 1 al valore 40, l’esito è deterministico. Per le mani a carte coperte, l’idea è stata quella di tradurre quella certezza visiva in un’euristica di soglia.
Dividendo idealmente il mazzo a metà, una carta con valore superiore a 20 ha una probabilità a priori superiore al 50% di risultare vincente rispetto a una carta estratta a caso dall’avversario:
def f(x):
return x > 20La prima versione: il conteggio semplice
Inizialmente, la strategia consisteva nel contare quante carte con punteggio superiore a 20 si avessero in mano e dichiarare quel numero come previsione. Con una mano da 5 carte, tuttavia, questa logica elementare si è rivelata corretta solo nel 43% dei casi.
L’intuizione: l’accoppiamento incrociato
Cercando di migliorare l’affidabilità del modello, l’intuizione è stata quella di cambiare il modo di confrontare le carte, confrontandole in senso opposto:
- La mia carta più alta contro la più bassa dell’avversario;
- La mia seconda più alta contro la sua seconda più bassa, e così via.
Ovviamente questo modello ha una visione globale delle carte distribuite, non è una logica che il giocatore umano può prendere in considerazione siccome conosce solo le proprie carte e non quelle dell’avversario.
L’ipotesi è semplice: se la probabilità di vincere il singolo scontro rimane alta anche con questo incrocio, allora anche la previsione complessiva sulle prese totali (5 in questo caso) migliorerà.
I Risultati: l’anomalia della curva e i limiti dell’euristica
La simulazione Monte Carlo su milioni di partite ha restituito la reale distribuzione dell’errore.
Su una mano di 5 carte, l’accoppiamento incrociato (la mia più alta contro la sua più bassa) porta il tasso di successo dal 43% della strategia ingenua a circa il 55%. Riducendo il numero di carte distribuite, la precisione cresce in modo lineare fino a toccare il 75% nella mano a singola carta, confermando l’efficacia dell’euristica iniziale.
I dati più interessanti, tuttavia, sono emersi aumentando progressivamente la dimensione della mano fino a 20 carte (ovvero la totalità del mazzo distribuita tra i due giocatori).
L’andamento della curva (Immagine 2) mostra un comportamento fortemente non lineare, caratterizzato da due punti critici:
- Il minimo a 12 carte (~50,2%): aumentando le carte in mano, il tasso di successo decade fino a toccare il punto di parità con il caso puro in corrispondenza delle 12 carte (24 carte totali in gioco su 40). Questa fascia rappresenta la massima varianza combinatoria: il mazzo residuo (16 carte) è sufficientemente piccolo da alterare la distribuzione attesa dei valori, ma ancora abbastanza grande da mantenere un’alta incertezza.
- Il crollo a 20 carte (50,0%): superate le 12 carte, la precisione risale fino al 56% a 19 carte, per poi crollare al 50% esatto quando ogni giocatore possiede metà del mazzo. La spiegazione è di natura sistemica: con 20 carte a testa, l’informazione nascosta si azzera. Avendo tutte le carte sul tavolo, il gioco cessa di essere probabilistico e diventa un problema deterministico puro. Applicare un’euristica di soglia basata sul valore medio ad un sistema di informazione perfetta porta alla forzatura di stime errate, riducendo l’efficacia della previsione a un semplice lancio di moneta.
Sviluppi futuri
L’analisi condotta tramite la simulazione Monte Carlo ha dimostrato come un’euristica statica presenti dei limiti strutturali al variare dell’informazione disponibile.
Il passo successivo per far evolvere questo lavoro è esplorare l’impiego del Reinforcement Learning (apprendimento per rinforzo). L’obiettivo sarà quello di costruire un ambiente simulato in cui un agente possa apprendere autonomamente le strategie di stima e di calata ottimali attraverso l’esperienza diretta, superando i limiti delle regole euristiche prefissate e adattandosi alla complessità non lineare del gioco.
**Testo scritto con in collaborazione con Vignali Martina che ringrazio per l’ aiuto e supporto.
import matplotlib.pyplot as plt
import numpy as np
import random
class Player:
def __init__(self, hand):
self.hand = hand
def __equals__(self, other):
return self.hand == other.hand
def sorted(self):
return sorted(self.hand)
def reversesorted(self):
return sorted(self.hand, reverse=True)
def forcasting(self):
forcasting = 0
for i in self.hand:
if i > 21:
forcasting += 1
return forcasting
def check(list1, list2):
check = 0
for i, e in enumerate(list1):
if e > list2[i]:
check += 1
return check
def dealer(deck, card_count=5):
hand = []
for i in range(card_count):
hand.append(deck.pop(random.randint(0, len(deck) - 1)))
return Player(hand)
if __name__ == "__main__":
times = 1000000
wins = 0
datas = []
curve = []
for i in range(times):
for i in range(1,10):
deck = [i for i in range(1, 41)]
player1 = dealer(deck, card_count=i)
player2 = dealer(deck, card_count=i)
player1_hand = player1.sorted()
player2_hand = player2.reversesorted()
player1_forcasting = player1.forcasting()
check_result = check(player1_hand, player2_hand)
diff = player1_forcasting - check_result
datas.append(diff)
if diff == 0:
wins += 1
print(f"Player 1 wins {wins} out of {times} games.\n Win rate: {wins/times*100}%")
curve.append([i, wins/times*100])
data = np.array(datas)
valori = np.arange(-5, 6)
conteggi = np.array([np.sum(data == v) for v in valori])
probabilita = conteggi / len(data)
plt.bar(valori, probabilita, width=0.8, edgecolor='black')
plt.xlabel("Valore")
plt.ylabel("Probabilità")
plt.title("Distribuzione di probabilità")
plt.xticks(valori)
plt.grid(axis='y', alpha=0.3)
plt.show()
x = [p[0] for p in curve]
y = [p[1] for p in curve]
plt.figure(figsize=(8,5))
plt.bar(valori, probabilita, width=0.8,
edgecolor='black', alpha=0.6,
label='Simulazione')
plt.plot(x, y,
color='red',
linewidth=2,
marker='o',
label='Distribuzione teorica')
plt.xlabel("Valore")
plt.ylabel("Probabilità")
plt.title("Confronto distribuzione simulata e teorica")
plt.xticks(valori)
plt.grid(axis='y', alpha=0.3)
plt.legend()
plt.show()







