Skip to content

Instantly share code, notes, and snippets.

@mcpower
Forked from mossbanay/bot.py
Created March 28, 2019 12:40
Show Gist options
  • Save mcpower/b901bef0ef88fa3215507dccdd8d6cce to your computer and use it in GitHub Desktop.
Save mcpower/b901bef0ef88fa3215507dccdd8d6cce to your computer and use it in GitHub Desktop.

WHO GIVES A TOSS?

The rules of the game are as follows:

  • We play a total of N_GAMES times
  • In each game, there are N_TOSSES made
  • You can choose to bet on any of those tosses
  • You are given the odds (which are constant for each game) before starting
  • You are given your starting money before starting
  • Each game a new probability of the coin landing heads is chosen from some distribution within [0, 1] (see judge.py)
  • The probability is constant for each game
  • You cannot bet more than your current money
  • There is no minimum or maximum bet size
.mypy_cache
__pycache__
import random
from typing import Callable
from tosstypes import BotAction, CoinSide, HEADS, TAILS
class Bot:
money: float
n_tosses: int
odds: float
def __init__(self, money: float, n_tosses: int, odds: float) -> None:
self.money = money
self.n_tosses = n_tosses
self.odds = odds
def handle_toss(self, toss_result: CoinSide, bet_return: float) -> None:
pass
def next_action(self) -> BotAction:
return None
def __repr__(self) -> str:
return f'{self.__class__.__qualname__}(money={self.money})'
BotFactory = Callable[[float, int, float], Bot]
class RandomBot(Bot):
"""
RandomBot randomly chooses a side to flip for each time. It will flip
up to `max_tosses` times, betting `wager` each time.
"""
max_tosses: int
played_tosses: int
wager: float
def __init__(self, money: float, n_tosses: int, odds: float) -> None:
super().__init__(money, n_tosses, odds)
self.max_tosses = 10
self.played_tosses = 0
self.wager = 5
def handle_toss(self, toss_result: CoinSide, bet_return: float) -> None:
# Update money and number of tosses
self.money += bet_return
self.played_tosses += 1
def next_action(self) -> BotAction:
# Quit if we've played enough games
if self.played_tosses >= self.max_tosses:
return None
# Toss a random side
side = random.choice([HEADS, TAILS])
return (side, self.wager)
"""
You probably want to run
git update-index --skip-worktree botfactories.py
and modify this file yourself.
"""
from typing import List
from bot import Bot, RandomBot, BotFactory
BOT_FACTORIES: List[BotFactory] = [Bot, RandomBot]
STARTING_MONEY = 100
N_TOSSES = 100
N_ROUNDS = 100000
ROUNDS_PER_PROCESS = 1000
ODDS = 2.00
import random
from typing import List
from multiprocessing import Pool
import numpy as np
import pandas as pd
from scipy.stats import norm
from bot import Bot, RandomBot, BotFactory
from botfactories import BOT_FACTORIES
from constants import *
from tosstypes import HEADS, TAILS, SIDE, WAGER
def get_results(round_number: int) -> List[float]:
# Uniform(0, 1)
# p = random.random()
# Truncated normal
LOC = 0.5
SCALE = 0.05
# p: float = np.random.normal(loc=LOC, scale=SCALE)
# Assume that there are N_ROUNDS rounds.
p: float = norm.ppf((round_number + 0.5) / N_ROUNDS, loc=LOC, scale=SCALE)
p = max(p, 0)
p = min(p, 1)
bots: List[Bot] = [bot(STARTING_MONEY, N_TOSSES, ODDS) for bot in BOT_FACTORIES]
bot_money: List[float] = [STARTING_MONEY for _ in range(len(bots))]
for toss_number in range(N_TOSSES):
toss_result = HEADS if random.random() < p else TAILS
for bot_id, bot in enumerate(bots):
resp = bot.next_action()
if resp is None:
bot.handle_toss(toss_result, 0.0)
else:
assert(resp[WAGER] <= bot_money[bot_id])
bet_return = (ODDS - 1) * resp[WAGER] if resp[SIDE] == toss_result else -1 * resp[WAGER]
bot.handle_toss(toss_result, bet_return)
bot_money[bot_id] += bet_return
return bot_money
def main() -> None:
results: List[List[float]] = [[] for _ in range(len(BOT_FACTORIES))]
print("N_ROUNDS =", N_ROUNDS)
with Pool() as pool:
for bot_money in pool.imap_unordered(get_results, range(N_ROUNDS), ROUNDS_PER_PROCESS):
for bot_id, money in enumerate(bot_money):
results[bot_id].append(money)
averages = [np.mean(scores) for scores in results]
stddevs = [np.std(scores) for scores in results]
results_df = pd.DataFrame({'names':[bot.__qualname__ for bot in BOT_FACTORIES],
'average':averages,
'median':[np.median(scores) for scores in results],
'0.05':[np.quantile(scores, q=0.05) for scores in results],
'0.95':[np.quantile(scores, q=0.95) for scores in results],
'stddev':stddevs,
'sharpe':[
(average - 100) / stddev
if stddev > 1e-9 else float("inf")
for average, stddev in zip(averages, stddevs)
]})
print(results_df)
if __name__ == '__main__':
main()
from typing import Tuple, Optional
HEADS = False
TAILS = True
SIDE = 0
WAGER = 1
BotAction = Optional[Tuple[
bool, # side
float # wager
]]
CoinSide = bool
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment