Skip to content

Instantly share code, notes, and snippets.

@dimo414
Created March 4, 2020 06:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dimo414/91d1c1c1431c8cb1264903469705056a to your computer and use it in GitHub Desktop.
Save dimo414/91d1c1c1431c8cb1264903469705056a to your computer and use it in GitHub Desktop.
St. Petersburg Paradox Simulation

The St. Petersburg paradox, in a nutshell, is: why would you refuse to play a game with an unlimited expected payout? Shouldn't you be willing to pay any finite price to play, since your expected return is infinite?

This simulation explores what the Stanford Encyclopedia refers to as the "Average Win" explanation, that:

Expected value is in effect average payback in the long run.... [I]f you're going to play St. Petersburg with a substantial fee per game, you're likely to have to play a very very long time before you come out with a positive net payoff. Ordinary practical considerations thus apply... You may have to play a very long time before winning once. But worse: in a series of losses, the amount needed for the next bet rises. How much bankroll would you need to guarantee success...? Whatever bankroll you take into the casino, there's a chance that this will not be enough.

The simulation demonstrates what sort of bankroll you'd need in order to confidently play the game. A 'Player' bids a certain amount per game, and plays until they break even. Given an infinite amount of time every player, no matter the cost, will earn an infinite payout, but in the meantime they risk losing very large sums of money even for very small bids. At just $10 a round, a player will need tens of thousands of dollars to confidently break even, and could easily be risking hundreds of thousands of dollars. It would be utterly foolish for anyone with less than many billions of dollars to pay $20 to play, and even that may prove insufficient.

In a sense, the St. Petersburg paradox is the inverse of the Martingale betting strategy; doubling your bet with every loss will, in infinite time, yield a (small) positive outcome. Here, the payout is what doubles, but it similarly requires vast sums of money and time to ensure a positive (let alone infinite) reward. The player with finite time and resources is wise to limit themselves to very small bets.

It is curious that people generally respond negatively to St. Petersburg but positively to the Martingale, despite the former being the better option (at the limit). This difference suggests a shared intuition; the average person finds it harder to believe that nothing will go right for them (a failed Martingale) than that everything will go their way (a successful St. Petersburg) though these events are of course equally probable. Eventually, they reason, the coin will land the other side up.

Example output of simulating 25 players bidding between $1 and $19 dollars until they break even.
$20 and up is somewhat prohibitively expensive to even compute.
Bid Max Loss Avg. Loss Avg. Rounds
1 -1 -1.0 1.92
2 -6 -3.32 3.88
3 -39 -13.4 10.2
4 -154 -32.4 24.08
5 -635 -94.16 65.8
6 -3891 -533.2 472.04
7 -35060 -3050.6 2317.6
8 -76995 -8164.88 6436.84
9 -41424 -9484.8 3996.04
10 -171313 -29358.88 13209.8
11 -1817817 -164647.4 75652.56
12 -6077369 -389487.52 198849.68
13 -29417405 -2235890.88 1204786.8
14 -68522251 -4690892.8 3312664.8
15 -697529345 -34524528.36 38543314.16
16 -981710460 -112370336.48 50189905.0
17 -8367220378 -530350283.36 316971080.32
18 -9630733554 -826860638.64 311685474.2
19 -16393583872 -878528082.04 362517051.32
#!/usr/bin/env python3
'''
Testing the St. Petersburg Paradox:
http://en.wikipedia.org/wiki/St._Petersburg_paradox
http://plato.stanford.edu/entries/paradox-stpetersburg/
Copyright 2014 Michael Diamond
'''
import argparse, random, sys
def st_peter():
'''Play one round of St. P's, returning winnings'''
payout = 1
# http://stackoverflow.com/a/6824868/113632
while not random.getrandbits(1):
payout *= 2
return payout
class Player:
'''A St. Petersburg player who stops as soon as his
return is positive, running as negative as necessary
until then'''
def __init__(self, bid):
self.bid = bid
self.bank = 0
self.max_loss = 0
self.rounds = 0
def play(self):
while self.bank <= 0:
self.rounds += 1
self.bank -= self.bid
self.max_loss = min(self.max_loss, self.bank)
self.bank += st_peter()
def __str__(self):
return "Player [Rounds: %s, Max Lost: %s, Winnings: %s]" % (self.rounds, self.max_loss, self.bank)
def runBid(bid, times):
players = [Player(bid) for _ in range(times)]
for player in players:
player.play()
max_loss = min(players, key=lambda x: x.max_loss).max_loss
avg_loss = sum(p.max_loss for p in players) / len(players)
avg_rounds = sum(p.rounds for p in players) / len(players)
return (max_loss, avg_loss, avg_rounds)
def _positive_int(arg):
value = int(arg)
if value <= 0:
raise argparse.ArgumentTypeError('%s is not a positive integer' % arg)
return value
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--header', help="Print header line", action='store_true')
parser.add_argument('--min_bid', help="Minimum amount to bid", default=1, type=_positive_int)
parser.add_argument('--max_bid', help="Maximum amount to bid", default=25, type=_positive_int)
parser.add_argument('--increment_bid', help="Increment bid by", default=1, type=_positive_int)
parser.add_argument('--times', help="Times to simulate each bid", default=25, type=_positive_int)
args = parser.parse_args()
if args.header:
print('\t'.join(("Bid", "Max Loss", "Avg. Loss", "Avg. Rounds")))
for bid in range(args.min_bid, args.max_bid+1, args.increment_bid):
ret = runBid(bid, args.times)
print('\t'.join(map(str, (bid,) + ret)))
print(bid, file=sys.stderr)
sys.stdout.flush()
if __name__ == '__main__':
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment