Essay - Published: 2022.10.14 | finance | lottery | reflect |
DISCLOSURE: If you buy through affiliate links, I may earn a small commission. (disclosures)
Every now and then I get the urge to play the lottery - like when Megamillions hits big milestones of $0.5B or $1B and everyone and their robot is posting affiliate links to buy them.
But when is a good time to actually play?
My gut instinct is telling me the answer is never but the answer is likely more nuanced than that.
Let's try to answer this by:
I think we'll use expected value to try and determine the value of the lottery ticket we buy. When we compare the expected value of the ticket to the cost of buying the ticket we should end up with a good understanding of whether it's worth it or not.
The definition of expected value (via Investopedia) is:
"Expected value (EV) describes the long-term average level of a random variable based on its probability distribution."
In simple terms we're basically saying:
We can calculate this by:
So bringing this back to determining if lottery tickets are worth it, we can set up our formula like this:
$ticketPrice
$lotteryWinnings
oddsOfWinninglotteryWinnings - ticketPrice1 - oddsOfWinning0 - ticketPriceWe can formalize this in fsharp code like this:
type OutcomeExpectedValue = {
Probability: float
Value: float }
let calculateExpectedValue (allOutcomeExpectedValues : OutcomeExpectedValue list) : float =
allOutcomeExpectedValues
|> List.map (fun outcome -> outcome.Probability * outcome.Value)
|> List.sum
let calculateExpectedValueOfLotteryTicket (ticketPriceUsd : float) (oddsOfWinning : float) (winPayoutUsd : float) : float =
let oddsOfLosing = (float 1) - oddsOfWinning
let winOutcome = {
Probability = oddsOfWinning
Value = winPayoutUsd - ticketPriceUsd
}
let loseOutcome = {
Probability = oddsOfLosing
Value = (float 0) - ticketPriceUsd
}
[ winOutcome ; loseOutcome ]
|> calculateExpectedValue
// Megamillions
// * Odds: 1 / 303M - https://www.nj.com/lottery/2022/07/mega-millions-odds-how-to-play-from-the-massive-128b-jackpot-to-8-smaller-prizes.html#:~:text=The%20dream%20for%20a%20lottery,a%20preposterous%20302%2C575%2C350%20to%201.
// * Payout: $494M (2022.10.14)
let megaMillionsExpectedValue = calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000) (float 500000000)
printfn "MegaMillionsEV: %A" megaMillionsExpectedValue
Running this on 2022.10.14's Megamillions numbers nets us with -0.349 or a loss of $0.35 per ticket.
Now the interesting thing here is that this is actually the best case scenario - in reality we'll never see the full amount due to federal, state, city (if in places like NYC), and misc expenses (like you prob want a lawyer / attorney if you win).
Further reading: What you should do if you win the lottery via Reddit.
Okay so how do we account for all these other expenses? Probably with a lot of work, but I think we can get close enough by just cutting winnings by 50% - this leaves us a bit on the lowside but better that than the opposite.
So our updated calculations for Megamillions will look something like this:
// We take 50% off winnings amount because #taxes and #miscexpenses
let megaMillionsWOMiscExpensesEV = calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000) ((float 500000000) * 0.5)
printfn "megaMillionsWOMiscExpensesEV: %A" megaMillionsWOMiscExpensesEV
This makes things look quite a lot worse with an expected value of -$1.17! Another way to think about this is we're buying something worth about $0.83 but paying $2.00 for it - not as bad a deal as a cup of coffee, but still not a good deal for us.
So assuming we don't want to lose money when we play the lottery (on average) then we need to find values at which the lottery ticket presents us with a positive expected value.
We could probably do some fancy algebra to do this but tbh I forgot how to do that so instead I'm just going to brute force find this. It's not the most efficient big O wise, but this is a small dataset and the most expensive thing here is me so that's worth optimizing for.
We can model this in fsharp by creating a new function that iterates over expected winning values until it finds a value that leads to a non-negative expected value:
let calculateMinimumWinningsForLotteryTicketBreakEvent (winningsToOutcomeFn : float -> float) (initialWinnings : float) (stepSize : float) : float =
let rec getBreakEvenLoop (count : int) =
let currentWinningsAmount = (initialWinnings + (initialWinnings * stepSize * (float count)))
let currentEV = winningsToOutcomeFn currentWinningsAmount
match currentEV with
| ev when ev > (float 0) -> currentWinningsAmount
| _ -> getBreakEvenLoop (count + 1)
getBreakEvenLoop 0
We are getting a little fancy here with our #functionalprogramming but the two probably confusing parts are:
(winningsToOutcomeFn : float -> float) - This tells fsharp that I'm going to give it a function that takes in a float and returns a float. This is a great example of how functional paradigms lead to easier to compose (often via delegation) code. We'll see this in action in a sec.let rec getBreakEvenLoop (count : int) = - this is just a normal inline function but I've passed a rec keyword so it can call itself. More in Recursive Functions: The rec KeywordNow that we have this function, we can call it with:
let megaMillionsWOMiscExpensesWinningsToOutcomeFn = (calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000))
let megaMillionsWOMiscExpensesBreakEvenWinnings = calculateMinimumWinningsForLotteryTicketBreakEvent megaMillionsWOMiscExpensesWinningsToOutcomeFn (((float 500000000) * 0.5)) (float 0.1)
printfn "MegaMillions break even winnings: %A" megaMillionsWOMiscExpensesBreakEvenWinnings
printfn "Megamillions prize pool to break even: %A" (megaMillionsWOMiscExpensesBreakEvenWinnings * (float 2))
This prints out:
MegaMillions break even winnings: 625000000.0
Megamillions prize pool to break even: 1250000000.0
This is telling us that the amount we need to win from MegaMillions to make it worthwhile is $625M. But remember that we said that actual winnings are cut in half from the winnings they say you'll get, so in reality we need twice that number or an advertised prize pool of about $1250M ($1.25B) to break even!
If we look at the Top 10 Mega Millions Jackpots, we see that there have only been 2 jackpots greater than our break even price of $1250M in the history of Megamillions. That's a lot of lost expected value!
Okay so we know the lottery is a bad value. What should we do with it instead?
The nice thing is that the lottery seems like such a bad value that pretty much anything you spend your money on that you think is "worth" the money you put in is a better way to spend money =)
My top 3 would probably be:
TBH even with these great reasons not to buy a lottery ticket I still want to buy a lottery ticket. Are there reasons I should do it anyway?
Sure we can fool ourselves into making this a decent decision:
Thanks for reading and please use your money responsibly so you can buy yourself the Financial Independence and freedom you deserve.
Here's the full notebook I created in fsharp:
type OutcomeExpectedValue = {
Probability: float
Value: float }
let calculateExpectedValue (allOutcomeExpectedValues : OutcomeExpectedValue list) : float =
allOutcomeExpectedValues
|> List.map (fun outcome -> outcome.Probability * outcome.Value)
|> List.sum
let calculateExpectedValueOfLotteryTicket (ticketPriceUsd : float) (oddsOfWinning : float) (winPayoutUsd : float) : float =
let oddsOfLosing = (float 1) - oddsOfWinning
let winOutcome = {
Probability = oddsOfWinning
Value = winPayoutUsd - ticketPriceUsd
}
let loseOutcome = {
Probability = oddsOfLosing
Value = (float 0) - ticketPriceUsd
}
[ winOutcome ; loseOutcome ]
|> calculateExpectedValue
// Megamillions
// * Odds: 1 / 303M - https://www.nj.com/lottery/2022/07/mega-millions-odds-how-to-play-from-the-massive-128b-jackpot-to-8-smaller-prizes.html#:~:text=The%20dream%20for%20a%20lottery,a%20preposterous%20302%2C575%2C350%20to%201.
// * Payout: $494M (2022.10.14)
let megaMillionsExpectedValue = calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000) (float 500000000)
printfn "MegaMillionsEV: %A" megaMillionsExpectedValue
// We take 50% off winnings amount because #taxes and #miscexpenses
let megaMillionsWOMiscExpensesEV = calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000) ((float 500000000) * 0.5)
printfn "megaMillionsWOMiscExpensesEV: %A" megaMillionsWOMiscExpensesEV
let calculateMinimumWinningsForLotteryTicketBreakEvent (winningsToOutcomeFn : float -> float) (initialWinnings : float) (stepSize : float) : float =
let rec getBreakEvenLoop (count : int) =
let currentWinningsAmount = (initialWinnings + (initialWinnings * stepSize * (float count)))
let currentEV = winningsToOutcomeFn currentWinningsAmount
match currentEV with
| ev when ev > (float 0) -> currentWinningsAmount
| _ -> getBreakEvenLoop (count + 1)
getBreakEvenLoop 0
let megaMillionsWOMiscExpensesWinningsToOutcomeFn = (calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000))
let megaMillionsWOMiscExpensesBreakEvenWinnings = calculateMinimumWinningsForLotteryTicketBreakEvent megaMillionsWOMiscExpensesWinningsToOutcomeFn (((float 500000000) * 0.5)) (float 0.1)
printfn "MegaMillions break even winnings: %A" megaMillionsWOMiscExpensesBreakEvenWinnings
printfn "Megamillions prize pool to break even: %A" (megaMillionsWOMiscExpensesBreakEvenWinnings * (float 2))
let megaMillionsWOMiscWoTicketExpensesEV = calculateExpectedValueOfLotteryTicket (float 0) (float 1 / float 303000000) ((float 500000000) * 0.5)
printfn "EV without ticket price: %A" megaMillionsWOMiscWoTicketExpensesEV
The best way to support my work is to like / comment / share for the algorithm and subscribe for future updates.