Make Tournament Winning DFS Lineups by Applying Optimization Problems (MLB) – Calculus in the Real World

As a math tutor, people have always asked me: how is calculus used in real life? There are many examples of calculus in the real world, and statistics in the real world too. These applications can be found in all kinds of industries, and overlapping with other sciences. However, there is one field that has always interested me most: sports.

I have always been fascinated by people using statistics and math to predict the outcome of sporting events. Or even to evaluate the performance of players, their impact on the outcome of the game, and their value to their team. However, I do not work in the sports world. I do not need to inform decisions about what players Billy Bean wanted to sign, or how big of a contract the Atlanta Braves should offer their 1st baseman with a career .859 OPS and a .234 strike out rate.

But what I can do is play fantasy sports. I can investigate how to construct the best possible lineup in a Daily Fantasy Sports (DFS) slate on FanDuel, and actually use the knowledge I gain from that investigation. So that’s what I have decided to do.

We will build the best FanDuel MLB lineup optimizer. Since we will be building it from scratch, we should have the best free DFS lineup optimizer on the internet by the time we’re done with this. I invite you to join me on this journey, and we’ll see what we can learn together. Let’s get started!

Keep in mind, I will be showing you investigative techniques that you could apply to DraftKings as well. Due to slight scoring or lineup differences between FanDuel and DraftKings, you may need to adjust our model a bit. However, the overall ideas should be the same.

Disclaimer: Nothing in this article, or any other content on this site or my YouTube channel should be interpreted as financial or gambling advise. This content is all created for educational and entertainment purposes. We will be investigating how to evaluate DFS MLB lineups mathematically in order to maximize the chances of winning a large tournament on FanDuel. If you decide to use these methods to create your own lineups and enter them in paid contests, please be smart, be safe, and only play with money you can afford to lose. There are no guarantees here.

Want to Play Along?

If you want to play along and enter some FanDuel lineups of your own as we conduct this investigation, you can use my FanDuel referral link here to get a deposit bonus. You should just need to deposit at least $15 within 30 days of signing up, and you’ll get a $15 bonus added to your account if you use that link. Can’t complain about some free money, right?

Background Research

Let’s start with the basic strategy we will investigate, and hopefully build on. Perhaps the most popular strategy in large tournaments on DFS is lineup stacking. This is a popular strategy in MLB DFS, but also in other sports. The idea is that we select multiple players from the same team to put in our lineup. If, for example, the New York Yankees blow out the Chicago Cubs by a final score of 8-1, there were likely a few Yankees with exceptional fantasy performances that day. Even better than that, many point producing events throughout the game would have most likely been double counted in your lineup, if you had multiple Yankees.

Sticking with the prior example, let’s say you have Aaron Judge and Giancarlo Stanton batting 3rd and 4th respectively, for the Yankees. You put both of them in your lineup. In the 3rd inning, Aaron Judge hits a double, getting you some points. Then Giancarlo Stanton comes up and hits a 2-run homer. You would get the points for 1 run for Judge scoring, and the HR, 1 run, and 2 RBI for Stanton’s swing of the bat. As you could imagine, this effect is compounded even more if you have 3 or 4 players lined up from the same team. If they start stringing hits together, that’s a lot of double counted points.

What players should you stack? What teams should you pick to stack from?

I found an article that addresses a few of these questions. I’ll summarize the main points I decided to start testing out, but if you want to see the full explanation of these strategies, you can see that article here.

There are 3 main ideas shown in that article that I decided to start building lineups around:

  1. We should target batters matching up against the pitchers with the lowest salaries on FanDuel. Lower salary implies it’s a worse pitcher, and will likely give up more fantasy points to opposing batters.
  2. We should target players batting as close to the top of the lineup as possible. Also when we stack players from the same team, they should be as close to each other in the lineup as possible.
  3. It is common for multiple players on the same team to be in the top 20 of all players in any given slate, and not uncommon at all for multiple teammates to be top 10, or top 5.

You can check out the article linked above for more details, but point #3 essentially just validates that stacking 2-4 players from the same team is a valid strategy. This is especially true when you enter a large tournament where you need your lineup to be in about the top 25th percentile just to finish in the money.

Points #1 and #2 though, tell us a bit about which teams to stack players from, and which players in the lineup we want.

Turning Our Strategy into an Optimization Problem

This is where the calculus comes into play. We won’t be solving any optimization problems here, or doing calculus. However, I do think it helps in the setup if you have at least a basic understanding of how to set up and solve a calculus based optimization problem. If you want a lesson on those, you can find that here.

Otherwise, let’s get into what I started with. When we sit down to set our lineup, we have something like this.

We need to fill our lineup (on the right) with the 9 players we think will score as many points as possible, from the available players shown (on the left). Each of them has a salary associated with them, and the sum of the 9 players’ salaries we choose needs to be below $35,000.

Don’t worry, you don’t have to actually spend 35,000 real dollars to fill your lineup. Think of that as game money. It’s just a way for the players we choose to have some limitation on it. It prevents us from just choosing the best player in each position across the board.

All an optimization problem is, is one equation that we are trying to maximize (or minimize), and one or more restriction equations that limit our variables somehow.

Well, it just so happens that’s all a FanDuel lineup is. Think about it. We have one thing that we are trying to make as big as possible: fantasy points scored.

And we have several restrictions addressing what our lineup is allowed to be:

  • We need 9 exactly players in total.
  • Total team salary less than or equal to $35,000.
  • Exactly 1 Pitcher (P).
  • At least 1 Catcher or 1st Baseman (C/1B).
  • At least 1 2nd Baseman (2B).
  • At least 1 3rd Baseman (3B).
  • At least 1 Short Stop (SS).
  • At least 3 Outfielders (OF).

This means, we need a lineup consisting of: P, C/1B, 2B, 3B, SS, OF, OF, OF, and UTIL. “UTIL” stands for utility and this position can be a C, 1B, 2B, 3B, SS, or OF. Basically, anything except a second Pitcher.

If we create a several variable optimization problem from this, using the one equation to optimize (sum of projected fantasy points), and the several restriction equations, we would at least be able to create legal lineups that we expect to do as well as possible.

However, we want to do better than that. Keep in mind there is always going to be variations in how many points players actually get. And sometimes, it’s not very close to their projected points. So, we want to use some of the stacking strategies I mentioned earlier to take advantage of those variations and give us a decent chance at having our lineup in the top 25th percentile. And it’s even better if we can be in the top 10%, 5%, 1%, or even the top lineup in our contest.

If we don’t take advantage of the stacking strategies, it’s less likely for many players in our lineup to do really well, or really poorly, on the same day. We want to create a boom or bust situation. This is because one big “boom” lineup can cover the cost of several “bust” lineups. Plus, a “middle of the road” lineup is just as useful as a bust.

It’s not…

They’re both worth $0.00.

Here Is Why

I’m sure by now, you are wondering why I have assumed that a “boom or bust” strategy is our best option. Let me elaborate.

Below is an image showing the results of a large tournament style contest. The gray and green bar represents all 3,217 lineups that were entered into the tournament. The green section shows the lineups that finished “in the money” and the gray is all the people that lost their $2.22 buy in. The blue figure shows where this lineup finished in there, 1,776th place of the 3,217 entries.

This lineup actually finished in the top half of this tournament, slightly better than “middle of the road.” But not high enough. The $2.22 entry fee was lost because we needed a bit more “boom” in the lineup. Might as well have been a “bust.” This is what we need to shoot for when only the top 23.3% of lineups win anything. I know it’s not shown in the image, but the top 750 lineups got money back in this tournament, and the 750th place finish got $5 from their $2.22 entry fee.

The top 10 lineups collectively won $1,295 of the total $6,000 in prizes for the whole contest. And the top 1 lineup won $500.

Hopefully this convinces you of the “boom or bust” strategy we will be adopting. One 1st place finish would pay for 225 losses of the $2.22 entry (in this specific contest structure). If we can really create a strategy with serious “boom or bust” potential, we would break even if we got 1st place 1 time, and finished “out of the money” 225 times.

This boom or bust approach likely would not be the best option for other contest types on FanDuel, like 50/50 or head to head contests. But for tournament style, the payout structure is always top heavy like the example above. Since we are starting with an investigation of winning tournament style contests, the boom or bust approach is a good starting place.

How do you optimize your lineup for stacking strategies?

Well, it’s quite simple to do when you’re treating your lineup like one big optimization problem. I should let you know, we don’t need to be able to solve this optimization problem. We have a computer for that. I’ll explain how we can have a computer solve this problem for us shortly, but for now we need to think about how to add in these extra considerations.

All we need to do, is add additional restriction equations to our optimization problem. Don’t worry about how these look mathematically for now. Let’s just think about the logic of what we want in our lineup.

Based on the 3 main takeaways I listed above, let’s start by applying the following restrictions to our lineup and see what happens. We can worry about fine tuning this later.

  • Stack players opposing the lowest salary pitchers. On FanDuel, you can only use up to 4 players on the same team in one lineup, so let’s start there. This may be 3 batters and the pitcher, or 4 batters on this targeted team. I figure the opposing pitcher is more likely to have run support and get the win, so he should still benefit from going up against a bad starting pitcher.
  • We will only use players in the starting lineup batting 1-5. This will guarantee that our stacked players are near each other in the lineup, and will help ensure that all of our batters are going to be getting a decent number of plate appearances (PA).
  • It will also be a good idea to add in one more thing. Let’s make sure we aren’t selecting any batters on the team opposing our pitcher. It is extremely difficult for our pitcher to have a good game and a batter on the opposing team to have a good game at the same time. One of them scoring points basically takes points away from the other, so not putting this in will lead to a “middle of the road” lineup. Again, useless in a big tournament.

Tools Used to Set up the Optimization Problem

In order to get this problem set up, I used a couple different tools. First, I used RotoWire projections for projected points for each player. This was the value that I wanted to maximize in this problem.

I used PuLP, which is a free Python package that is essentially made to create and solve optimization problems like this. You can check out the documentation on PuLP here. It can be used to solve the kinds of optimization problems you would need to solve in a calculus class, but it’s very helpful when you are trying to solve more complex problems like this. It would take an unreasonable amount of time to solve a problem like this by hand.

There was some setup I had to do to get all of the data from the RotoWire projections .csv file and decide which team would be targeted to stack 4 players from, but here is the portion of the code that basically creates our optimization problem. You will likely have to do some clean up or restructuring of variables here if you want to use this code yourself, but I wanted to give you a glimpse of how we can actually set up an optimization problem for a computer to solve for us.

import pulp

x = pulp.LpVariable.dict("player", range(0, len(all_player_list)), 0, 1, \
prob = pulp.LpProblem("Lineup", pulp.LpMaximize)
prob += pulp.lpSum(float(all_player_list[i][8]) * x[i] for i in range(0, \
prob += sum(x[i] for i in range(0, len(all_player_list))) ==  9 # 9 total players
prob += sum(x[i] for i in range(0, len(all_player_list)) if 'P' in \
            all_player_list[i][3]) == 1 # 1 P
prob += sum(x[i] for i in range(0, len(all_player_list)) if 'C' in \
            all_player_list[i][3] or '1B' in all_player_list[i][3]) >= 1 # 1 C/1B
prob += sum(x[i] for i in range(0, len(all_player_list)) if '2B' in \
            all_player_list[i][3]) >= 1 # 1 2B
prob += sum(x[i] for i in range(0, len(all_player_list)) if '3B' in \
            all_player_list[i][3]) >= 1 # 1 3B
prob += sum(x[i] for i in range(0, len(all_player_list)) if 'SS' in \
            all_player_list[i][3]) >= 1 # 1 SS
prob += sum(x[i] for i in range(0, len(all_player_list)) if 'OF' in \
            all_player_list[i][3]) >= 3 # 3 OF
prob += sum(x[i] * int(all_player_list[i][7]) for i in range(0, \
            len(all_player_list))) <= 35000 # total salary

# Used to make sure there is at least x players stacked from same team
support_min_hires = pulp.LpVariable.dicts('team hires', teams, cat='Binary') 

# Makes sure that the targeted team has at least the desired stack.
for team in teams: 
    prob += pulp.lpSum(x[i] for i in range(0, len(all_player_list)) if \
                       all_player_list[i][4] == stacked_team) >= \
                       support_min_hires[team] * 4
prob += pulp.lpSum(support_min_hires[team]) >= 1

# Prevent lineups from having pitchers and batters going against each other.
for p in range(0, len(all_player_list)): 
    if 'P' in all_player_list[p][3]:
        prob += pulp.lpSum([x[i] for i in range(0, len(all_player_list)) if \
        all_player_list[i][4] == ''.join(filter(str.isalnum, \
        all_player_list[p][5]))] + [8 * x[p]]) <= 8

# Makes sure we don't select two of the same player in two different positions.
# This was needed because I created multiple player objects for the same
# player if they could be used in 2 positions.
for player in players: 
    prob += pulp.lpSum(x[i] for i in range(0, len(all_player_list)) if \
                       all_player_list[i][0] == player) <= 1

What does this do?

To start, I ran this problem solver a few times. I started with creating a restriction equation that would force our lineup to stack 4 players from the team opposing the cheapest pitcher on the slate. This created the top lineup – “LINEUP #1.”

Then I repeated the process, this time forcing a 4 player stack opposing the second cheapest pitcher of the slate. Then the third cheapest, fourth cheapest, etc. Until I was left with 10 lineups for the given slate of players. For example, running this on the main slate for Friday, September 2, 2022 left us with this:

Stack number: 4 – Stacked Team: SEA
[‘Cody Morris’, ‘R’, ‘P’, ‘P’, ‘CLE’, ‘SEA’, ‘Yes’, ‘5500’]
=== LINEUP #1 ===
Solution status: Optimal
Charlie Morton with projected 35.56 points. He is P, cost 9800
Carson Kelly with projected 12.13 points. He is C, cost 2300
Jose Altuve with projected 14.26 points. He is 2B, cost 4000
Eugenio Suarez with projected 11.20 points. He is 3B, cost 3700
Carlos Correa with projected 13.94 points. He is SS, cost 3300
Max Kepler with projected 13.70 points. He is OF, cost 2600
Julio Rodriguez with projected 12.73 points. He is OF, cost 3600
Mitch Haniger with projected 11.71 points. He is OF, cost 3300
Jesse Winker with projected 11.58 points. He is OF, cost 2300
Total cost: 34900, Total Projected Points: 136.81

Stack number: 4 – Stacked Team: BOS
[‘Dallas Keuchel’, ‘L’, ‘P’, ‘P’, ‘TEX’, ‘@BOS’, ‘Yes’, ‘5500’]
=== LINEUP #2 ===
Solution status: Optimal
Charlie Morton with projected 35.56 points. He is P, cost 9800
Carson Kelly with projected 12.13 points. He is C, cost 2300
Javier Baez with projected 12.51 points. He is 2B, cost 2800
Alex Bregman with projected 13.25 points. He is 3B, cost 3600
Xander Bogaerts with projected 13.15 points. He is SS, cost 3800
Max Kepler with projected 13.70 points. He is OF, cost 2600
Tommy Pham with projected 13.57 points. He is OF, cost 3500
J.D. Martinez with projected 13.08 points. He is OF, cost 3200
Alex Verdugo with projected 11.10 points. He is OF, cost 3300
Total cost: 34900, Total Projected Points: 138.05

Stack number: 4 – Stacked Team: MIN
[‘Joe Kelly’, ‘R’, ‘P’, ‘P’, ‘CWS’, ‘MIN’, ‘Yes’, ‘5500’]
=== LINEUP #3 ===
Solution status: Optimal
Charlie Morton with projected 35.56 points. He is P, cost 9800
Carson Kelly with projected 12.13 points. He is C, cost 2300
Jose Altuve with projected 14.26 points. He is 2B, cost 4000
Jose Miranda with projected 10.82 points. He is 3B, cost 2900
Corey Seager with projected 14.41 points. He is SS, cost 4100
Max Kepler with projected 13.70 points. He is OF, cost 2600
Tyler O’Neill with projected 13.61 points. He is OF, cost 3300
Nick Gordon with projected 11.66 points. He is OF, cost 2500
Carlos Correa with projected 13.94 points. He is SS, cost 3300
Total cost: 34800, Total Projected Points: 140.09

You can see above each lineup, the team we are stacking 4 players from, and the opposing pitcher listed that we are trying to target. This is just the first 3 lineups created, but there were 7 more like them. You could easily loop through all the pitchers and create any number of lineups. Maybe you just want the top one, or maybe you want all available options. In theory, this should give us a list of 10 lineups with decent “boom or bust” potential.

What this creates for us is a list of optimized lineups that we can choose from on any given day, any given slate. And a system that we can use to create lineups for historical contests and slates and see how they would have done, as well as future contests to speed up our lineup creating process when we want to play.


This is a great start as this should be a list of mathematically optimized lineups. The fact that we treated it as an optimization problem, means that we selected the 9 players with the highest number of projected points as possible, while making sure it’s still a legal lineup.

In theory, we should get the maximum bang for our buck with these players in terms of the points they score based on the salary they cost. But this does rely on one very big assumption: the projected points provided by RotoWire are good projections.

Are they?

If so, then this system would certainly provide optimal lineups with great opportunity to “boom” and get us to the top of a tournament. This is thanks to the fact that we used the stacking strategies discussed earlier.

But if the projections are not very good, then we might as well have just picked random players, or assumed average points based on their position in the lineup, or some other (likely not very useful) method for projecting individual player performance.

Essentially what we have here, is a great system for creating lineups with a high potential for a maximized team score, as long as we can reasonably predict the individual players’ performance.

How well does this system work?

This method seems to give us a good shot at winning a top heavy payout structured tournament. Or at least placing “in the money.” But how well does it actually work? Would it be profitable, break even, or lose us money long term?

Those are important questions to answer, and those will be questions that we attempt to answer in the next section of our investigation. Without doing some testing and confirming the performance of this strategy in actual FanDuel tournaments, we can’t say that it’s a good strategy with any confidence.

Next we will create a system for back testing this strategy throughout the 2022 MLB season, as well as using some statistical analysis to evaluate how confidently we can expect these results to be repeated. Or extended into a larger sample of contests. This will allow us to confirm how much money we would have won or lost in paid games on FanDuel throughout the season, up to this point. And if we can confidently enter lineups from this system with a reasonable expectation of winning some of them in the future.

This does give us a great starting place for creating lineups of our own though. So, until then, I think I’ll enter some of these lineups myself and see what happens. Have a little fun with it. That is what this is all about after all. Then we take this investigation a bit further.

If you’d like to join me, you can go get signed up on FanDuel right here.

Some links in this article may be affiliate links or referral links, meaning I would get a small commission for your purchase at no additional cost to you.