War of Attrition
Two-player waiting game exploring mixed strategies and the option value of waiting.
Level:Intermediate
FAQ
- How are players' quitting times chosen?
- Each player draws a quit time from an exponential distribution inversely proportional to their cost of waiting.
- How is the winner determined?
- Whichever player does not trigger first_quit wins the prize when the other leaves.
- How is surplus calculated?
- The prize minus the sum of both players' waiting costs over the contest length.
simulation.py
War of Attrition with endogenous stopping
Two players compete for a single prize by waiting each other out. Each draws a private cost of waiting from an exponential distribution and independently chooses when to quit. Whoever quits first forfeits the prize but pays less in waiting cost.
We repeat many rounds to build up a distribution of contest lengths and the surplus captured from the prize. Lower waiting costs translate into longer expected times before quitting, illustrating the real option value of holding out.
from tys import probe, progress
def simulate(cfg: dict):
"""Run repeated wars of attrition and track outcomes."""
import random
import simpy
prize = cfg["prize"]
mean_cost = cfg["mean_cost"]
rounds = cfg.get("rounds", 100)
rng = random.Random(cfg.get("seed"))
lengths: list[float] = []
surpluses: list[float] = []
for i in range(rounds):
cost_a = rng.expovariate(1 / mean_cost)
cost_b = rng.expovariate(1 / mean_cost)
env = simpy.Environment()
first_quit = env.event()
Each player waits a random time inversely proportional to their cost.
def player(name: str, cost: float):
t_quit = rng.expovariate(cost)
yield env.timeout(t_quit)
if not first_quit.triggered:
first_quit.succeed((name, t_quit, cost))
env.process(player("A", cost_a))
env.process(player("B", cost_b))
env.run(until=first_quit)
loser, t, loser_cost = first_quit.value
winner_cost = cost_b if loser == "A" else cost_a
lengths.append(t)
total_cost = (loser_cost + winner_cost) * t
surpluses.append(prize - total_cost)
probe("contest_length", i, t)
probe("surplus", i, prize - total_cost)
probe("cost_variance", i, abs(cost_a - cost_b))
progress(int(100 * (i + 1) / rounds))
avg_len = sum(lengths) / rounds
avg_surplus = sum(surpluses) / rounds
return {
"avg_length": avg_len,
"avg_surplus": avg_surplus,
}
def requirements():
return {
"builtin": ["micropip", "pyyaml"],
"external": ["simpy==4.1.1"],
}
Default.yaml
prize: 10
mean_cost: 1.0
rounds: 200
seed: 42
Charts (Default)
contest_length
Samples | 200 @ 0.00–199.00 |
---|---|
Values | min 0.00, mean 0.87, median 0.44, max 9.06, σ 1.40 |
surplus
Samples | 200 @ 0.00–199.00 |
---|---|
Values | min 2.29, mean 8.97, median 9.35, max 10.00, σ 1.10 |
cost_variance
Samples | 200 @ 0.00–199.00 |
---|---|
Values | min 0.01, mean 1.06, median 0.71, max 8.86, σ 1.11 |
Final Results (Default)
Metric | Value |
---|---|
avg_length | 0.87 |
avg_surplus | 8.97 |