Trade Value Analysis in Hockey

Beginner 10 min read 0 views Nov 27, 2025

Quantifying Trade Value

Evaluating hockey trades requires assessing player value, contract situations, draft pick values, and team contexts. Statistical models can help determine whether trades are fair and identify which team gained more value.

Trade Value Components

  • Player WAR: On-ice value contribution
  • Contract Value: Remaining cap hit and term
  • Age/Development Curve: Future value trajectory
  • Draft Pick Value: Expected value of picks
  • Positional Fit: Team-specific needs

Python: Trade Value Calculator

import pandas as pd
import numpy as np

# Draft pick value chart (based on historical NHL success rates)
DRAFT_PICK_VALUES = {
    # First round
    1: 100, 2: 95, 3: 90, 4: 85, 5: 80,
    10: 65, 15: 55, 20: 45, 25: 38, 30: 32,
    # Second round
    31: 28, 40: 22, 50: 18, 60: 15,
    # Third round
    61: 12, 70: 10, 90: 7,
    # Later rounds
    91: 5, 120: 3, 150: 2, 180: 1
}

def get_pick_value(pick_number):
    """Get draft pick value from chart"""
    # Find closest pick in chart
    if pick_number in DRAFT_PICK_VALUES:
        return DRAFT_PICK_VALUES[pick_number]

    # Interpolate for picks not in chart
    lower_picks = [p for p in DRAFT_PICK_VALUES.keys() if p < pick_number]
    upper_picks = [p for p in DRAFT_PICK_VALUES.keys() if p > pick_number]

    if lower_picks and upper_picks:
        lower = max(lower_picks)
        upper = min(upper_picks)

        # Linear interpolation
        lower_val = DRAFT_PICK_VALUES[lower]
        upper_val = DRAFT_PICK_VALUES[upper]

        ratio = (pick_number - lower) / (upper - lower)
        return lower_val + (upper_val - lower_val) * ratio

    return 1  # Default for very late picks

# Player value calculation
def calculate_player_trade_value(player_data):
    """Calculate total trade value for a player"""

    war = player_data['war']
    age = player_data['age']
    cap_hit = player_data['cap_hit']
    years_remaining = player_data['years_remaining']

    # Base value from WAR
    base_value = war * 20  # Rough conversion to value points

    # Age adjustment (younger = more valuable)
    age_multiplier = 1.0
    if age < 24:
        age_multiplier = 1.3  # Young with upside
    elif age <= 27:
        age_multiplier = 1.2  # Prime years ahead
    elif age <= 30:
        age_multiplier = 1.0  # Prime now
    elif age <= 33:
        age_multiplier = 0.8  # Declining years
    else:
        age_multiplier = 0.6  # High risk

    # Contract value adjustment
    # Favorable contracts (high WAR, low cap hit) are more valuable
    if war > 0:
        dollars_per_war = cap_hit / war
        if dollars_per_war < 2_000_000:
            contract_multiplier = 1.3  # Great value contract
        elif dollars_per_war < 4_000_000:
            contract_multiplier = 1.1  # Good value
        elif dollars_per_war < 6_000_000:
            contract_multiplier = 1.0  # Market rate
        else:
            contract_multiplier = 0.7  # Overpaid
    else:
        contract_multiplier = 0.5  # Negative value player

    # Term adjustment (more years = more risk/reward)
    term_multiplier = 1.0 + (years_remaining * 0.05)

    total_value = (base_value * age_multiplier *
                   contract_multiplier * term_multiplier)

    return {
        'base_value': base_value,
        'age_multiplier': age_multiplier,
        'contract_multiplier': contract_multiplier,
        'term_multiplier': term_multiplier,
        'total_trade_value': total_value
    }

