Created
December 13, 2025 22:55
-
-
Save hughpearse/83af5cb52d0d3afe1f427904b08db396 to your computer and use it in GitHub Desktop.
Christmas Raffle Monte Carlo Simulation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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") |
Author
hughpearse
commented
Dec 13, 2025


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