The Trade Analyzer helps you evaluate potential trades by comparing NBA player statistics and showing how the trade impacts your team’s category rankings.
How It Works
The Trade Analyzer integrates live NBA player data to calculate the statistical impact of proposed trades:
Search NBA Players
Type a player’s name to search the NBA player database. The autocomplete dropdown shows matching players with their team and position.// Fetches all active NBA players from nba_api
const response = await fetch('/api/nba_players');
Build Your Trade
Add players to either side of the trade:
- Trading Away: Players you’ll give up
- Acquiring: Players you’ll receive
You can add up to 5 players per side. Evaluate Impact
Click “Evaluate Trade” to see the statistical breakdown and ranking changes across all fantasy categories.
NBA Player Integration
Player data comes from the NBA API via the Python backend:
# main.py:834-854
@app.route("/api/nba_players")
def api_nba_players():
if "token" not in session: return jsonify({"error": "authentication required"}), 401
try:
season_str = current_nba_season()
log.info(f"Fetching NBA players for season: {season_str} using PlayerIndex.")
player_data_df = playerindex.PlayerIndex(season=season_str, league_id="00").get_data_frames()[0]
players_list = [{
'id': int(row['PERSON_ID']),
'full_name': f"{row['PLAYER_FIRST_NAME']} {row['PLAYER_LAST_NAME']}",
'team_id': int(row['TEAM_ID']) if row['TEAM_ID'] else None,
'team_abbreviation': row['TEAM_ABBREVIATION'],
'position': row['POSITION']
} for _, row in player_data_df.iterrows()]
return jsonify(players_list)
Player Stats API
For each player in the trade, detailed per-game statistics are fetched:
# main.py:873-911
@app.route("/api/nba_player_stats/<int:player_id>")
def api_nba_player_stats(player_id):
try:
season = current_nba_season()
dashboard = PlayerDashboardByGeneralSplits(
player_id=player_id,
season=season,
per_mode_detailed="PerGame"
)
stats_series = data_frames[0].iloc[0]
stat_keys_float = ['PTS', 'REB', 'AST', 'STL', 'BLK', 'TOV', 'FG3M',
'FGM', 'FGA', 'FTM', 'FTA', 'FG3A', 'FG_PCT', 'FT_PCT',
'FG3_PCT', 'DD2', 'TD3', 'MIN']
required_stats = {'GP': int(stats_series.get('GP', 0) or 0)}
for key in stat_keys_float:
required_stats[key] = float(stats_series.get(key, 0.0) or 0.0)
return jsonify(required_stats)
Statistical Impact Analysis
The analyzer calculates impact across all fantasy categories:
// trade_analyzer.js:554-625
const aggregatedStats = {
tradingAway: { totals: {}, components: { FGM: 0, FGA: 0, FTM: 0, FTA: 0 } },
acquiring: { totals: {}, components: { FGM: 0, FGA: 0, FTM: 0, FTA: 0 } },
impact: {}
};
// Process each group of players
const processGroup = (playersStatsArray, groupAgg) => {
playersStatsArray.forEach(playerData => {
if (!playerData || playerData.GP === 0) return;
categoryConfigs.forEach(([statId, , ]) => {
const statMapInfo = STATE.NBA_STAT_MAP[statId];
if (statMapInfo.type === 'counting') {
const value = parseFloat(playerData[statMapInfo.nbaKey] || 0);
groupAgg.totals[statId] = (groupAgg.totals[statId] || 0) + value;
} else if (statMapInfo.type === 'percentage') {
// Aggregate makes/attempts for accurate percentage calculation
groupAgg.components[statMapInfo.components.made] +=
parseFloat(playerData[statMapInfo.components.made] || 0);
groupAgg.components[statMapInfo.components.attempted] +=
parseFloat(playerData[statMapInfo.components.attempted] || 0);
}
});
});
};
Category Mapping
// trade_analyzer.js:14-28
NBA_STAT_MAP: {
'12': { nbaKey: 'PTS', type: 'counting', precision: 1 }, // Points
'15': { nbaKey: 'REB', type: 'counting', precision: 1 }, // Rebounds
'16': { nbaKey: 'AST', type: 'counting', precision: 1 }, // Assists
'17': { nbaKey: 'STL', type: 'counting', precision: 1 }, // Steals
'18': { nbaKey: 'BLK', type: 'counting', precision: 1 }, // Blocks
'10': { nbaKey: 'FG3M', type: 'counting', precision: 1 }, // 3-Pointers Made
'19': { nbaKey: 'TOV', type: 'counting', precision: 1 }, // Turnovers
'5': { type: 'percentage', components: { made: 'FGM', attempted: 'FGA' } }, // FG%
'8': { type: 'percentage', components: { made: 'FTM', attempted: 'FTA' } }, // FT%
}
Ranking Changes Visualization
The analyzer shows your current rank in each category and how the trade would impact it:
// trade_analyzer.js:640-684
calculateRankingAnalysis: (categoryConfigs) => {
if (!STATE.currentSeasonData) return { currentRanks: {}, weakCategories: [], strongCategories: [] };
// Compute current rankings
const allRanks = Utils.computeRanks(STATE.currentSeasonData);
const myTeamIndex = STATE.currentSeasonData.findIndex(team => team.isMine);
const currentRanks = myTeamIndex >= 0 ? allRanks[myTeamIndex] : {};
// Identify weak and strong categories
const totalTeams = STATE.currentSeasonData.length;
const weakCategories = [];
const strongCategories = [];
categoryConfigs.forEach(([statId, statName, sortDir]) => {
const rank = currentRanks[statId];
if (rank && rank !== '-' && typeof rank === 'number') {
// Bottom 30% = weak, top 30% = strong
const weakThreshold = Math.ceil(totalTeams * 0.7);
const strongThreshold = Math.ceil(totalTeams * 0.3);
if (rank >= weakThreshold) {
weakCategories.push({ name: statName, rank, statId });
} else if (rank <= strongThreshold) {
strongCategories.push({ name: statName, rank, statId });
}
}
});
return { currentRanks, weakCategories, strongCategories, totalTeams };
}
Strategic Insights
The analyzer provides contextual insights based on your team’s strengths and weaknesses:
// trade_analyzer.js:316-392
generateContextualSummary: (summary, rankingAnalysis) => {
const { weakCategories, strongCategories, currentRanks } = rankingAnalysis;
const { gainingCategories, losingCategories } = summary;
// Which improvements help with weak areas?
const helpingWeakAreas = gainingCategories.filter(cat =>
weakCategories.some(weak => weak.name === cat)
);
// Which declines hurt strong areas?
const hurtingStrongAreas = losingCategories.filter(cat =>
strongCategories.some(strong => strong.name === cat)
);
let strategicInsights = [];
if (helpingWeakAreas.length > 0) {
strategicInsights.push({
type: 'positive',
icon: '✓',
title: 'Addresses Weaknesses',
text: `This trade helps improve ${helpingWeakAreas.join(', ')}`
});
}
if (hurtingStrongAreas.length > 0) {
strategicInsights.push({
type: 'warning',
icon: '!',
title: 'Weakens Strengths',
text: `Be cautious - this trade hurts ${hurtingStrongAreas.join(', ')}`
});
}
}
The Trade Analyzer requires an active session and automatically refreshes your league’s current standings to provide accurate ranking context.
Real-Time Updates
Player cards update dynamically as stats are loaded:
// trade_analyzer.js:490-502
DataService.fetchNbaPlayerStats(playerDataFromApi.id).then(playerStats => {
if (playerStats && playerStats.MIN !== undefined) {
newPlayer.mpg = playerStats.MIN;
Renderer.updatePlayerMPG(playerDataFromApi.id, playerStats.MIN);
} else {
Renderer.updatePlayerMPG(playerDataFromApi.id, null);
}
}).catch(error => {
console.error(`Error fetching MPG for player ${playerDataFromApi.id}:`, error);
Renderer.updatePlayerMPG(playerDataFromApi.id, null);
});
The analyzer requires NBA API access. If the API is unavailable, you’ll see an error message. Player statistics are per-game averages for the current NBA season.