Vai al contenuto

Calcoli scientifici con Julia/Modello base delle scommesse sportive

Wikibooks, manuali e libri di testo liberi.
Indice del libro

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)

Distribuzione di Poisson - L'asse orizzontale è l'indice k o numero di occorrenze. λ è il tasso di occorrenze previsto. L'asse verticale è la probabilità di eventi k dato λ. La funzione è definita solo per valori interi di k

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...

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.