Bots: características avanzadas

Estas son características avanzadas que en su mayoría no están soportadas en oTree Studio.

Bots de línea de comandos

Una alternativa a ejecutar bots en tu navegador web es ejecutarlos en la línea de comandos. Los bots de línea de comandos se ejecutan más rápido y requieren menos configuración.

Ejecuta esto:

otree test mysession

Para probar con un número específico de participantes (de lo contrario, se utilizará por defecto num_demo_participants):

otree test mysession 6

Para ejecutar pruebas para todas las configuraciones de sesión:

otree test

Exportando datos

Utiliza el --export para exportar los resultados a un archivo CSV:

otree test mysession --export

Para especificar la carpeta donde se guardan los datos, haz lo siguiente:

otree test mysession --export=myfolder

Bots de navegador de línea de comandos

Puedes lanzar bots de navegador desde la línea de comandos, utilizando otree browser_bots.

  • Asegúrate de que Google Chrome esté instalado, o configura BROWSER_COMMAND en settings.py (más información a continuación).

  • Configura la variable de entorno OTREE_REST_KEY como se describe en REST.

  • Ejecuta tu servidor.

  • Cierra todas las ventanas de Chrome.

  • Ejecuta esto:

    otree browser_bots mysession
    

Esto abrirá varias pestañas de Chrome y ejecutará los bots. Cuando termine, las pestañas se cerrarán y verás un informe en tu ventana de terminal.

Si Chrome no cierra las ventanas correctamente, asegúrate de haber cerrado todas las ventanas de Chrome antes de lanzar el comando.

Bots de navegador en línea de comandos en un servidor remoto (por ejemplo, Heroku)

Si el servidor se está ejecutando en un host/puerto diferente al habitual http://localhost:8000, necesitas pasar --server-url. Por ejemplo, si está en Heroku, lo harías así:

otree browser_bots mysession --server-url=https://YOUR-SITE.herokuapp.com

Eligiendo configuraciones y tamaños de sesión

Puedes especificar el número de participantes:

otree browser_bots mysession 6

Para probar todas las configuraciones de sesión, simplemente ejecuta esto:

otree browser_bots

Bots de navegador: notas varias

Puedes usar un navegador diferente a Chrome configurando BROWSER_COMMAND en settings.py. Luego, oTree abrirá el navegador ejecutando algo como subprocess.Popen(settings.BROWSER_COMMAND).

Casos de prueba

Puedes definir un atributo cases en tu clase PlayerBot que liste diferentes casos de prueba. Por ejemplo, en un juego de bienes públicos, puede que desees probar 3 escenarios:

  • Todos los jugadores contribuyen con la mitad de su dotación.

  • Todos los jugadores no contribuyen en absoluto.

  • Todos los jugadores contribuyen con su dotación completa (100 puntos).

Podemos llamar a estos 3 casos de prueba «basic», «min» y «max», respectivamente, y ponerlos en cases. Luego, oTree ejecutará el bot 3 veces, una para cada caso de prueba. Cada vez, un valor diferente de cases se asignará a self.case en el bot.

Por ejemplo:

class PlayerBot(Bot):

    cases = ['basic', 'min', 'max']

    def play_round(self):
        yield (pages.Introduction)

        if self.case == 'basic':
            assert self.player.payoff == None

        if self.case == 'basic':
            if self.player.id_in_group == 1:
                for invalid_contribution in [-1, 101]:
                    yield SubmissionMustFail(pages.Contribute, {'contribution': invalid_contribution})
        contribution = {
            'min': 0,
            'max': 100,
            'basic': 50,
        }[self.case]

        yield (pages.Contribute, {"contribution": contribution})
        yield (pages.Results)

        if self.player.id_in_group == 1:

            if self.case == 'min':
                expected_payoff = 110
            elif self.case == 'max':
                expected_payoff = 190
            else:
                expected_payoff = 150
            assert self.player.payoff == expected_payoff

Nota

Si utilizas casos, es mejor usar Bots de línea de comandos ya que los bots de navegador solo ejecutarán un solo caso.

cases debe ser una lista, pero puede contener cualquier tipo de dato, como cadenas, enteros o incluso diccionarios. Aquí hay un bot de juego de confianza que utiliza diccionarios como casos.

class PlayerBot(Bot):

    cases = [
        {'offer': 0, 'return': 0, 'p1_payoff': 10, 'p2_payoff': 0},
        {'offer': 5, 'return': 10, 'p1_payoff': 15, 'p2_payoff': 5},
        {'offer': 10, 'return': 30, 'p1_payoff': 30, 'p2_payoff': 0}
    ]

    def play_round(self):
        case = self.case
        if self.player.id_in_group == 1:
            yield (pages.Send, {"sent_amount": case['offer']})

        else:
            for invalid_return in [-1, case['offer'] * C.MULTIPLICATION_FACTOR + 1]:
                yield SubmissionMustFail(pages.SendBack, {'sent_back_amount': invalid_return})
            yield (pages.SendBack, {'sent_back_amount': case['return']})

        yield (pages.Results)


        if self.player.id_in_group == 1:
            expected_payoff = case['p1_payoff']
        else:
            expected_payoff = case['p2_payoff']

        assert self.player.payoff == expected_payoff

error_fields

Al usar SubmissionMustFail en formularios con múltiples campos, puedes usar error_fields para mayor exhaustividad.

Por ejemplo, supongamos que enviamos un age válido, pero un weight y height inválidos:

yield SubmissionMustFail(
    pages.Survey,
    dict(
        age=20,
        weight=-1,
        height=-1,
    )
)

Lo que falta es que el sistema de bots no nos dice exactamente por qué falla la presentación. ¿Es un weight, height inválido, o ambos? error_fields puede resolver la ambigüedad:

yield SubmissionMustFail(
    pages.Survey,
    dict(
        age=20,
        weight=-1,
        height=-1,
    ),
    error_fields=['weight', 'height']
)

Esto verificará que weight y height contenían errores, pero age no.

Si error_message devuelve un error, entonces error_fields será ['__all__'].

Nota miscelánea

En los bots, es arriesgado asignar player = self.player (o participant = self.participant, etc.), aunque ese tipo de código se fomente en otros lugares.

Porque si hay un yield en medio, los datos pueden estar desactualizados:

player = self.player
expect(player.money_left, cu(10))
yield pages.Contribute, dict(contribution=cu(1))
# don't do this!
# "player" variable still has the data from BEFORE pages.Contribute was submitted.
expect(player.money_left, cu(9))

Es más seguro usar self.player.money_left directamente, porque hacer self.player obtiene los datos más recientes de la base de datos.

Páginas en vivo

Para probar métodos en vivo con bots, define call_live_method como una función de nivel superior en tests.py. (No disponible en oTree Studio.) Esta función debe simular la secuencia de llamadas a tu live_method. El argumento method simula el método en vivo en tu modelo de Player. Por ejemplo, method(3, 'hello') llama al método en vivo en el Jugador 3 con data establecido en 'hello'. Por ejemplo:

def call_live_method(method, **kwargs):
    method(1, {"offer": 50})
    method(2, {"accepted": False})
    method(1, {"offer": 60})
    retval = method(2, {"accepted": True})
    # you can do asserts on retval

kwargs contiene al menos los siguientes parámetros.

  • case como se describe en Casos de prueba.

  • page_class: la clase de página actual, por ejemplo, pages.MyPage.

  • round_number

call_live_method se ejecutará automáticamente cuando el bot más rápido del grupo llegue a una página con live_method. (Otros bots pueden estar en páginas anteriores en ese momento, a menos que restrinjas esto con un WaitPage.)