Дисклеймер
По приведенным ниже прогнозам не стоит делать ставки, т.к. они не учитывают букмекерскую маржу, форму команд, а также много других факторов. В целом ставки на спорт весьма специфичное занятие, рассчитанное во многом на психологию человека, скрытые слабости и т. д., поэтому в целом не стоит ставить на основании любых прогнозов в интернете.
Для проведения исследования использовались: датасет: https://www.kaggle.com/datasets/martj42/international-football-results-from-1872-to-2017?resource=download , язык программирования R, чат GPT 4.0.
Цели исследования: 1) проверить точность прогноза в результате машинного обучения на основании базы данных за 20 лет 2) узнать размер выигрыша/проигрыша в букмекерской конторе при использовании приведенного подхода.
Проблематика исследования: здесь не учтены важные факторы такие как уровень и стоимость игроков, текущая форма команд, фактор домашнего турнира для сборной Германии и многое другое.
Это все учтено самими букмекерами при выставлении коэффициентов на матчи, минус 10-15 процентов их маржи, поэтому просто выбирая фаворитов выиграть невозможно.
Лично для меня больший интерес представляет ответ на вопрос — удастся ли машине обнаружить неочевидные закономерности и обыграть букмекера, а не определение фаворита.
Методология
В первую очередь был обработан датасет, так как он включает результаты более 47 000 матчей за 152 года, в том числе — различных африканских квалификаций, которые нам не интересны и замедлили бы обработку данных, датасет был сокращен до результатов евро, квалификации к нему и лиге наций.
*квалификацию к миру брать не стал, т.к. хотя команды те же, это другой турнир и формат немного отличается*
За точку отсчёта взят евро 1996 года и как следствие квалификация к нему, начиная с 1994 года. Такое решение связано с изменением формата турнира, а также с развалом стран социалистического блока (увеличилось количество стран участниц).
Таким образом мы получаем примерно один состав участников и результаты за последние 20 лет. Финальный датасет составил 2 758 матчей.
Далее с помощью чата GPT я перебрал несколько вариантов машинного обучения в Python (использовал: pandas, numpy, train_test_split, GridSearchCV, RandomForestClassifier, accuracy_score).
Лучшим результатом стала точность прогноза — 53.51%.
Точность прогноза получилось улучшить, используя язык R.
Лучшим результатом на R стала точность прогноза — 57.65%
Весьма неплохой процент, учитывая, что игра идет на 3 результата. Так как процент точности на R у нас выше, будем использовать его для прогнозирования.
> library(randomForest) > library(dplyr) > > # Загрузка данных > data <- read.csv("filtered_results.csv") > > # Преобразование столбца date в формат даты > data$date <- as.Date(data$date, format="%Y-%m-%d") > > # Создание целевой переменной > data$result <- ifelse(data$home_score > data$away_score, 1, + ifelse(data$home_score < data$away_score, -1, 0)) > > # Преобразование данных в единый формат > home_games <- data %>% + select(team = home_team, opponent = away_team, score = home_score, opponent_score = away_score, result) > > away_games <- data %>% + select(team = away_team, opponent = home_team, score = away_score, opponent_score = home_score, result) %>% + mutate(result = ifelse(result == 1, -1, ifelse(result == -1, 1, 0))) > > all_games <- bind_rows(home_games, away_games) > > # Создание новых признаков > team_stats <- all_games %>% + group_by(team) %>% + summarise(total_games = n(), + total_win_rate = mean(result == 1), + total_avg_score = mean(score)) > > # Подготовка данных для модели > data <- data %>% + left_join(team_stats, by = c("home_team" = "team")) %>% + rename(home_team_total_games = total_games, + home_team_total_win_rate = total_win_rate, + home_team_total_avg_score = total_avg_score) %>% + left_join(team_stats, by = c("away_team" = "team")) %>% + rename(away_team_total_games = total_games, + away_team_total_win_rate = total_win_rate, + away_team_total_avg_score = total_avg_score) > > # Проверка и замена NA значений > data[is.na(data)] <- 0 > > # Подготовка данных для модели > features <- c("home_team_total_win_rate", "away_team_total_win_rate", + "home_team_total_games", "away_team_total_games", + "home_team_total_avg_score", "away_team_total_avg_score") > X <- data[features] > y <- factor(data$result) > > # Разделение данных на обучающую и тестовую выборки > set.seed(42) > train_indices <- sample(seq_len(nrow(data)), size = 0.8 * nrow(data)) > X_train <- X[train_indices, ] > y_train <- y[train_indices] > X_test <- X[-train_indices, ] > y_test <- y[-train_indices] > > # Обучение модели Random Forest > rf_model <- randomForest(X_train, y_train, ntree=200, mtry=3, importance=TRUE) > > # Предсказание на тестовой выборке > y_pred <- predict(rf_model, X_test) > accuracy <- sum(y_pred == y_test) / length(y_test) > print(paste("Accuracy:", accuracy)) [1] "Accuracy: 0.576576576576577" > > # Пример новых матчей > new_matches <- data.frame( + home_team = c("Germany", "Hungary", "Spain", "Italy", "Poland", "Slovenia", "Serbia", "Romania", "Belgium", "Austria", + "Turkey", "Portugal", "Croatia", "Germany", "Scotland", "Slovenia", "Denmark", "Spain", "Slovakia", + "Poland", "Netherlands", "Georgia", "Turkey", "Belgium", "Switzerland", "Scotland", "Albania", "Croatia", + "Netherlands", "France", "England", "Denmark", "Slovakia", "Ukraine", "Georgia", "Czech Republic"), + away_team = c("Scotland", "Switzerland", "Croatia", "Albania", "Netherlands", "Denmark", "England", "Ukraine", "Slovakia", + "France", "Georgia", "Czech Republic", "Albania", "Hungary", "Switzerland", "Serbia", "England", "Italy", + "Ukraine", "Austria", "France", "Czech Republic", "Portugal", "Romania", "Germany", "Hungary", "Spain", + "Italy", "Austria", "Poland", "Slovenia", "Serbia", "Romania", "Belgium", "Portugal", "Turkey") + ) > > # Расчет признаков для новых матчей > new_matches <- new_matches %>% + left_join(team_stats, by = c("home_team" = "team")) %>% + rename(home_team_total_win_rate = total_win_rate, + home_team_total_games = total_games, + home_team_total_avg_score = total_avg_score) %>% + left_join(team_stats, by = c("away_team" = "team")) %>% + rename(away_team_total_win_rate = total_win_rate, + away_team_total_games = total_games, + away_team_total_avg_score = total_avg_score) > > # Проверка и замена NA значений > new_matches[is.na(new_matches)] <- 0 > > # Предсказание результатов новых матчей > predictions <- predict(rf_model, new_matches[features]) > results <- ifelse(predictions == 1, "Home Win", ifelse(predictions == 0, "Draw", "Away Win")) > > # Вывод результатов > for (i in 1:nrow(new_matches)) { + print(paste(new_matches$home_team[i], "vs", new_matches$away_team[i], "-> Prediction:", results[i]))
Результаты группового этапа:
1. Germany vs Scotland -> Prediction: Home Win
2. Hungary vs Switzerland -> Prediction: Home Win
3. Spain vs Croatia -> Prediction: Home Win
4. Italy vs Albania -> Prediction: Home Win
5. Poland vs Netherlands -> Prediction: Away Win
6. Slovenia vs Denmark -> Prediction: Draw
7. Serbia vs England -> Prediction: Draw
8. Romania vs Ukraine -> Prediction: Home Win
9. Belgium vs Slovakia -> Prediction: Home Win
10. Austria vs France -> Prediction: Away Win
11. Turkey vs Georgia -> Prediction: Home Win
12. Portugal vs Czech Republic -> Prediction: Home Win
13. Croatia vs Albania -> Prediction: Home Win
14. Germany vs Hungary -> Prediction: Home Win
15. Scotland vs Switzerland -> Prediction: Home Win
16. Slovenia vs Serbia -> Prediction: Home Win
17. Denmark vs England -> Prediction: Draw
18. Spain vs Italy -> Prediction: Home Win
19. Slovakia vs Ukraine -> Prediction: Home Win
20. Poland vs Austria -> Prediction: Home Win
21. Netherlands vs France -> Prediction: Away Win
22. Georgia vs Czech Republic -> Prediction: Away Win
23. Turkey vs Portugal -> Prediction: Away Win
24. Belgium vs Romania -> Prediction: Draw
25. Switzerland vs Germany -> Prediction: Away Win
26. Scotland vs Hungary -> Prediction: Home Win
27. Albania vs Spain -> Prediction: Away Win
28. Croatia vs Italy -> Prediction: Draw
29. Netherlands vs Austria -> Prediction: Home Win
30. France vs Poland -> Prediction: Home Win
31. England vs Slovenia -> Prediction: Home Win
32. Denmark vs Serbia -> Prediction: Home Win
33. Slovakia vs Romania -> Prediction: Away Win
34. Ukraine vs Belgium -> Prediction: Home Win
35. Georgia vs Portugal -> Prediction: Away Win
36. Czech Republic vs Turkey -> Prediction: Home Win
Посмотрим, каким образом сформировалась сетка 1/8 плей-офф с учетом полученных результатов матчей.
library(randomForest) > library(dplyr) > > # Загрузка данных > data <- read.csv("filtered_results.csv") > > # Преобразование столбца date в формат даты > data$date <- as.Date(data$date, format="%Y-%m-%d") > > # Создание целевой переменной > data$result <- ifelse(data$home_score > data$away_score, 1, + ifelse(data$home_score < data$away_score, -1, 0)) > > # Преобразование данных в единый формат > home_games <- data %>% + select(team = home_team, opponent = away_team, score = home_score, opponent_score = away_score, result) > > away_games <- data %>% + select(team = away_team, opponent = home_team, score = away_score, opponent_score = home_score, result) %>% + mutate(result = ifelse(result == 1, -1, ifelse(result == -1, 1, 0))) > > all_games <- bind_rows(home_games, away_games) > > # Создание новых признаков > team_stats <- all_games %>% + group_by(team) %>% + summarise(total_games = n(), + total_win_rate = mean(result == 1), + total_avg_score = mean(score)) > > # Подготовка данных для модели > data <- data %>% + left_join(team_stats, by = c("home_team" = "team")) %>% + rename(home_team_total_games = total_games, + home_team_total_win_rate = total_win_rate, + home_team_total_avg_score = total_avg_score) %>% + left_join(team_stats, by = c("away_team" = "team")) %>% + rename(away_team_total_games = total_games, + away_team_total_win_rate = total_win_rate, + away_team_total_avg_score = total_avg_score) > > # Проверка и замена NA значений > data[is.na(data)] <- 0 > > # Подготовка данных для модели > features <- c("home_team_total_win_rate", "away_team_total_win_rate", + "home_team_total_games", "away_team_total_games", + "home_team_total_avg_score", "away_team_total_avg_score") > X <- data[features] > y <- factor(data$result) > > # Разделение данных на обучающую и тестовую выборки > set.seed(42) > train_indices <- sample(seq_len(nrow(data)), size = 0.8 * nrow(data)) > X_train <- X[train_indices, ] > y_train <- y[train_indices] > X_test <- X[-train_indices, ] > y_test <- y[-train_indices] > > # Обучение модели Random Forest > rf_model <- randomForest(X_train, y_train, ntree=200, mtry=3, importance=TRUE) > > # Предсказание на тестовой выборке > y_pred <- predict(rf_model, X_test) > accuracy <- sum(y_pred == y_test) / length(y_test) > print(paste("Accuracy:", accuracy)) [1] "Accuracy: 0.576576576576577" > > # Групповой этап > group_stage_matches <- data.frame( + home_team = c("Germany", "Hungary", "Spain", "Italy", "Poland", "Slovenia", "Serbia", "Romania", "Belgium", "Austria", + "Turkey", "Portugal", "Croatia", "Germany", "Scotland", "Slovenia", "Denmark", "Spain", "Slovakia", + "Poland", "Netherlands", "Georgia", "Turkey", "Belgium", "Switzerland", "Scotland", "Albania", "Croatia", + "Netherlands", "France", "England", "Denmark", "Slovakia", "Ukraine", "Georgia", "Czech Republic"), + away_team = c("Scotland", "Switzerland", "Croatia", "Albania", "Netherlands", "Denmark", "England", "Ukraine", "Slovakia", + "France", "Georgia", "Czech Republic", "Albania", "Hungary", "Switzerland", "Serbia", "England", "Italy", + "Ukraine", "Austria", "France", "Czech Republic", "Portugal", "Romania", "Germany", "Hungary", "Spain", + "Italy", "Austria", "Poland", "Slovenia", "Serbia", "Romania", "Belgium", "Portugal", "Turkey") + ) > > # Расчет признаков для группового этапа > group_stage_matches <- group_stage_matches %>% + left_join(team_stats, by = c("home_team" = "team")) %>% + rename(home_team_total_win_rate = total_win_rate, + home_team_total_games = total_games, + home_team_total_avg_score = total_avg_score) %>% + left_join(team_stats, by = c("away_team" = "team")) %>% + rename(away_team_total_win_rate = total_win_rate, + away_team_total_games = total_games, + away_team_total_avg_score = total_avg_score) > > # Проверка и замена NA значений > group_stage_matches[is.na(group_stage_matches)] <- 0 > > # Предсказание результатов группового этапа > predictions <- predict(rf_model, group_stage_matches[features]) > results <- ifelse(predictions == 1, "Home Win", ifelse(predictions == 0, "Draw", "Away Win")) > > # Вывод результатов и подсчет очков > group_stage_matches <- group_stage_matches %>% + mutate(result = results, + home_points = ifelse(result == "Home Win", 3, ifelse(result == "Draw", 1, 0)), + away_points = ifelse(result == "Away Win", 3, ifelse(result == "Draw", 1, 0))) > > # Создание таблицы очков > group_points <- group_stage_matches %>% + select(home_team, home_points) %>% + rename(team = home_team, points = home_points) %>% + bind_rows(group_stage_matches %>% + select(away_team, away_points) %>% + rename(team = away_team, points = away_points)) %>% + group_by(team) %>% + summarise(total_points = sum(points)) %>% + arrange(desc(total_points)) > > # Вывод очков команд > print(group_points) # A tibble: 24 × 2 team total_points <chr> <dbl> 1 France 9 2 Germany 9 3 Portugal 9 4 Spain 9 5 Romania 7 6 Czech Republic 6 7 Netherlands 6 8 Scotland 6 9 Denmark 5 10 England 5 # ℹ 14 more rows # ℹ Use `print(n = ...)` to see more rows > > # Определение команд, вышедших в плей-офф > groups <- list( + A = c("Germany", "Scotland", "Hungary", "Switzerland"), + B = c("Spain", "Croatia", "Italy", "Albania"), + C = c("Slovenia", "Denmark", "Serbia", "England"), + D = c("Poland", "Netherlands", "Austria", "France"), + E = c("Belgium", "Slovakia", "Romania", "Ukraine"), + F = c("Turkey", "Georgia", "Portugal", "Czech Republic") + ) > > playoff_teams <- list() > third_place_teams <- list() > > for (group in names(groups)) { + group_teams <- groups[[group]] + group_points_filtered <- group_points %>% filter(team %in% group_teams) + playoff_teams[[group]] <- group_points_filtered$team[1:2] + third_place_teams[[group]] <- group_points_filtered$team[3] + } > > # Определение лучших третьих мест > third_place_teams_points <- group_points %>% filter(team %in% unlist(third_place_teams)) > best_third_place_teams <- third_place_teams_points %>% arrange(desc(total_points)) %>% head(4) %>% pull(team) > > # Заполнение расписания матчей плей-офф > playoff_schedule <- data.frame( + match = c("Match № 38", "Match № 37", "Match № 40", "Match № 39", "Match № 42", "Match № 41", "Match № 43", "Match № 44"), + home_team = c(playoff_teams$A[2], playoff_teams$A[1], playoff_teams$C[1], playoff_teams$B[1], playoff_teams$D[2], playoff_teams$F[1], playoff_teams$E[1], playoff_teams$D[1]), + away_team = c(playoff_teams$B[2], playoff_teams$C[2], best_third_place_teams[1], best_third_place_teams[2], playoff_teams$E[2], best_third_place_teams[3], best_third_place_teams[4], playoff_teams$F[2]) + ) > > print(playoff_schedule)
1/8 плей-офф:
Match № 38 |
Scotland |
Croatia |
Match № 37 |
Germany |
England |
Match № 40 |
Denmark |
Italy |
Match № 39 |
Spain |
Slovenia |
Match № 42 |
Netherlands |
Belgium |
Match № 41 |
Portugal |
Hungary |
Match № 43 |
Romania |
Poland |
Match № 44 |
France |
Czech Republic |
На этом завершается первый этап исследования.
На втором этапе я подведу промежуточные итоги и дам прогноз на плей-офф с учетом реально образовавшихся пар в 1/8.
На третьем этапе подведу общие итоги.
Оценка результатов исследования:
1) Посмотрим, сколько результатов было предсказано верно и сравним процент с 57.65. Так проверим, насколько верно компьютер оценил точность своего прогноза.
2) Посмотрим виртуальный банк после турнира и проверим, удалось ли машине обыграть букмекера.
Виртуальный банк
Для того чтобы узнать, принесет нам прибыль или убыток в букмекерской конторе такая стратегия, мы создадим виртуальный банк в размере 5 300 долларов. 51 матч будет сыгран на этом турнире, на каждый будет совершена условная ставка в размере 100 долларов на основании прогноза машины + 2 раза по 100 долларов мы поставим на чемпиона — до начала турнира и после окончания групповой стадии.
Я буду брать средний коэффициент на сайте https://www.flashscore.com.ua/, чтобы не рекламировать какого-то конкретного букмекера.
А чемпионом Евро 2024 по версии машины будет Испания.
ссылка на оригинал статьи https://habr.com/ru/articles/821133/
Добавить комментарий