Simulação de Monte Carlo: Otimização de Budget e Previsão de ROAS

A Simulação de Monte Carlo é uma técnica computacional que utiliza números aleatórios para resolver problemas complexos através de amostragem repetida. O nome vem do famoso cassino de Monte Carlo em Mônaco, fazendo referência aos jogos de azar e aleatoriedade.

Esta técnica é amplamente utilizada em finanças, física, engenharia, marketing e ciência de dados para:

  • Estimar probabilidades
  • Calcular integrais complexas
  • Avaliar riscos
  • Otimizar processos
  • Prever comportamentos futuros

No marketing digital, enfrentamos constantemente incertezas: taxas de conversão variam, custos por clique flutuam, e o comportamento do consumidor é imprevisível. A Simulação de Monte Carlo nos ajuda a modelar essas incertezas e tomar decisões mais informadas sobre alocação de budget e estratégias de campanha.

Exemplo Prático: Otimização de Budget e Previsão de ROAS

Vamos trabalhar com um cenário fictício: uma empresa precisa distribuir seu budget entre diferentes canais de marketing (Google Ads, Facebook Ads, Email Marketing, etc.) e quer prever o retorno sobre investimento (ROAS) considerando as incertezas de cada canal. Ao final, está disponibilizado o código em Python e em R.

Configuração Inicial e Importação de Bibliotecas
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

# Configuração para melhor visualização
plt.style.use('seaborn-v0_8-darkgrid')
sns.set_palette("husl")

O que este código faz: Importa as bibliotecas necessárias para análise de dados, visualização e cálculos estatísticos. A configuração de estilo melhora a aparência dos gráficos.

Definindo os Parâmetros dos Canais de Marketing
def criar_parametros_canais():
    """
    Define os parâmetros de cada canal de marketing com suas incertezas
    
    Retorna:
    DataFrame com parâmetros de cada canal
    """
    canais = {
        'canal': ['Google Ads', 'Facebook Ads', 'Instagram Ads', 'Email Marketing', 'LinkedIn Ads'],
        'cpc_medio': [2.5, 1.8, 1.5, 0.05, 5.0],  # Custo por clique médio
        'cpc_desvio': [0.5, 0.4, 0.3, 0.01, 1.0],  # Desvio padrão do CPC
        'ctr_medio': [0.035, 0.025, 0.022, 0.15, 0.028],  # Taxa de clique média
        'ctr_desvio': [0.008, 0.006, 0.005, 0.03, 0.007],  # Desvio da CTR
        'conversao_media': [0.03, 0.025, 0.02, 0.05, 0.04],  # Taxa de conversão média
        'conversao_desvio': [0.008, 0.007, 0.005, 0.015, 0.01],  # Desvio da conversão
        'ticket_medio': [150, 120, 100, 200, 300],  # Valor médio da venda
        'ticket_desvio': [30, 25, 20, 50, 80]  # Desvio do ticket médio
    }
    
    return pd.DataFrame(canais)

O que este código faz: Cria um DataFrame com os parâmetros de desempenho de cada canal de marketing, incluindo médias e desvios padrão para modelar a incerteza de cada métrica.

