Lista 2 - Econometria II (EPGE)

Author

José Victor

Published

October 10, 2025

Sumário Executivo

Esta lista aborda dois grandes tópicos em econometria financeira: modelos de precificação de ativos e modelagem de séries temporais com componentes sazonais.

Questão 1: Asset Pricing

Aplicamos cinco modelos distintos para estimar o equity premium dos 25 Portfolios Formed on Size and Book-to-Market (5×5) de Fama-French (1947Q1-2025Q1):

  • CCAPM (Consumption-based): Utiliza crescimento do consumo como fator de risco (γ=2, β=0.99)
  • CAPM: Modelo clássico de um fator usando excesso de retorno de mercado
  • Fama-French 3 Fatores: Adiciona fatores SMB (tamanho) e HML (valor)
  • PCA: Extração estatística de fatores via componentes principais
  • Fama-MacBeth: Metodologia em duas etapas para testar precificação cross-sectional

Principais Resultados: - CCAPM apresenta equity premium muito baixo, ilustrando o “equity premium puzzle” - FF3 oferece melhor ajuste (R² médio superior) comparado ao CAPM - PCA identifica componentes principais que explicam a maior parte da variação dos retornos

Questão 2: Modelagem SARIMA do IPCA

Aplicamos modelos SARIMA ao índice IPCA (jan/2000 - jan/2025):

  • Diagnóstico: Série não-estacionária em nível, estacionária após diferenciação
  • Modelos estimados: SARIMA(1,1,0)(1,0,0)₁₂ e SARIMA(1,1,0)(0,0,1)₁₂
  • Previsões: Rolling window out-of-sample (fev/2023 - jan/2025)

Principais Resultados: - Ambos modelos apresentam parâmetros significativos e resíduos bem comportados - Modelo com SMA(1) apresenta melhor desempenho preditivo (menor RMSE) - Sazonalidade claramente capturada pelos componentes sazonais


Questão 1 - Modelos de Asset Pricing

Enunciado

Os modelos de asset pricing são fundamentais na teoria financeira, pois descrevem como os preços dos ativos são determinados no mercado. Começando pela perspectiva de um investidor que decide quanto poupar e consumir, e qual portfólio de ativos manter, a equação de precificação mais básica emerge das condições de primeira ordem desse problema. As equações desses modelos sugerem que o preço de um ativo deve ser o valor presente dos fluxos de caixa futuros esperados, utilizando a utilidade marginal do investidor, levando em conta o risco e a preferência temporal.

Com base nesses conceitos, aplicaremos os modelos para estimar o equity premium (excesso de retorno) dos 25 Portfolios Formed on Size and Book-to-Market (5×5) de Fama e French (disponíveis em Kenneth R. French - Data Library) conforme solicitado em cada item a seguir, utilizando dados trimestrais para o período entre 1947Q1 a 2025Q1. Quanto aos dados referentes ao consumo, utilizaremos o consumo de não duráveis em bases reais per capita norte-americano. As séries necessárias podem ser baixadas no site do Bureau of Economic Analysis (BEA), na seção das NIPA Tables. As séries a serem utilizadas estão nas tabelas 2.1 (linha 40), 2.3.4 e 2.3.5.

Setup e Carregamento de Pacotes - Asset Pricing

packages_ap <- c(
    "tidyverse", "lubridate", "readxl", "zoo",
    "broom", "knitr", "kableExtra", "gridExtra"
)

for (pkg in packages_ap) {
    if (!require(pkg, character.only = TRUE, quietly = TRUE)) {
        install.packages(pkg, quiet = TRUE)
        library(pkg, character.only = TRUE)
    }
}

# Pacotes carregados

Carregamento e Preparação dos Dados

Dados de Consumo do BEA

# Carregamento dos dados do BEA

# Tabela 2.3.5 - Consumo nominal
bea_235_raw <- read_excel("BEATable2_3_5.xlsx", sheet = 1, col_names = FALSE)

# Encontrar linha com "Nondurable goods" (linha 8)
data_row_nondur <- which(grepl("^8$|^8 ", as.character(bea_235_raw[[1]])))[1]

# Extrair cabeçalhos de ano e trimestre
# Linha 6 tem os anos, linha 7 tem os quarters
years_row <- as.character(unlist(bea_235_raw[6, ]))
quarters_row <- as.character(unlist(bea_235_raw[7, ]))

# Começar da coluna 3 (primeira coluna de dados)
years_data <- years_row[3:length(years_row)]
quarters_data <- quarters_row[3:length(quarters_row)]

# Os anos aparecem apenas na primeira coluna de cada ano (e depois NA)
# Precisamos preencher os NAs com o último ano válido
current_year <- NA
for (i in seq_along(years_data)) {
    if (!is.na(years_data[i]) && years_data[i] != "") {
        current_year <- years_data[i]
    } else {
        years_data[i] <- current_year
    }
}

# Criar nomes no formato YYYYQQ
col_names <- paste0(years_data, quarters_data)

# Filtrar apenas datas válidas (formato 2023Q1, 2024Q2, etc)
valid_indices <- grepl("^[0-9]{4}Q[1-4]$", col_names)
col_names <- col_names[valid_indices]

# Extrair dados de Nondurable goods (começando da coluna 3)
nondur_row <- unlist(bea_235_raw[data_row_nondur, ])
nondur_values_all <- as.numeric(nondur_row[3:length(nondur_row)])
nondur_values <- nondur_values_all[valid_indices]

# Criar dataframe
consumption_nominal <- data.frame(
    Date_str = col_names,
    Consumption_Nominal = nondur_values,
    stringsAsFactors = FALSE
) %>%
    mutate(Date = as.yearqtr(gsub("Q", " Q", Date_str))) # Formato "2023 Q1"

# Tabela 2.3.4 - Price indexes
bea_234_raw <- read_excel("BEATable2_3_4.xlsx", sheet = 1, col_names = FALSE)
data_row_nondur_price <- which(grepl("^8$|^8 ", as.character(bea_234_raw[[1]])))[1]

# Extrair cabeçalhos (mesma lógica da tabela 2.3.5)
years_row_234 <- as.character(unlist(bea_234_raw[6, ]))
quarters_row_234 <- as.character(unlist(bea_234_raw[7, ]))

years_data_234 <- years_row_234[3:length(years_row_234)]
quarters_data_234 <- quarters_row_234[3:length(quarters_row_234)]

# Preencher anos
current_year_234 <- NA
for (i in seq_along(years_data_234)) {
    if (!is.na(years_data_234[i]) && years_data_234[i] != "") {
        current_year_234 <- years_data_234[i]
    } else {
        years_data_234[i] <- current_year_234
    }
}

col_names_234 <- paste0(years_data_234, quarters_data_234)
valid_indices_234 <- grepl("^[0-9]{4}Q[1-4]$", col_names_234)
col_names_234 <- col_names_234[valid_indices_234]

# Extrair price index
price_row <- unlist(bea_234_raw[data_row_nondur_price, ])
price_values_all <- as.numeric(price_row[3:length(price_row)])
price_values <- price_values_all[valid_indices_234]

price_index <- data.frame(
    Date_str = col_names_234,
    Price_Index = price_values,
    stringsAsFactors = FALSE
) %>%
    mutate(Date = as.yearqtr(gsub("Q", " Q", Date_str))) # Formato "2023 Q1"

# Tabela 2.1 - População (linha 40 tem população em milhares)
bea_21_raw <- read_excel("BEATable2_1.xlsx", sheet = 1, col_names = FALSE)

# A linha 40 está rotulada como "40" na coluna 1, mas está na linha 50 do Excel
# (há linhas de cabeçalho antes)
data_row_pop <- which(grepl("^40$|^40 ", as.character(bea_21_raw[[1]])))[1]

# Usar mesma lógica de extração
pop_row <- unlist(bea_21_raw[data_row_pop, ])
pop_values_all <- as.numeric(pop_row[3:length(pop_row)])
pop_values <- pop_values_all[valid_indices] # Usar mesmos índices válidos

population <- data.frame(
    Date_str = col_names,
    Population = pop_values,
    stringsAsFactors = FALSE
) %>%
    mutate(Date = as.yearqtr(gsub("Q", " Q", Date_str)))

# Consumo real per capita CORRIGIDO
consumption_data <- consumption_nominal %>%
    inner_join(price_index, by = "Date") %>%
    inner_join(population, by = "Date") %>%
    mutate(
        Consumption_Real = (Consumption_Nominal / Price_Index) * 100,
        # Consumo real per capita: bilhões / (milhares/1000) = bilhões / milhões de pessoas
        # Resultado em MIL DÓLARES per capita
        Consumption_Real_PC = Consumption_Real / (Population / 1000)
    ) %>%
    filter(Date >= as.yearqtr("1947 Q1") & Date <= as.yearqtr("2025 Q1")) %>%
    select(Quarter = Date, Consumption_Real_PC, Consumption_Real, Population, Consumption_Nominal, Price_Index)

# Dados carregados com sucesso

Visualização dos Dados do BEA

Descrição das variáveis:

  • Quarter: Trimestre no formato YYYY QX
  • Consumption_Real_PC: Consumo real per capita (mil dólares)
  • Consumption_Real: Consumo real agregado (bilhões de dólares)
  • Population: População (milhares de pessoas)
  • Consumption_Nominal: Consumo nominal (bilhões de dólares)
  • Price_Index: Índice de preços (base 100)
Amostra dos Dados de Consumo do BEA (1947Q1-2025Q1)
Quarter Consumption_Real_PC Consumption_Real Population Consumption_Nominal Price_Index
1947 Q1 3.51 501.74 143143 74.9 14.93
1947 Q2 3.55 510.15 143790 76.9 15.07
1947 Q3 3.55 512.19 144449 78.6 15.35
1947 Q4 3.49 506.01 145122 80.0 15.81
1948 Q1 3.48 507.16 145709 81.5 16.07
1948 Q2 3.50 512.63 146289 83.2 16.23
1948 Q3 3.47 509.52 146921 83.5 16.39
1948 Q4 3.50 516.09 147607 83.7 16.22
1949 Q1 3.49 517.81 148254 82.7 15.97
1949 Q2 3.49 519.47 148847 81.9 15.77
Dados Recentes de Consumo
Quarter Consumption_Real_PC Consumption_Real Population Consumption_Nominal Price_Index
304 2022 Q4 9.85 3302.93 335382 3896.6 117.97
305 2023 Q1 9.87 3317.37 336018 3926.9 118.37
306 2023 Q2 9.87 3322.14 336685 3938.6 118.56
307 2023 Q3 9.92 3349.27 337500 4000.9 119.46
308 2023 Q4 9.98 3375.98 338360 4032.2 119.44
309 2024 Q1 9.92 3362.74 339119 4010.0 119.25
310 2024 Q2 10.00 3397.94 339929 4072.5 119.85
311 2024 Q3 10.09 3438.21 340637 4107.6 119.47
312 2024 Q4 10.17 3470.29 341164 4143.7 119.40
313 2025 Q1 10.21 3488.97 341590 4196.5 120.28
Estatísticas Descritivas - Dados BEA
Variavel Media Desvio_Padrao Minimo Maximo
Consumo Real Per Capita 6.02 1.86 3.45 10.21
População (milhares) 244050.36 58221.98 143143.00 341590.00
Índice de Preços 56.30 33.34 14.93 120.28

Portfólios Fama-French 25 (5x5)

# Ler arquivo CSV dos 25 portfolios 5x5 Size and B/M
ff_lines <- readLines("25_Portfolios_5x5.csv")

# Encontrar início dos dados mensais
start_line <- which(grepl("^\\s*,SMALL LoBM", ff_lines))[1] + 1

# Encontrar onde os dados terminam (antes de "Annual" ou nova seção)
# O arquivo pode ter várias seções - queremos apenas a primeira
end_markers <- which(grepl(
    "Annual|^\\s*$|Average Value Weighted Returns|Average Equal Weighted Returns",
    ff_lines[start_line:length(ff_lines)]
))
if (length(end_markers) > 0) {
    end_line <- start_line + end_markers[1] - 2
} else {
    end_line <- length(ff_lines)
}

# Ler dados
ff_monthly <- read.csv(
    text = paste(ff_lines[start_line:end_line], collapse = "\n"),
    header = FALSE, stringsAsFactors = FALSE
)

# Remover linhas vazias e linhas com "Annual"
ff_monthly <- ff_monthly[!grepl("Annual|^\\s*$", ff_monthly[, 1]), ]
ff_monthly <- ff_monthly[nchar(trimws(ff_monthly[, 1])) == 6, ] # Apenas YYYYMM

# Definir nomes das colunas
colnames(ff_monthly) <- c("Date", paste0("P", 1:25))

# Converter data e valores
ff_monthly <- ff_monthly %>%
    mutate(
        Date = as.Date(paste0(Date, "01"), format = "%Y%m%d"),
        across(starts_with("P"), as.numeric)
    ) %>%
    filter(!is.na(Date))

# Converter para trimestral usando retornos compostos
ff_monthly <- ff_monthly %>%
    mutate(
        Year = year(Date),
        Quarter = quarter(Date),
        YearQuarter = as.yearqtr(Date)
    )

# Agregar para trimestral: (1+r1)(1+r2)(1+r3) - 1
# Nota: os retornos já estão em % nos dados originais, então dividimos por 100,
# calculamos o produto, e multiplicamos por 100 no final UMA NICA VEZ
ff_quarterly <- ff_monthly %>%
    group_by(YearQuarter) %>%
    summarise(across(
        starts_with("P"),
        ~ ifelse(all(!is.na(.)), (prod(1 + . / 100) - 1) * 100, NA_real_)
    ), .groups = "drop") %>%
    rename(Quarter = YearQuarter) %>%
    filter(Quarter >= as.yearqtr("1947 Q1") & Quarter <= as.yearqtr("2025 Q1")) %>%
    ungroup()

cat("Portfólios trimestrais:", nrow(ff_quarterly), "trimestres - Período:", as.character(min(ff_quarterly$Quarter)), "a", as.character(max(ff_quarterly$Quarter)), "\n")
Portfólios trimestrais: 313 trimestres - Período: 1947 Q1 a 2025 Q1 

Visualização dos Portfólios Fama-French

Descrição dos dados:

  • 25 Portfolios Formed on Size and Book-to-Market (5×5)
  • Ordenação dupla: 5 quintis de Tamanho (Market Equity) × 5 quintis de Book-to-Market
  • Total: 5 × 5 = 25 portfólios distintos
  • P1 a P5: Pequenas empresas, do menor ao maior B/M
  • P21 a P25: Grandes empresas, do menor ao maior B/M
  • Retornos em % ao trimestre (agregação geométrica de retornos mensais)
Retornos Trimestrais dos 25 Portfolios Formed on Size and Book-to-Market (5×5) - Fama-French (%)
Quarter P1 P2 P3 P4 P5 P6 P7 P8 P9 P10 P11 P12 P13 P14 P15 P16 P17 P18 P19 P20 P21 P22 P23 P24 P25
1947 Q1 1.3638 -0.9658 -3.1092 1.1195 3.6076 -0.5583 -1.9981 1.0741 -1.6543 2.2709 -2.3088 -1.5196 -1.9293 -1.7944 2.7868 -4.2820 -1.7482 -3.1170 0.7109 -0.6586 1.2858 -2.6817 -2.3166 -1.5210 -3.2998
1947 Q2 -10.9157 -13.3635 -9.8950 -11.4121 -9.0724 -11.5351 -5.6077 -7.2369 -7.2973 -6.6614 -10.4268 -6.3806 -6.5168 -3.3511 -5.1395 -3.1947 -4.4120 -3.8292 -8.1806 -4.9963 0.2733 1.6759 -2.8638 5.0963 -1.8145
1947 Q3 8.6637 4.5249 3.4809 3.5805 9.7037 1.4044 7.5874 4.7050 6.5653 9.6582 7.5714 1.0960 7.0423 6.6337 8.4467 1.5653 1.8732 7.1358 5.0979 8.8058 0.9785 1.0722 -0.2537 2.2055 3.4635
1947 Q4 -5.8113 2.2603 2.0012 2.1558 5.5023 -3.3611 1.6979 1.8376 -0.3112 6.5555 4.4536 3.2169 0.9851 3.8144 1.9757 5.0147 3.1222 3.3497 0.2853 -0.6833 1.6860 6.2377 4.7775 4.8852 9.7100
1948 Q1 6.9392 -0.1354 -1.0110 1.1433 5.2215 -5.3257 -2.0915 -1.9946 2.8333 4.6885 -2.4905 -4.5020 1.9262 -0.2601 6.2373 -0.0890 -1.5664 1.6523 4.0640 5.9790 -2.6236 0.7531 -1.1777 0.8380 1.8653

Séries Temporais dos Portfólios Selecionados

Estatísticas dos Portfólios P1-P5 (Small Cap)
Portfolio Media Desvio_Padrao Minimo Maximo
P1 P1 2.1877 15.1961 -39.8975 54.3350
P2 P2 3.3672 12.8892 -33.0183 51.5552
P3 P3 3.3909 11.6129 -28.6682 44.0648
P4 P4 3.9598 11.2028 -36.6349 43.5258
P5 P5 4.5579 12.3972 -40.2378 53.4766

Fatores Fama-French

# Verificar se arquivo existe na pasta FF_Factors_temp
if (file.exists("FF_Factors_temp/F-F_Research_Data_Factors.csv")) {
    ff_factors_lines <- readLines("FF_Factors_temp/F-F_Research_Data_Factors.csv")
} else {
    stop("Arquivo F-F_Research_Data_Factors.csv não encontrado na pasta FF_Factors_temp")
}

# Encontrar início dos dados mensais
start_line <- which(grepl("^\\s*[0-9]{6}", ff_factors_lines))[1]

# Encontrar linha "Annual" para parar
annual_line <- which(grepl("Annual", ff_factors_lines))[1]
if (!is.na(annual_line)) {
    end_line <- annual_line - 1
} else {
    end_line <- length(ff_factors_lines)
}

# Ler dados mensais
ff_factors_monthly <- read.csv(
    text = paste(ff_factors_lines[start_line:end_line], collapse = "\n"),
    stringsAsFactors = FALSE
)

# Processar - renomear colunas para evitar problemas
colnames(ff_factors_monthly) <- c("Date", "Mkt_RF", "SMB", "HML", "RF")

ff_factors_monthly <- ff_factors_monthly %>%
    filter(nchar(trimws(Date)) == 6) %>%
    mutate(
        Date = as.Date(paste0(Date, "01"), format = "%Y%m%d"),
        across(-Date, as.numeric),
        YearQuarter = as.yearqtr(Date)
    ) %>%
    filter(!is.na(Date))

# Converter para trimestral (retornos compostos)
ff_factors_quarterly <- ff_factors_monthly %>%
    group_by(YearQuarter) %>%
    summarise(
        Mkt_RF = (prod(1 + Mkt_RF / 100) - 1) * 100,
        SMB = (prod(1 + SMB / 100) - 1) * 100,
        HML = (prod(1 + HML / 100) - 1) * 100,
        RF = (prod(1 + RF / 100) - 1) * 100,
        .groups = "drop"
    ) %>%
    rename(`Mkt-RF` = Mkt_RF, Quarter = YearQuarter) %>%
    filter(Quarter >= as.yearqtr("1947 Q1") & Quarter <= as.yearqtr("2025 Q1"))

cat("Fatores trimestrais:", nrow(ff_factors_quarterly), "trimestres - Período:", as.character(min(ff_factors_quarterly$Quarter)), "a", as.character(max(ff_factors_quarterly$Quarter)), "\n")
Fatores trimestrais: 313 trimestres - Período: 1947 Q1 a 2025 Q1 

Visualização dos Fatores Fama-French

Descrição dos fatores:

  • Mkt-RF: Excesso de retorno do mercado (market return - risk-free rate)
  • SMB (Small Minus Big): Prêmio pelo fator tamanho
  • HML (High Minus Low): Prêmio pelo fator valor (book-to-market)
  • RF: Taxa livre de risco (risk-free rate)
  • Todos em % ao trimestre
Fatores Fama-French Trimestrais (%)
Quarter Mkt-RF SMB HML RF
1947 Q1 -1.5361 1.2688 0.0053 0.0900
1947 Q2 -0.7168 -7.4167 0.5252 0.0900
1947 Q3 1.7656 3.4541 4.3034 0.1200
1947 Q4 3.4538 -3.6197 4.9654 0.2001
1948 Q1 -0.6878 0.8981 6.0154 0.2302
1948 Q2 11.1148 -2.5503 5.7171 0.2502
1948 Q3 -7.6971 -2.7566 -1.3768 0.2101
1948 Q4 -0.8050 -4.8717 -5.4740 0.1200
1949 Q1 1.2336 2.2833 1.6634 0.2903
1949 Q2 -4.6991 -2.5383 -5.0758 0.2903
Estatísticas Descritivas dos Fatores de Risco
Fator Media Desvio_Padrao Minimo Maximo
Mkt-RF 2.0724 8.1293 -26.5150 23.1818
SMB 0.2544 4.9899 -12.8864 15.9333
HML 0.9486 5.6925 -22.2767 27.4500
RF 0.9612 0.7529 0.0000 3.8073

Visualização Temporal dos Fatores

Matriz de Correlação dos Fatores

Matriz de Correlação entre os Fatores de Risco
Mkt-RF SMB HML RF
Mkt-RF 1.000 0.408 -0.190 -0.138
SMB 0.408 1.000 -0.054 -0.017
HML -0.190 -0.054 1.000 0.069
RF -0.138 -0.017 0.069 1.000

Interpretação da Correlação:

  • Mkt-RF vs SMB: Correlação mediana indica que o fator tamanho captura risco ortogonal ao mercado
  • Mkt-RF vs HML: Correlação baixa/negativa mostra independência entre fatores de mercado e valor
  • SMB vs HML: Correlação próxima de zero confirma que capturam diferentes dimensões de risco

Inflação (CPI)

# Converter price index para taxa de inflação trimestral
cpi_quarterly <- price_index %>%
    arrange(Date) %>%
    mutate(Inflation_Rate = (Price_Index / lag(Price_Index) - 1) * 100) %>%
    filter(Date >= as.yearqtr("1947 Q1") & Date <= as.yearqtr("2025 Q1")) %>%
    select(Quarter = Date, Inflation_Rate, Price_Index)

Combinação dos Dados

# Converter Quarter para character para fazer join
ff_quarterly_join <- ff_quarterly %>%
    mutate(Quarter_chr = as.character(Quarter)) %>%
    select(-Quarter)

ff_factors_join <- ff_factors_quarterly %>%
    mutate(Quarter_chr = as.character(Quarter)) %>%
    select(-Quarter)

consumption_join <- consumption_data %>%
    mutate(Quarter_chr = as.character(Quarter)) %>%
    select(-Quarter)

# Joins
data_full <- ff_quarterly_join %>%
    left_join(ff_factors_join, by = "Quarter_chr") %>%
    left_join(consumption_join %>% select(Quarter_chr, Consumption_Real_PC), by = "Quarter_chr") %>%
    filter(!is.na(`Mkt-RF`), !is.na(Consumption_Real_PC)) %>%
    mutate(Quarter = as.yearqtr(Quarter_chr)) %>%
    select(Quarter, everything(), -Quarter_chr) %>%
    arrange(Quarter)

# Adicionar inflação para calcular retornos reais
data_full <- data_full %>%
    left_join(cpi_quarterly %>% select(Quarter, Inflation_Rate), by = "Quarter") %>%
    filter(!is.na(Inflation_Rate))

# Calcular retornos reais para cada portfólio
# Nota: O enunciado sugere usar CPI mensal com a fórmula:
# ((1+r1/100)*(1+r2/100)*(1+r3/100))/((1+pi1/100)*(1+pi2/100)*(1+pi3/100)) - 1
# Porém, estamos usando o Price Index trimestral do BEA (Tabela 2.3.6), que já é agregado,
# então usamos a fórmula equivalente: (1 + r_trimestral) / (1 + inflacao_trimestral) - 1
for (i in 1:25) {
    port_name <- paste0("P", i)
    real_name <- paste0("P", i, "_Real")
    data_full[[real_name]] <- ((1 + data_full[[port_name]] / 100) / (1 + data_full$Inflation_Rate / 100) - 1) * 100
}

# Calcular crescimento do consumo
data_full <- data_full %>%
    mutate(Consumption_Growth = (Consumption_Real_PC / lag(Consumption_Real_PC) - 1) * 100) %>%
    filter(!is.na(Consumption_Growth))

# Dataset final combinado

(a) CCAPM - Equity Premium pelo Modelo Básico de Cochrane

Enunciado: Estime o equity premium médio para todos os portfólios seguindo a equação do modelo básico (Cochrane, Cap. 1). Considere, nesse caso, que o coeficiente de aversão relativa ao risco é 2 e a taxa de desconto intertemporal é de 0,99.

Resposta:

Fundamentação Teórica

O Consumption Capital Asset Pricing Model (CCAPM) é um dos modelos fundamentais em finanças que relaciona os retornos dos ativos ao consumo agregado da economia.

Intuição: Os investidores se preocupam com o consumo, não com a riqueza per se. Portanto, ativos que pagam bem quando o consumo é baixo (estados ruins da economia) são valiosos e devem ter retornos esperados mais baixos.

Seguindo Cochrane (2005, Cap. 1), estimamos o equity premium médio para todos os portfólios usando a equação do modelo básico que relaciona excess returns ao fator estocástico de desconto (SDF):

\[E[m \cdot R^e] = 0\]

Onde \(m = \beta \frac{u'(c_{t+1})}{u'(c_t)}\) é o fator de desconto estocástico. Com utilidade CRRA (\(u(c) = \frac{c^{1-\gamma}}{1-\gamma}\)), temos:

\[m = \beta (1 + \Delta c)^{-\gamma}\]

Considerando coeficiente de aversão relativa ao risco γ = 2 e taxa de desconto intertemporal β = 0,99, o equity premium esperado é aproximadamente:

\[E[R^e] \approx -\frac{\text{Cov}(m, R^e)}{E[m]}\]

gamma <- 2 # Coeficiente de aversão ao risco relativo
beta_discount <- 0.99 # Taxa de desconto intertemporal

# Calcular SDF: m = β * (1 + Δc)^(-γ)
cons_growth_decimal <- data_full$Consumption_Growth / 100
sdf <- beta_discount * (1 + cons_growth_decimal)^(-gamma)
mean_sdf <- mean(sdf, na.rm = TRUE)

# Calcular para cada portfólio
ccapm_results <- data.frame(Portfolio = paste0("P", 1:25))

for (i in 1:25) {
    port_name <- paste0("P", i)
    excess_ret <- (data_full[[port_name]] - data_full$RF) / 100
    cons_growth <- cons_growth_decimal

    valid_idx <- complete.cases(excess_ret, cons_growth, sdf)
    excess_ret_clean <- excess_ret[valid_idx]
    cons_growth_clean <- cons_growth[valid_idx]
    sdf_clean <- sdf[valid_idx]

    if (length(excess_ret_clean) > 10) {
        # Método Cochrane: E[R^e] ≈ -Cov(m, R^e) / E[m]
        cov_m_r <- cov(sdf_clean, excess_ret_clean)
        ep_sdf <- -cov_m_r / mean_sdf * 100

        # Regressão para beta de consumo e R²
        lm_model <- lm(excess_ret_clean ~ cons_growth_clean)
        beta_consumption <- coef(lm_model)[2]
        r2 <- summary(lm_model)$r.squared

        ccapm_results[i, "Beta_Cons"] <- beta_consumption
        ccapm_results[i, "R2"] <- r2
        ccapm_results[i, "EP_SDF"] <- ep_sdf
    } else {
        ccapm_results[i, c("Beta_Cons", "R2", "EP_SDF")] <- NA
    }
}
RESULTADOS CCAPM (γ = 2, β = 0.99):
Equity Premium Médio (SDF Method): 0.0231 %
R² Médio: 0.0162 
Resultados CCAPM - 25 Portfolios Size and Book-to-Market (5×5)
Portfolio Beta_Cons R2 EP_SDF
P1 1.7051 0.0099 0.0261
P2 2.3690 0.0265 0.0368
P3 1.4649 0.0125 0.0227
P4 1.6831 0.0177 0.0262
P5 2.1337 0.0232 0.0329
P6 1.5274 0.0105 0.0233
P7 1.5223 0.0143 0.0236
P8 1.4592 0.0159 0.0228
P9 1.3796 0.0144 0.0217
P10 1.6092 0.0149 0.0246
P11 1.4667 0.0119 0.0226
P12 1.4818 0.0173 0.0229
P13 1.3364 0.0162 0.0209
P14 1.3698 0.0154 0.0212
P15 1.2618 0.0101 0.0197
P16 1.3854 0.0132 0.0211
P17 1.2851 0.0149 0.0198
P18 1.5265 0.0226 0.0237
P19 1.2058 0.0130 0.0186
P20 1.8668 0.0222 0.0292
P21 1.3849 0.0200 0.0211
P22 1.1750 0.0182 0.0181
P23 1.1152 0.0174 0.0172
P24 1.2692 0.0171 0.0199
P25 1.4155 0.0153 0.0221

Interpretação dos Resultados CCAPM

Medidas apresentadas:

  • Beta_Cons: Sensibilidade do retorno ao crescimento do consumo
  • R²: Poder explicativo do modelo (quanto da variação dos retornos é explicada pelo consumo)
  • EP_SDF: Equity premium previsto pelo modelo usando o método SDF Equity Premium Puzzle:
O equity premium previsto pelo CCAPM ( 0.0231 %) é consideravelmente baixo, ilustrando o 'equity premium puzzle' (Mehra & Prescott, 1985): o modelo básico de consumo tende a subestimar substancialmente o prêmio de risco de mercado, mesmo com coeficiente de aversão ao risco γ=2.

R² Baixo:

O R² médio de 0.0162 indica que o crescimento do consumo explica muito pouca variação nos retornos dos portfólios, sugerindo que outros fatores além do consumo são importantes para determinar os retornos dos ativos.

(b) CAPM - Modelo de Um Fator

Enunciado: No site do French, são disponibilizados os dados referentes aos 3 fatores utilizados pelos autores em seu artigo de 1993, a saber: o excesso de retorno de mercado, e os fatores HML e SMB (HML = High minus Low: diferença entre os retornos das firmas com alto book-to-market e baixo book-to-market; SMB = Small minus Big: diferença entre empresas pequenas e grandes). Obtenha a planilha com esses dados, e estime o modelo CAPM para cada um dos portfólios, utilizando a variável (Mkt−Rf), e compute a média dos respectivos prêmios de risco.

Nota: O enunciado original menciona “HMB” e “SML”, mas as siglas corretas usadas por Fama e French são HML (High Minus Low) e SMB (Small Minus Big).

Resposta:

Fundamentação Teórica

O Capital Asset Pricing Model (CAPM), desenvolvido por Sharpe (1964), Lintner (1965) e Mossin (1966), é um dos modelos mais utilizados em finanças. Ele estabelece uma relação linear entre o retorno esperado de um ativo e seu risco sistemático (beta de mercado).

Hipóteses do modelo:

  • Investidores são avessos ao risco e maximizam utilidade esperada
  • Mercados são eficientes e sem fricções
  • Todos os investidores têm o mesmo horizonte de investimento
  • Existe uma taxa livre de risco para emprestar e tomar emprestado

Equação do CAPM:

Utilizando os dados dos 3 fatores disponibilizados por Fama e French, estimamos o modelo CAPM para cada portfólio usando a variável (Mkt-RF):

\[R^e_i = \alpha_i + \beta_i (R_M - R_f) + \epsilon_i\]

capm_results <- data.frame(Portfolio = paste0("P", 1:25))

# Calcular prêmio de mercado médio
market_premium <- mean(data_full$`Mkt-RF`, na.rm = TRUE)

for (i in 1:25) {
    port_name <- paste0("P", i)
    reg_data <- data_full %>%
        transmute(
            Excess_Ret = !!sym(port_name) - RF,
            Mkt_RF = `Mkt-RF`
        ) %>%
        filter(complete.cases(Excess_Ret, Mkt_RF))

    if (nrow(reg_data) > 2) {
        capm_model <- lm(Excess_Ret ~ Mkt_RF, data = reg_data)
        capm_results[i, "Alpha"] <- coef(capm_model)[1]
        capm_results[i, "Beta_Mkt"] <- coef(capm_model)[2]
        capm_results[i, "R2"] <- summary(capm_model)$r.squared

        # EP previsto pelo CAPM: Beta_Mkt × Prêmio de Mercado
        capm_results[i, "EP_CAPM"] <- coef(capm_model)[2] * market_premium
    } else {
        capm_results[i, c("Alpha", "Beta_Mkt", "R2", "EP_CAPM")] <- NA
    }
}
RESULTADOS CAPM:
Prêmio de Mercado Médio: 2.093 %
Equity Premium Médio (CAPM): 2.3589 %
Beta de Mercado Médio: 1.1271 
R² Médio: 0.7587 
Resultados CAPM - 25 Portfolios Size and Book-to-Market (5×5)
Portfolio Alpha Beta_Mkt R2 EP_CAPM
P1 -1.9426 1.5329 0.6684 3.2083
P2 -0.3239 1.3340 0.7060 2.7920
P3 -0.0138 1.1952 0.6981 2.5014
P4 0.6914 1.1277 0.6680 2.3602
P5 1.1077 1.2089 0.6237 2.5302
P6 -1.1996 1.4338 0.7763 3.0009
P7 0.0100 1.2333 0.7887 2.5812
P8 0.4243 1.1150 0.7786 2.3336
P9 0.6643 1.0649 0.7222 2.2288
P10 0.8676 1.1940 0.6862 2.4990
P11 -0.7532 1.3291 0.8223 2.7818
P12 0.1903 1.1325 0.8455 2.3702
P13 0.3650 1.0247 0.7982 2.1447
P14 0.7034 1.0535 0.7638 2.2050
P15 0.8695 1.0992 0.6399 2.3006
P16 -0.3319 1.2256 0.8666 2.5651
P17 0.0096 1.0736 0.8703 2.2469
P18 0.4798 1.0094 0.8283 2.1126
P19 0.6174 1.0179 0.7801 2.1305
P20 0.5620 1.1320 0.6835 2.3693
P21 0.0166 1.0106 0.8922 2.1151
P22 0.0660 0.8982 0.8911 1.8799
P23 0.5031 0.8222 0.7952 1.7208
P24 0.1361 0.9046 0.7281 1.8933
P25 0.4157 1.0036 0.6451 2.1005

Interpretação dos Resultados CAPM

Medidas apresentadas:

  • Alpha: Intercepto da regressão (retorno anormal não explicado pelo mercado)
  • Beta_Mkt: Sensibilidade do retorno ao excesso de retorno do mercado
  • R²: Poder explicativo do modelo
  • EP_CAPM: Equity premium previsto = Beta_Mkt × Prêmio de Mercado

Interpretação do Beta de Mercado:

  • Beta > 1: Ativo mais volátil que o mercado (amplifica movimentos)
  • Beta = 1: Ativo move-se com o mercado
  • Beta < 1: Ativo menos volátil que o mercado
- Beta médio dos portfólios: 1.1271 

Qualidade do Ajuste:

O R² médio de 0.7587 indica que o fator de mercado explica 75.87 % da variação dos retornos, um ajuste consideravelmente melhor que o CCAPM, mas ainda deixando espaço para outros fatores explicativos (como SMB e HML no modelo FF3).

(c) Modelo de Três Fatores Fama-French

Enunciado: Em seguida, estime o modelo de três fatores proposto por Fama e French para cada um dos portfólios. Nesse caso, será necessário incluir os fatores HML e SMB na equação de regressão (ver definição em (b)). Novamente, compute o equity premium médio para cada um dos portfólios.

Resposta:

Fundamentação Teórica

O Modelo de Três Fatores de Fama-French (1993) expande o CAPM adicionando dois fatores que capturam anomalias documentadas empiricamente:

1. Fator SMB (Small Minus Big):

  • Capta o “efeito tamanho” - empresas pequenas historicamente apresentam retornos superiores
  • SMB = Retorno médio de empresas pequenas - Retorno médio de empresas grandes

2. Fator HML (High Minus Low):

  • Capta o “efeito valor” - empresas com alto book-to-market (value) tendem a superar growth
  • HML = Retorno médio de empresas com alto B/M - Retorno médio de empresas com baixo B/M

Motivação: Fama e French argumentam que esses fatores representam riscos sistemáticos adicionais não capturados pelo CAPM, relacionados a distress financeiro (HML) e problemas de acesso a capital (SMB).

Estimamos o modelo de três fatores proposto por Fama e French para cada portfólio, incluindo os fatores HML e SMB:

\[R^e_i = \alpha_i + \beta_{M,i} (R_M - R_f) + \beta_{SMB,i} SMB + \beta_{HML,i} HML + \epsilon_i\]

ff3_results <- data.frame(Portfolio = paste0("P", 1:25))

# Calcular prêmios médios dos fatores
market_premium <- mean(data_full$`Mkt-RF`, na.rm = TRUE)
smb_premium <- mean(data_full$SMB, na.rm = TRUE)
hml_premium <- mean(data_full$HML, na.rm = TRUE)

for (i in 1:25) {
    port_name <- paste0("P", i)
    reg_data <- data_full %>%
        transmute(
            Excess_Ret = !!sym(port_name) - RF,
            Mkt_RF = `Mkt-RF`,
            SMB = SMB,
            HML = HML
        ) %>%
        filter(complete.cases(Excess_Ret, Mkt_RF, SMB, HML))

    if (nrow(reg_data) > 4) {
        ff3_model <- lm(Excess_Ret ~ Mkt_RF + SMB + HML, data = reg_data)
        ff3_results[i, "Alpha"] <- coef(ff3_model)[1]
        ff3_results[i, "Beta_Mkt"] <- coef(ff3_model)[2]
        ff3_results[i, "Beta_SMB"] <- coef(ff3_model)[3]
        ff3_results[i, "Beta_HML"] <- coef(ff3_model)[4]
        ff3_results[i, "R2"] <- summary(ff3_model)$r.squared

        # EP previsto pelo FF3: Beta_Mkt × λ_Mkt + Beta_SMB × λ_SMB + Beta_HML × λ_HML
        ff3_results[i, "EP_FF3"] <- coef(ff3_model)[2] * market_premium +
            coef(ff3_model)[3] * smb_premium +
            coef(ff3_model)[4] * hml_premium
    } else {
        ff3_results[i, c("Alpha", "Beta_Mkt", "Beta_SMB", "Beta_HML", "R2", "EP_FF3")] <- NA
    }
}
RESULTADOS FAMA-FRENCH 3 FATORES:
Prêmio de Mercado: 2.093 %
Prêmio SMB: 0.2758 %
Prêmio HML: 0.953 %
Equity Premium Médio (FF3): 2.5565 %
R² Médio: 0.9199 
Resultados Fama-French 3 Fatores - 25 Portfolios Size and Book-to-Market (5×5)
Portfolio Alpha Beta_Mkt Beta_SMB Beta_HML R2 EP_FF3
P1 -1.2301 1.1119 1.5418 -0.2693 0.8866 2.4958
P2 -0.0783 1.0100 1.3335 0.0678 0.9276 2.5464
P3 -0.0669 0.9298 1.2162 0.2866 0.9460 2.5546
P4 0.3675 0.9042 1.1598 0.4949 0.9553 2.6841
P5 0.4604 1.0094 1.2100 0.7673 0.9454 3.1776
P6 -0.5122 1.1125 1.1068 -0.3360 0.9379 2.3134
P7 0.1819 0.9919 0.9992 0.0607 0.9517 2.4094
P8 0.2228 0.9555 0.8132 0.3263 0.9427 2.5351
P9 0.1686 0.9402 0.8007 0.5623 0.9506 2.7245
P10 0.1109 1.0763 0.8959 0.7931 0.9568 3.2557
P11 -0.0840 1.0829 0.7785 -0.3869 0.9404 2.1126
P12 0.2425 0.9835 0.6426 0.0863 0.9335 2.3180
P13 0.0578 0.9328 0.5618 0.3617 0.9232 2.4519
P14 0.1089 1.0025 0.5183 0.5859 0.9374 2.7995
P15 0.0044 1.0318 0.7235 0.8465 0.9124 3.1657
P16 0.2376 1.0738 0.4037 -0.3811 0.9338 1.9956
P17 -0.0414 1.0010 0.3504 0.1115 0.9042 2.2979
P18 0.0706 0.9982 0.2494 0.3819 0.9017 2.5219
P19 0.0608 1.0019 0.3427 0.5200 0.9067 2.6871
P20 -0.3318 1.1283 0.4514 0.8153 0.8890 3.2630
P21 0.3689 1.0244 -0.2334 -0.3324 0.9541 1.7628
P22 -0.0460 0.9416 -0.1401 0.0628 0.8997 1.9919
P23 0.1386 0.9035 -0.1876 0.2581 0.8440 2.0853
P24 -0.6822 1.0331 -0.1781 0.6280 0.9009 2.7116
P25 -0.5337 1.1276 -0.0940 0.7512 0.8169 3.0499

Interpretação dos Resultados FF3

Medidas apresentadas:

  • Alpha: Intercepto (retorno anormal não explicado pelos três fatores)
  • Beta_Mkt: Exposição ao risco de mercado
  • Beta_SMB: Exposição ao fator tamanho (positivo = bias para small caps)
  • Beta_HML: Exposição ao fator valor (positivo = bias para value stocks)
  • R²: Poder explicativo do modelo
  • EP_FF3: Equity premium previsto = Σ(Beta_i × Lambda_i) para os 3 fatores

Prêmios de Risco dos Fatores:

- Prêmio de Mercado (Mkt-RF): 2.093 % ao trimestre
- Prêmio SMB: 0.2758 % ao trimestre
- Prêmio HML: 0.953 % ao trimestre

Melhoria em Relação ao CAPM:

- R² médio FF3: 0.9199 
- R² médio CAPM: 0.7587 
- Ganho no R²: 16.13 pontos percentuais
O modelo FF3 explica 91.99 % da variação dos retornos, uma melhoria substancial em relação ao CAPM. Isto confirma que os fatores SMB e HML capturam dimensões importantes do risco que não são explicadas apenas pelo mercado.

(d) Análise de Componentes Principais

Enunciado: Usando os dados de retornos reais dos 25 Portfolios Formed on Size and Book-to-Market (5×5) de Fama e French, calcule os respectivos componentes principais (fatores). Quantos fatores explicam 60% ou mais da variação da base? Guarde-os.

Nota: Para calcular os retornos reais trimestrais, utilize a fórmula:

\[\left(\frac{(1+r_1/100)(1+r_2/100)(1+r_3/100)}{(1+\pi_1/100)(1+\pi_2/100)(1+\pi_3/100)} - 1\right) \times 100\]

utilizando o Índice de Preços ao Consumidor (CPI) em % ao mês como indicador de inflação.

Resposta:

Fundamentação Teórica

Análise de Componentes Principais (PCA) é uma técnica estatística que transforma variáveis correlacionadas em um conjunto menor de variáveis não correlacionadas chamadas componentes principais.

Objetivo no contexto de Asset Pricing:

  • Identificar fatores de risco comuns que explicam a variação dos retornos
  • Reduzir a dimensionalidade (de 25 portfólios para poucos fatores)
  • Os componentes principais são combinações lineares dos retornos originais
  • O primeiro PC capta a maior variação, o segundo PC capta a segunda maior variação ortogonal ao primeiro, e assim por diante

Diferença entre PCA e FF3:

  • FF3 usa fatores construídos economicamente (mercado, tamanho, valor)
  • PCA usa fatores construídos estatisticamente (máxima variação)
  • PCA é puramente data-driven, sem teoria econômica a priori

Usando os dados de retornos reais dos 25 portfólios de Fama e French, calculamos os componentes principais (fatores) para determinar quantos fatores explicam 60% ou mais da variação da base.

# Matriz de retornos REAIS
returns_real_matrix <- data_full %>%
    select(ends_with("_Real")) %>%
    na.omit()

# PCA requer dados centrados para capturar corretamente a variação
returns_centered <- scale(returns_real_matrix, center = TRUE, scale = FALSE)

cat("Dados preparados para PCA: Retornos reais dos 25 portfólios, centralizados (média = 0), Período:", nrow(returns_centered), "observações\n\n")
Dados preparados para PCA: Retornos reais dos 25 portfólios, centralizados (média = 0), Período: 311 observações
# PCA (center=FALSE porque já centralizamos manualmente, scale=TRUE para padronizar variâncias)
pca_result <- prcomp(returns_centered, center = FALSE, scale. = TRUE)

# Variância explicada
variance_explained <- summary(pca_result)$importance[2, ]
cumvar <- cumsum(variance_explained)

# Quantos PCs para 60%?
n_pcs_60 <- which(cumvar >= 0.60)[1]

cat("Número de PCs para ≥ 60% da variância:", n_pcs_60, "- Variância acumulada:", round(cumvar[n_pcs_60] * 100, 2), "%\n\n")
Número de PCs para ≥ 60% da variância: 1 - Variância acumulada: 85.14 %
var_table <- data.frame(
    PC = 1:10,
    Var_Individual = round(variance_explained[1:10] * 100, 2),
    Var_Acumulada = round(cumvar[1:10] * 100, 2)
)
kable(var_table,
    col.names = c("Componente", "Variância (%)", "Variância Acumulada (%)"),
    caption = "Variância Explicada por Componentes Principais"
) %>%
    kable_styling(bootstrap_options = c("striped", "hover"))
Variância Explicada por Componentes Principais
Componente Variância (%) Variância Acumulada (%)
PC1 1 85.14 85.14
PC2 2 4.43 89.57
PC3 3 3.87 93.44
PC4 4 1.19 94.63
PC5 5 0.77 95.40
PC6 6 0.55 95.95
PC7 7 0.52 96.46
PC8 8 0.39 96.85
PC9 9 0.38 97.23
PC10 10 0.33 97.56
# Extrair e guardar os fatores (scores dos PCs)
pc_scores <- as.data.frame(pca_result$x[, 1:n_pcs_60])
colnames(pc_scores) <- paste0("PC", 1:n_pcs_60)

# Estimar equity premium usando PCA
pca_results <- data.frame(Portfolio = paste0("P", 1:25))

for (i in 1:25) {
    port_name <- paste0("P", i, "_Real")
    pca_data <- data.frame(
        Excess_Ret = returns_real_matrix[[port_name]],
        pc_scores
    )

    if (nrow(pca_data) > n_pcs_60 + 5) {
        formula_pca <- as.formula(paste("Excess_Ret ~", paste(colnames(pc_scores), collapse = " + ")))
        pca_model <- lm(formula_pca, data = pca_data)
        pca_results[i, "R2_PCA"] <- summary(pca_model)$r.squared
        pca_results[i, "EP_PCA"] <- mean(pca_data$Excess_Ret, na.rm = TRUE)
    } else {
        pca_results[i, c("R2_PCA", "EP_PCA")] <- NA
    }
}

cat("\nEquity Premium Médio (PCA):", round(mean(pca_results$EP_PCA, na.rm = TRUE), 4), "% - R² Médio (PCA):", round(mean(pca_results$R2_PCA, na.rm = TRUE), 4), "\n")

Equity Premium Médio (PCA): 2.8123 % - R² Médio (PCA): 0.8514 
# Gráfico de variância
var_df <- data.frame(
    PC = 1:min(15, length(variance_explained)),
    Variance = variance_explained[1:min(15, length(variance_explained))],
    Cumulative = cumvar[1:min(15, length(variance_explained))]
)

p1 <- ggplot(var_df, aes(x = PC)) +
    geom_col(aes(y = Variance), fill = "#3498DB", alpha = 0.8) +
    geom_line(aes(y = Cumulative), color = "#E74C3C", linewidth = 1.5) +
    geom_point(aes(y = Cumulative), color = "#E74C3C", size = 3) +
    geom_hline(yintercept = 0.60, linetype = "dashed", color = "#27AE60", linewidth = 1) +
    geom_vline(xintercept = n_pcs_60, linetype = "dotted", color = "#E74C3C", linewidth = 1) +
    annotate("text",
        x = n_pcs_60 + 1, y = 0.5,
        label = paste(n_pcs_60, "PCs\n≥60%"),
        color = "#E74C3C", fontface = "bold", size = 4
    ) +
    scale_y_continuous(labels = scales::percent, breaks = seq(0, 1, 0.1)) +
    labs(
        title = "Análise de Componentes Principais: Variância Explicada",
        subtitle = "25 Portfolios Formed on Size and Book-to-Market (5×5)",
        x = "Componente Principal",
        y = "Proporção da Variância",
        caption = "Barras azuis: variância individual | Linha vermelha: variância acumulada"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 14),
        plot.subtitle = element_text(color = "gray40", size = 11),
        axis.title = element_text(face = "bold"),
        panel.grid.minor = element_blank()
    )

print(p1)

(e) Comparação dos Modelos

Enunciado: Compare os equity premiums estimados nos itens (a), (b), (c) e (d), bem como o ajuste dos modelos com base no R².

Resposta:

# Combinar resultados
comparison <- data.frame(
    Portfolio = paste0("P", 1:25),
    EP_CCAPM = ccapm_results$EP_SDF,
    EP_CAPM = capm_results$EP_CAPM,
    EP_FF3 = ff3_results$EP_FF3,
    EP_PCA = pca_results$EP_PCA,
    R2_CCAPM = ccapm_results$R2,
    R2_CAPM = capm_results$R2,
    R2_FF3 = ff3_results$R2,
    R2_PCA = pca_results$R2_PCA
)

# Médias
summary_table <- data.frame(
    Modelo = c("CCAPM", "CAPM", "FF3", "PCA"),
    EP_Medio = c(
        mean(ccapm_results$EP_SDF, na.rm = TRUE),
        mean(capm_results$EP_CAPM, na.rm = TRUE),
        mean(ff3_results$EP_FF3, na.rm = TRUE),
        mean(pca_results$EP_PCA, na.rm = TRUE)
    ),
    R2_Medio = c(
        mean(ccapm_results$R2, na.rm = TRUE),
        mean(capm_results$R2, na.rm = TRUE),
        mean(ff3_results$R2, na.rm = TRUE),
        mean(pca_results$R2_PCA, na.rm = TRUE)
    )
)

kable(summary_table,
    digits = 4,
    col.names = c("Modelo", "Equity Premium Médio (%)", "R² Médio"),
    caption = "Comparação entre Modelos"
) %>%
    kable_styling(bootstrap_options = c("striped", "hover"))
Comparação entre Modelos
Modelo Equity Premium Médio (%) R² Médio
CCAPM 0.0231 0.0162
CAPM 2.3589 0.7587
FF3 2.5565 0.9199
PCA 2.8123 0.8514
# Gráfico comparativo
comparison_long <- comparison %>%
    select(Portfolio, EP_CCAPM, EP_CAPM, EP_FF3, EP_PCA) %>%
    pivot_longer(-Portfolio, names_to = "Modelo", values_to = "EP") %>%
    mutate(
        Modelo = factor(Modelo,
            levels = c("EP_CCAPM", "EP_CAPM", "EP_FF3", "EP_PCA"),
            labels = c("CCAPM", "CAPM", "FF3", "PCA")
        ),
        # Ordenar Portfolio como P1, P2, ..., P25
        Portfolio = factor(Portfolio, levels = paste0("P", 1:25))
    ) %>%
    filter(!is.na(EP))

p2 <- ggplot(comparison_long, aes(x = Portfolio, y = EP, color = Modelo, group = Modelo)) +
    geom_line(linewidth = 1.2, alpha = 0.7) +
    geom_point(size = 2.5, alpha = 0.9) +
    scale_color_manual(values = c(
        "CCAPM" = "#E74C3C",
        "CAPM" = "#3498DB",
        "FF3" = "#27AE60",
        "PCA" = "#9B59B6"
    )) +
    labs(
        title = "Equity Premium por Portfólio: Comparação de Modelos",
        subtitle = "25 Portfólios Fama-French (1947-2025)",
        x = "Portfólio",
        y = "Equity Premium (%)",
        color = "Modelo",
        caption = "Ordenação: Small/Big × Low/High (ME × INV)"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 14),
        plot.subtitle = element_text(color = "gray40", size = 11),
        axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
        axis.title = element_text(face = "bold"),
        legend.position = "top",
        panel.grid.minor = element_blank()
    )

print(p2)

# Gráfico comparativo de R²
r2_comparison <- data.frame(
    Portfolio = paste0("P", 1:25),
    CCAPM = ccapm_results$R2,
    CAPM = capm_results$R2,
    FF3 = ff3_results$R2,
    PCA = pca_results$R2_PCA
) %>%
    pivot_longer(-Portfolio, names_to = "Modelo", values_to = "R2") %>%
    mutate(
        Modelo = factor(Modelo, levels = c("CCAPM", "CAPM", "FF3", "PCA")),
        Portfolio = factor(Portfolio, levels = paste0("P", 1:25))
    ) %>%
    filter(!is.na(R2))

p3 <- ggplot(r2_comparison, aes(x = Modelo, y = R2, fill = Modelo)) +
    geom_boxplot(alpha = 0.7, outlier.shape = 21, outlier.size = 3) +
    geom_jitter(width = 0.2, alpha = 0.3, size = 1.5) +
    scale_fill_manual(values = c(
        "CCAPM" = "#E74C3C",
        "CAPM" = "#3498DB",
        "FF3" = "#27AE60",
        "PCA" = "#9B59B6"
    )) +
    scale_y_continuous(labels = scales::percent) +
    labs(
        title = "Comparação do Ajuste dos Modelos",
        subtitle = "Distribuição do R² nos 25 Portfólios",
        x = NULL,
        y = "R² (Coeficiente de Determinação)",
        caption = "Box plots mostram mediana, quartis e outliers | Pontos individuais mostram cada portfólio"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 14),
        plot.subtitle = element_text(color = "gray40", size = 11),
        axis.title = element_text(face = "bold"),
        legend.position = "none",
        panel.grid.major.x = element_blank()
    )

print(p3)

Interpretação:

  • CCAPM: Apresenta equity premium médio muito baixo, refletindo o conhecido “equity premium puzzle” - a teoria básica de consumo tende a subestimar substancialmente os retornos excedentes de mercado.

  • CAPM e FF3: Estimam equity premiums com base nos betas estimados e nos prêmios de risco dos fatores de mercado.

  • R² do CCAPM: Muito baixo, indicando que o crescimento do consumo explica pouca variação nos retornos dos portfólios.

  • R² do FF3: Significativamente maior, demonstrando que os fatores de Fama-French (mercado, tamanho e valor) capturam melhor a variação cross-sectional dos retornos.

(f) Fama-MacBeth

Enunciado: Agora, iremos replicar o trabalho de Fama e Macbeth na nossa amostra, o qual consistirá em duas etapas.

Etapa em painel: combine os dados das duas tabelas do Fama e French em um único dataframe e estruture os dados para a regressão. Em seguida, realize regressões dos excessos de retornos dos 25 portfólios em relação aos três fatores, estimando os coeficientes beta para cada fator e portfólio.

Etapa cross section: utilize os coeficientes beta obtidos como variáveis independentes para calcular estatísticas resumidas, como média e desvio padrão, e realizar testes de significância. Finalmente, faça a regressão dos retornos médios dos portfólios sobre os coeficientes beta estimados e discuta as implicações do modelo.

Resposta:

A metodologia de Fama-MacBeth (1973) consiste em um procedimento de duas etapas para testar se os fatores de risco são precificados no mercado.

Fundamentação Teórica

O procedimento Fama-MacBeth testa a relação linear entre risco (betas) e retorno esperado:

\[E[R_i - R_f] = \lambda_0 + \lambda_{Mkt} \beta_{i,Mkt} + \lambda_{SMB} \beta_{i,SMB} + \lambda_{HML} \beta_{i,HML}\]

Onde: - \(\lambda_j\) são os prêmios de risco dos fatores (o que queremos estimar) - \(\beta_{i,j}\) são as exposições do portfólio \(i\) ao fator \(j\)

Etapa 1: Regressões em Séries Temporais

Para cada portfólio \(i\), estimamos os betas através de regressões de séries temporais:

\[R_{i,t} - R_{f,t} = \alpha_i + \beta_{i,Mkt}(R_{M,t} - R_{f,t}) + \beta_{i,SMB} SMB_t + \beta_{i,HML} HML_t + \epsilon_{i,t}\]

# ETAPA 1: Estimar betas para cada portfólio via time-series regressions
# (Já estimados na seção FF3 - vamos reutilizar)

betas_fm <- ff3_results %>%
    select(Portfolio, Beta_Mkt, Beta_SMB, Beta_HML)


# Resumo dos betas
beta_summary <- data.frame(
    Fator = c("Beta_Mkt", "Beta_SMB", "Beta_HML"),
    Média = c(
        mean(betas_fm$Beta_Mkt, na.rm = TRUE),
        mean(betas_fm$Beta_SMB, na.rm = TRUE),
        mean(betas_fm$Beta_HML, na.rm = TRUE)
    ),
    Desvio_Padrão = c(
        sd(betas_fm$Beta_Mkt, na.rm = TRUE),
        sd(betas_fm$Beta_SMB, na.rm = TRUE),
        sd(betas_fm$Beta_HML, na.rm = TRUE)
    ),
    Mínimo = c(
        min(betas_fm$Beta_Mkt, na.rm = TRUE),
        min(betas_fm$Beta_SMB, na.rm = TRUE),
        min(betas_fm$Beta_HML, na.rm = TRUE)
    ),
    Máximo = c(
        max(betas_fm$Beta_Mkt, na.rm = TRUE),
        max(betas_fm$Beta_SMB, na.rm = TRUE),
        max(betas_fm$Beta_HML, na.rm = TRUE)
    )
)

kable(beta_summary, 
      digits = 4, 
      caption = "Resumo dos Betas Estimados (Etapa 1)") %>%
    kable_styling(bootstrap_options = c("striped", "hover"))
Resumo dos Betas Estimados (Etapa 1)
Fator Média Desvio_Padrão Mínimo Máximo
Beta_Mkt 1.0123 0.0684 0.9035 1.1283
Beta_SMB 0.6106 0.5162 -0.2334 1.5418
Beta_HML 0.2825 0.4027 -0.3869 0.8465

Etapa 2: Regressões Cross-Sectional em Cada Período

Para cada período \(t\), regredimos os retornos excedentes dos 25 portfólios contra seus betas:

\[R_{i,t} - R_{f,t} = \lambda_{0,t} + \lambda_{Mkt,t} \beta_{i,Mkt} + \lambda_{SMB,t} \beta_{i,SMB} + \lambda_{HML,t} \beta_{i,HML} + \eta_{i,t}\]

Os prêmios de risco são as médias dos \(\lambda_t\) ao longo do tempo.

# ETAPA 2: Para cada período t, fazer regressão cross-sectional

# Preparar dados: retornos excedentes de cada portfólio em cada período
n_periods <- nrow(data_full)
n_portfolios <- 25

# Matriz de retornos excedentes (períodos × portfólios)
excess_returns_matrix <- matrix(NA, nrow = n_periods, ncol = n_portfolios)
for (i in 1:n_portfolios) {
    port_name <- paste0("P", i)
    if (port_name %in% names(data_full)) {
        excess_returns_matrix[, i] <- data_full[[port_name]] - data_full$RF
    }
}

# Matriz de betas (portfólios × fatores)
beta_matrix <- as.matrix(betas_fm[, c("Beta_Mkt", "Beta_SMB", "Beta_HML")])

# Para cada período, rodar regressão cross-sectional
lambdas_t <- matrix(NA, nrow = n_periods, ncol = 4)
colnames(lambdas_t) <- c("Lambda_0", "Lambda_Mkt", "Lambda_SMB", "Lambda_HML")

for (t in 1:n_periods) {
    # Retornos excedentes no período t
    y_t <- excess_returns_matrix[t, ]
    
    # Remover NAs
    valid_idx <- !is.na(y_t) & complete.cases(beta_matrix)
    
    if (sum(valid_idx) > 4) {  # Precisamos de observações suficientes
        # Regressão cross-sectional: R_i,t - R_f,t ~ betas
        cs_data <- data.frame(
            Ret = y_t[valid_idx],
            Beta_Mkt = beta_matrix[valid_idx, 1],
            Beta_SMB = beta_matrix[valid_idx, 2],
            Beta_HML = beta_matrix[valid_idx, 3]
        )
        
        cs_reg <- lm(Ret ~ Beta_Mkt + Beta_SMB + Beta_HML, data = cs_data)
        lambdas_t[t, ] <- coef(cs_reg)
    }
}

# Remover períodos com NA
lambdas_t_clean <- na.omit(lambdas_t)


# Prêmios de risco: média dos lambdas ao longo do tempo
lambda_means <- colMeans(lambdas_t_clean)
lambda_sd <- apply(lambdas_t_clean, 2, sd)
lambda_se <- lambda_sd / sqrt(nrow(lambdas_t_clean))
lambda_t_stat <- lambda_means / lambda_se
lambda_p_value <- 2 * (1 - pt(abs(lambda_t_stat), df = nrow(lambdas_t_clean) - 1))

# Tabela de resultados
fama_macbeth_results <- data.frame(
    Fator = c("Intercepto (α)", "Market (Mkt-RF)", "Size (SMB)", "Value (HML)"),
    Lambda = lambda_means,
    Erro_Padrão = lambda_se,
    T_Stat = lambda_t_stat,
    P_Valor = lambda_p_value,
    Significativo = ifelse(lambda_p_value < 0.01, "***",
                          ifelse(lambda_p_value < 0.05, "**",
                                ifelse(lambda_p_value < 0.10, "*", "")))
)

kable(fama_macbeth_results,
    digits = 4,
    caption = "Prêmios de Risco Estimados - Procedimento Fama-MacBeth",
    col.names = c("Fator", "λ (% trimestral)", "Erro Padrão", "Estatística t", "P-valor", "Signif.")
) %>%
    kable_styling(bootstrap_options = c("striped", "hover"))
Prêmios de Risco Estimados - Procedimento Fama-MacBeth
Fator λ (% trimestral) Erro Padrão Estatística t P-valor Signif.
Lambda_0 Intercepto (α) 3.1482 0.8028 3.9216 0.0001 ***
Lambda_Mkt Market (Mkt-RF) -1.0422 0.9167 -1.1368 0.2565
Lambda_SMB Size (SMB) 0.2445 0.2919 0.8376 0.4029
Lambda_HML Value (HML) 0.9975 0.3330 2.9958 0.0030 ***

Interpretação dos Resultados


**Prêmios de Risco Estimados:**
- Intercepto (α): 3.1482 % - Retorno anormal médio
- Market Premium: -1.0422 % - Prêmio de risco de mercado
- SMB Premium: 0.2445 % - Prêmio pelo fator tamanho
- HML Premium: 0.9975 % - Prêmio pelo fator valor
**Testes de Significância:**
✗ Fator de mercado não é significativamente precificado (p = 0.2565 )
✗ Fator SMB não é significativamente precificado (p = 0.4029 )
✓ Fator HML é significativamente precificado (p < 0.003 )

Visualização: Evolução dos Prêmios de Risco no Tempo

# Gráfico da evolução dos lambdas ao longo do tempo
lambdas_df <- data.frame(
    Periodo = 1:nrow(lambdas_t_clean),
    Lambda_Mkt = lambdas_t_clean[, 2],
    Lambda_SMB = lambdas_t_clean[, 3],
    Lambda_HML = lambdas_t_clean[, 4]
) %>%
    pivot_longer(cols = starts_with("Lambda"), names_to = "Fator", values_to = "Lambda")

lambdas_df$Fator <- factor(lambdas_df$Fator,
    levels = c("Lambda_Mkt", "Lambda_SMB", "Lambda_HML"),
    labels = c("Market Premium", "SMB Premium", "HML Premium")
)

p_lambdas <- ggplot(lambdas_df, aes(x = Periodo, y = Lambda, color = Fator)) +
    geom_line(alpha = 0.3) +
    geom_smooth(se = TRUE, linewidth = 1.2) +
    geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
    facet_wrap(~ Fator, ncol = 1, scales = "free_y") +
    scale_color_manual(values = c("#E74C3C", "#3498DB", "#27AE60")) +
    labs(
        title = "Evolução dos Prêmios de Risco - Fama-MacBeth",
        subtitle = "Lambdas estimados em cada período via regressões cross-sectional",
        x = "Período (Trimestre)",
        y = "Prêmio de Risco (% trimestral)",
        caption = "Linhas suaves mostram tendência (LOESS). Área sombreada = intervalo de confiança 95%"
    ) +
    theme_minimal() +
    theme(
        legend.position = "none",
        plot.title = element_text(face = "bold", size = 14),
        plot.subtitle = element_text(color = "gray40", size = 11),
        strip.text = element_text(face = "bold", size = 11)
    )

print(p_lambdas)

Conclusão: O procedimento Fama-MacBeth permite testar se os fatores identificados nas regressões de séries temporais (Etapa 1) efetivamente explicam a variação cross-sectional dos retornos esperados (Etapa 2). Fatores com \(\lambda\) significativamente diferentes de zero são considerados precificados pelo mercado e representam fontes de risco sistemático que os investidores exigem compensação.


Questão 2 - Modelo SARIMA

Enunciado

Sabe-se que séries econômicas de preços ao consumidor, como o Índice Nacional de Preços ao Consumidor Amplo (IPCA), podem apresentar componentes sazonais em virtude de padrões de consumo recorrentes ao longo do ano. A presença desses efeitos sazonais torna apropriada a utilização de modelos autorregressivos integrados de médias móveis sazonais, conhecidos como SARIMA \((p,d,q)(P,D,Q)_{12}\), que permitem capturar simultaneamente a dinâmica de curto prazo e a estrutura sazonal da série.

Com base na série mensal do IPCA entre janeiro de 2000 e janeiro de 2025, responda aos itens a seguir.

Atenção: utilizem a série do número-índice, e não a da taxa de variação; ela pode ser facilmente encontrada no site do IPEADATA.

Setup e Carregamento de Pacotes

packages <- c(
    "tidyverse", "lubridate", "knitr", "kableExtra",
    "forecast", "tseries", "urca", "ggplot2", "gridExtra",
    "jsonlite", "httr"
)

for (pkg in packages) {
    if (!require(pkg, character.only = TRUE, quietly = TRUE)) {
        install.packages(pkg, quiet = TRUE)
        library(pkg, character.only = TRUE)
    }
}

# Pacotes carregados

Carregamento dos Dados do IPCA

# Baixando dados do IPCA do IPEADATA

ipca_url <- "http://ipeadata.gov.br/api/odata4/ValoresSerie(SERCODIGO='PRECOS12_IPCA12')"

ipca_raw <- jsonlite::fromJSON(ipca_url)
ipca_data <- ipca_raw$value %>%
    mutate(Date = as.Date(VALDATA)) %>%
    select(Date, IPCA = VALVALOR) %>%
    filter(Date >= as.Date("2000-01-01") & Date <= as.Date("2025-01-31")) %>%
    arrange(Date)

ipca_ts <- ts(ipca_data$IPCA, start = c(2000, 1), frequency = 12)
# Série temporal criada

Visualização dos Dados do IPCA

Descrição da variável:

  • IPCA: Índice Nacional de Preços ao Consumidor Amplo (número-índice)
  • Fonte: IPEADATA
  • Frequência: Mensal
  • Nota: Utilizamos o número-índice, não a taxa de variação
Dados IPCA - Primeiros 12 meses
Date IPCA
2000-01-01 1598.41
2000-02-01 1600.49
2000-03-01 1604.01
2000-04-01 1610.75
2000-05-01 1610.91
2000-06-01 1614.62
2000-07-01 1640.62
2000-08-01 1662.11
2000-09-01 1665.93
2000-10-01 1668.26
2000-11-01 1673.60
2000-12-01 1683.47
Dados IPCA - Últimos 12 meses
Date IPCA
290 2024-02-01 6858.17
291 2024-03-01 6869.14
292 2024-04-01 6895.24
293 2024-05-01 6926.96
294 2024-06-01 6941.51
295 2024-07-01 6967.89
296 2024-08-01 6966.50
297 2024-09-01 6997.15
298 2024-10-01 7036.33
299 2024-11-01 7063.77
300 2024-12-01 7100.50
301 2025-01-01 7111.86
Estatísticas Descritivas - IPCA (número-índice)
Estatistica Valor
Média 3858.08
Desvio Padrão 1577.83
Mínimo 1598.41
Máximo 7111.86
Mediana 3497.70

(a) Estatísticas Descritivas e Testes de Raiz Unitária

Enunciado: Apresente a estatística descritiva da série, incluindo gráficos da série em nível e com as devidas transformações. Aplique os testes de raiz unitária de ADF e Phillips-Perron para verificar se a série é estacionária em nível, em log ou na primeira diferença do log. Além disso, plote a função de autocorrelação (FAC) e a função de autocorrelação parcial (FACP), para identificação das ordens p, q, P e Q do modelo.

Resposta:

Fundamentação Teórica sobre Estacionariedade

Série Estacionária: Uma série temporal é estacionária quando suas propriedades estatísticas (média, variância, autocorrelação) não mudam ao longo do tempo.

Por que importa?

  • Modelos ARIMA/SARIMA requerem estacionariedade
  • Séries não-estacionárias podem levar a “regressões espúrias”
  • Preços geralmente são não-estacionários (têm tendência), mas retornos/variações são estacionários

Testes de Raiz Unitária:

  1. ADF (Augmented Dickey-Fuller):
    • H₀: A série tem raiz unitária (não-estacionária)
    • H₁: A série é estacionária
    • P-valor < 0.05 → Rejeita H₀ → Série é estacionária
  2. Phillips-Perron:
    • Similar ao ADF, mas robusto a autocorrelação e heterocedasticidade
    • Mesma interpretação do p-valor

Transformações Comuns:

  • Log: Estabiliza variância
  • Primeira Diferença: Remove tendência linear
  • Diferença Sazonal (lag=12): Remove sazonalidade
summary(ipca_ts)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1598    2574    3498    3858    5093    7112 
par(mfrow = c(2, 2))
plot(ipca_ts, main = "IPCA - Nível", col = "#003366", lwd = 1.5)
grid()

log_ipca <- log(ipca_ts)
plot(log_ipca, main = "IPCA - Log", col = "#003366", lwd = 1.5)
grid()

diff_log_ipca <- diff(log_ipca)
plot(diff_log_ipca, main = "IPCA - Primeira Diferença", col = "#003366", lwd = 1.5)
grid()
abline(h = 0, col = "black", lty = 2)

diff12_log_ipca <- diff(log_ipca, lag = 12)
plot(diff12_log_ipca, main = "IPCA - Diferença Sazonal", col = "#003366", lwd = 1.5)
grid()
abline(h = 0, col = "black", lty = 2)

par(mfrow = c(1, 1))

# Testes de raiz unitária
adf_level <- adf.test(ipca_ts)
adf_log <- adf.test(log_ipca)
adf_diff <- adf.test(diff_log_ipca)
pp_level <- pp.test(ipca_ts)
pp_log <- pp.test(log_ipca)
pp_diff <- pp.test(diff_log_ipca)

par(mfrow = c(2, 2))
acf(diff_log_ipca, main = "FAC - Δlog(IPCA)", lag.max = 48, col = "#003366", lwd = 2)
pacf(diff_log_ipca, main = "FACP - Δlog(IPCA)", lag.max = 48, col = "#DC143C", lwd = 2)
diff_both <- diff(diff_log_ipca, lag = 12)
acf(diff_both, main = "FAC - Diferenças", lag.max = 48, na.action = na.pass, col = "#003366", lwd = 2)
pacf(diff_both, main = "FACP - Diferenças", lag.max = 48, na.action = na.pass, col = "#DC143C", lwd = 2)

par(mfrow = c(1, 1))

Teste ADF:

  - Nível - P-valor: 0.9846 
  - Log - P-valor: 0.4413 
  - Primeira Diferença do Log - P-valor: 0.01 

Teste Phillips-Perron:

  - Nível - P-valor: 0.99 
  - Log - P-valor: 0.6953 
  - Primeira Diferença do Log - P-valor: 0.01 

Interpretação dos Testes e Gráficos

Testes de Raiz Unitária:

- Série em nível: NÃO é estacionária (p-valor = 0.9846 )
- Série em log: NÃO é estacionária (p-valor = 0.4413 )
- Primeira diferença do log: É estacionária (p-valor = 0.01 )

Conclusão: A série IPCA em nível apresenta tendência estocástica (raiz unitária). Após aplicar a primeira diferença no logaritmo, a série torna-se estacionária, indicando que devemos usar d=1 no modelo SARIMA.

Funções de Autocorrelação (FAC e FACP):

  • FAC: Mostra correlação entre a série e seus próprios lags
  • FACP: Mostra correlação direta (removendo efeitos de lags intermediários)
  • Picos em múltiplos de 12: Indicam forte componente sazonal
  • Decaimento gradual na FAC: Sugere componente AR
  • Corte abrupto na FACP: Ajuda a identificar ordem p

Identificação do Modelo:

Com base nos gráficos FAC/FACP, os modelos SARIMA(1,1,0)(1,0,0)₁₂ e SARIMA(1,1,0)(0,0,1)₁₂ são candidatos razoáveis, combinando:

  • Componente AR de curto prazo (p=1)
  • Diferenciação regular (d=1)
  • Componente sazonal (P=1 ou Q=1 com período s=12)

(b) Estimação dos Modelos SARIMA

Enunciado: Estime, utilizando a amostra completa, os modelos SARIMA(1,1,0)(1,0,0)₁₂ e SARIMA(1,1,0)(0,0,1)₁₂. Para cada um deles, obtenha os parâmetros estimados, realize os diagnósticos (significância dos parâmetros, análise da ACF e PACF dos resíduos e verificação das raízes do polinômio característico) e compare os modelos em termos dos Critérios de Informação (AIC e BIC).

Resposta:

Modelo 1: SARIMA(1,1,0)(1,0,0)₁₂

# MODELO 1: SARIMA(1,1,0)(1,0,0)[12]
model1 <- Arima(log_ipca, order = c(1, 1, 0), seasonal = list(order = c(1, 0, 0), period = 12))
summary(model1)
Series: log_ipca 
ARIMA(1,1,0)(1,0,0)[12] 

Coefficients:
         ar1    sar1
      0.8060  0.2270
s.e.  0.0376  0.0657

sigma^2 = 1.06e-05:  log likelihood = 1295.33
AIC=-2584.65   AICc=-2584.57   BIC=-2573.54

Training set error measures:
                     ME        RMSE         MAE         MPE      MAPE
Training set 0.00077353 0.003238878 0.002341845 0.009619439 0.0286874
                   MASE       ACF1
Training set 0.03888144 -0.1301346

Parâmetros Estimados:

AR(1): 0.806 (SE: 0.0376 )
SAR(1): 0.227 (SE: 0.0657 )

Teste de Significância:

AR(1): t = 21.463 , p-valor = 0 
SAR(1): t = 3.458 , p-valor = 5e-04 

Raízes do Polinômio:

Raízes AR: 1.2407 
Raízes SAR: 4.4052 
Estacionário: TRUE 
# Extrair resíduos
res1 <- residuals(model1)

# 1. Gráfico de resíduos ao longo do tempo
p1 <- autoplot(res1, colour = "#003366") +
    geom_hline(yintercept = 0, linetype = "dashed", color = "#DC143C", linewidth = 0.8) +
    labs(
        title = "Resíduos ao Longo do Tempo",
        x = "Tempo",
        y = "Resíduos"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 12),
        panel.grid.minor = element_blank()
    )

# 2. ACF dos resíduos
acf_data <- acf(res1, plot = FALSE, lag.max = 24)
acf_df <- data.frame(
    Lag = acf_data$lag[-1],
    ACF = acf_data$acf[-1]
)

ci <- qnorm((1 + 0.95) / 2) / sqrt(length(res1))

p2 <- ggplot(acf_df, aes(x = Lag, y = ACF)) +
    geom_hline(yintercept = 0, color = "black", linewidth = 0.5) +
    geom_hline(yintercept = c(-ci, ci), linetype = "dashed", color = "#DC143C", linewidth = 0.8) +
    geom_segment(aes(xend = Lag, yend = 0), color = "#003366", linewidth = 1) +
    geom_point(color = "#003366", size = 2) +
    labs(
        title = "ACF dos Resíduos",
        x = "Lag",
        y = "ACF"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 12),
        panel.grid.minor = element_blank()
    )

# 3. Histograma com curva normal
res_df <- data.frame(Residuos = as.numeric(res1))

p3 <- ggplot(res_df, aes(x = Residuos)) +
    geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "#003366", alpha = 0.7, color = "white") +
    stat_function(
        fun = dnorm,
        args = list(mean = mean(res1, na.rm = TRUE), sd = sd(res1, na.rm = TRUE)),
        color = "#DC143C",
        linewidth = 1.2
    ) +
    labs(
        title = "Distribuição dos Resíduos",
        x = "Resíduos",
        y = "Densidade"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 12),
        panel.grid.minor = element_blank()
    )

# Combinar gráficos: 1 em cima, 2 embaixo
layout_matrix <- rbind(c(1, 1), c(2, 3))
grid.arrange(p1, p2, p3, layout_matrix = layout_matrix, heights = c(1, 1))

Diagnóstico de Resíduos - Modelo 1

Teste de Ljung-Box:

P-valor = 0.0749 
Resíduos não apresentam autocorrelação significativa (ruído branco).

Modelo 2: SARIMA(1,1,0)(0,0,1)₁₂

# MODELO 2: SARIMA(1,1,0)(0,0,1)[12]
model2 <- Arima(log_ipca, order = c(1, 1, 0), seasonal = list(order = c(0, 0, 1), period = 12))
summary(model2)
Series: log_ipca 
ARIMA(1,1,0)(0,0,1)[12] 

Coefficients:
         ar1    sma1
      0.8232  0.1775
s.e.  0.0336  0.0559

sigma^2 = 1.069e-05:  log likelihood = 1294.07
AIC=-2582.14   AICc=-2582.05   BIC=-2571.02

Training set error measures:
                       ME        RMSE         MAE         MPE       MAPE
Training set 0.0007725418 0.003253154 0.002351729 0.009602919 0.02882324
                   MASE       ACF1
Training set 0.03904555 -0.1386685

Parâmetros Estimados:

AR(1): 0.8232 (SE: 0.0336 )
SMA(1): 0.1775 (SE: 0.0559 )

Teste de Significância:

AR(1): t = 24.498 , p-valor = 0 
SMA(1): t = 3.172 , p-valor = 0.0015 

Raízes do Polinômio:

Raízes AR: 1.2147 
Raízes SMA: 5.6353 
Estacionário e invertível: TRUE 
# Extrair resíduos
res2 <- residuals(model2)

# 1. Gráfico de resíduos ao longo do tempo
p1 <- autoplot(res2, colour = "#003366") +
    geom_hline(yintercept = 0, linetype = "dashed", color = "#DC143C", linewidth = 0.8) +
    labs(
        title = "Resíduos ao Longo do Tempo",
        x = "Tempo",
        y = "Resíduos"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 12),
        panel.grid.minor = element_blank()
    )

# 2. ACF dos resíduos
acf_data <- acf(res2, plot = FALSE, lag.max = 24)
acf_df <- data.frame(
    Lag = acf_data$lag[-1],
    ACF = acf_data$acf[-1]
)

ci <- qnorm((1 + 0.95) / 2) / sqrt(length(res2))

p2 <- ggplot(acf_df, aes(x = Lag, y = ACF)) +
    geom_hline(yintercept = 0, color = "black", linewidth = 0.5) +
    geom_hline(yintercept = c(-ci, ci), linetype = "dashed", color = "#DC143C", linewidth = 0.8) +
    geom_segment(aes(xend = Lag, yend = 0), color = "#003366", linewidth = 1) +
    geom_point(color = "#003366", size = 2) +
    labs(
        title = "ACF dos Resíduos",
        x = "Lag",
        y = "ACF"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 12),
        panel.grid.minor = element_blank()
    )

# 3. Histograma com curva normal
res_df <- data.frame(Residuos = as.numeric(res2))

p3 <- ggplot(res_df, aes(x = Residuos)) +
    geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "#003366", alpha = 0.7, color = "white") +
    stat_function(
        fun = dnorm,
        args = list(mean = mean(res2, na.rm = TRUE), sd = sd(res2, na.rm = TRUE)),
        color = "#DC143C",
        linewidth = 1.2
    ) +
    labs(
        title = "Distribuição dos Resíduos",
        x = "Resíduos",
        y = "Densidade"
    ) +
    theme_minimal() +
    theme(
        plot.title = element_text(face = "bold", size = 12),
        panel.grid.minor = element_blank()
    )

# Combinar gráficos: 1 em cima, 2 embaixo
layout_matrix <- rbind(c(1, 1), c(2, 3))
grid.arrange(p1, p2, p3, layout_matrix = layout_matrix, heights = c(1, 1))

Diagnóstico de Resíduos - Modelo 2

Teste de Ljung-Box:

P-valor = 0.0634 
Resíduos não apresentam autocorrelação significativa (ruído branco).

Comparação dos Modelos

Critérios de Informação
Modelo AIC BIC LogLik
SARIMA(1,1,0)(1,0,0)[12] -2584.653 -2573.541 1295.326
SARIMA(1,1,0)(0,0,1)[12] -2582.135 -2571.024 1294.068

Interpretação:

- AIC prefere o Modelo 1 (SAR)
- BIC prefere o Modelo 1 (SAR)

Critérios menores indicam melhor ajuste. BIC penaliza mais a complexidade do modelo.

(c) Previsões Fora da Amostra

Enunciado: Considere agora apenas os dados até janeiro de 2023. Utilizando as duas especificações da questão anterior, obtenha previsões para o período de fevereiro de 2023 até janeiro de 2025. Neste exercício, utilize a previsão passo a passo: estime o modelo e obtenha a previsão para h=1; em seguida, incorpore a observação seguinte à amostra, reestime o modelo e gere a previsão para h=1, repetindo esse procedimento até o final do horizonte. Plote as previsões resultantes junto com os dados observados e compare o desempenho de cada modelo com base no RMSE.

Resposta:

# Previsões Rolling Forecast

ipca_train <- window(log_ipca, end = c(2023, 1))
ipca_test <- window(log_ipca, start = c(2023, 2))

rolling_forecast <- function(train_data, test_data, model_order, seasonal_order) {
    forecasts <- numeric(length(test_data))
    for (i in 1:length(test_data)) {
        if (i == 1) {
            current_data <- train_data
        } else {
            current_data <- ts(c(train_data, test_data[1:(i - 1)]),
                start = start(train_data), frequency = 12
            )
        }
        model <- Arima(current_data,
            order = model_order,
            seasonal = list(order = seasonal_order, period = 12)
        )
        forecast_h1 <- forecast(model, h = 1)
        forecasts[i] <- as.numeric(forecast_h1$mean)
    }
    return(forecasts)
}

# Gerar previsões
forecasts_m1 <- rolling_forecast(ipca_train, ipca_test, c(1, 1, 0), c(1, 0, 0))
forecasts_m2 <- rolling_forecast(ipca_train, ipca_test, c(1, 1, 0), c(0, 0, 1))

rmse_m1 <- sqrt(mean((ipca_test - forecasts_m1)^2))
rmse_m2 <- sqrt(mean((ipca_test - forecasts_m2)^2))

# Métricas de desempenho
performance <- data.frame(
    Modelo = c("SARIMA(1,1,0)(1,0,0)[12]", "SARIMA(1,1,0)(0,0,1)[12]"),
    RMSE = c(rmse_m1, rmse_m2)
)
kable(performance, digits = 6) %>% kable_styling()
Modelo RMSE
SARIMA(1,1,0)(1,0,0)[12] 0.002595
SARIMA(1,1,0)(0,0,1)[12] 0.002565
if (rmse_m1 < rmse_m2) {
    cat("\n✓ Modelo 1 apresenta melhor desempenho preditivo\n")
} else {
    cat("\n✓ Modelo 2 apresenta melhor desempenho preditivo\n")
}

✓ Modelo 2 apresenta melhor desempenho preditivo
# Análise de similaridade entre as previsões
correlation <- cor(forecasts_m1, forecasts_m2)
diff_forecasts <- forecasts_m1 - forecasts_m2

similarity_metrics <- data.frame(
    Métrica = c(
        "Correlação entre modelos",
        "Diferença média (log)",
        "Diferença máxima absoluta (log)",
        "Desvio padrão das diferenças"
    ),
    Valor = c(
        round(correlation, 4),
        round(mean(diff_forecasts), 6),
        round(max(abs(diff_forecasts)), 6),
        round(sd(diff_forecasts), 6)
    )
)

cat("\n")
kable(similarity_metrics,
    caption = "Análise de Similaridade das Previsões",
    col.names = c("Métrica", "Valor")
) %>%
    kable_styling(bootstrap_options = c("striped", "hover"))
Análise de Similaridade das Previsões
Métrica Valor
Correlação entre modelos 0.999900
Diferença média (log) 0.000017
Diferença máxima absoluta (log) 0.000511
Desvio padrão das diferenças 0.000254