# Example: Evaluate a trade
def evaluate_trade(team_a_assets, team_b_assets):
    """Evaluate fairness of a trade"""

    team_a_value = 0
    team_b_value = 0

    print("=== Trade Analysis ===\n")

    # Team A assets
    print("Team A Gives:")
    for asset in team_a_assets:
        if asset['type'] == 'player':
            value_breakdown = calculate_player_trade_value(asset)
            value = value_breakdown['total_trade_value']
            print(f"  {asset['name']}: {value:.1f} points")
            print(f"    WAR: {asset['war']:.2f}, Age: {asset['age']}, "
                  f"Cap: ${asset['cap_hit']:,.0f}, Years: {asset['years_remaining']}")
            team_a_value += value
        elif asset['type'] == 'pick':
            value = get_pick_value(asset['pick_number'])
            print(f"  {asset['pick_number']} overall pick: {value:.1f} points")
            team_a_value += value

    print(f"Total Team A Value: {team_a_value:.1f}\n")

    # Team B assets
    print("Team B Gives:")
    for asset in team_b_assets:
        if asset['type'] == 'player':
            value_breakdown = calculate_player_trade_value(asset)
            value = value_breakdown['total_trade_value']
            print(f"  {asset['name']}: {value:.1f} points")
            print(f"    WAR: {asset['war']:.2f}, Age: {asset['age']}, "
                  f"Cap: ${asset['cap_hit']:,.0f}, Years: {asset['years_remaining']}")
            team_b_value += value
        elif asset['type'] == 'pick':
            value = get_pick_value(asset['pick_number'])
            print(f"  {asset['pick_number']} overall pick: {value:.1f} points")
            team_b_value += value

    print(f"Total Team B Value: {team_b_value:.1f}\n")

    # Trade balance
    difference = abs(team_a_value - team_b_value)
    pct_difference = (difference / max(team_a_value, team_b_value)) * 100

    print("=== Trade Evaluation ===")
    if pct_difference < 5:
        print("✓ FAIR TRADE - Values are balanced")
    elif pct_difference < 15:
        winner = "Team A" if team_a_value < team_b_value else "Team B"
        print(f"⚠ SLIGHT ADVANTAGE - {winner} receives more value")
    else:
        winner = "Team A" if team_a_value < team_b_value else "Team B"
        print(f"✗ UNBALANCED - {winner} receives significantly more value")

    print(f"Value Difference: {difference:.1f} points ({pct_difference:.1f}%)")

    return {
        'team_a_value': team_a_value,
        'team_b_value': team_b_value,
        'difference': difference,
        'pct_difference': pct_difference
    }

# Example trade scenario
team_a_gives = [
    {
        'type': 'player',
        'name': 'Star Forward',
        'war': 4.5,
        'age': 28,
        'cap_hit': 8_500_000,
        'years_remaining': 3
    },
    {
        'type': 'pick',
        'pick_number': 45
    }
]

team_b_gives = [
    {
        'type': 'player',
        'name': 'Young Center',
        'war': 2.8,
        'age': 23,
        'cap_hit': 925_000,
        'years_remaining': 1
    },
    {
        'type': 'player',
        'name': 'Defenseman',
        'war': 1.5,
        'age': 26,
        'cap_hit': 4_000_000,
        'years_remaining': 2
    },
    {
        'type': 'pick',
        'pick_number': 15
    }
]

trade_result = evaluate_trade(team_a_gives, team_b_gives)

# Rental player value (deadline acquisition)
def calculate_rental_value(player_war, games_remaining, playoff_rounds=0):
    """Calculate value of rental player (expiring contract)"""

    # Regular season value
    regular_season_value = (games_remaining / 82) * player_war * 15

    # Playoff value multiplier
    playoff_value = 0
    if playoff_rounds > 0:
        # Each playoff round adds significant value
        playoff_multiplier = 1.5 ** playoff_rounds
        playoff_value = player_war * 10 * playoff_multiplier

    total_value = regular_season_value + playoff_value

    return {
        'regular_season_value': regular_season_value,
        'playoff_value': playoff_value,
        'total_rental_value': total_value
    }

# Example: Deadline rental acquisition
rental = calculate_rental_value(
    player_war=3.5,
    games_remaining=20,
    playoff_rounds=2  # Expected playoff run
)

print("\n=== Rental Player Value (Deadline) ===")
print(f"Regular Season Value: {rental['regular_season_value']:.1f}")
print(f"Playoff Value (2 rounds): {rental['playoff_value']:.1f}")
print(f"Total Rental Value: {rental['total_rental_value']:.1f}")
print(f"Approximate Pick Equivalent: {rental['total_rental_value']:.0f} overall")

# Multi-team trade analysis
def analyze_multi_team_trade(teams_dict):
    """Analyze trade involving 3+ teams"""

    print("\n=== Multi-Team Trade Analysis ===\n")

    team_values = {}

    for team, assets in teams_dict.items():
        total_value = 0
        print(f"{team} Receives:")

        for asset in assets:
            if asset['type'] == 'player':
                value = calculate_player_trade_value(asset)['total_trade_value']
                print(f"  {asset['name']}: {value:.1f}")
                total_value += value
            elif asset['type'] == 'pick':
                value = get_pick_value(asset['pick_number'])
                print(f"  Pick #{asset['pick_number']}: {value:.1f}")
                total_value += value

        team_values[team] = total_value
        print(f"Total Value: {total_value:.1f}\n")

    # Identify winners and losers
    avg_value = np.mean(list(team_values.values()))

    print("Trade Balance:")
    for team, value in sorted(team_values.items(), key=lambda x: x[1], reverse=True):
        diff_from_avg = value - avg_value
        status = "↑ Winner" if diff_from_avg > 10 else "→ Fair" if diff_from_avg > -10 else "↓ Loser"
        print(f"  {team}: {value:.1f} ({status}, {diff_from_avg:+.1f} from avg)")

    return team_values

R: Trade Analysis Framework

library(tidyverse)

# Draft pick value chart
create_pick_value_chart <- function() {
  tibble(
    pick = c(1, 2, 3, 4, 5, 10, 15, 20, 25, 30,
             31, 40, 50, 60, 61, 70, 90, 91, 120, 150, 180),
    value = c(100, 95, 90, 85, 80, 65, 55, 45, 38, 32,
              28, 22, 18, 15, 12, 10, 7, 5, 3, 2, 1)
  )
}

pick_values <- create_pick_value_chart()

get_pick_value <- function(pick_number, value_chart = pick_values) {
  # Exact match
  if (pick_number %in% value_chart$pick) {
    return(value_chart$value[value_chart$pick == pick_number])
  }

  # Interpolate
  lower_picks <- value_chart %>% filter(pick < pick_number)
  upper_picks <- value_chart %>% filter(pick > pick_number)

  if (nrow(lower_picks) > 0 & nrow(upper_picks) > 0) {
    lower <- lower_picks %>% slice_max(pick, n = 1)
    upper <- upper_picks %>% slice_min(pick, n = 1)

    ratio <- (pick_number - lower$pick) / (upper$pick - lower$pick)
    return(lower$value + (upper$value - lower$value) * ratio)
  }

  return(1)  # Default
}

# Player trade value calculation
calculate_player_trade_value <- function(war, age, cap_hit, years_remaining) {
  base_value <- war * 20

  age_multiplier <- case_when(
    age < 24 ~ 1.3,
    age <= 27 ~ 1.2,
    age <= 30 ~ 1.0,
    age <= 33 ~ 0.8,
    TRUE ~ 0.6
  )

  dollars_per_war <- ifelse(war > 0, cap_hit / war, Inf)
  contract_multiplier <- case_when(
    war <= 0 ~ 0.5,
    dollars_per_war < 2000000 ~ 1.3,
    dollars_per_war < 4000000 ~ 1.1,
    dollars_per_war < 6000000 ~ 1.0,
    TRUE ~ 0.7
  )

  term_multiplier <- 1.0 + (years_remaining * 0.05)

  total_value <- base_value * age_multiplier * contract_multiplier * term_multiplier

  list(
    base_value = base_value,
    age_mult = age_multiplier,
    contract_mult = contract_multiplier,
    total_value = total_value
  )
}

