Every Constraint in My Excel NBA DFS Optimizer

A few days ago I wrote that a basketball rules file would come out nearly empty, and that the interesting part of an NBA optimizer is not the optimization. Then I went looking for something else and found a workbook I had forgotten I wrote: FCP Optimizer — Master v6.7, an NBA daily fantasy optimizer in Excel, with a change log that starts at version 4. So rather than speculate about NBA DFS optimizer constraints, here is the actual model, read straight out of the file.

It is a mixed integer program with about 200 binary variables, solved by CBC through the OpenSolver add-in. Everything below is what the sheet and its macros really do, not a cleaned-up version of it.

Diagram of the mixed integer program inside the Excel NBA daily fantasy optimizer, showing objective and seven constraints
Continue reading “Every Constraint in My Excel NBA DFS Optimizer”

The Textbook Fix That Made My MILP Twice As Slow

The textbook move, when a mixed-integer program solves slowly, is to tighten the linear relaxation. Add the constraint that makes the LP bound closer to the integer optimum, give branch and bound less room to wander, watch it finish faster.

I did that to the stacking constraint in my hockey lineup optimizer last week. It got twice as slow. Same slate, same solver, same lineups out the other end — 2.20 seconds a lineup before, 4.38 after. I reverted it and left a comment telling future-me not to helpfully fix it again.

Bar chart of seconds per lineup by stacking formulation: naive 3.00, cover inequalities 2.20, two-sided counters 4.38
Three formulations of the same constraint, same slate and solver. The textbook tightening is the slowest of the three.

Continue reading “The Textbook Fix That Made My MILP Twice As Slow”

NBA DFS Correlation: 3 Seasons, 500,000 Pairs

In the last post I argued that basketball gives an optimizer nothing to stack, because no rule of the sport links two players’ scores the way a batting order or a quarterback does. That was reasoning, not evidence. So I pulled three seasons of box scores and actually measured NBA DFS correlation — 62,681 player-games, 3,690 games, and 505,721 pairs of players who shared a floor.

The short version: the structural argument holds, the numbers are far smaller than most stacking advice implies, and the most interesting thing in the data is something I had predicted backwards.

Line chart of NBA teammate and opponent DraftKings score correlation crossing as the final margin widens
Continue reading “NBA DFS Correlation: 3 Seasons, 500,000 Pairs”

Roth Conversion Calculator With IRMAA and State Taxes

Somebody ran twelve impossible scenarios through my Roth conversion optimizer in seven minutes and then left with nothing. That’s the most useful thing I learned from finally sitting down and reading the tool’s usage log, and it’s why it’s now a Roth conversion calculator with IRMAA priced in, real state brackets for all fifty states, and the ability to tell you in plain English what your plan is actually doing.

The log covers roughly 190 solves across about 31 visitor sessions, which makes this the most-used of the optimization tools I’ve put online. Almost everyone got a plan out of it. Two people got nothing at all, and the way they failed turned out to be the most interesting thing in the whole file — they weren’t defeated by a bug, they were defeated by an answer that was technically correct and completely useless.

Plain-English notes under a Roth conversion plan, including one reading “Your income is pinned at $109,000 for 13 years”
The optimizer explaining its own plan — fixed rules, not generated text.
Continue reading “Roth Conversion Calculator With IRMAA and State Taxes”

My Solver Said Optimal. Two Shifts Were Empty.

A solver status of optimal is a statement about a tolerance, not about your business rule.

A test suite told me my scheduler was broken. It wasn’t. What actually happened is stranger and more useful: on one of the shipped sample rosters my solver said optimal and handed back a schedule with two night posts nobody was assigned to. Not a crash, not an infeasibility, not a timeout. A clean optimal, with holes in it. Here is what I measured, what I had wrong in my own code comments, and the one dependency I had been describing to people as a speed optimization when it is really a correctness one.

Continue reading “My Solver Said Optimal. Two Shifts Were Empty.”