Trade Value Analysis in Hockey
Beginner
10 min read
1 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.
Table of Contents
Related Topics
Quick Actions