# Trade evaluation function
evaluate_trade <- function(team_a_assets, team_b_assets) {

  # Calculate Team A value
  team_a_value <- sum(sapply(team_a_assets, function(asset) {
    if (asset$type == "player") {
      calculate_player_trade_value(asset$war, asset$age,
                                   asset$cap_hit, asset$years_remaining)$total_value
    } else if (asset$type == "pick") {
      get_pick_value(asset$pick_number)
    }
  }))

  # Calculate Team B value
  team_b_value <- sum(sapply(team_b_assets, function(asset) {
    if (asset$type == "player") {
      calculate_player_trade_value(asset$war, asset$age,
                                   asset$cap_hit, asset$years_remaining)$total_value
    } else if (asset$type == "pick") {
      get_pick_value(asset$pick_number)
    }
  }))

  difference <- abs(team_a_value - team_b_value)
  pct_difference <- (difference / max(team_a_value, team_b_value)) * 100

  cat("=== Trade Evaluation ===\n")
  cat(sprintf("Team A Value: %.1f\n", team_a_value))
  cat(sprintf("Team B Value: %.1f\n", team_b_value))
  cat(sprintf("Difference: %.1f points (%.1f%%)\n", difference, pct_difference))

  if (pct_difference < 5) {
    cat("✓ FAIR TRADE\n")
  } else if (pct_difference < 15) {
    winner <- ifelse(team_a_value < team_b_value, "Team B", "Team A")
    cat(sprintf("⚠ SLIGHT ADVANTAGE: %s\n", winner))
  } else {
    winner <- ifelse(team_a_value < team_b_value, "Team B", "Team A")
    cat(sprintf("✗ UNBALANCED: %s wins\n", winner))
  }

  list(
    team_a_value = team_a_value,
    team_b_value = team_b_value,
    difference = difference,
    pct_difference = pct_difference
  )
}

# Example trade
team_a <- list(
  list(type = "player", name = "Star Forward", war = 4.5, age = 28,
       cap_hit = 8500000, years_remaining = 3),
  list(type = "pick", pick_number = 45)
)

team_b <- list(
  list(type = "player", name = "Young Center", war = 2.8, age = 23,
       cap_hit = 925000, years_remaining = 1),
  list(type = "player", name = "Defenseman", war = 1.5, age = 26,
       cap_hit = 4000000, years_remaining = 2),
  list(type = "pick", pick_number = 15)
)

trade_result <- evaluate_trade(team_a, team_b)

# Rental value calculation
calculate_rental_value <- function(player_war, games_remaining, playoff_rounds = 0) {
  regular_season_value <- (games_remaining / 82) * player_war * 15

  playoff_value <- 0
  if (playoff_rounds > 0) {
    playoff_multiplier <- 1.5 ^ playoff_rounds
    playoff_value <- player_war * 10 * playoff_multiplier
  }

  list(
    regular_value = regular_season_value,
    playoff_value = playoff_value,
    total_value = regular_season_value + playoff_value
  )
}

rental <- calculate_rental_value(player_war = 3.5, games_remaining = 20,
                                 playoff_rounds = 2)

cat("\n=== Rental Player Value ===\n")
cat(sprintf("Regular Season: %.1f\n", rental$regular_value))
cat(sprintf("Playoff: %.1f\n", rental$playoff_value))
cat(sprintf("Total: %.1f\n", rental$total_value))

Context-Dependent Trade Value

Trade value isn't absolute—it depends on team context. A contender values rentals and proven veterans differently than a rebuilding team values young players and picks. Understanding each team's situation is crucial for fair trade evaluation.

Trade Evaluation Best Practices

  • Consider both immediate and future value of assets
  • Account for contract terms and cap implications
  • Adjust for team context (contending vs rebuilding)
  • Use multiple valuation methods to confirm fairness
  • Factor in positional needs and roster construction

Discussion

Have questions or feedback? Join our community discussion on Discord or GitHub Discussions.