Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added __init__.py
Empty file.
35 changes: 35 additions & 0 deletions examples/gambling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Luck vs Skill in Short-Term Gambling

This example demonstrates how short-term gambling success is dominated by luck,
even when agents differ in skill.

## Model Description

Each agent has a fixed skill level between 0 and 1. Skill slightly increases the
probability of winning a bet, but outcomes are still stochastic.

Agents repeatedly place fixed-size bets. After a short number of rounds, agents
are ranked by wealth.

The model compares the average skill of:
- The top 10% of agents by wealth
- The bottom 10% of agents by wealth

## Key Insight

Over short horizons, the skill distributions of winners and losers differ only slightly.
Early success is therefore a poor indicator of true skill.

This illustrates why gambling success, early trading profits, or beginner's luck
are often misattributed to ability rather than chance.

This model demonstrates:
1. Beginner’s luck is a real statistical phenomenon
2. Early winners are not reliable indicators of skill
3. Skill emerges only over long horizons
4. Human inference from short samples is systematically biased

## How to Run

```bash
solara run app.py
16 changes: 16 additions & 0 deletions examples/gambling/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from mesa import Agent


class GamblingAgent(Agent):
def __init__(self, model, skill, wealth, bet_size):
super().__init__(model)
self.skill = skill
self.wealth = wealth
self.bet_size = bet_size

def step(self):
p_win = 0.5 + self.model.alpha * self.skill
if self.model.random.random() < p_win:
self.wealth += self.bet_size
else:
self.wealth -= self.bet_size
38 changes: 38 additions & 0 deletions examples/gambling/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from mesa.visualization import SolaraViz, make_plot_component
from mesa.visualization.user_param import Slider
from model.model import LuckVsSkillModel


def post_process_lines(ax):
ax.set_xlabel("Simulation Step")
ax.set_ylabel("Average True Skill")
ax.set_title("Luck vs Skill in Short-Term Gambling")
ax.legend()


COLORS = {
"Top 10": "#d62728",
"Bottom 10": "#1f77b4",
}


lineplot_component = make_plot_component(
COLORS,
post_process=post_process_lines,
)

model = LuckVsSkillModel()

model_params = {
"num_agents": 200,
"alpha": Slider("Skill impact (α)", 0.05, 0.0, 0.2, 0.01), # noqa: RUF001
"initial_wealth": 100,
"bet_size": 1,
}

page = SolaraViz(
model,
components=[lineplot_component],
model_params=model_params,
name="Luck vs Skill: Short-Term Gambling",
)
57 changes: 57 additions & 0 deletions examples/gambling/model.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to a subfolder, ‎examples/gambling/ for example

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for taking the time to review my PR. I’ve moved the example into examples/gambling, restored the main README, and added a dedicated README for the example.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from mesa import Model
from mesa.datacollection import DataCollector

from .agent import GamblingAgent


class LuckVsSkillModel(Model):
def __init__(
self,
num_agents=200,
alpha=0.05,
initial_wealth=100,
bet_size=1,
seed=None,
):
super().__init__(seed=seed)

self.alpha = alpha
self.steps = 0

# Create agents
for _ in range(num_agents):
agent = GamblingAgent(
model=self,
skill=self.random.random(),
wealth=initial_wealth,
bet_size=bet_size,
)
self.agents.add(agent)

self.datacollector = DataCollector(
model_reporters={
"Step": lambda m: m.steps,
"Top 10": self.top_10_skill,
"Bottom 10": self.bottom_10_skill,
}
)

def step(self):
self.steps += 1

for agent in list(self.agents):
agent.step()

self.datacollector.collect(self)

def top_10_skill(self):
agents = sorted(self.agents, key=lambda a: a.wealth)
k = max(1, int(0.1 * len(agents)))
top = agents[-k:]
return sum(a.skill for a in top) / k

def bottom_10_skill(self):
agents = sorted(self.agents, key=lambda a: a.wealth)
k = max(1, int(0.1 * len(agents)))
bottom = agents[:k]
return sum(a.skill for a in bottom) / k