How I Built a DFS Lineup Optimizer (And What It Does Every Week)

Every week during NFL season, a Python ILP optimizer pulls projections from 4 sources, blends them, and builds optimal DraftKings lineups. Here's how it works.

Share

Every week during NFL season, I run a Python-based lineup optimizer that pulls projections from multiple sources, blends them, and spits out the optimal DraftKings lineups for cash games and tournaments. Here's how it works.

📥 Download the Week 1 lineup workbook (Excel)

The Problem With Manual Lineup Building

Most DFS players pick lineups based on gut feel, one projection source, or whoever is trending on Twitter. The problem: projection sites disagree significantly, gut feel is biased toward name recognition, and trending players are already highly owned — exactly what you don't want in a tournament.

A systematic approach fixes all of this.

The Data Pipeline

The optimizer pulls from four sources every week:

FantasyPros — consensus projections aggregated from 60+ fantasy analysts. Now accessed via their official API, which returns full-universe coverage (120+ RBs, 200+ WRs) with clean stat breakdowns by position.

Sleeper — per-week projected stats from their public API. These are matchup-adjusted, which matters: a RB going against the Texans in Week 1 looks very different from the same RB against the 49ers in Week 8.

CBS Sports — season projections from their public stats feed, useful for universe coverage of depth players the other sources miss.

ESPN Fantasy — extracted from their internal fantasy API. Gives us another independent signal on skill players.

Each source is normalized to the same stat columns (rush yards, rec yards, TDs, receptions, etc.), then averaged into a consensus projection. An outlier filter drops any source's value if it's more than 10x the mean of the others — this catches cases where a data feed returns a stale or corrupted value.

The Optimizer

Once we have consensus projections, the lineup optimizer solves an Integer Linear Programming problem using PuLP. The constraints mirror DraftKings exactly:

  • 1 QB, 2 RB, 3 WR, 1 TE, 1 FLEX (RB/WR/TE), 1 DST
  • $50,000 salary cap, $45,000 floor
  • Max 8 players from the same team

The objective function changes depending on contest type:

Cash games (50/50s): Weight 70% floor + 30% projected points. You need consistency — hitting 30 DK points is more valuable than a coin flip at 45.

GPP tournaments: Weight 40% floor + 60% ceiling, minus an ownership penalty. In large-field tournaments, you need upside and differentiation. A chalk lineup that hits 40 points loses to 10,000 other people with the same lineup.

Public lineups: Pure ceiling optimization with a QB stack bonus — force the optimizer to pair the QB with at least one receiver from the same team, since correlated scoring is how you win tournaments.

QB Stacking

For tournament lineups, the optimizer includes a stack bonus that rewards pairing a QB with his pass catchers. When a QB throws 3 TDs, his receivers score those touchdowns — so you want both in your lineup. The ILP encodes this as a binary variable that activates when the QB and a same-team receiver are both selected, adding a small points bonus to the objective.

Name Matching

One of the trickier engineering problems: every data source has slightly different player names. "Josh Jacobs" might appear as "J. Jacobs" in CBS, with "Jr." appended in Sleeper, and with a different team abbreviation in ESPN.

The solution is a multi-tier matching pipeline:

  1. Exact match
  2. Suffix-stripped normalized match (removes Jr./Sr./III/II/IV)
  3. First + last token match
  4. Abbreviated initial + last name (catches "J. Jacobs" -> "Josh Jacobs")

This runs across all merge points so no player falls through the cracks.

The Output

The tool generates an Excel workbook with:

  • One tab per position showing all projection sources side-by-side
  • A consensus column with averaged projections and a variance indicator (high variance = disagreement between sources, interesting for GPP)
  • A DraftKings projected points column using a live Excel formula referencing a Settings sheet (change your scoring settings once, all positions update)
  • A Lineups tab with the optimized 50/50 and GPP lineups auto-generated from whichever DKSalaries.csv you dropped on the Desktop

Week 1 is this week. Let's see how it does.

Read more