Simulação de Monte Carlo para um Canal
def simular_canal(nome_canal, budget, parametros, n_simulacoes=10000):
    """
    Simula o desempenho de um canal de marketing usando Monte Carlo
    
    Parâmetros:
    nome_canal: nome do canal a simular
    budget: orçamento alocado para o canal
    parametros: DataFrame com parâmetros dos canais
    n_simulacoes: número de simulações
    
    Retorna:
    DataFrame com resultados das simulações
    """
    # Filtra parâmetros do canal
    canal = parametros[parametros['canal'] == nome_canal].iloc[0]
    
    # Arrays para armazenar resultados
    resultados = {
        'simulacao': range(n_simulacoes),
        'cpc': np.zeros(n_simulacoes),
        'ctr': np.zeros(n_simulacoes),
        'taxa_conversao': np.zeros(n_simulacoes),
        'ticket_medio': np.zeros(n_simulacoes),
        'cliques': np.zeros(n_simulacoes),
        'conversoes': np.zeros(n_simulacoes),
        'receita': np.zeros(n_simulacoes),
        'roas': np.zeros(n_simulacoes),
        'lucro': np.zeros(n_simulacoes)
    }
    
    for i in range(n_simulacoes):
        # Simula variáveis com distribuição normal truncada (valores positivos)
        cpc = max(0.01, np.random.normal(canal['cpc_medio'], canal['cpc_desvio']))
        ctr = np.clip(np.random.normal(canal['ctr_medio'], canal['ctr_desvio']), 0.001, 1)
        conversao = np.clip(np.random.normal(canal['conversao_media'], canal['conversao_desvio']), 0.001, 1)
        ticket = max(10, np.random.normal(canal['ticket_medio'], canal['ticket_desvio']))
        
        # Calcula métricas
        cliques = int(budget / cpc)
        impressoes = int(cliques / ctr)
        conversoes = int(cliques * conversao)
        receita = conversoes * ticket
        lucro = receita - budget
        roas = (lucro / budget) * 100 if budget > 0 else 0
        
        # Armazena resultados
        resultados['cpc'][i] = cpc
        resultados['ctr'][i] = ctr
        resultados['taxa_conversao'][i] = conversao
        resultados['ticket_medio'][i] = ticket
        resultados['cliques'][i] = cliques
        resultados['conversoes'][i] = conversoes
        resultados['receita'][i] = receita
        resultados['roas'][i] = roas
        resultados['lucro'][i] = lucro
    
    df_resultados = pd.DataFrame(resultados)
    df_resultados['canal'] = nome_canal
    
    return df_resultados

O que este código faz: Executa milhares de simulações para um canal específico, variando aleatoriamente as métricas de desempenho dentro de suas distribuições de probabilidade, calculando ROAS e lucro para cada cenário.

Análise de Resultados por Canal
def analisar_resultados_canal(df_simulacao, canal_nome):
    """
    Cria visualizações e estatísticas dos resultados de simulação
    """
    fig, axes = plt.subplots(2, 2, figsize=(15, 10))
    fig.suptitle(f'Análise de Monte Carlo - {canal_nome}', fontsize=16)
    
    # Distribuição do ROAS
    ax1 = axes[0, 0]
    ax1.hist(df_simulacao['roas'], bins=50, alpha=0.7, color='blue', edgecolor='black')
    ax1.axvline(df_simulacao['roas'].mean(), color='red', linestyle='--', 
                label=f'Média: {df_simulacao["roas"].mean():.1f}%')
    ax1.set_xlabel('ROAS (%)')
    ax1.set_ylabel('Frequência')
    ax1.set_title('Distribuição do ROAS')
    ax1.legend()
    
    # Distribuição do Lucro
    ax2 = axes[0, 1]
    ax2.hist(df_simulacao['lucro'], bins=50, alpha=0.7, color='green', edgecolor='black')
    ax2.axvline(df_simulacao['lucro'].mean(), color='red', linestyle='--',
                label=f'Média: R$ {df_simulacao["lucro"].mean():.2f}')
    ax2.set_xlabel('Lucro (R$)')
    ax2.set_ylabel('Frequência')
    ax2.set_title('Distribuição do Lucro')
    ax2.legend()
    
    # Análise de Risco (Probabilidade de Prejuízo)
    ax3 = axes[1, 0]
    prob_prejuizo = (df_simulacao['lucro'] < 0).mean() * 100
    prob_lucro = (df_simulacao['lucro'] >= 0).mean() * 100
    
    ax3.bar(['Prejuízo', 'Lucro'], [prob_prejuizo, prob_lucro], 
            color=['red', 'green'], alpha=0.7)
    ax3.set_ylabel('Probabilidade (%)')
    ax3.set_title('Análise de Risco')
    for i, v in enumerate([prob_prejuizo, prob_lucro]):
        ax3.text(i, v + 1, f'{v:.1f}%', ha='center', fontweight='bold')
    
    # Box plot de métricas chave
    ax4 = axes[1, 1]
    metricas = df_simulacao[['conversoes', 'receita', 'lucro']].copy()
    metricas['receita'] = metricas['receita'] / 100  # Escala para melhor visualização
    metricas['lucro'] = metricas['lucro'] / 100
    
    metricas.boxplot(ax=ax4)
    ax4.set_title('Variabilidade das Métricas (Receita e Lucro /100)')
    ax4.set_ylabel('Valor')
    
    plt.tight_layout()
    plt.show()
    
    # Estatísticas resumidas
    print(f"\n=== Estatísticas para {canal_nome} ===")
    print(f"ROAS Médio: {df_simulacao['roas'].mean():.2f}%")
    print(f"ROAS Mediano: {df_simulacao['roas'].median():.2f}%")
    print(f"Desvio Padrão ROAS: {df_simulacao['roas'].std():.2f}%")
    print(f"Probabilidade de Prejuízo: {prob_prejuizo:.1f}%")
    print(f"Value at Risk (5%): R$ {df_simulacao['lucro'].quantile(0.05):.2f}")
    print(f"Lucro Esperado: R$ {df_simulacao['lucro'].mean():.2f}")

O que este código faz: Cria visualizações detalhadas dos resultados da simulação, incluindo distribuições de ROAS e lucro, análise de risco e estatísticas resumidas para apoiar a tomada de decisão.

Otimização de Portfolio de Marketing
def otimizar_portfolio(budget_total, parametros, n_simulacoes=5000):
    """
    Encontra a melhor alocação de budget entre canais usando Monte Carlo
    """
    canais = parametros['canal'].values
    n_canais = len(canais)
    
    # Gera alocações aleatórias
    n_portfolios = 1000
    alocacoes = np.random.dirichlet(np.ones(n_canais), size=n_portfolios)
    
    resultados_portfolio = []
    
    for i, alocacao in enumerate(alocacoes):
        roas_total = 0
        lucro_total = 0
        risco_total = 0
        
        # Simula cada canal com sua alocação
        for j, canal in enumerate(canais):
            budget_canal = budget_total * alocacao[j]
            if budget_canal > 0:
                sim = simular_canal(canal, budget_canal, parametros, n_simulacoes=1000)
                roas_medio = sim['roas'].mean()
                lucro_medio = sim['lucro'].mean()
                risco = sim['lucro'].std()
                
                # Ponderação pelo budget
                roas_total += roas_medio * alocacao[j]
                lucro_total += lucro_medio
                risco_total += risco * alocacao[j]
        
        resultados_portfolio.append({
            'portfolio': i,
            'roas_esperado': roas_total,
            'lucro_esperado': lucro_total,
            'risco': risco_total,
            'sharpe_ratio': lucro_total / risco_total if risco_total > 0 else 0,
            'alocacao': alocacao
        })
    
    df_portfolios = pd.DataFrame(resultados_portfolio)
    
    # Encontra portfolios ótimos
    melhor_roas = df_portfolios.loc[df_portfolios['roas_esperado'].idxmax()]
    melhor_sharpe = df_portfolios.loc[df_portfolios['sharpe_ratio'].idxmax()]
    
    return df_portfolios, melhor_roas, melhor_sharpe

O que este código faz: Testa múltiplas combinações de alocação de budget entre canais, calculando o retorno esperado e o risco de cada portfolio para encontrar as alocações ótimas.

Visualização da Fronteira Eficiente
def plotar_fronteira_eficiente(df_portfolios, melhor_roas, melhor_sharpe):
    """
    Plota a fronteira eficiente de portfolios de marketing
    """
    plt.figure(figsize=(12, 8))
    
    # Scatter plot de todos os portfolios
    scatter = plt.scatter(df_portfolios['risco'], 
                         df_portfolios['lucro_esperado'],
                         c=df_portfolios['sharpe_ratio'], 
                         cmap='viridis', 
                         alpha=0.6,
                         s=50)
    
    # Destaca portfolios ótimos
    plt.scatter(melhor_roas['risco'], melhor_roas['lucro_esperado'], 
               color='red', s=200, marker='*', 
               label='Melhor ROAS', edgecolor='black', linewidth=2)
    
    plt.scatter(melhor_sharpe['risco'], melhor_sharpe['lucro_esperado'], 
               color='orange', s=200, marker='*', 
               label='Melhor Sharpe Ratio', edgecolor='black', linewidth=2)
    
    plt.colorbar(scatter, label='Sharpe Ratio')
    plt.xlabel('Risco (Desvio Padrão do Lucro)')
    plt.ylabel('Lucro Esperado (R$)')
    plt.title('Fronteira Eficiente - Portfolios de Marketing')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

O que este código faz: Cria um gráfico mostrando a relação risco-retorno de diferentes alocações de budget, destacando os portfolios ótimos segundo diferentes critérios.

 # Define parâmetros
 parametros = criar_parametros_canais()
 budget_total = 50000  # R$ 50.000
 
 # 1. Análise individual de cada canal
 print("=== ANÁLISE INDIVIDUAL DOS CANAIS ===")
 for canal in parametros['canal']:
     print(f"\nSimulando {canal}...")
     simulacao = simular_canal(canal, budget_total/5, parametros, n_simulacoes=10000)
     analisar_resultados_canal(simulacao, canal)
 
=== Estatísticas para Google Ads ===
ROAS Médio: 86.88%
ROAS Mediano: 75.72%
Desvio Padrão ROAS: 78.57%
Probabilidade de Prejuízo: 9.7%
Value at Risk (5%): R$ -1736.16
Lucro Esperado: R$ 8688.49

 # 2. Otimização de portfolio
 print("\n=== OTIMIZAÇÃO DE PORTFOLIO ===")
 df_portfolios, melhor_roas, melhor_sharpe = otimizar_portfolio(budget_total, parametros)
 
 # 3. Visualização da fronteira eficiente
 plotar_fronteira_eficiente(df_portfolios, melhor_roas, melhor_sharpe)
 

 # 4. Exibição das alocações ótimas
 exibir_alocacao_otima(melhor_roas, parametros, "Portfolio de Melhor ROAS")
 exibir_alocacao_otima(melhor_sharpe, parametros, "Portfolio de Melhor Sharpe Ratio")
 


 # 5. Comparação de estratégias
 print("\n=== COMPARAÇÃO DE ESTRATÉGIAS ===")
 print(f"Portfolio de Melhor ROAS:")
 print(f"  - ROAS Esperado: {melhor_roas['roas_esperado']:.2f}%")
 print(f"  - Lucro Esperado: R$ {melhor_roas['lucro_esperado']:.2f}")
 print(f"  - Risco: R$ {melhor_roas['risco']:.2f}")
 
 print(f"\nPortfolio de Melhor Sharpe Ratio:")
 print(f"  - ROAS Esperado: {melhor_sharpe['roas_esperado']:.2f}%")
 print(f"  - Lucro Esperado: R$ {melhor_sharpe['lucro_esperado']:.2f}")
 print(f"  - Risco: R$ {melhor_sharpe['risco']:.2f}")
=== COMPARAÇÃO DE ESTRATÉGIAS ===
Portfolio de Melhor ROAS:
  - ROAS Esperado: 17897.99%
  - Lucro Esperado: R$ 8948994.49
  - Risco: R$ 3491372.13

Portfolio de Melhor Sharpe Ratio:
  - ROAS Esperado: 1260.24%
  - Lucro Esperado: R$ 630120.98
  - Risco: R$ 23835.62

Conclusão

No caso acima temos apenas uma sinplificação, um modelo mais robusto levaria em consideração uma distribuição não-normal, uma vez que temos métricas que não assume valor negativo (não existe investimento, cpc e ctr negativo) e tal. De qualquer forma, a Simulação de Monte Carlo é uma ferramenta poderosa para resolver problemas complexos através de amostragem aleatória. Permitindo:

  1. Quantificar Incertezas: Modelar variações em métricas como CPC, CTR e taxa de conversão
  2. Avaliar Riscos: Calcular probabilidades de prejuízo e Value at Risk
  3. Otimizar Alocações: Encontrar a distribuição ideal de budget entre canais
  4. Comparar Estratégias: Avaliar trade-offs entre retorno e risco

Benefícios práticos:

  • Tomada de decisão baseada em dados probabilísticos
  • Identificação de canais com melhor relação risco-retorno
  • Planejamento de budget com margem de segurança
  • Previsões mais realistas considerando incertezas do mercado

A escolha entre maximizar ROAS ou Sharpe Ratio depende do perfil de risco da empresa: startups podem preferir alto ROAS aceitando mais risco, enquanto empresas estabelecidas podem priorizar consistência (Sharpe Ratio).

Código completo:


Comentários

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *