Skip to content

Instantly share code, notes, and snippets.

@hughpearse
Created December 13, 2025 22:55
Show Gist options
  • Select an option

  • Save hughpearse/83af5cb52d0d3afe1f427904b08db396 to your computer and use it in GitHub Desktop.

Select an option

Save hughpearse/83af5cb52d0d3afe1f427904b08db396 to your computer and use it in GitHub Desktop.
Christmas Raffle Monte Carlo Simulation
#!/usr/bin/env python
# Author: Hugh Pearse
# Title: Christmas Raffle Monte Carlo Simulation
# Description:
# This program simulates a Christmas raffle using Monte Carlo experiments.
# It allows the user to:
# 1. Define the total number of raffle tickets and possible prizes.
# 2. Specify the number of tickets they plan to purchase and the cost per ticket.
# 3. Run multiple trials (samples) to estimate expected winnings for different ticket purchase counts.
# 4. Aggregate results to calculate average winnings and average amount spent.
# 5. Export aggregated results to a CSV file.
# 6. Visualize the relationship between tickets purchased, amount spent, and average winnings using scatter plots.
#
# The Monte Carlo approach provides a statistical estimation of expected returns
# based on repeated randomized raffle simulations, helping the user understand
# the likely outcomes of purchasing different numbers of tickets.
#
# Setup instructions:
# python -m venv sandbox
# ./sandbox/Scripts/activate
# pip install pandas matplotlib
# python ./monte_carlo_christmas_raffle.py
import random
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
class RaffleResults:
"""
Stores the results of a single raffle run.
Attributes:
purchasedTicketCount (int): Number of tickets purchased.
myWinningsTotal (float): Total winnings from the raffle.
amountSpent (float): Total amount spent on tickets.
"""
def __init__(self, purchasedTicketCount, myWinningsTotal, amountSpent):
self.purchasedTicketCount = purchasedTicketCount
self.myWinningsTotal = myWinningsTotal
self.amountSpent = amountSpent
class ChristmasRaffle:
"""
Simulates a single Christmas raffle.
Attributes:
possiblePrizes (list): Array of prize values.
ticketCount (int): Total number of tickets in the raffle.
purchasedTicketCount (int): Number of tickets purchased by the player.
costPerTicket (float): Cost of a single ticket.
"""
def __init__(self, possiblePrizes, ticketCount, purchasedTicketCount, costPerTicket):
self.possiblePrizes = possiblePrizes
self.ticketCount = ticketCount
self.purchasedTicketCount = purchasedTicketCount
self.costPerTicket = costPerTicket
def fillRaffleDrum(self, ticketCount, possiblePrizes):
"""
Creates the raffle drum with prizes and losing tickets (zeros).
Args:
ticketCount (int): Total tickets in the raffle.
possiblePrizes (list): List of prize values.
Returns:
list: Drum contents containing zeros and prizes.
"""
zeros_count = ticketCount - len(possiblePrizes)
drumContents = np.concatenate((np.zeros(zeros_count, dtype=float), possiblePrizes))
return drumContents
def rollDrum(self, drumContents):
"""
Randomly shuffles the raffle drum.
Args:
drumContents (list): The array of ticket values (prizes and zeros).
Returns:
list: Shuffled drum contents.
"""
shuffled = np.copy(drumContents)
np.random.shuffle(shuffled)
return shuffled
def selectWinners(self, shuffledDrumContents, purchasedTicketCount):
"""
Selects the winning tickets for the player.
Args:
shuffledDrumContents (list): Shuffled raffle drum.
purchasedTicketCount (int): Number of tickets purchased by the player.
Returns:
list: List of winnings for the purchased tickets.
"""
return shuffledDrumContents[:purchasedTicketCount]
def runRaffle(self):
"""
Runs a single raffle, calculates winnings and amount spent.
Returns:
RaffleResults: Results of this raffle run.
"""
drumContents = self.fillRaffleDrum(self.ticketCount, self.possiblePrizes)
shuffledDrumContents = self.rollDrum(drumContents)
myWinnings = self.selectWinners(shuffledDrumContents, self.purchasedTicketCount)
myWinningsTotal = np.sum(myWinnings)
amountSpent = self.purchasedTicketCount * self.costPerTicket
return RaffleResults(self.purchasedTicketCount, myWinningsTotal, amountSpent)
class SampleResult:
"""
Aggregated results of multiple raffle trials.
Attributes:
purchasedTicketCount (int): Number of tickets purchased.
avgWinningsTotal (float): Average winnings over multiple trials.
amountSpent (float): Average amount spent over multiple trials.
"""
def __init__(self, purchasedTicketCount, avgWinningsTotal, amountSpent):
self.purchasedTicketCount = purchasedTicketCount
self.avgWinningsTotal = avgWinningsTotal
self.amountSpent = amountSpent
class MonteCarloExperiment:
"""
Performs Monte Carlo experiments for the Christmas raffle.
Attributes:
experimentResults (list): Stores aggregated results (SampleResult objects) across parameter sweeps.
"""
def __init__(self):
self.experimentResults = []
def performTrial(self, possiblePrizes, ticketCount, purchasedTicketCount, costPerTicket):
"""
Performs a single trial of the Christmas raffle.
Args:
possiblePrizes (list): List of prizes.
ticketCount (int): Total tickets in the raffle.
purchasedTicketCount (int): Tickets purchased by player.
costPerTicket (float): Cost per ticket.
Returns:
RaffleResults: Results of a single raffle trial.
"""
raffle = ChristmasRaffle(possiblePrizes, ticketCount, purchasedTicketCount, costPerTicket)
return raffle.runRaffle()
def collectSampleDistribution(self, possiblePrizes, ticketCount, purchasedTicketCount, costPerTicket, sampleSize):
"""
Collects a distribution of trial results and aggregates them.
Args:
possiblePrizes (list): List of prizes.
ticketCount (int): Total tickets in the raffle.
purchasedTicketCount (int): Tickets purchased by player.
costPerTicket (float): Cost per ticket.
sampleSize (int): Number of trials to run for this sample.
Returns:
SampleResult: Aggregated results (average winnings) for the sample.
"""
possiblePrizes = np.array(possiblePrizes, dtype=float)
numPrizes = len(possiblePrizes)
numZeros = ticketCount - numPrizes
# Preallocate results array
winnings_array = np.zeros(sampleSize, dtype=float)
for i in range(sampleSize):
# Simulate purchased tickets as random draws
drum = np.concatenate((np.zeros(numZeros, dtype=float), possiblePrizes))
np.random.shuffle(drum)
selected_tickets = drum[:purchasedTicketCount]
winnings_array[i] = np.sum(selected_tickets)
avg_winnings = np.mean(winnings_array)
avg_amount_spent = purchasedTicketCount * costPerTicket
return SampleResult(purchasedTicketCount, avg_winnings, avg_amount_spent)
def runSimulationParameterSweep(self, possiblePrizes, ticketCount, purchasedTicketCounts, costPerTicket, sampleSize):
"""
Runs a Monte Carlo simulation across multiple ticket purchase counts.
Args:
possiblePrizes (list): List of prizes.
ticketCount (int): Total tickets in the raffle.
purchasedTicketCounts (list): List of ticket counts to simulate.
costPerTicket (float): Cost per ticket.
sampleSize (int): Number of trials per ticket count.
Returns:
list: List of SampleResult objects for each ticket count.
"""
sampleResults = []
for purchasedTicketCount in purchasedTicketCounts:
aggregated_result = self.collectSampleDistribution(
possiblePrizes, ticketCount, purchasedTicketCount, costPerTicket, sampleSize
)
sampleResults.append(aggregated_result)
self.experimentResults = sampleResults
return sampleResults
def exportResultsToCSV(simulationResults):
"""
Exports simulation results to a CSV file.
Args:
simulationResults (list): List of SampleResult objects to export.
"""
rows = []
for r in simulationResults:
rows.append({
"purchasedTicketCount": r.purchasedTicketCount,
"avgWinningsTotal": r.avgWinningsTotal,
"amountSpent": r.amountSpent
})
df = pd.DataFrame(rows)
df.to_csv("raffle_simulation_results_aggregated.csv", index=False)
def plot_raffle_results(filename):
"""
Reads a CSV of raffle simulation results and plots two scatter plots side by side in a non-blocking way.
Args:
filename (str): Path to the CSV file containing simulation results.
"""
df = pd.read_csv(filename)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].scatter(df["purchasedTicketCount"], df["avgWinningsTotal"], color='blue')
axes[0].set_xlabel("Purchased Ticket Count")
axes[0].set_ylabel("Average Winnings Total")
axes[0].set_title("Tickets Purchased vs Average Winnings")
axes[0].grid(True)
axes[1].scatter(df["amountSpent"], df["avgWinningsTotal"], color='green')
axes[1].set_xlabel("Amount Spent")
axes[1].set_ylabel("Average Winnings Total")
axes[1].set_title("Amount Spent vs Average Winnings")
axes[1].grid(True)
plt.tight_layout()
plt.show(block=True)
# ----------------- Run the Monte Carlo Simulation -----------------
experiment = MonteCarloExperiment()
results = experiment.runSimulationParameterSweep(
possiblePrizes=[1000, 800, 500, 400, 400, 400, 300, 300, 300, 300, 200, 200, 200, 200, 200, 200, 200, 200, 200, 500],
ticketCount=700,
purchasedTicketCounts=list(range(1, 201)),
costPerTicket=10,
sampleSize=10000
)
exportResultsToCSV(results)
plot_raffle_results("raffle_simulation_results_aggregated.csv")
@hughpearse
Copy link
Author

IMG-20251213-WA0003
image (2)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment