Junho de 2016

Volume 31 - Número 6

Execução de Teste - Introdução aos Mercados de Previsão

Por James McCaffrey

James McCaffreyImaginemos que você queira prever o resultado de um jogo futuro do campeonato de futebol entre Xrays e Yanks. Você encontra um grupo de 20 especialistas em futebol e dá a cada um deles R$ 500 em tokens. Os especialistas podem comprar e vender cotas de cada uma das duas equipes, de forma que seja semelhante à forma como o mercado de ações funciona.

Quando um especialista compra cotas de uma equipe, digamos do Xrays, o preço de uma cota dessa equipe aumenta e o preço de uma cota da outra equipe diminui. Com o tempo, os especialistas comprarão e venderão cotas das duas equipes até os preços estabilizarem e, em seguida, você poderá deduzir a probabilidade de cada equipe ganhar.

Você interrompe as negociações no dia antes do jogo do campeonato. Após a conclusão do jogo e a determinação do vencedor, você paga aos especialistas que possuem cotas na equipe vencedora de acordo com o último preço da equipe quando as negociações fecharam. Como os especialistas sabem que serão pagos, eles são incentivados a dar sua opinião real durante as negociações.

O que eu acabei de descrever é chamado de mercado de previsão. Neste artigo, descreverei a matemática por trás dos mercados de previsão e mostrarei como implementar as funções de chave em código. É pouco provável que você tenha que criar um mercado de previsão no seu trabalho do dia a dia, mas creio que você achará as ideias bastante interessantes. Além disso, algumas das técnicas de programação apresentadas neste artigo podem ser usadas em cenários de desenvolvimento de software mais comuns.

Este artigo pressupõe que você tenha habilidades de codificação que sejam, pelo menos, de iniciante, mas não pressupõe que você saiba algo sobre mercados de previsão. Apresento um programa de demonstração completo e você também pode obter o código-fonte do download que acompanha este artigo. A demonstração usa C#, mas você não deve ter problemas ao refatorar o código para outra linguagem, se desejar.

Observe que esta é uma introdução informal aos mercados de previsão, pretendida principalmente para desenvolvedores de software. Tenho flexibilidade quanto à terminologia e definições para manter as ideias principais o mais clara possíveis.

Um exemplo

É provável que os mercados de previsão sejam explicados de forma mais clara com um exemplo concreto. Veja o programa de demonstração na Figura 1. Após algumas mensagens preliminares, a saída de demonstração começa com:

Setting liquidity parameter = 100.0

Initial number of shares owned of teams [0] and [1] are:
0 0

Initial inferred probabilities of winning are:
 0.5000  0.5000

Uma Demonstração do Mercado de Previsão
Figura 1 - Uma Demonstração do Mercado de Previsão

O parâmetro de liquidez será explicado em detalhes em breve, mas, por agora, é suficiente saber que a liquidez controla a forma como os preços do mercado reagem à compra e venda. Valores maiores de liquidez produzem alterações menores nos preços.

Inicialmente, nenhuma cota pertence aos especialistas. Como o número de cotas adquirido por cada equipe é o mesmo (zero), é justo que a probabilidade inicial deduzida que a equipe ganhará seja de 0,50.

A próxima parte da saída da demonstração é:

Current costs for one share of each team are:
 $0.5012  $0.5012

A qualquer momento, uma cota de cada equipe tem um determinado preço. Os especialistas precisam saber esse preço porque lidam com dinheiro real. Como as probabilidades iniciais de ganho são iguais, é justo que os preços para uma cota de cada equipe também sejam iguais.

A próxima parte da saída da demonstração é:

Update: expert [01] buys 20 shares of team [0]

Cost of transaction to expert was: $10.50

O Especialista nº 1 acredita que a equipe 0, Xrays, vencerão e comprarão 20 cotas da equipe 0. O custo para o especialista é de R$ 10,50. Observe que o preço para 20 cotas (R$ 10,50) não é o mesmo que 20 vezes o preço de uma única cota (20 * R$0,5012 = R$ 10,02). Ao passo que cada cota é adquirida, o preço de uma cota adicional da equipe aumenta. A próxima parte da saída da demonstração é:

New number of shares owned of teams [0] and [1] are:
20 0

New inferred probabilities of winning are:
 0.5498  0.4502

A demonstração exibe o número atualizado de cotas pendente em cada equipe, (x,y) = (20,0) e computa e exibe as probabilidades deduzidas atualizadas de cada equipe vencedora (0,55, 0,45). Como os especialistas compraram mais cotas da equipe 0 do que da equipe 1, a probabilidade deduzida da equipe 0 ganhar deve ser maior que da equipe 1. O cálculo das probabilidades será explicado em breve.

A seguir, a demonstração exibe:

Current costs for one share of each team are:
 $0.5511  $0.4514

Update: expert [02] buys 20 shares of team [1]

Cost of transaction to expert was: $9.50

O novo custo por cota para cada equipe é calculado e exibido. Observe que o preço de uma cota da equipe 0 (R$ 0,55) é agora um pouco mais caro que o da equipe 1 (R$ 0,45). Isso dá aos especialistas um incentivo para comprar cotas da equipe 1, caso eles pensem que o preço é o valor ideal relativo à probabilidade da equipe 1 vencer. Nesse caso, a demonstração simula que o especialista nº 2 compra 20 cotas da equipe 1 por R$ 9,50. Próximo:

New number of shares owned of teams [0] and [1] are:
20 20

New inferred probabilities of winning are:
 0.5000  0.5000

Agora há 20 cotas pendentes para cada equipe. Então, as probabilidades deduzidas de cada equipe vencer revertem para 0,50 e 0,50.

A próxima parte da saída da demonstração é:

Current costs for one share of each team are:
 $0.5012  $0.5012

Update: expert [03] buys 60 shares of team [0]
Cost of transaction to expert was: $34.43

New number of shares owned of teams [0] and [1] are:
80 20

New inferred probabilities of winning are:
 0.6457  0.3543

O Especialista nº 3 acredita fortemente que a equipe 0 ganhará, então ele compra 60 cotas da equipe 0 por R$ 34,43. Esta transação altera o número de cotas pendentes para (80,20) e faz com que as novas probabilidades deduzidas de vencer pendam fortemente para a equipe 0 (0,65, 0,35).

A seguir, o especialista nº 1 vê que o valor de sua cota na equipe 0 aumentou bastante, para cerca de R$0,6468 por cota:

Current costs for one share of each team are:
 $0.6468  $0.3555

Update: expert [01] sells 10 shares of team [0]
Cost of transaction to expert was: $-6.34

New number of shares owned of teams [0] and [1] are:
70 20

New inferred probabilities of winning are:
 0.6225  0.3775

O Especialista nº 1 acha que a equipe 0 agora está com um preço elevado em relação às chances de vencer e vende 10 de suas 20 cotas, obtendo R$6,34 (indicado pelo sinal negativo). As novas probabilidades deduzidas se ajustam novamente e ficam um pouco iguais, mas a equipe 0 ainda tem previsão de ganhar com uma probabilidade de 0,63.

A demonstração termina quando as negociações são encerradas. As probabilidades finais são o objetivo do mercado de previsão. Após a conclusão do jogo entre Xrays e Yanks, os especialistas seriam pagos pelas cotas que eles tivessem na equipe vencedora, com base no preço da cota final da equipe vencedora. Os pagamentos encorajam os especialistas a dar sua opinião verdadeira.

As Quatro Principais Equações do Mercado de Previsão

Um mercado de previsão básico usa quatro equações matemáticas, conforme mostrado na Figura 2. As equações não são tão complicadas quanto parecem à primeira vista. Há vários modelos matemáticos que podem ser usados para definir um mercado de previsão. O modelo apresentado neste artigo é baseado no que é chamado de Regra de Pontuação do Mercado Logarítmico (LMSR).

As Quatro Principais Equações do Mercado de Previsão
Figura 2 - As Quatro Principais Equações do Mercado de Previsão

A equação 1 é a função de custo associada ao conjunto de cotas pendentes (x,y). A equação, que não é nada óbvia, vem da teoria da economia. Do ponto de vista do desenvolvedor, você pode pensar na equação como uma função auxiliar. Ela aceita x, que é o número de cotas da opção 0 e y, que é o número de cotas da opção 1, e retorna um valor. A variável b em todas as quatro equações é o parâmetro de liquidez. Suponhamos que x=20 e y=10. Se b=100,0, então C(x,y) = 100,0 * ln(exp(20/100) + exp(10/100)) = 100,0 * ln(1,22 + 1,11) = 100,0 * 0,8444 = $84,44. O valor de retorno é usado na equação 2.

A equação 2 é o custo de uma transação para um comprador. Suponhamos que um conjunto atual de cotas pendentes seja (20,10) e um especialista compre 30 cotas da opção 0. O custo dessa transação para o especialista é computado usando a equação 2 como C(20+30, 10) - C(20, 10) = C(50, 10) - C(20, 10) = 101,30 - 84,44 = $16,86. Se um especialista vender cotas, o custo da transação será um valor negativo indicando que o especialista foi pago.

A equação 3 é tecnicamente o preço de margem da opção 0 com base em um conjunto de cotas pendentes (x,y). Mas um preço de margem pode ser interpretado como a probabilidade de uma opção ganhar. A equação 4 é o preço de margem (probabilidade) da opção 1. Se você observar as duas equações de perto, perceberá que elas devem somar 1,0, conforme solicitado para um conjunto de probabilidades.

A implementação das quatro principais equações do mercado de previsão é simples. O programa de demonstração implementa o custo e a equação 1, como:

static double Cost(int[] outstanding, double liq)
{
  double sum = 0.0;
  for (int i = 0; i < 2; ++i)
    sum += Math.Exp(outstanding[i] / liq);
  return liq * Math.Log(sum);
}

O método Custo é virtualmente uma translação exata da equação 1. Observe que o método Custo assume que há apenas duas opções. Para simplificar, nenhuma verificação de erro será realizada.

A equação 2 também é simples de implementar:

static double CostOfTrans(int[] outstanding, int idx, int nShares, double liq)
{
  int[] after = new int[2];
  Array.Copy(outstanding, after, 2);
  after[idx] += nShares;
  return Cost(after, liq) - Cost(outstanding, liq);
}

A matriz nomeada depois coloca em espera o novo número de cotas pendentes após uma transação e o método apenas chama o método auxiliar Custo duas vezes. Com um método para calcular o custo de uma transação em curso, é fácil gravar um método que calcule o custo de compra de uma cota única de cada uma das duas opções:

static double[] CostForOneShare(int[] outstanding, double liq)
{
  double[] result = new double[2];
  result[0] = CostOfTrans(outstanding, 0, 1, liq);
  result[1] = CostOfTrans(outstanding, 1, 1, liq);
  return result;
}

O custo de uma cota única pode ser usado por especialistas para obter uma aproximação de quanto custaria comprar n cotas de uma opção.

As probabilidades do método retornam os dois preços de margem (probabilidades deduzidas) de cada opção vencedora em uma matriz:

static double[] Probabilities(int[] outstanding, double liq)
{
  double[] result = new double[2];
  double denom = 0.0;
  for (int i = 0; i < 2; ++i)
    denom += Math.Exp(outstanding[i] / liq);
  for (int i = 0; i < 2; ++i)
    result[i] = Math.Exp(outstanding[i] / liq) / denom;
  return result;
}

Se você comparar o código das Probabilidades do método com as equações 3 e 4, verá que, novamente, o código acompanha diretamente a definição da matemática.

O programa de demonstração

Para criar o programa de demonstração, iniciei o Visual Studio e selecionei o modelo do programa do aplicativo do console C#. Nomeei o projeto de PredictionMarket. A demonstração não tem dependências significativas do Microsoft .NET Framework, portanto, funcionará em qualquer versão do Visual Studio.

Após o carregamento do código de modelo, na janela Gerenciador de Soluções, renomeei o arquivo Program.cs para o PredictionMarketProgram.cs mais descritivo e o Visual Studio para automaticamente renomear a classe Program para mim. Na parte superior do código de origem, exclui todas as instruções que faziam referência a namespaces não usados do .NET, deixando somente a referência ao namespace System de nível superior.

O código de demonstração completo, com algumas pequenas edições e algumas instruções WriteLine excluídas para poupar espaço, é apresentado na Figura 3. Toda a lógica de controle do programa está contida no método Principal. Todas as funcionalidades do mercado de previsão estão em quatro métodos estáticos e há dois métodos de exibição auxiliar ShowVector.

Figura 3 - Demonstração do Mercado de Previsão

using System;
namespace PredictionMarket
{
  class PredictionMarketProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Begin prediction market demo ");
      Console.WriteLine("Goal is to predict winner of Xrays");
      Console.WriteLine("vs. Yanks using expert opinions");
      double liq = 100.0;
      Console.WriteLine("Setting liquidity parameter = " +
        liq.ToString("F1"));
      int[] outstanding = new int[] { 0, 0 };
      Console.WriteLine("Initial number of shares owned are:");
      ShowVector(outstanding);
      double[] probs = Probabilities(outstanding, liq);
      Console.WriteLine("Initial probabilities of winning:");
      ShowVector(probs, 4, " ");
      Console.WriteLine("=================================");
      double[] costPerShare = CostForOneShare(outstanding, liq);
      Console.WriteLine("Current costs for one share are: ");
      ShowVector(costPerShare, 4, " $");
      Console.WriteLine("Update: expert [01] buys 20 shares " +
        "of team [0]");
      double costTrans = CostOfTrans(outstanding, 0, 20, liq);
      Console.WriteLine("Cost of transaction to expert was: $" +
        costTrans.ToString("F2"));
      outstanding = new int[] { 20, 0 };
      Console.WriteLine("New number of shares owned are: ");
      ShowVector(outstanding);
      probs = Probabilities(outstanding, liq);
      Console.WriteLine("New inferred probs of winning:");
      ShowVector(probs, 4, " ");
      Console.WriteLine("=================================");
      costPerShare = CostForOneShare(outstanding, liq);
      Console.WriteLine("Current costs for one share are:");
      ShowVector(costPerShare, 4, " $");
      Console.WriteLine("Update: expert [02] buys 20 shares " +
        "of team [1]");
      costTrans = CostOfTrans(outstanding, 1, 20, liq);
      Console.WriteLine("Cost of transaction to expert was: $" +
        costTrans.ToString("F2"));
      outstanding = new int[] { 20, 20 };
      Console.WriteLine("New number of shares owned are:");
      ShowVector(outstanding);
      probs = Probabilities(outstanding, liq);
      Console.WriteLine("New inferred probs of winning:");
      ShowVector(probs, 4, " ");
      Console.WriteLine("=================================");
      costPerShare = CostForOneShare(outstanding, liq);
      Console.WriteLine("Current costs for one share are:");
      ShowVector(costPerShare, 4, " $");
      Console.WriteLine("Update: expert [03] buys 60 shares " +
        "of team [0]");
      costTrans = CostOfTrans(outstanding, 0, 60, liq);
      Console.WriteLine("Cost of transaction to expert was: $" +
        costTrans.ToString("F2"));
      outstanding = new int[] { 80, 20 };
      Console.WriteLine("New number of shares owned are:");
      ShowVector(outstanding);
      probs = Probabilities(outstanding, liq);
      Console.WriteLine("New inferred probs of winning:");
      ShowVector(probs, 4, " ");
      Console.WriteLine("=================================");
      costPerShare = CostForOneShare(outstanding, liq);
      Console.WriteLine("Current costs for one share are: ");
      ShowVector(costPerShare, 4, " $");
      Console.WriteLine("Update: expert [01] sells 10 shares " +
        "of team [0]");
      costTrans = CostOfTrans(outstanding, 0, -10, liq);
      Console.WriteLine("Cost of transaction to expert was: $" +
        costTrans.ToString("F2"));
      outstanding = new int[] { 70, 20 };
      Console.WriteLine("New number of shares owned are:");
      ShowVector(outstanding);
      probs = Probabilities(outstanding, liq);
      Console.WriteLine("New inferred probs of winning:");
      ShowVector(probs, 4, " ");
      Console.WriteLine("=================================");
      Console.WriteLine("Update: Market Closed");
      Console.WriteLine("\nEnd prediction market demo \n");
      Console.ReadLine();
    } // Main()
    static double[]Probabilities(int[] outstanding,
      double liq)
    {
      double[] result = new double[2];
      double denom = 0.0;
      for (int i = 0; i < 2; ++i)
        denom += Math.Exp(outstanding[i] / liq);
      for (int i = 0; i < 2; ++i)
        result[i] = Math.Exp(outstanding[i] / liq) / denom;
      return result;
    }
    static double Cost(int[] outstanding, double liq)
    {
      double sum = 0.0;
      for (int i = 0; i < 2; ++i)
        sum += Math.Exp(outstanding[i] / liq);
      return liq * Math.Log(sum);
    }
    static double CostOfTrans(int[] outstanding, int idx,
      int nShares, double liq)
    {
      int[] after = new int[2];
      Array.Copy(outstanding, after, 2);
      after[idx] += nShares;
      return Cost(after, liq) - Cost(outstanding, liq);
    }
    static double[] CostForOneShare(int[] outstanding,
      double liq)
    {
      double[] result = new double[2];
      result[0] = CostOfTrans(outstanding, 0, 1, liq);
      result[1] = CostOfTrans(outstanding, 1, 1, liq);
      return result;
    }
    static void ShowVector(double[] vector, int dec, string pre)
    {
      for (int i = 0; i < vector.Length; ++i)
        Console.Write(pre + vector[i].ToString("F" + dec) + " ");
      Console.WriteLine("\n");
    }
    static void ShowVector(int[] vector)
    {
      for (int i = 0; i < vector.Length; ++i)
        Console.Write(vector[i] + " ");
      Console.WriteLine("\n");
    }
  } // Program class
} // ns

Após exibir algumas mensagens preliminares, a execução do programa no método Main começa com:

double liq = 100.0;
int[] outstanding = new int[] { 0, 0 };
ShowVector(outstanding);

A variável liq é o parâmetro de liquidez. Um valor de 100,0 é típico, mas se você experimentar ajustar o valor, verá como ele afeta a alteração nos preços das cotas após uma transação. Valores de liquidez maiores produzem alterações menores. A matriz nomeada pendente mantém o número total de cotas pertencentes a todos os especialistas, em cada uma das duas equipes. Observe que o parâmetro de liquidez deve ser aprovado para os quatro métodos de previsão de mercado estático. Um design alternativo é encapsular os métodos em uma classe C# e definir a liquidez como um campo de membro.

Em seguida, o número de cotas pendentes é usado para determinar as probabilidades deduzidas de cada equipe vencedora:

double[] probs = Probabilities(outstanding, liq);
Console.WriteLine("Initial probabilities of winning:");
ShowVector(probs, 4, " ");

Em seguida, a demonstração exibe os custos para comprar uma cota única de cada uma das duas equipes:

double[] costPerShare = CostForOneShare(outstanding, liq);
Console.WriteLine("Current costs for one share are: ");
ShowVector(costPerShare, 4, " $");

Em um mercado de previsão realístico, esta informação seria útil para os especialistas de mercado para ajudá-los a avaliar se o preço da cota de uma equipe é muito alto ou muito baixo em relação à percepção do especialista de que essa equipe ganhará.

O programa de demonstração simula um dos especialistas comprando algumas cotas, assim:

Console.WriteLine("Update: expert [01] buys 20 shares of team [0]");
double costTrans = CostOfTrans(outstanding, 0, 20, liq);
Console.WriteLine("Cost of transaction to expert was: $" +
  costTrans.ToString("F2"));

Em um mercado de previsão real, o sistema teria que manter muitas informações sobre os saldos das contas dos especialistas e o número de cotas adquiridas.

Em seguida, o número de cotas pendentes é atualizado, assim:

outstanding = new int[] { 20, 0 };
Console.WriteLine("New number of shares owned on teams [0] " +
  "and [1] are: ");
ShowVector(outstanding);

Se você voltar para a equação matemática na Figura 2, observará que o número de cotas pendentes para cada equipe/opção, (x,y), é necessário em todas as equações.

Após a atualização das cotas pendentes, essa informação é usada para estimar as probabilidades revisadas de cada equipe ou opção vencedora:

probs = Probabilities(outstanding, liq);
Console.WriteLine("New inferred probabilities of
  winning are: ");
ShowVector(probs, 4, " ");

Lembre-se que esses valores são preços de margem, mas é útil pensar neles como probabilidades. Por fim, o propósito de um mercado de previsão é produzir a probabilidade que cada equipe ou opção terá de ganhar. Então, o conjunto final de probabilidades após o mercado estabilizar é o que você procura.

O programa de demonstração é concluído após repetir as cinco operações a seguir mais três vezes:

  • Mostrar custo atual para uma cota de cada equipe
  • Realizar uma compra ou vender uma transação
  • Mostrar o custo da transação
  • Atualizar o número total de cotas pendentes
  • Atualizar a probabilidade de cada equipe vencer

Observe que o programa de demonstração começa com as probabilidades de ambas as equipes sendo iguais. Isso não é real em muitos cenários do mercado de previsão. É possível inicializar um mercado de previsão com probabilidades desiguais resolvendo x e y nas equações 3 e 4.

Conclusão

As informações neste artigo se baseiam no documento de pesquisa de 2002, “Logarithmic Market Scoring Rules for Modular Combinatorial Information Aggregation”, de Robin Hanson. Você pode encontrar uma versão em PDF do documento em vários locais na Internet usando qualquer ferramenta de pesquisa.

Os mercados de previsão não são apenas uma ideia teórica abstrata. Nos últimos anos, várias empresas foram criadas e implementaram os mercados de previsão por dinheiro de verdade.

Uma área de pesquisa ativa está no que chamamos de mercados de previsão combinatórios. Em vez de escolher apenas uma de duas opções para ganhar, os especialistas podem comprar cotas em eventos de combinação como: a equipe A ganhará da equipe B e a equipe J ganhará da equipe K. Os mercados de previsão combinatórios são muito mais complexos do que os mercados simples.


Dr. James McCaffreytrabalha para a Microsoft Research em Redmond, Washington. Ele trabalhou em vários produtos da Microsoft, incluindo Internet Explorer e Bing. Entre em contato com o Dr. McCaffrey pelo email jammc@microsoft.com.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Pallavi Choudhury, Gaz Iqbal, Umesh Madan e Tien Suwandy