Métodos de Monte Carlo usando Google Cloud Dataproc e Apache Spark

Usando o Google Cloud Dataproc e o Apache Spark, você dispõe da infraestrutura e da capacidade para executar simulações de Monte Carlo desenvolvidas em Java, Python ou Scala.

Os métodos Monte Carlo ajudam a responder a uma grande variedade de perguntas relacionadas a negócios, engenharia, ciências, matemática e outros campos. Em uma simulação Monte Carlo, com o uso de amostragem aleatória repetida para criar uma distribuição de probabilidade para uma variável, você consegue respostas para perguntas que não conseguiria com outros métodos. Em finanças, por exemplo, a determinação do preço de uma opção de equidade requer a análise de milhares de possibilidades de mudança do preço da ação ao longo do tempo. Com os métodos Monte Carlo, você simula essas oscilações em uma ampla variedade de resultados possíveis e, ao mesmo tempo, mantém o controle sobre o domínio das entradas para o problema.

No passado, a execução de milhares de simulações poderia levar um longo tempo e ter um alto custo. Com o Cloud Dataproc, a capacidade é provisionada sob demanda e paga por minuto. Com o Apache Spark, você usa os clusters de dezenas, centenas ou milhares de servidores para executar simulações de maneira intuitiva, e faz o escalonamento de acordo com as suas necessidades. Isso significa que você consegue executar mais simulações com maior rapidez, o que ajuda a inovar no seu negócio com mais agilidade e possibilita um melhor gerenciamento dos riscos.

A segurança é sempre importante quando lidamos com dados financeiros. O Cloud Dataproc é executado no GCP, o que ajuda a manter seus dados seguros, protegidos e particulares de várias maneiras. Por exemplo, todos os dados são criptografados durante a transmissão e em repouso, e o Cloud Platform está em conformidade com as diretrizes ISO 27001,SOC3, FINRA e PCI.

Objetivos

  • Criar um cluster gerenciado do Cloud Dataproc, com o Apache Spark pré-instalado.
  • Executar uma simulação Monte Carlo com o Python que faça a estimativa do crescimento de um portfólio de ações ao longo do tempo.
  • Executar uma simulação Monte Carlo com o Scala que simule os lucros de um cassino.

Pré-requisitos

Criar um cluster do Cloud Dataproc

Siga as etapas para criar um cluster do Cloud Dataproc no console do Google Cloud Platform. As configurações padrão do cluster, que incluem dois worker nodes, serão suficientes para este tutorial.

Desativar a geração de registros para avisos

Por padrão, um registro detalhado é impresso na janela de terminal pelo Apache Spark. Para fazer esse tutorial, sugerimos alterar o nível de geração de registros para registrar somente erros. Siga estas etapas:

Conectar-se ao node mestre do cluster do Cloud Dataproc com o SSH

  1. No Console do GCP, acesse a página "Instâncias de VM".

    Acessar a página "Instâncias da VM"

  2. Na lista de instâncias de máquinas virtuais, clique em SSH na linha da instância com que você quer se conectar.

Uma janela de navegador é aberta no diretório principal do node mestre.

Connected, host fingerprint: ssh-rsa 2048 ...
...
user@clusterName-m:~$

Alterar a configuração de geração de registros

Edite /etc/spark/conf/log4j.properties.

sudo nano /etc/spark/conf/log4j.properties

Defina log4j.rootCategory igual a ERROR.

# Set only errors to be logged to the console
log4j.rootCategory=ERROR, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n

Salve as alterações e saia do editor. Se você quiser reativar a geração de registros para desfazer a alteração, restaure o valor de .rootCategory para o valor original INFO.

Linguagens de programação do Spark

O Spark é compatível com Python, Scala e Java como linguagens de programação para aplicativos autônomos, e fornece interpretadores interativos para o Python e o Scala. A escolha da linguagem é uma questão de preferência pessoal. Neste tutorial, usamos os interpretadores interativos porque são uma maneira fácil e rápida de fazer testes. Basta alterar o código, testar com valores diferentes de entrada e ver os resultados.

Como estimar o crescimento do portfólio

Em finanças, os métodos Monte Carlo são, às vezes, usados para executar simulações que tentam prever o desempenho de um investimento. Para responder a perguntas sobre o desempenho do portfólio na média ou nos cenários mais pessimistas, amostras aleatórias dos resultados em várias condições prováveis de mercado são produzidas por essas simulações.

Siga estas etapas para criar uma simulação que usa os métodos Monte Carlo para estimar o crescimento de um investimento financeiro com base em alguns fatores de mercado comuns.

  1. Inicie o interpretador do Python a partir do node mestre do Cloud Dataproc.

    pyspark
    

    Aguarde pelo prompt do Spark (>>>).

  2. Insira o código a seguir. Como de costume, ao fazer a programação com Python, tenha o cuidado de manter os recuos na definição da função.

    >>> import random
    >>> import time
    >>> from operator import add
    
    >>> def grow(seed):
            random.seed(seed)
            portfolio_value = INVESTMENT_INIT
            for i in range(TERM):
                growth = random.normalvariate(MKT_AVG_RETURN, MKT_STD_DEV)
                portfolio_value += portfolio_value * growth + INVESTMENT_ANN
            return portfolio_value
    
  3. Pressione "Return" até que o prompt do Spark apareça novamente.

    No código acima, foi definida uma função que modela o que pode acontecer quando um investidor tem uma conta de aposentadoria existente que é investida no mercado de ações, onde eles depositam dinheiro anualmente. A função gera um retorno de investimento aleatório, como uma porcentagem, para cada ano que um termo especificado durar. Essa função gera um valor de semente como parâmetro que é usado para realimentar o gerador de números aleatórios. Isso garante que a função não receba a mesma lista de números cada vez que ela é executada. Com a função random.normalvariate, esses valores aleatórios ocorrem em uma distribuição normal para a média e o desvio padrão especificados. A função aumenta o valor do portfólio de acordo com o volume de crescimento, que pode ser positivo ou negativo, e adiciona uma soma anual que representa o investimento adicional.

    As constantes obrigatórias serão definidas em uma etapa posterior.

  4. A próxima etapa é criar várias sementes para alimentar a função. No prompt do Spark, insira o seguinte código, que gera 10.000 sementes:

    >>> seeds = sc.parallelize([time.time() + i for i in xrange(10000)])
    

    O resultado da operação parallelize é um conjunto de dados distribuídos resiliente (RDD, na sigla em inglês), que é uma coleção de elementos otimizados para processamento paralelo. Nesse caso, o RDD contém sementes que são baseadas no horário atual do sistema.

    Quando o RDD é criado, os dados são divididos no Spark com base no número de workers e núcleos disponíveis. Nesse caso, o Spark escolhe usar oito partes, uma parte para cada núcleo. Isso atende a essa simulação, que tem 10.000 itens de dados. Para simulações maiores, cada parte pode ser maior do que o limite padrão. Nesse caso, basta especificar um segundo parâmetro no parallelize para aumentar o número de partes. Isso ajuda a manter o tamanho de cada parte gerenciável e, ao mesmo tempo, o Spark continua a usar oito núcleos.

  5. Para alimentar o RDD que contém as sementes para a função de crescimento, insira o seguinte código:

    >>> results = seeds.map(grow)
    

    Com o método map, cada semente do RDD é passada para a função grow e cada resultado é anexado a um novo RDD, que é armazenado em results. Observe que essa operação de transformação não produz os resultados imediatamente. Isso não é feito no Spark antes dos resultados serem necessários. Devido a essa avaliação lenta, o código pode ser inserido sem ter as constantes definidas.

  6. Especifique alguns valores para que a função trabalhe com eles. Insira o código a seguir:

    >>> INVESTMENT_INIT = 100000  # starting amount
    >>> INVESTMENT_ANN = 10000  # yearly new investment
    >>> TERM = 30  # number of years
    >>> MKT_AVG_RETURN = 0.11 # percentage
    >>> MKT_STD_DEV = 0.18  # standard deviation
    
  7. Chame reduce para agregar os valores no RDD. Insira o código a seguir para somar os resultados nesse conjunto:

    >>>  sum = results.reduce(add)
    
  8. Insira o código a seguir para estimar e exibir o retorno médio:

    >>> print sum / 10000.
    

    Verifique se o caractere ponto (.) foi incluído no final. Ele representa um valor aritmético de ponto flutuante.

  9. Agora, altere uma pressuposição e veja como os resultados mudam. Por exemplo, insira um novo valor para o retorno médio do mercado:

    >>> MKT_AVG_RETURN = 0.07
    
  10. Execute a simulação novamente. Tudo pode ser feito com o seguinte código:

    >>> print sc.parallelize([time.time() + i for i in xrange(10000)]) \
                .map(grow).reduce(add)/10000.
    
  11. Ao concluir o teste, pressione CTRL+D para sair do interpretador do Python.

Como programar uma simulação Monte Carlo no Scala

A cidade de Monte Carlo é conhecida como um destino de apostas. Nesta seção, você usará o Scala para criar uma simulação que modela os fatores matemáticos dos quais um cassino tira proveito em um jogo de azar. O "house edge" de um cassino real varia bastante entre os jogos. Ele pode ser maior do que 20% no keno, por exemplo. Neste tutorial, um jogo simples será criado, onde a casa tem somente uma vantagem de 1%. Veja como ele funciona:

  • O jogador faz uma aposta, que consiste de algumas fichas do fundo de apostas.
  • O jogador rola um dado de cem lados (que legal, não?).
  • Quando o resultado do rolamento é um número entre 1 e 49, o jogador ganha o dobro do dinheiro.
  • Caso o resultado seja entre 50 e 100, o jogador perde a aposta.

Veja que, nesse jogo, o jogador tem uma desvantagem de 1%: em 51 dos 100 resultados possíveis de cada rolamento, ele perde.

Siga estas etapas para criar e executar o jogo:

  1. Inicie o interpretador do Scala a partir do node mestre do Cloud Dataproc.

    spark-shell
    
  2. Copie e cole o código a seguir para criar o jogo. O Scala não tem os mesmos requisitos que o Phyton em relação ao recuo. Portanto, é possível simplesmente copiar e colar o código no prompt scala>.

    val STARTING_FUND = 10
    val STAKE = 1   // the amount of the bet
    val NUMBER_OF_GAMES = 25
    
    def rollDie: Int = {
        val r = scala.util.Random
        r.nextInt(99) + 1
    }
    
    def playGame(stake: Int): (Int) = {
        val faceValue = rollDie
        if (faceValue < 50)
            (2*stake)
        else
            (0)
    }
    
    // Function to play the game multiple times
    // Returns the final fund amount
    def playSession(
       startingFund: Int = STARTING_FUND,
       stake: Int = STAKE,
       numberOfGames: Int = NUMBER_OF_GAMES):
       (Int) = {
    
        // Initialize values
        var (currentFund, currentStake, currentGame) = (startingFund, 0, 1)
    
        // Keep playing until number of games is reached or funds run out
        while (currentGame <= numberOfGames && currentFund > 0) {
    
            // Set the current bet and deduct it from the fund
            currentStake = math.min(stake, currentFund)
            currentFund -= currentStake
    
            // Play the game
            val (winnings) = playGame(currentStake)
    
            // Add any winnings
            currentFund += winnings
    
            // Increment the loop counter
            currentGame += 1
        }
        (currentFund)
    }
    
  3. Pressione Enter até que o prompt scala> apareça.

  4. Insira o código a seguir para jogar 25 vezes, que é o valor padrão de NUMBER_OF_GAMES.

    playSession()
    

    Seu fundo de apostas começou com o valor de 10 unidades. E agora, está mais alto ou mais baixo?

  5. Agora simule 10.000 jogadores apostando 100 fichas por jogo. Jogue 10.000 vezes em uma sessão. Nessa simulação do Monte Carlo, a probabilidade de você perder todo o dinheiro será calculada antes do fim da sessão. Insira o código a seguir:

    (sc.parallelize(1 to 10000, 500)
      .map(i => playSession(100000, 100, 250000))
      .map(i => if (i == 0) 1 else 0)
      .reduce(_+_)/10000.0)
    

    Observe que a sintaxe .reduce(_+_) é uma abreviação do Scala para a agregação usando a função de soma. A funcionalidade dela é equivalente à sintaxe .reduce(add), que você viu no exemplo do Python.

    No código anterior, as seguintes etapas foram executadas:

    • criação de RDD com os resultados da sessão jogada;
    • substituição dos resultados dos jogadores falidos pelo número 1 e dos resultados diferentes de zero pelo número 0;
    • soma da contagem de jogadores falidos;
    • divisão da contagem pelo número de jogadores.

Um resultado típico seria:

0.998

Isso representa uma garantia quase total de que você perderia todo o dinheiro, mesmo se o cassino tivesse somente uma vantagem de 1%.

Próximas etapas

Consulte Cloud Dataproc: enviar um job para saber mais sobre o envio de jobs do Spark para o serviço do Cloud Dataproc sem a necessidade de usar SSH para se conectar ao cluster.

  • Teste outros recursos do Google Cloud Platform. Veja nossos tutoriais.
  • Aprenda a usar os produtos do Google Cloud Platform para criar soluções completas.
Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…