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.

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:

<th>
  Cuenta del banco
</th>

<th>
  Descripción de la estrategia
</th>
<td>
  -6663480
</td>

<td>
  Elige siempre 3
</td>
<td>
  -6665452
</td>

<td>
  Elige siempre 6
</td>
<td>
  -6668190
</td>

<td>
  Elige un número al azar
</td>
<td>
  -6670780
</td>

<td>
  Elige 1, luego 2, luego 3, etc hasta llegar al 6 y vuelta a empezar
</td>
Estrategia
Always3
Always6
Random
Stair

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.

Casino de Monte Carlo.

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