This analysis creates a comprehensive table of real NFL games with live moneyline odds from The Odds API, then performs detailed sportsbook comparison analysis.
Objective: Compare how closely DraftKings, FanDuel, and BetMGM price the same games using actual current market data.
# ============================================================================
# THE ODDS API CONFIGURATION
# ============================================================================
# Replace "YOUR_API_KEY_HERE" with your actual API key from the-odds-api.com
API_KEY <- "de0f8fff21b8dad443046bfffc2946c7" # <-- REPLACE THIS WITH YOUR REAL API KEY
SPORT <- "americanfootball_nfl"
REGIONS <- "us"
MARKETS <- "h2h" # head-to-head (moneyline)
ODDS_FORMAT <- "american"
DATE_FORMAT <- "iso"
# ============================================================================
# FUNCTION TO FETCH AND PROCESS REAL NFL DATA
# ============================================================================
fetch_real_nfl_data <- function(api_key) {
# Construct API URL
api_url <- paste0(
"https://api.the-odds-api.com/v4/sports/", SPORT, "/odds/",
"?apiKey=", api_key,
"®ions=", REGIONS,
"&markets=", MARKETS,
"&oddsFormat=", ODDS_FORMAT,
"&dateFormat=", DATE_FORMAT
)
cat("📡 Making API request to The Odds API...\n")
# Make API request
response <- httr::GET(api_url)
cat("📊 API Response Status:", httr::status_code(response), "\n")
if (httr::status_code(response) != 200) {
stop(paste("API request failed with status:", httr::status_code(response)))
}
# Parse JSON response
api_data <- jsonlite::fromJSON(httr::content(response, "text"), flatten = TRUE)
if (is.null(api_data) || length(api_data) == 0) {
stop("No data returned from API")
}
cat("✅ Found", nrow(api_data), "games from The Odds API\n")
# Check remaining API calls
tryCatch({
remaining_calls <- httr::headers(response)$`x-requests-remaining`
if (!is.null(remaining_calls)) {
cat("📈 Remaining API calls today:", remaining_calls, "\n")
}
}, error = function(e) {
cat("📈 Could not retrieve API calls info\n")
})
# Transform API data
return(transform_api_data(api_data))
}
# ============================================================================
# FUNCTION TO TRANSFORM API DATA
# ============================================================================
transform_api_data <- function(api_data) {
cat("🔄 Processing", nrow(api_data), "games...\n")
all_games <- tibble()
for (i in 1:nrow(api_data)) {
game <- api_data[i, ]
# Extract game info
game_date <- as.Date(game$commence_time)
kickoff_time <- format(as.POSIXct(game$commence_time), "%H:%M:%S")
home_team <- game$home_team
away_team <- game$away_team
if (is.na(game_date) || is.null(home_team) || is.null(away_team)) {
next
}
# Calculate week number
week_number <- case_when(
game_date >= as.Date("2024-09-05") & game_date <= as.Date("2024-09-09") ~ 1,
game_date >= as.Date("2024-09-12") & game_date <= as.Date("2024-09-16") ~ 2,
game_date >= as.Date("2024-09-19") & game_date <= as.Date("2024-09-23") ~ 3,
game_date >= as.Date("2024-09-26") & game_date <= as.Date("2024-09-30") ~ 4,
game_date >= as.Date("2024-10-03") & game_date <= as.Date("2024-10-07") ~ 5,
game_date >= as.Date("2024-10-10") & game_date <= as.Date("2024-10-14") ~ 6,
game_date >= as.Date("2024-10-17") & game_date <= as.Date("2024-10-21") ~ 7,
game_date >= as.Date("2024-10-24") & game_date <= as.Date("2024-10-28") ~ 8,
game_date >= as.Date("2024-10-31") & game_date <= as.Date("2024-11-04") ~ 9,
game_date >= as.Date("2024-11-07") & game_date <= as.Date("2024-11-11") ~ 10,
game_date >= as.Date("2024-11-14") & game_date <= as.Date("2024-11-18") ~ 11,
game_date >= as.Date("2024-11-21") & game_date <= as.Date("2024-11-25") ~ 12,
game_date >= as.Date("2024-11-28") & game_date <= as.Date("2024-12-02") ~ 13,
game_date >= as.Date("2024-12-05") & game_date <= as.Date("2024-12-09") ~ 14,
game_date >= as.Date("2024-12-12") & game_date <= as.Date("2024-12-16") ~ 15,
game_date >= as.Date("2024-12-19") & game_date <= as.Date("2024-12-23") ~ 16,
game_date >= as.Date("2024-12-26") & game_date <= as.Date("2024-12-30") ~ 17,
game_date >= as.Date("2025-01-02") & game_date <= as.Date("2025-01-06") ~ 18,
TRUE ~ NA_real_
)
# Initialize game row
game_row <- tibble(
game_id = i,
week = week_number,
game_date = game_date,
kickoff_time = kickoff_time,
home_team = home_team,
away_team = away_team,
dk_home = NA_real_, dk_away = NA_real_,
fd_home = NA_real_, fd_away = NA_real_,
mgm_home = NA_real_, mgm_away = NA_real_,
data_source = "The Odds API"
)
# Extract bookmaker odds (excluding Caesars)
bookmakers <- game$bookmakers
if (!is.null(bookmakers) && length(bookmakers) > 0) {
bookmakers_df <- bookmakers[[1]]
for (j in 1:nrow(bookmakers_df)) {
bookmaker <- bookmakers_df[j, ]
bookie_title <- tolower(bookmaker$title)
# Map bookmaker names (removed Caesars)
bookie_key <- case_when(
str_detect(bookie_title, "draftkings") ~ "dk",
str_detect(bookie_title, "fanduel") ~ "fd",
str_detect(bookie_title, "betmgm") | str_detect(bookie_title, "mgm") ~ "mgm",
TRUE ~ NA_character_ # Skip Caesars and others
)
if (is.na(bookie_key)) next
# Extract markets
markets <- bookmaker$markets
if (!is.null(markets) && length(markets) > 0) {
markets_df <- markets[[1]]
h2h_market <- markets_df[markets_df$key == "h2h", ]
if (nrow(h2h_market) > 0) {
outcomes <- h2h_market$outcomes[[1]]
if (!is.null(outcomes) && nrow(outcomes) > 0) {
# Find home and away odds
home_odds <- outcomes$price[outcomes$name == home_team]
away_odds <- outcomes$price[outcomes$name == away_team]
if (length(home_odds) > 0) {
game_row[[paste0(bookie_key, "_home")]] <- as.numeric(home_odds[1])
}
if (length(away_odds) > 0) {
game_row[[paste0(bookie_key, "_away")]] <- as.numeric(away_odds[1])
}
}
}
}
}
}
all_games <- bind_rows(all_games, game_row)
}
cat("✅ Successfully processed", nrow(all_games), "games\n")
return(all_games)
}
# ============================================================================
# LOAD REAL NFL DATA
# ============================================================================
cat("=== LOADING REAL NFL GAMES ===\n")
## === LOADING REAL NFL GAMES ===
if (API_KEY == "YOUR_API_KEY_HERE") {
cat("❌ No API key provided\n")
cat("🔗 Get your free API key at: https://the-odds-api.com\n")
# Create placeholder for demo (removed Caesars)
nfl_prematch_table <- tibble(
game_id = 1,
week = 1,
game_date = as.Date("2024-09-08"),
kickoff_time = "13:00:00",
home_team = "Get API Key",
away_team = "the-odds-api.com",
dk_home = NA_real_, dk_away = NA_real_,
fd_home = NA_real_, fd_away = NA_real_,
mgm_home = NA_real_, mgm_away = NA_real_,
data_source = "No API Key"
)
} else {
cat("✅ API key detected!\n")
tryCatch({
nfl_prematch_table <- fetch_real_nfl_data(API_KEY)
if (is.null(nfl_prematch_table) || nrow(nfl_prematch_table) == 0) {
stop("API returned no games")
}
cat("🎯 SUCCESS: Loaded", nrow(nfl_prematch_table), "real NFL games!\n")
}, error = function(e) {
cat("❌ Error:", as.character(e), "\n")
stop("Cannot proceed without real data")
})
}
## ✅ API key detected!
## 📡 Making API request to The Odds API...
## 📊 API Response Status: 200
## ✅ Found 17 games from The Odds API
## 📈 Remaining API calls today: 486
## 🔄 Processing 17 games...
## ✅ Successfully processed 17 games
## 🎯 SUCCESS: Loaded 17 real NFL games!
# Verify data
cat("📊 Dataset ready:", nrow(nfl_prematch_table), "games\n")
## 📊 Dataset ready: 17 games
cat("📅 Data source:", unique(nfl_prematch_table$data_source)[1], "\n")
## 📅 Data source: The Odds API
# Create table for real NFL games only
real_games_table <- nfl_prematch_table %>%
arrange(week, game_date, kickoff_time) %>%
mutate(
# Format odds with +/- signs
dk_home_display = case_when(
is.na(dk_home) ~ "N/A",
dk_home > 0 ~ paste0("+", dk_home),
TRUE ~ as.character(dk_home)
),
dk_away_display = case_when(
is.na(dk_away) ~ "N/A",
dk_away > 0 ~ paste0("+", dk_away),
TRUE ~ as.character(dk_away)
),
fd_home_display = case_when(
is.na(fd_home) ~ "N/A",
fd_home > 0 ~ paste0("+", fd_home),
TRUE ~ as.character(fd_home)
),
fd_away_display = case_when(
is.na(fd_away) ~ "N/A",
fd_away > 0 ~ paste0("+", fd_away),
TRUE ~ as.character(fd_away)
),
mgm_home_display = case_when(
is.na(mgm_home) ~ "N/A",
mgm_home > 0 ~ paste0("+", mgm_home),
TRUE ~ as.character(mgm_home)
),
mgm_away_display = case_when(
is.na(mgm_away) ~ "N/A",
mgm_away > 0 ~ paste0("+", mgm_away),
TRUE ~ as.character(mgm_away)
)
)
# Display the table
real_games_table %>%
select(
Week = week,
Date = game_date,
Kickoff = kickoff_time,
`Home Team` = home_team,
`Away Team` = away_team,
`DK Home` = dk_home_display,
`DK Away` = dk_away_display,
`FD Home` = fd_home_display,
`FD Away` = fd_away_display,
`MGM Home` = mgm_home_display,
`MGM Away` = mgm_away_display
) %>%
DT::datatable(
caption = paste0("REAL NFL GAMES WITH LIVE ODDS | Total Games: ", nrow(real_games_table)),
options = list(
pageLength = -1, # Show all games
scrollX = TRUE,
scrollY = "600px",
fixedHeader = TRUE,
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel'),
columnDefs = list(
list(className = "dt-center", targets = "_all")
)
),
extensions = 'Buttons',
class = 'cell-border stripe hover'
) %>%
DT::formatStyle(
columns = c("DK Home", "FD Home", "MGM Home"),
backgroundColor = "#dbeafe",
border = "1px solid #3b82f6"
) %>%
DT::formatStyle(
columns = c("DK Away", "FD Away", "MGM Away"),
backgroundColor = "#fef3c7",
border = "1px solid #f59e0b"
) %>%
DT::formatStyle(
columns = c("Home Team", "Away Team"),
fontWeight = "bold"
)
# Summary statistics
cat("\n=== REAL NFL GAMES SUMMARY ===\n")
##
## === REAL NFL GAMES SUMMARY ===
total_games <- nrow(real_games_table)
data_source <- unique(nfl_prematch_table$data_source)[1]
cat("📊 Data Source:", data_source, "\n")
## 📊 Data Source: The Odds API
cat("🏈 Total Games:", total_games, "\n")
## 🏈 Total Games: 17
if (total_games > 1 && data_source == "The Odds API") {
cat("📅 Date Range:", min(real_games_table$game_date), "to", max(real_games_table$game_date), "\n")
cat("📋 Weeks:", paste(sort(unique(real_games_table$week)), collapse = ", "), "\n")
# Sportsbook coverage (removed Caesars)
coverage <- nfl_prematch_table %>%
summarise(
DraftKings = sum(!is.na(dk_home)),
FanDuel = sum(!is.na(fd_home)),
BetMGM = sum(!is.na(mgm_home))
)
cat("\n📈 Sportsbook Coverage:\n")
cat("• DraftKings:", coverage$DraftKings, "games\n")
cat("• FanDuel:", coverage$FanDuel, "games\n")
cat("• BetMGM:", coverage$BetMGM, "games\n")
cat("\n✅ All data represents REAL NFL games with LIVE odds\n")
} else if (data_source != "The Odds API") {
cat("\n⚠️ No real data - API key required\n")
cat("🔧 Get your free API key at https://the-odds-api.com\n")
}
## 📅 Date Range: 20346 to 20354
## 📋 Weeks:
##
## 📈 Sportsbook Coverage:
## • DraftKings: 17 games
## • FanDuel: 17 games
## • BetMGM: 17 games
##
## ✅ All data represents REAL NFL games with LIVE odds
# Only analyze if we have real data
if (unique(nfl_prematch_table$data_source)[1] == "The Odds API" && nrow(nfl_prematch_table) > 1) {
cat("=== COMPREHENSIVE SPORTSBOOK ANALYSIS ===\n\n")
# Prepare data for analysis (3 sportsbooks: DK, FD, MGM)
analysis_data <- nfl_prematch_table %>%
filter(!is.na(dk_home), !is.na(dk_away), !is.na(fd_home), !is.na(fd_away))
if (nrow(analysis_data) > 5) {
# ========================================================================
# 1. CORRELATION ANALYSIS
# ========================================================================
cat("1. MARKET CORRELATION ANALYSIS\n")
cat(rep("-", 40), "\n")
# Home odds correlations
home_odds <- analysis_data %>%
select(DraftKings = dk_home, FanDuel = fd_home, BetMGM = mgm_home) %>%
select_if(~!all(is.na(.)))
# Away odds correlations
away_odds <- analysis_data %>%
select(DraftKings = dk_away, FanDuel = fd_away, BetMGM = mgm_away) %>%
select_if(~!all(is.na(.)))
if (ncol(home_odds) > 1) {
home_corr <- cor(home_odds, use = "complete.obs")
cat("Home Team Odds Correlations:\n")
print(round(home_corr, 4))
# Visualize correlation
corrplot(home_corr, method = "color", type = "upper",
title = "Home Odds Correlations", mar = c(0,0,3,0),
addCoef.col = "black", number.cex = 1.2)
}
if (ncol(away_odds) > 1) {
away_corr <- cor(away_odds, use = "complete.obs")
cat("\nAway Team Odds Correlations:\n")
print(round(away_corr, 4))
}
# Calculate market efficiency metrics
if (exists("home_corr") && exists("away_corr")) {
avg_corr <- mean(c(home_corr[upper.tri(home_corr)], away_corr[upper.tri(away_corr)]))
cat("\n📊 MARKET EFFICIENCY SCORE:", round(avg_corr, 4), "\n")
if (avg_corr > 0.95) {
cat("✅ HIGHLY EFFICIENT MARKET - Books price very similarly\n")
} else if (avg_corr > 0.90) {
cat("📈 EFFICIENT MARKET - Good agreement between books\n")
} else if (avg_corr > 0.85) {
cat("📊 MODERATELY EFFICIENT - Some pricing differences\n")
} else {
cat("⚠️ LESS EFFICIENT - Significant pricing differences\n")
}
}
# ========================================================================
# 2. ODDS SPREAD ANALYSIS
# ========================================================================
cat("\n\n2. ODDS SPREAD ANALYSIS\n")
cat(rep("-", 40), "\n")
spread_analysis <- analysis_data %>%
rowwise() %>%
mutate(
# Calculate spread between highest and lowest odds
home_max = max(c(dk_home, fd_home, mgm_home), na.rm = TRUE),
home_min = min(c(dk_home, fd_home, mgm_home), na.rm = TRUE),
away_max = max(c(dk_away, fd_away, mgm_away), na.rm = TRUE),
away_min = min(c(dk_away, fd_away, mgm_away), na.rm = TRUE),
home_spread = home_max - home_min,
away_spread = away_max - away_min,
max_spread = max(home_spread, away_spread),
# Find best odds provider
best_home_book = case_when(
dk_home == home_max ~ "DraftKings",
fd_home == home_max ~ "FanDuel",
mgm_home == home_max ~ "BetMGM",
TRUE ~ "Tie"
),
best_away_book = case_when(
dk_away == away_max ~ "DraftKings",
fd_away == away_max ~ "FanDuel",
mgm_away == away_max ~ "BetMGM",
TRUE ~ "Tie"
)
) %>%
ungroup()
# Spread statistics
spread_stats <- spread_analysis %>%
summarise(
avg_home_spread = round(mean(home_spread, na.rm = TRUE), 1),
max_home_spread = max(home_spread, na.rm = TRUE),
avg_away_spread = round(mean(away_spread, na.rm = TRUE), 1),
max_away_spread = max(away_spread, na.rm = TRUE),
avg_total_spread = round(mean(max_spread, na.rm = TRUE), 1),
games_no_diff = sum(max_spread == 0, na.rm = TRUE),
pct_identical = round(mean(max_spread == 0, na.rm = TRUE) * 100, 1)
)
cat("Average home odds spread:", spread_stats$avg_home_spread, "points\n")
cat("Maximum home odds spread:", spread_stats$max_home_spread, "points\n")
cat("Average away odds spread:", spread_stats$avg_away_spread, "points\n")
cat("Maximum away odds spread:", spread_stats$max_away_spread, "points\n")
cat("Games with identical odds:", spread_stats$games_no_diff, "(", spread_stats$pct_identical, "%)\n")
# Best odds frequency
best_odds_freq <- spread_analysis %>%
select(best_home_book, best_away_book) %>%
pivot_longer(everything(), names_to = "type", values_to = "book") %>%
filter(book != "Tie") %>%
count(book, sort = TRUE) %>%
mutate(percentage = round(n / sum(n) * 100, 1))
cat("\n🏆 BEST ODDS FREQUENCY:\n")
for(i in 1:nrow(best_odds_freq)) {
cat("•", best_odds_freq$book[i], ":", best_odds_freq$n[i], "times (", best_odds_freq$percentage[i], "%)\n")
}
# ========================================================================
# 3. IMPLIED PROBABILITY ANALYSIS
# ========================================================================
cat("\n\n3. IMPLIED PROBABILITY ANALYSIS\n")
cat(rep("-", 40), "\n")
prob_analysis <- analysis_data %>%
mutate(
# Convert American odds to implied probabilities
dk_home_prob = ifelse(dk_home > 0, 100/(dk_home + 100), abs(dk_home)/(abs(dk_home) + 100)),
dk_away_prob = ifelse(dk_away > 0, 100/(dk_away + 100), abs(dk_away)/(abs(dk_away) + 100)),
fd_home_prob = ifelse(fd_home > 0, 100/(fd_home + 100), abs(fd_home)/(abs(fd_home) + 100)),
fd_away_prob = ifelse(fd_away > 0, 100/(fd_away + 100), abs(fd_away)/(abs(fd_away) + 100)),
mgm_home_prob = ifelse(mgm_home > 0, 100/(mgm_home + 100), abs(mgm_home)/(abs(mgm_home) + 100)),
mgm_away_prob = ifelse(mgm_away > 0, 100/(mgm_away + 100), abs(mgm_away)/(abs(mgm_away) + 100)),
# Calculate vigorish (bookmaker edge)
dk_vig = (dk_home_prob + dk_away_prob - 1) * 100,
fd_vig = (fd_home_prob + fd_away_prob - 1) * 100,
mgm_vig = (mgm_home_prob + mgm_away_prob - 1) * 100
)
# Vigorish analysis
vig_summary <- prob_analysis %>%
summarise(
dk_avg_vig = round(mean(dk_vig, na.rm = TRUE), 2),
fd_avg_vig = round(mean(fd_vig, na.rm = TRUE), 2),
mgm_avg_vig = round(mean(mgm_vig, na.rm = TRUE), 2)
) %>%
pivot_longer(everything(), names_to = "book", values_to = "avg_vig") %>%
mutate(
book = case_when(
book == "dk_avg_vig" ~ "DraftKings",
book == "fd_avg_vig" ~ "FanDuel",
book == "mgm_avg_vig" ~ "BetMGM"
)
) %>%
arrange(avg_vig)
cat("📊 VIGORISH (Bookmaker Edge) - Lower is better for bettors:\n")
for(i in 1:nrow(vig_summary)) {
cat("•", vig_summary$book[i], ":", vig_summary$avg_vig[i], "%\n")
}
best_vig_book <- vig_summary$book[1]
worst_vig_book <- vig_summary$book[nrow(vig_summary)]
cat("\n🎯 MOST COMPETITIVE:", best_vig_book, "(lowest vig)\n")
cat("💰 LEAST COMPETITIVE:", worst_vig_book, "(highest vig)\n")
# ========================================================================
# 4. ARBITRAGE ANALYSIS
# ========================================================================
cat("\n\n4. ARBITRAGE OPPORTUNITY ANALYSIS\n")
cat(rep("-", 40), "\n")
arbitrage_analysis <- prob_analysis %>%
rowwise() %>%
mutate(
# Find best odds across all books
best_home_odds = max(c(dk_home, fd_home, mgm_home), na.rm = TRUE),
best_away_odds = max(c(dk_away, fd_away, mgm_away), na.rm = TRUE),
# Calculate implied probabilities from best odds
best_home_prob = ifelse(best_home_odds > 0, 100/(best_home_odds + 100),
abs(best_home_odds)/(abs(best_home_odds) + 100)),
best_away_prob = ifelse(best_away_odds > 0, 100/(best_away_odds + 100),
abs(best_away_odds)/(abs(best_away_odds) + 100)),
total_prob = best_home_prob + best_away_prob,
arbitrage_opportunity = total_prob < 1,
potential_profit = ifelse(arbitrage_opportunity, round((1/total_prob - 1) * 100, 2), 0)
) %>%
ungroup()
arbitrage_games <- arbitrage_analysis %>%
filter(arbitrage_opportunity) %>%
arrange(desc(potential_profit))
cat("Potential arbitrage opportunities:", nrow(arbitrage_games), "out of", nrow(analysis_data), "games\n")
if (nrow(arbitrage_games) > 0) {
cat("💰 ARBITRAGE OPPORTUNITIES FOUND:\n")
arbitrage_games %>%
select(game_date, home_team, away_team, best_home_odds, best_away_odds, potential_profit) %>%
head(5) %>%
kable(col.names = c("Date", "Home", "Away", "Best Home", "Best Away", "Profit %"))
} else {
cat("✅ No arbitrage opportunities - market is efficient\n")
}
# ========================================================================
# 5. BEST VALUE OPPORTUNITIES
# ========================================================================
cat("\n\n5. BEST VALUE OPPORTUNITIES\n")
cat(rep("-", 40), "\n")
value_games <- spread_analysis %>%
filter(max_spread > 10) %>% # Only games with meaningful differences
arrange(desc(max_spread)) %>%
head(10)
if (nrow(value_games) > 0) {
cat("🎯 TOP VALUE OPPORTUNITIES (Biggest odds differences):\n")
value_games %>%
select(game_date, home_team, away_team, home_spread, away_spread,
best_home_book, best_away_book) %>%
kable(col.names = c("Date", "Home Team", "Away Team", "Home Spread",
"Away Spread", "Best Home Book", "Best Away Book"))
} else {
cat("All games have small odds differences (< 10 points)\n")
}
# ========================================================================
# 6. VISUALIZATIONS
# ========================================================================
cat("\n\n6. VISUAL ANALYSIS\n")
cat(rep("-", 40), "\n")
# Odds distribution
odds_data <- analysis_data %>%
select(dk_home, dk_away, fd_home, fd_away, mgm_home, mgm_away) %>%
pivot_longer(everything(), names_to = "book_team", values_to = "odds") %>%
separate(book_team, into = c("book", "team"), sep = "_") %>%
mutate(
book = case_when(
book == "dk" ~ "DraftKings",
book == "fd" ~ "FanDuel",
book == "mgm" ~ "BetMGM"
)
)
p1 <- odds_data %>%
ggplot(aes(x = odds, fill = book)) +
geom_histogram(alpha = 0.7, bins = 20, position = "identity") +
facet_wrap(~book) +
labs(title = "Odds Distribution by Sportsbook",
subtitle = "Real NFL Games - American Odds Format",
x = "American Odds",
y = "Frequency") +
theme_minimal() +
scale_fill_brewer(type = "qual", palette = "Set2")
print(p1)
# Vigorish comparison visualization
vig_data <- prob_analysis %>%
select(game_id, dk_vig, fd_vig, mgm_vig) %>%
pivot_longer(cols = c(dk_vig, fd_vig, mgm_vig), names_to = "book", values_to = "vig") %>%
mutate(
book = case_when(
book == "dk_vig" ~ "DraftKings",
book == "fd_vig" ~ "FanDuel",
book == "mgm_vig" ~ "BetMGM"
)
)
p2 <- vig_data %>%
ggplot(aes(x = book, y = vig, fill = book)) +
geom_boxplot(alpha = 0.7) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
labs(title = "Vigorish Distribution by Sportsbook",
subtitle = "Lower vigorish = better value for bettors",
x = "Sportsbook",
y = "Vigorish (%)") +
theme_minimal() +
scale_fill_brewer(type = "qual", palette = "Set1") +
theme(legend.position = "none")
print(p2)
# ========================================================================
# 7. FINAL SUMMARY
# ========================================================================
cat("\n\n7. FINAL ANALYSIS SUMMARY\n")
cat(rep("=", 40), "\n")
overall_stats <- list(
total_games = nrow(analysis_data),
avg_correlation = if(exists("avg_corr")) round(avg_corr, 4) else "N/A",
avg_spread = spread_stats$avg_total_spread,
best_value_book = if(nrow(best_odds_freq) > 0) best_odds_freq$book[1] else "N/A",
most_competitive_vig = best_vig_book,
arbitrage_rate = round(nrow(arbitrage_games)/nrow(analysis_data)*100, 2)
)
cat("📊 COMPREHENSIVE MARKET ANALYSIS:\n")
cat("• Total games analyzed:", overall_stats$total_games, "\n")
cat("• Market correlation:", overall_stats$avg_correlation, "\n")
cat("• Average odds spread:", overall_stats$avg_spread, "points\n")
cat("• Best value provider:", overall_stats$best_value_book, "\n")
cat("• Most competitive vig:", overall_stats$most_competitive_vig, "\n")
cat("• Arbitrage rate:", overall_stats$arbitrage_rate, "%\n")
cat("\n💡 STRATEGIC RECOMMENDATIONS:\n")
cat("1. Primary sportsbook:", best_vig_book, "(lowest average vigorish)\n")
if (spread_stats$pct_identical < 80) {
cat("2. Line shopping recommended - significant price differences exist\n")
} else {
cat("2. Line shopping optional - books price very similarly\n")
}
if (nrow(best_odds_freq) > 0) {
cat("3. For best odds, check:", best_odds_freq$book[1], "first\n")
}
if (overall_stats$arbitrage_rate > 0) {
cat("4. Monitor for arbitrage opportunities (", overall_stats$arbitrage_rate, "% rate)\n")
} else {
cat("4. Market is efficient - no arbitrage opportunities detected\n")
}
} else {
cat("❌ Insufficient data for comprehensive analysis\n")
cat("Need at least 5 complete games for statistical analysis\n")
}
} else {
cat("=== NO ANALYSIS AVAILABLE ===\n")
cat("Real API data required for comprehensive sportsbook analysis\n")
cat("🔧 Get your free API key at https://the-odds-api.com\n")
}
## === COMPREHENSIVE SPORTSBOOK ANALYSIS ===
##
## 1. MARKET CORRELATION ANALYSIS
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
## Home Team Odds Correlations:
## DraftKings FanDuel BetMGM
## DraftKings 1.0000 0.9966 0.9990
## FanDuel 0.9966 1.0000 0.9963
## BetMGM 0.9990 0.9963 1.0000
##
## Away Team Odds Correlations:
## DraftKings FanDuel BetMGM
## DraftKings 1.0000 0.9805 0.9801
## FanDuel 0.9805 1.0000 0.9980
## BetMGM 0.9801 0.9980 1.0000
##
## 📊 MARKET EFFICIENCY SCORE: 0.9917
## ✅ HIGHLY EFFICIENT MARKET - Books price very similarly
##
##
## 2. ODDS SPREAD ANALYSIS
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
## Average home odds spread: 19.6 points
## Maximum home odds spread: 100 points
## Average away odds spread: 27.3 points
## Maximum away odds spread: 205 points
## Games with identical odds: 0 ( 0 %)
##
## 🏆 BEST ODDS FREQUENCY:
## • DraftKings : 14 times ( 41.2 %)
## • FanDuel : 14 times ( 41.2 %)
## • BetMGM : 6 times ( 17.6 %)
##
##
## 3. IMPLIED PROBABILITY ANALYSIS
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
## 📊 VIGORISH (Bookmaker Edge) - Lower is better for bettors:
## • FanDuel : 4.16 %
## • DraftKings : 4.43 %
## • BetMGM : 4.49 %
##
## 🎯 MOST COMPETITIVE: FanDuel (lowest vig)
## 💰 LEAST COMPETITIVE: BetMGM (highest vig)
##
##
## 4. ARBITRAGE OPPORTUNITY ANALYSIS
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
## Potential arbitrage opportunities: 0 out of 17 games
## ✅ No arbitrage opportunities - market is efficient
##
##
## 5. BEST VALUE OPPORTUNITIES
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
## 🎯 TOP VALUE OPPORTUNITIES (Biggest odds differences):
##
##
## 6. VISUAL ANALYSIS
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
##
##
## 7. FINAL ANALYSIS SUMMARY
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
## 📊 COMPREHENSIVE MARKET ANALYSIS:
## • Total games analyzed: 17
## • Market correlation: 0.9917
## • Average odds spread: 32.6 points
## • Best value provider: DraftKings
## • Most competitive vig: FanDuel
## • Arbitrage rate: 0 %
##
## 💡 STRATEGIC RECOMMENDATIONS:
## 1. Primary sportsbook: FanDuel (lowest average vigorish)
## 2. Line shopping recommended - significant price differences exist
## 3. For best odds, check: DraftKings first
## 4. Market is efficient - no arbitrage opportunities detected
Real NFL Data Analysis - Powered by The Odds
API
Focus: DraftKings, FanDuel, BetMGM comparison with live
market data