Introduction

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,
    "&regions=", 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

Real NFL Games Table

# 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

Comprehensive Sportsbook Analysis

# 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