Calcoli scientifici con Julia/Modello base delle scommesse sportive
Un modello base per scommesse sportive di calcio si può fare usando la distribuzione di Poisson per stimare i gol, la matrice delle probabilità dei risultati relativa ai punteggi 0-0,1-0,2-1 ecc. e il valore atteso della scommessa (Expected Value)

La distribuzione di Poisson si utilizza spesso nelle scommesse sportive (soprattutto nel calcio) perché modella bene il numero di eventi rari che avvengono in un intervallo di tempo fisso.
In una partita di calcio i gol sono pochi, avvengono in modo discreto e si contano come 0,1,2,3... Esempio tipico:
| gol | probabilità | | --- | ----------- | | 0 | alta | | 1 | alta | | 2 | media | | 3 | bassa | | 4+ | molto bassa |
Questo tipo di fenomeno è esattamente quello per cui è stata progettata la distribuzione di Poisson.
La probabilità di segnare (k) gol è:
- per ogni
dove
- (λ) = media gol
- (k) = numero di gol
Esempio:
se (λ = 1.5)
| gol | probabilità | | --- | ----------- | | 0 | 22% | | 1 | 33% | | 2 | 25% | | 3 | 12% |
Distribuzione molto simile ai dati reali del calcio.
La matrice delle probabilità dei risultati relativa ai punteggi 0-0,1-0,2-1 ecc. è quella più usata nei modelli di calcio . Per esempio l'elemento (1,0) della matrice è dato dalla probabilità che la squadra in casa segni 1 gol, sapendo che la sua media di gol è λ = 1.6 secondo la relativa Poisson, moltiplicata per la probabilità che la squadra in trasferta segni 0 gol , sapendo che la sua media di gol è λ = 1.1 secondo la relativa Poisson . Quindi in sostanza bisogna moltiplicare i valori di 2 Poisson distinte. Nel modello base si assume che i gol della squadra casa e i gol della squadra trasferta siano indipendenti per cui le probabilità si moltiplicano : P(i,j) = P(i) X P(j).
La matrice costruita diventa per esempio:
| | 0 | 1 | 2 | 3 | | - | ---- | ---- | ---- | --- | | 0 | 0.07 | 0.08 | 0.04 | ... | | 1 | 0.10 | 0.11 | 0.06 | ... | | 2 | 0.08 | 0.09 | 0.05 | ... |
per cui il risultato (0,1) ha probabilità 8% ecc.
Il valore atteso della scommessa (Expected Value) è il calcolo più importante perché indica quanto si guadagna o si perde in media per ogni puntata.
EV = (P X Quota) - 1
dove P = probabilità reale stimata e Quota = quota del bookmaker
Ad esempio se la probabilità stimata è 50% e la quota: 2.50:
EV = (0,50 X 2,50) - 1 = + 0,25 è una scommessa teoricamente profittevole, perché si guadagna teoricamente 0,25€ per ogni euro puntato in media.
Quindi :
| EV | significato | | ------ | ----------------------------------- | | EV > 0 | scommessa teoricamente profittevole | | EV = 0 | gioco equo | | EV < 0 | scommessa sfavorevole |
Implementazione in Julia
[modifica | modifica sorgente]Innanzitutto installo le librerie di Julia che mi servono:
using Pkg
Pkg.add("Distributions")
Pkg.add("CSV")
Pkg.add("DataFrames")
Innanzitutto traccio la matrice dei risultati con massimo 5 gol per squadra:
max_goals = 5
matrix = [(i,j) for i in 0:max_goals, j in 0:max_goals]
matrix
6×6 Matrix{Tuple{Int64, Int64}}:
(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5)
(1, 0) (1, 1) (1, 2) (1, 3) (1, 4) (1, 5)
(2, 0) (2, 1) (2, 2) (2, 3) (2, 4) (2, 5)
(3, 0) (3, 1) (3, 2) (3, 3) (3, 4) (3, 5)
(4, 0) (4, 1) (4, 2) (4, 3) (4, 4) (4, 5)
(5, 0) (5, 1) (5, 2) (5, 3) (5, 4) (5, 5)
Supponiamo che la squadra casa abbia media gol λ = 1.6 e la squadra in trasferta media gol λ = 1.1 e tracciamo la matrice delle probabilità dei risultati:
using Distributions
using CSV
using DataFrames
lambda_home = 1.6
lambda_away = 1.1
home_goals = Poisson(lambda_home)
away_goals = Poisson(lambda_away)
prob_matrix = zeros(max_goals+1, max_goals+1)
for i in 0:max_goals
for j in 0:max_goals
prob_matrix[i+1, j+1] = pdf(home_goals, i) * pdf(away_goals, j)
end
end
prob_matrix
6×6 Matrix{Float64}:
0.0672055 0.0739261 0.0406593 0.0149084 0.00409982 0.00090196
0.107529 0.118282 0.0650549 0.0238535 0.00655971 0.00144314
0.0860231 0.0946254 0.0520439 0.0190828 0.00524776 0.00115451
0.045879 0.0504669 0.0277568 0.0101775 0.00279881 0.000615738
0.0183516 0.0201867 0.0111027 0.00407099 0.00111952 0.000246295
0.00587251 0.00645976 0.00355287 0.00130272 0.000358247 7.88144e-5
Calcoliamo le probabilità di vittoria, pareggio e sconfitta:
p_home = 0.0
p_draw = 0.0
p_away = 0.0
for i in 0:max_goals
for j in 0:max_goals
p = prob_matrix[i+1, j+1]
if i > j
p_home += p
elseif i == j
p_draw += p
else
p_away += p
end
end
end
println("Probabilità vittoria casa: ", p_home)
println("Probabilità pareggio: ", p_draw)
println("Probabilità vittoria trasferta: ", p_away)
Probabilità vittoria casa: 0.48353796379688296 Probabilità pareggio: 0.24890698520246393 Probabilità vittoria trasferta: 0.260552747649295
Quindi è più probabile la vittoria in casa col 48,35% .
Supponiamo le seguenti quote bookmaker e calcoliamo il valore atteso:
- Casa = 2.30
- Pareggio = 3.40
- Trasferta = 3.10
odds_home = 2.30
odds_draw = 3.40
odds_away = 3.10
ev_home = p_home * odds_home - 1
ev_draw = p_draw * odds_draw - 1
ev_away = p_away * odds_away - 1
println("EV casa: ", ev_home)
println("EV pareggio: ", ev_draw)
println("EV trasferta: ", ev_away)
EV casa: 0.11213731673283078 EV pareggio: -0.15371625031162262 EV trasferta: -0.1922864822871856
Allora:
- casa → favorevole
- pareggio → sfavorevole
- trasferta → sfavorevole
Possiamo trovare anche il risultato esatto più probabile:
max_prob = 0
score = (0,0)
for i in 0:max_goals
for j in 0:max_goals
p = prob_matrix[i+1,j+1]
if p > max_prob
max_prob = p
score = (i,j)
end
end
end
println("Risultato più probabile: ", score)
println("Probabilità: ", max_prob)
Risultato più probabile: (1, 1) Probabilità: 0.1182817024219596
Stima delle lambda λ (medie gol) con dati della serie A
[modifica | modifica sorgente]Scarichiamo i dati con queste colonne:
| Date | HomeTeam | AwayTeam | FTHG | FTAG | | ---------- | -------- | -------- | ---- | ---- | | 2023-08-19 | Empoli | Verona | 0 | 1 |
- FTHG = Full Time Home Goals
- FTAG = Full Time Away Goals
da: https://www.football-data.co.uk/mmz4281/2324/I1.csv e creiamo il relativo DataFrame:
df = CSV.read("I1.csv", DataFrame)
df = select(df, :HomeTeam, :AwayTeam, :FTHG, :FTAG)
rename!(df,
:FTHG => :HomeGoals,
:FTAG => :AwayGoals
)
dropmissing!(df)
teams = unique(vcat(df.HomeTeam, df.AwayTeam))
# Costruiamo un dataset con **gol fatti e subiti**:
stats = DataFrame(
team = teams,
goals_scored = zeros(length(teams)),
goals_conceded = zeros(length(teams)),
matches = zeros(Int,length(teams))
)
# Calcolo delle medie:
for row in eachrow(df)
home = row.HomeTeam
away = row.AwayTeam
hg = row.HomeGoals
ag = row.AwayGoals
stats[stats.team .== home, :goals_scored] .+= hg
stats[stats.team .== home, :goals_conceded] .+= ag
stats[stats.team .== home, :matches] .+= 1
stats[stats.team .== away, :goals_scored] .+= ag
stats[stats.team .== away, :goals_conceded] .+= hg
stats[stats.team .== away, :matches] .+= 1
end
# Media gol (λ):
stats.lambda_attack = stats.goals_scored ./ stats.matches
stats.lambda_defense = stats.goals_conceded ./ stats.matches
Esempio risultato: | team | attack λ | defense λ | | ----- | -------- | --------- | | Inter | 1.9 | 0.8 | | Milan | 1.6 | 1.0 | | Roma | 1.4 | 1.2 |
Ora si possono stimare ad esempio le due λ Inter vs Milan:
team_home = "Inter"
team_away = "Milan"
lambda_home = stats[stats.team .== team_home, :lambda_attack][1]
lambda_away = stats[stats.team .== team_away, :lambda_attack][1]
println("lambda_home =",lambda_home)
println("lambda_away =",lambda_away)
lambda_home =2.3421052631578947 lambda_away =2.0
Ottenute le 2 lambda si può procedere calcolando la matrice delle probabilità dei risultati come prima...
Conclusioni
[modifica | modifica sorgente]Questo modello base non tiene conto del fatto, che c'è in realtà una dipendenza tra i gol delle 2 squadre, perché se una squadra segna può chiudersi in difesa, se una squadra perde attacca di più, le espulsioni cambiano la dinamica, la strategia cambia dopo un gol, per cui il modello Dixon-Coles introduce una correzione per risultati come: 0-0, 1-0, 1-1 che la Poisson indipendente tende a stimare male. Inoltre questo nuovo modello stima diversamente lambda e introduce 2 nuovi parametri mu e ro (parametro Dixon-Coles) con :
λ = attack_home x defense_away x home_advantage μ = attack_away x defense_home
Questo perché suppone che la squadra in casa abbia maggiore forza in attacco , la squadra ospite maggiore forza in difesa e poi vi sia un vantaggio in casa.
Comunque il modello con due Poisson indipendenti è molto usato perché è semplice, è facile da stimare, funziona sorprendentemente bene e richiede pochi parametri, per cui è il modello base nella letteratura sulle scommesse sportive.