Most fantasy seasons, I’d have a dozen tabs open. ESPN for rosters, FanGraphs for projections, spreadsheets for tracking my own rankings. It was tedious, easy to screw up, and full of guesswork. I figured I could save time (and maybe win more often) by building something that pulled it all together.
The result is a tool that connects directly to ESPN, scrapes FanGraphs, runs some scoring models, and gives me data-driven recommendations during the draft, weekly waivers, and season-long roster moves.
What It Does
- Pulls league and player data from ESPN
- Merges it with FanGraphs projections and recent stats
- Normalizes stats using z-scores
- Lets you tweak the balance between projections and recent performance
- Accounts for positional scarcity
- Highlights draft value based on tiers and ADP gaps
- Suggests adds and drops based on your current roster
How It Works
Data Pipeline
The pipeline pulls from ESPN and FanGraphs, merges everything, and calculates scores.
from espn_data import get_all_players, get_league_info
from fangraphs_api import get_fangraphs_merged_data
from analysis import calculate_composite_scores, rank_players
- Extract: ESPN cookies for league info and player pools
- Transform: Scrape Steamer projections and live stats from FanGraphs
- Load: Merge, normalize names, align positions
- Analyze: Z-score everything and apply weighted scoring
Scoring Model
The main scoring engine combines projections with current stats. The weighting is adjustable.
def calculate_composite_scores(df, weights=None):
if weights is None:
weights = {'proj': 0.8, 'curr': 0.2}
...
Metrics used:
- Hitters: wOBA, wRC+, ISO, wBsR
- Pitchers: FIP, WHIP, K-BB%, SV
- Delta: Tracks performance vs projection
- Composite score: Weighted blend for ranking
Draft Logic
Draft rankings adjust based on value and positional depth. There’s also tiering and ADP comparison.
def analyze_and_adjust_rankings(file_path):
...
Core logic:
- Bumps for thin positions
- Penalizes deep ones
- VADP: value vs average draft position
- Tiering: clusters players by value
- Caps: avoids overreaction to ADP
Interface
Streamlit handles the frontend with modular pages.
from app_pages.draft_strategy import show_draft_strategy
from app_pages.add_drop_recommendations import show_add_drop_recommendations
from app_pages.player_comparison import show_player_comparison
It includes:
- Mobile-friendly layout
- Filters by team, position, or stat
- Draft board with click-to-draft
- Player comparison view
- Excel export for offline review
Features
Roster Suggestions
Compares your team against the free agent pool, highlighting weak spots and better options. Accounts for matchups and playing time.
Draft Preparation
Pre-draft rankings by position, tier, and VADP. Tracks your picks and adjusts live.
League Analysis
Shows team strength across the league, trade targets, and where positions are thin.
Technical Notes
Data
- Handles broken/missing data gracefully
- Cleans up inconsistent player names
- Uses Streamlit caching to avoid slow reloads
- Modularized for easier debugging
Performance
- Vectorized with Pandas
- File-based caching with modification checks
- Lazy loading for responsiveness
UI
- Sidebar nav
- Loading and error feedback
- Responsive design
- Light CSS for readability
Running It
# Standard Python
python run_new_app.py
# Or via Streamlit
streamlit run src/main_app.py
Why I Built It
This started as a weekend project to stop juggling spreadsheets. Over time, it became a tool that actually helped me make better calls during drafts and weekly lineup decisions. It’s not perfect, but it saves time and gives me more confidence in the moves I make.
Skills Used
- API interaction with ESPN
- Web scraping from FanGraphs
- ETL and data cleanup
- Statistical modeling with z-scores and weights
- Streamlit UI