Value Bet Identification

Summary

A value bet is a wager where the bettor's estimated probability of an outcome exceeds the bookmaker's implied probability, creating positive expected value (+EV). The identification of value bets is the central skill in profitable sports betting — the bettor must be more accurate than the market in estimating true probabilities.

Value bet identification requires: (1) a predictive model that produces better probabilities than the market, (2) de-vigged odds to get fair market probabilities, and (3) a filter/threshold for placing bets based on minimum EV or edge requirements.

The process works as follows: the model produces probabilities → odds are de-vigged → EV = model_prob × bookie_odds - 1 → if EV > threshold, bet is placed.

The challenge is that bookmakers are generally efficient — especially for high-profile events like the World Cup. Finding consistent value requires either better models, faster information access, or specialized data sources.

Key Concepts

  • True probability: The actual likelihood of an outcome (never known exactly). The model's job is to estimate this better than the market.
  • Implied probability: The probability embedded in bookmaker odds = 1/decimal_odds. Must be de-vigged to get fair market estimate.
  • Overlay: When bookmaker odds are higher than fair odds → the bet is "overlaid" or has positive edge.
  • Minimum edge threshold: Most professional bettors only bet when edge exceeds 3-5%. Lower thresholds generate too many low-edge bets that don't cover transaction costs.
  • Closing line value as validator: If a bettor consistently finds value (gets positive CLV), it confirms genuine predictive edge.
  • Market efficiency hierarchy: Sharp books (Pinnacle) are more efficient than soft books (DraftKings). Finding value is easier against soft books.

Formulas

Value / Edge:
$$\text{edge} = \frac{\text{bookie_odds}}{\text{fair_odds}} - 1$$

Or equivalently: edge = (model_prob × bookie_odds) - 1

Minimum edge filter:
$$\text{bet_if}: \left(\frac{\text{model_prob}}{\text{implied_prob}} - 1\right) > \text{threshold}$$

Example: model estimates Brazil 60% win (fair odds = 1.667), bookie offers 1.85 (implied = 54.1%). Edge = 60/54.1 - 1 = 10.9%. This exceeds a 5% threshold → bet.

Value bet probability:
$$P(\text{value bet}) = \frac{\text{odds}}{\text{fair_odds}} = \frac{p_{\text{model}}}{p_{\text{implied}}}$$

Python Implementation

import pandas as pd
import numpy as np

def find_value_bets(matches_df, min_edge=0.05, bookie_col='odds'):
    """
    Identify value bets from model predictions and bookmaker odds.

    Args:
        matches_df: DataFrame with model_prob and odds columns
        min_edge: Minimum edge threshold to place bet (default 5%)
        bookie_col: Column name with bookmaker decimal odds

    Returns:
        DataFrame of value bets with edge and Kelly stake
    """
    matches = matches_df.copy()

    # Implied probability (de-vigged)
    matches['implied_prob'] = 1 / matches[bookie_col]

    # Overround removal (multiplicative)
    matches['prob_sum'] = matches['implied_prob']  # For multi-outcome: sum over all
    # For 2-way: simple approach
    matches['fair_prob'] = matches['implied_prob'] / matches['prob_sum']

    # Edge calculation
    matches['edge'] = (matches['model_prob'] / matches['fair_prob']) - 1

    # Filter for value bets
    value_bets = matches[matches['edge'] >= min_edge].copy()
    value_bets['ev'] = matches['model_prob'] * matches[bookie_col] - 1

    # Sort by edge (highest first)
    value_bets = value_bets.sort_values('edge', ascending=False)

    return value_bets

def simulate_value_betting(bets_df, bankroll=10000, kelly_fraction=0.5, min_edge=0.03):
    """
    Simulate value betting strategy over historical bets.
    """
    results = []
    current_bankroll = bankroll

    for _, bet in bets_df.iterrows():
        edge = bet['edge']
        if edge < min_edge:
            continue

        # Kelly stake
        b = bet['odds'] - 1
        p = bet['model_prob']
        f_full = max((p * b - (1-p)) / b, 0)
        stake = current_bankroll * f_full * kelly_fraction

        # Outcome
        result = 1 if np.random.random() < p else 0
        pnl = stake * (bet['odds'] - 1) if result == 1 else -stake

        current_bankroll += pnl
        results.append({
            'bankroll': current_bankroll,
            'stake': stake,
            'pnl': pnl,
            'edge': edge
        })

    return pd.DataFrame(results)

# Real usage: combine model predictions with The Odds API
# 1. Fetch odds from The Odds API
# 2. De-vig to get fair probabilities
# 3. Compare model output to fair probabilities
# 4. Place bets where edge > threshold (e.g., 5%)

Notes

  • Value betting requires discipline: bet only when edge exceeds threshold, not based on "feeling" or intuition
  • Market efficiency varies: World Cup odds are highly efficient due to public attention; less prominent leagues have more inefficiency and more value opportunity
  • The key question for the World Cup model: does it produce probabilities that are consistently more accurate than the closing line? If yes, value betting is profitable. If not, even +EV bets lose money.
  • OddsJam and similar tools automate value bet finding by comparing odds across dozens of sportsbooks
  • For the MVP: the pipeline should score all matches, compute EV vs. de-vigged odds, filter for positive EV, then rank by EV magnitude before sending Telegram notifications