Jugando con simulaciones de Monte Carlo

Las simulaciones de Monte Carlo son una forma de resolver problemas que se basan en repetir un experimento muchísimas veces y simplemente ver lo que ocurre. Sirven por ejemplo para cuando te enfrentas a un problema que tiene posibles soluciones o estrategias y no sabes muy bien cuál escoger. Se pueden utilizar en bolsa para evaluar el resultado de diferentes formas de actuar (por ejemplo: si me espero a vender mis acciones hasta que estén como mínimo al 120% del precio al que las compré ganaré o perderé dinero comparado con venderlas cuando lleguen al 100%?)

Otro ejemplo más sencillo de modelar en Python. Supongamos que cada vez que compramos una caja de cereales nos regalan un cromo aleatorio y que en total hay 6 cromos. ¿De media cuántas cajas espero tener que comprar para tener la colección completa?

El código que sigue a continuación simula la compra de cajas de cereales hasta alcanzar los 6 cromos y repite el juego un millón de veces. (Idea inspirada de un ejemplo de MathMan)

import random

num_cards = 6
games_to_play = 100000

class Game(object):
    def __init__(self):
        self.tosses = 0
        self.cards = []

    def pickCard(self):
        self.tosses += 1
        newcard = random.randint(1, num_cards)
        if not newcard in self.cards:
            self.cards.append(newcard)

    def run(self):
        while len(self.cards) < num_cards:
            self.pickCard()
        return self.tosses

if __name__ == '__main__':
    tosses = []
    for i in range(games_to_play):
        g = Game()
        tosses.append(g.run())

    print 'Cajas necesarias: %.2f' % (float(sum(tosses)) / len(tosses))

Tras ejecutar el script el resultado es 15 (14.70), lo que quiere decir que podemos esperar tener que comprar ese número de cajas de cereales antes de tener los 6 cromos.

Este otro juego simula diferentes estrategias a la hora de jugar a los dados. Si se acierta a la hora de elegir el número se gana 1 punto y si se falla se pierde 1 punto. Al haber solo una posibilidad entre 6 de acertar no esperamos hacernos ricos.

import random

games_to_play = 10000000

class Game(object):
    def __init__(self, strategy):
        self.money = 0
        self.strategy = strategy

    def run(self):
        if self.strategy.makeGuess() == random.randint(1, 6):
            self.money += 1
        else:
            self.money -= 1

class Always6(object):
    def makeGuess(self):
        return 6

class Always3(object):
    def makeGuess(self):
        return 3

class Random(object):
    def makeGuess(self):
        return random.randint(1, 6)

class Stair(object):
    def __init__(self):
        self.count = 0

    def makeGuess(self):
        self.count += 1
        return self.count % 5 + 1

if __name__ == '__main__':
    tosses = []
    for inst in (Always3(), Always6(), Random(), Stair()):
        g = Game(inst)
        for i in range(games_to_play):
            g.run()

        print "%s\t %d " % (inst.__class__.__name__, g.money)

Los resultados tras jugar 10 millones de veces son:

Estrategia Cuenta del banco Descripción de la estrategia
Always3 -6663480 Elige siempre 3
Always6 -6665452 Elige siempre 6
Random -6668190 Elige un número al azar
Stair -6670780 Elige 1, luego 2, luego 3, etc hasta llegar al 6 y vuelta a empezar

De las estrategias usadas la ganadora (que aquí significa que es con la que menos dinero perdemos) es elegir siempre 3, así que ya sabéis si alguna vez váis a jugar a los dados siempre al 3.

Para los curiosos, Python usa Mersenne Twister como generador de números pseudoaleatorios.