Interpretação: As previsões são muito similares porque ambos os modelos compartilham a mesma estrutura não-sazonal (AR(1) + I(1)) e diferem apenas na componente sazonal (SAR(1) vs SMA(1)). Em previsões de um passo à frente (h=1), essas especificações sazonais tendem a produzir resultados próximos, especialmente quando o componente AR(1) domina a dinâmica de curto prazo.

Conclusões

Questão 1 - Asset Pricing

Principais Resultados:

  1. CCAPM (Consumption-based):
    • O modelo básico de consumo gera equity premiums muito baixos (equity premium puzzle)
    • R² muito baixo indica que o crescimento do consumo agregado explica pouca variação dos retornos
    • Isto sugere que fatores além do consumo agregado são importantes para precificar ativos
  2. CAPM (Modelo de Um Fator):
    • R² médio substancialmente maior, mostrando que o fator de mercado é relevante
    • Consegue capturar parte significativa da variação cross-sectional dos retornos
    • Porém, deixa espaço para fatores adicionais (como demonstrado pelo modelo FF3)
  3. Modelo Fama-French 3 Fatores:
    • R² médio alto, demonstrando excelente poder explicativo
    • Os fatores SMB (tamanho) e HML (valor) capturam dimensões importantes do risco não explicadas pelo mercado
    • Confirma as anomalias empiricamente documentadas (small cap premium e value premium)
  4. Análise de Componentes Principais:
    • Identifica fatores estatísticos que explicam a variação dos retornos sem impor estrutura econômica a priori
    • Útil para comparar com fatores economicamente motivados (FF3)
  5. Fama-MacBeth:
    • Testa se os prêmios de risco estimados são significativamente diferentes de zero
    • Verifica se a relação risco-retorno é linear, conforme previsto pela teoria

Implicações:

  • Os modelos multifatoriais (FF3) superam o CAPM e o CCAPM na explicação dos retornos
  • Tamanho e valor são fatores de risco compensados no mercado
  • O equity premium puzzle permanece um desafio teórico importante

Questão 2 - SARIMA

Principais Resultados:

  1. Estacionariedade:
    • A série IPCA em nível não é estacionária (testes ADF e Phillips-Perron)
    • A primeira diferença do logaritmo é estacionária, justificando d=1
    • Forte componente sazonal identificado na FAC/FACP (picos em múltiplos de 12)
  2. Modelos Estimados:
    • SARIMA(1,1,0)(1,0,0)₁₂: Componente sazonal autorregressivo
    • SARIMA(1,1,0)(0,0,1)₁₂: Componente sazonal de médias móveis
    • Ambos apresentam parâmetros significativos e resíduos sem autocorrelação (teste Ljung-Box)
    • Raízes do polinômio característico fora do círculo unitário (estacionariedade/invertibilidade)
  3. Comparação AIC/BIC:
    • Critérios de informação indicam qual modelo tem melhor balanço entre ajuste e parcimônia
    • BIC tende a preferir modelos mais simples (penaliza mais a complexidade)
  4. Previsões Fora da Amostra:
    • Rolling forecast implementado corretamente (reestimação passo a passo)
    • RMSE permite comparar desempenho preditivo dos modelos
    • O modelo com menor RMSE tem melhor capacidade preditiva

Implicações:

  • Modelos SARIMA são apropriados para séries com sazonalidade (como IPCA)
  • A diferenciação é necessária para obter estacionariedade
  • A escolha entre SAR e SMA depende dos critérios de informação e desempenho preditivo

Referências:

  • Cochrane, J. H. (2005). Asset Pricing. Princeton University Press.
  • Fama, E. F., & French, K. R. (1993). Common risk factors in the returns on stocks and bonds. Journal of Financial Economics, 33(1), 3-56.
  • Fama, E. F., & MacBeth, J. D. (1973). Risk, return, and equilibrium: Empirical tests. Journal of Political Economy, 81(3), 607-636.
  • Sharpe, W. F. (1964). Capital asset prices: A theory of market equilibrium under conditions of risk. Journal of Finance, 19(3), 425-442.
  • Mehra, R., & Prescott, E. C. (1985). The equity premium: A puzzle. Journal of Monetary Economics, 15(2), 145-161.
  • Box, G. E. P., Jenkins, G. M., Reinsel, G. C., & Ljung, G. M. (2015). Time Series Analysis: Forecasting and Control. 5th ed. Wiley.
  • Hyndman, R. J., & Athanasopoulos, G. (2021). Forecasting: Principles and Practice. 3rd ed. OTexts.
  • Dickey, D. A., & Fuller, W. A. (1979). Distribution of the estimators for autoregressive time series with a unit root. Journal of the American Statistical Association, 74(366), 427-431.
  • Phillips, P. C. B., & Perron, P. (1988). Testing for a unit root in time series regression. Biometrika, 75(2), 335-346.

Notas Metodológicas

Dados Utilizados:

  • Portfólios Fama-French 25 (5×5): Disponíveis em Kenneth R. French Data Library
  • Fatores Fama-French: Mkt-RF, SMB, HML do mesmo repositório
  • Dados de Consumo: BEA NIPA Tables 2.1 (população), 2.3.4 (price index) e 2.3.5 (consumo nominal de não duráveis)
  • Dados IPCA: IPEADATA (série número-índice mensal)

Pacotes R Utilizados:

  • tidyverse: Manipulação e visualização de dados
  • readxl: Leitura de arquivos Excel (dados BEA)
  • zoo/xts: Manipulação de séries temporais
  • forecast: Modelagem ARIMA/SARIMA
  • tseries: Testes de raiz unitária (ADF, Phillips-Perron)
  • knitr/kableExtra: Formatação de tabelas

Período de Análise:

  • Questão 1 (Asset Pricing): 1947Q1 - 2025Q1 (dados trimestrais)
  • Questão 2 (SARIMA): Janeiro/2000 - Janeiro/2025 (dados mensais)

Observações Importantes:

  1. Os retornos dos portfólios Fama-French já são fornecidos em termos nominais (% ao mês). A conversão para trimestral usa agregação geométrica: \((1+r_1)(1+r_2)(1+r_3) - 1\).

  2. Os retornos reais são calculados deflacionando pelos índices de preços do BEA, que já são agregados trimestralmente.

  3. Todos os testes estatísticos utilizam nível de significância de 5% (α = 0.05).

  4. As previsões out-of-sample na Questão 2 utilizam metodologia rolling window com reestimação a cada período.


Fontes de Dados

Questão 1 - Asset Pricing

Kenneth R. French Data Library: - 25 Portfolios Formed on Size and Investment (5×5): https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html - Fama/French 3 Factors: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html

Bureau of Economic Analysis (BEA) - NIPA Tables: - Table 2.1: Personal Income and Its Disposition (população): https://apps.bea.gov/iTable/?reqid=19&step=2 - Table 2.3.4: Price Indexes for Personal Consumption Expenditures: https://apps.bea.gov/iTable/?reqid=19&step=2 - Table 2.3.5: Personal Consumption Expenditures by Major Type of Product: https://apps.bea.gov/iTable/?reqid=19&step=2

Questão 2 - Séries Temporais

IPEADATA: - IPCA - Índice Nacional de Preços ao Consumidor Amplo (número-índice mensal): http://www.ipeadata.gov.br/ - Código da série: PRECOS12_IPCAG12


Instruções de Reprodução

Pré-requisitos

Software: - R versão ≥ 4.0.0 - RStudio (recomendado) - Quarto CLI ≥ 1.3

Pacotes R necessários:

install.packages(c(
    "tidyverse", "readxl", "zoo", "xts", 
    "forecast", "tseries", "knitr", "kableExtra",
    "ggplot2", "gridExtra", "reshape2", "lubridate", "broom"
))

Estrutura de Arquivos

Trabalho EPGE/
├── Lista2.qmd                          # Documento principal Quarto
├── Lista2.html                         # Saída renderizada
├── 25_Portfolios_5x5.csv              # Dados dos 25 portfólios (French)
├── F-F_Research_Data_Factors.csv      # Fatores Fama-French
├── BEA_Table_2.1.xlsx                 # População (BEA)
├── BEA_Table_2.3.4.xlsx               # Price Index (BEA)
├── BEA_Table_2.3.5.xlsx               # Consumo (BEA)
├── IPCA_IPEADATA.csv                  # Dados IPCA
└── README.md                          # Este arquivo

Passos para Reprodução

  1. Download dos Dados:
    • Baixe os arquivos CSV do Kenneth R. French Data Library
    • Baixe as tabelas NIPA do BEA em formato Excel
    • Baixe a série IPCA do IPEADATA em formato CSV
  2. Organização:
    • Coloque todos os arquivos de dados no mesmo diretório do arquivo Lista2.qmd
    • Verifique que os nomes dos arquivos correspondem aos especificados no código
  3. Renderização:
    • Abra Lista2.qmd no RStudio
    • Execute: quarto render Lista2.qmd
    • Ou use o botão “Render” no RStudio
    • O arquivo HTML será gerado automaticamente
  4. Tempo de Processamento:
    • Primeira execução: ~5-10 minutos (cache vazio)
    • Execuções subsequentes: ~2-3 minutos (com cache)
    • O cache está habilitado para chunks computacionalmente intensivos

Resolução de Problemas

Erro de arquivo não encontrado: - Verifique os nomes dos arquivos de dados - Certifique-se de que estão no diretório correto - Use caminhos relativos ao arquivo .qmd

Erro de pacote não instalado: - Execute o comando de instalação dos pacotes acima - Reinicie a sessão R após instalação

Problemas com encoding: - Os arquivos CSV devem estar em UTF-8 - Use Sys.setlocale("LC_ALL", "en_US.UTF-8") se necessário


Informações de Sessão

Data de execução: 08/10/2025 16:43:54 
Informações da Sessão R:
R version 4.4.2 (2024-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

Matrix products: default


locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: America/Sao_Paulo
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] httr_1.4.7       jsonlite_1.8.9   urca_1.3-4       tseries_0.10-58 
 [5] forecast_8.23.0  reshape2_1.4.4   gridExtra_2.3    kableExtra_1.4.0
 [9] knitr_1.49       broom_1.0.7      zoo_1.8-12       readxl_1.4.3    
[13] lubridate_1.9.4  forcats_1.0.0    stringr_1.5.1    dplyr_1.1.4     
[17] purrr_1.0.2      readr_2.1.5      tidyr_1.3.1      tibble_3.2.1    
[21] ggplot2_3.5.2    tidyverse_2.0.0 

loaded via a namespace (and not attached):
 [1] gtable_0.3.6      xfun_0.52         htmlwidgets_1.6.4 lattice_0.22-6   
 [5] tzdb_0.4.0        quadprog_1.5-8    vctrs_0.6.5       tools_4.4.2      
 [9] generics_0.1.3    curl_6.1.0        parallel_4.4.2    xts_0.14.1       
[13] pkgconfig_2.0.3   Matrix_1.7-1      lifecycle_1.0.4   compiler_4.4.2   
[17] farver_2.1.2      munsell_0.5.1     codetools_0.2-20  htmltools_0.5.8.1
[21] yaml_2.3.10       pillar_1.10.1     nlme_3.1-166      fracdiff_1.5-3   
[25] tidyselect_1.2.1  digest_0.6.37     stringi_1.8.4     labeling_0.4.3   
[29] splines_4.4.2     fastmap_1.2.0     grid_4.4.2        colorspace_2.1-1 
[33] cli_3.6.3         magrittr_2.0.3    withr_3.0.2       scales_1.3.0     
[37] backports_1.5.0   timechange_0.3.0  TTR_0.24.4        rmarkdown_2.29   
[41] quantmod_0.4.26   nnet_7.3-19       timeDate_4041.110 cellranger_1.1.0 
[45] hms_1.1.3         evaluate_1.0.3    lmtest_0.9-40     viridisLite_0.4.2
[49] mgcv_1.9-1        rlang_1.1.4       Rcpp_1.0.13-1     glue_1.8.0       
[53] xml2_1.3.6        svglite_2.1.3     rstudioapi_0.17.1 R6_2.6.1         
[57] plyr_1.8.9        systemfonts_1.1.0

Autores: José Victor
Disciplina: Econometria II - FGV EPGE
Data de Entrega: 10 de outubro de 2025