Code
library(fpp3)
library(rugarch)
Quiz: https://forms.gle/CcMvpyXdhaacQtPi8
library(fpp3)
library(rugarch)
<- '2018-01-01'
start_date # esses são ativos de fundos imobiliários que eu ja tive
# e queria saber fiz um péssimo investimento
# ou apenas ruim.
<- c(
ativos "HGRE11.SA",
"BTLG11.SA",
"HGRU11.SA",
"VGIR11.SA",
"MGFF11.SA"
)
Vamos trabalhar tanto com os dados no formado de tibble quanto no formato de tsibble.
# library(curl)
# has_internet_via_proxy <<- TRUE
<- yfR::yf_get(
da
ativos,first_date = start_date,
type_return = "log",
freq_data = "daily",
do_complete_data = TRUE
)<- da |>
da_tsibble as_tsibble(key = ticker, index = ref_date, regular = FALSE)
Plotar
|>
da_tsibble autoplot(price_close, colour = "black") +
facet_wrap(~ticker, scales = "free_y", ncol = 1)
|>
da_tsibble autoplot(ret_closing_prices, colour = "black") +
facet_wrap(~ticker, scales = "free_y", ncol = 1)
Warning: Removed 1 row(s) containing missing values (geom_path).
Data mínima comum a todas as séries
<- da |>
data_corte ::group_by(ticker) |>
dplyr::filter(ref_date == min(ref_date)) |>
dplyr::ungroup() |>
dplyrwith(max(ref_date))
data_corte
[1] "2018-07-31"
<- da |>
da_train ::filter(ref_date > data_corte) dplyr
|>
da_tsibble ACF(ret_closing_prices) |>
autoplot()
Warning: Provided data has an irregular interval, results should be treated with
caution. Computing ACF by observation.
|>
da_tsibble PACF(ret_closing_prices) |>
autoplot()
Warning: Provided data has an irregular interval, results should be treated with
caution. Computing ACF by observation.
|>
da_tsibble ::mutate(ret2 = ret_closing_prices^2) |>
dplyrautoplot(ret2, colour = "black") +
facet_wrap(~ticker, ncol = 1)
Warning: Removed 1 row(s) containing missing values (geom_path).
|>
da_tsibble ::mutate(ret2 = ret_closing_prices^2) |>
dplyrACF(ret2) |>
autoplot()
Warning: Provided data has an irregular interval, results should be treated with
caution. Computing ACF by observation.
|>
da_tsibble ::mutate(ret2 = ret_closing_prices^2) |>
dplyrPACF(ret2) |>
autoplot()
Warning: Provided data has an irregular interval, results should be treated with
caution. Computing ACF by observation.
Normalidade
|>
da_train group_by(ticker) |>
summarise(
gg = list(
ggplot(cur_data(), aes(sample = ret_closing_prices)) +
geom_qq() +
geom_qq_line() +
labs(title = cur_group())
)|>
) ::pull(gg) |>
dplyr::wrap_plots() patchwork
Com outra distribuição
|>
da_train group_by(ticker) |>
summarise(
gg = list(
ggplot(cur_data(), aes(sample = ret_closing_prices)) +
geom_qq(distribution = qt, dparams = list(df = 3)) +
geom_qq_line(distribution = qt, dparams = list(df = 3)) +
labs(title = cur_group())
)|>
) ::pull(gg) |>
dplyr::wrap_plots() patchwork
Função para ajustar um garch
<- function(parms, ret, prog = NULL) {
garch_individual if (!is.null(prog)) prog()
# daria para adicionar mais hiperparametros!!!
= ugarchspec(
garch_model variance.model = list(
model = "fGARCH",
submodel = "GARCH",
garchOrder = c(parms$m, parms$n)
),mean.model = list(
armaOrder = c(parms$p, parms$q),
include.mean = TRUE
),distribution.model = parms$dist
)# as vezes ele nao converge
suppressWarnings({
<- ugarchfit(garch_model, data = ret)
fit
})
fit }
Função para ajustar uma grid de garchs e pegar as informações
<- function(ativo, p = 0:3, q = 0:3, m = 0:2, n = 0:2, dist = "std") {
melhor_garch
# faz uma grid de hiperparâmetros
<- expand_grid(p = p, q = q, m = m, n = n, dist = dist) |>
grid ::rownames_to_column("id")
tibble
# pega o retorno do ativo
<- da_train |>
ret ::filter(ticker == ativo) |>
dplyrpull(ret_closing_prices)
::ui_info("Ajustando modelos para {ativo}...")
usethis
::with_progress({
progressr<- progressr::progressor(nrow(grid))
prog <- grid |>
modelos group_split(id) |>
::map(garch_individual, ret = ret, prog)
purrr
})
<- purrr::possibly(infocriteria, tibble::tibble())
safe_info
suppressWarnings({
<- purrr::map(modelos, safe_info) |>
informacao ::map(tibble::as_tibble, rownames = "criteria") |>
purrr::bind_rows(.id = "id")
dplyr
})
<- informacao |>
melhores ::inner_join(grid, "id") |>
dplyr::pivot_wider(names_from = criteria, values_from = V1) |>
tidyr::clean_names() |>
janitor::arrange(akaike)
dplyr
::ui_info(c(
usethis"Melhor modelo:",
"p <- {melhores$p[1]}",
"q <- {melhores$q[1]}",
"m <- {melhores$m[1]}",
"n <- {melhores$n[1]}"
))
melhores
}
Rodando as funções
<- ativos |>
melhores_por_ativo ::set_names() |>
purrr::map(melhor_garch) |>
purrr::bind_rows(.id = "ticker") dplyr
Função que ajusta o modelo e faz as previsões
<- function(parms, n_steps = 5) {
prever_volatilidade
<- da_train |>
ret ::filter(ticker == parms$ticker) |>
dplyrpull(ret_closing_prices)
= ugarchspec(
garch_model variance.model = list(
model = "fGARCH",
submodel = "GARCH",
garchOrder = c(parms$m, parms$n)
),mean.model = list(
armaOrder = c(parms$p, parms$q),
include.mean = TRUE
),distribution.model = parms$dist
)
<- ugarchfit(garch_model, data = ret, out.sample = n_steps - 1)
fit
# browser()
if (parms$dist == "std") {
<- as.numeric(fit@fit$coef["shape"])
shape else {
} <- NA_real_
shape
}
<- ugarchforecast(fit, n.ahead = n_steps)@forecast
forecasts ::tibble(
tibbleticker = parms$ticker,
serie = as.numeric(forecasts$seriesFor),
volatilidade = as.numeric(forecasts$sigmaFor),
shape = shape
) }
Ajustando modelos finais e prevendo volatilidade futura
<- melhores_por_ativo |>
parametros_melhores group_by(ticker) |>
slice_head(n = 1) |>
ungroup()
<- parametros_melhores |>
vol_futuro group_split(ticker) |>
::map(prever_volatilidade, n_steps = 1) |>
purrr::bind_rows()
dplyr
vol_futuro
# A tibble: 5 × 4
ticker serie volatilidade shape
<chr> <dbl> <dbl> <dbl>
1 BTLG11.SA 0.000292 0.00905 2.50
2 HGRE11.SA 0.000190 0.0144 3.11
3 HGRU11.SA -0.000130 0.0128 2.32
4 MGFF11.SA -0.00128 0.0130 3.65
5 VGIR11.SA -0.00000968 0.00649 2.90
…
Vamos prever a volatilidade um passo a frente para um portfolio gerado aleatoriamente
set.seed(42)
<- runif(5)
pesos <- pesos / sum(pesos)
pesos
<- da_train |>
portfolio_returns ::tq_portfolio(
tidyquant
ticker,
ret_closing_prices, weights = pesos,
col_rename = "portfolio"
)
Warning: `spread_()` was deprecated in tidyr 1.2.0.
ℹ Please use `spread()` instead.
ℹ The deprecated feature was likely used in the tidyquant package.
Please report the issue at
<https://github.com/business-science/tidyquant/issues>.
Warning in PerformanceAnalytics::Return.portfolio(., weights = weights, : NA's
detected: filling NA's with zeros
# faz uma grid de hiperparâmetros
<- expand_grid(p = 0:3, q = 0:3, m = 0:1, n = 0:1, dist = c("std", "norm")) |>
grid ::rownames_to_column("id")
tibble
# pega o retorno do ativo
<- portfolio_returns$portfolio
ret
::ui_info("Ajustando modelos para portfolio...") usethis
ℹ Ajustando modelos para portfolio...
::plan(future::multisession, workers = 8)
future::with_progress({
progressr<- progressr::progressor(nrow(grid))
prog <- grid |>
modelos group_split(id) |>
::future_map(garch_individual, ret = ret, prog)
furrr
})
<- purrr::possibly(infocriteria, tibble::tibble())
safe_info
suppressWarnings({
<- purrr::map(modelos, safe_info) |>
informacao ::map(tibble::as_tibble, rownames = "criteria") |>
purrr::bind_rows(.id = "id")
dplyr
})
<- informacao |>
melhores ::inner_join(grid, "id") |>
dplyr::pivot_wider(names_from = criteria, values_from = V1) |>
tidyr::clean_names() |>
janitor::arrange(akaike)
dplyr
::ui_info(c(
usethis"Melhor modelo:",
"p <- {melhores$p[1]}",
"q <- {melhores$q[1]}",
"m <- {melhores$m[1]}",
"n <- {melhores$n[1]}"
))
ℹ Melhor modelo:
p <- 2
q <- 0
m <- 1
n <- 1
= ugarchspec(
garch_model variance.model = list(
model = "fGARCH",
submodel = "GARCH",
garchOrder = c(melhores$m[1], melhores$n[1])
),mean.model = list(
armaOrder = c(melhores$p[1], melhores$q[1]),
include.mean = TRUE
),distribution.model = melhores$dist[1]
)
<- ugarchfit(garch_model, data = ret)
fit
# browser()
if (melhores$dist[1] == "std") {
<- as.numeric(fit@fit$coef["shape"])
shape else {
} <- NA_real_
shape
}
<- ugarchforecast(fit, n.ahead = 1)@forecast
forecasts
::tibble(
tibbleserie = as.numeric(forecasts$seriesFor),
volatilidade = as.numeric(forecasts$sigmaFor),
shape = shape
)
# A tibble: 1 × 3
serie volatilidade shape
<dbl> <dbl> <dbl>
1 -0.000867 0.00649 4.12
Reproduzindo código daqui:
https://www.codingfinance.com/post/2018-05-31-portfolio-opt-in-r/
Versão em python
https://www.codingfinance.com/post/2018-05-31-portfolio-opt-in-python/
<- da_train |>
da_wide ::select(ref_date, name = ticker, value = ret_closing_prices) |>
dplyr::pivot_wider()
tidyr
<- da_wide |>
da_xts ::tk_xts(select = -ref_date, date_var = ref_date) timetk
<- colMeans(da_xts, na.rm = TRUE)
mean_ret print(round(mean_ret, 5))
BTLG11.SA HGRE11.SA HGRU11.SA MGFF11.SA VGIR11.SA
0.00035 0.00007 0.00013 -0.00039 0.00000
Next we will calculate the covariance matrix for all these stocks. We will NOT annualize it by multiplying by 252.
<- cov(da_xts, use = "complete.obs")
cov_mat print(round(cov_mat,6))
BTLG11.SA HGRE11.SA HGRU11.SA MGFF11.SA VGIR11.SA
BTLG11.SA 0.000144 0.000065 0.000055 0.000058 0.000040
HGRE11.SA 0.000065 0.000207 0.000079 0.000084 0.000068
HGRU11.SA 0.000055 0.000079 0.000148 0.000063 0.000056
MGFF11.SA 0.000058 0.000084 0.000063 0.000192 0.000060
VGIR11.SA 0.000040 0.000068 0.000056 0.000060 0.000143
Before we apply our methods to thousands of random portfolio, let us demonstrate the steps on a single portfolio.
To calculate the portfolio returns and risk (standard deviation) we will us need
set.seed(2)
# Calculate the random weights
<- runif(n = length(ativos))
wts
<- wts/sum(wts)) (wts
[1] 0.07186944 0.27303447 0.22286964 0.06532697 0.36689948
# Calculate the portfolio returns
<- sum(wts * mean_ret)) (port_returns
[1] 4.950717e-05
# Calculate the portfolio risk
<- sqrt(t(wts) %*% (cov_mat %*% wts))) (port_risk
[,1]
[1,] 0.009518973
# Calculate the Sharpe Ratio
<- port_returns/port_risk) (sharpe_ratio
[,1]
[1,] 0.005200893
We have everything we need to perform our optimization. All we need now is to run this code on 5000 random portfolios. For that we will use a for loop.
~Before we do that, we need to create empty vectors and matrix for storing our values.~
<- function(i) {
sim_returns <- runif(length(ativos))
wts <- wts / sum(wts)
wts <- sum(wts * mean_ret)
port_ret <- as.numeric(sqrt(t(wts) %*% (cov_mat %*% wts)))
port_sd <- port_ret / port_sd
sr
|>
wts ::set_names(ativos) |>
purrr::enframe() |>
tibble::pivot_wider() |>
tidyr::mutate(
dplyrreturn = port_ret,
risk = port_sd,
sharpe = sr
)
}
<- purrr::map(1:5000, sim_returns) |>
portfolio_values bind_rows(.id = "run")
<- portfolio_values[which.min(portfolio_values$risk),]
min_var <- portfolio_values[which.max(portfolio_values$sharpe),] max_sr
Lets plot the weights of each portfolio. First with the minimum variance portfolio.
|>
min_var pivot_longer(2:6) |>
mutate(name = forcats::fct_reorder(name, value)) |>
ggplot(aes(name, value)) +
geom_col() +
scale_y_continuous(labels = scales::percent) +
labs(
x = "Asset",
y = "Weight",
title = "Minimum variance portfolio weights"
)
|>
max_sr pivot_longer(2:6) |>
mutate(name = forcats::fct_reorder(name, value)) |>
ggplot(aes(name, value)) +
geom_col() +
scale_y_continuous(labels = scales::percent) +
labs(
x = "Asset",
y = "Weight",
title = "Tangency portfolio weights"
)
|>
portfolio_values ggplot(aes(x = risk, y = return, color = sharpe)) +
geom_point() +
theme_classic() +
scale_y_continuous(labels = scales::percent) +
scale_x_continuous(labels = scales::percent) +
labs(
x = 'Risk',
y = 'Returns',
title = "Portfolio Optimization & Efficient Frontier"
+
) geom_point(
aes(x = risk, y = return),
data = min_var,
color = 'red'
+
) geom_point(
aes(x = risk, y = return),
data = max_sr,
color = 'red'
)
<- min_var |>
pesos_finais ::select(2:6) |>
dplyras.numeric()
<- mean(vol_futuro$serie * pesos_finais)
rt_final <- sqrt(pesos_finais %*% cov_mat %*% pesos_finais)
st_dev_final <- min(vol_futuro$shape)
nu <- qt(.95, nu)
valor_t
<- rt_final + valor_t * st_dev_final / sqrt(nu/(nu-2))) (VaR
[,1]
[1,] 0.008717397
<- da_train |>
portfolio_returns ::tq_portfolio(
tidyquant
ticker,
ret_closing_prices, weights = pesos_finais,
col_rename = "portfolio"
)
Warning in PerformanceAnalytics::Return.portfolio(., weights = weights, : NA's
detected: filling NA's with zeros
<- yfR::yf_get(
market_returns "^BVSP",
first_date = data_corte + 1,
type_return = "log",
freq_data = "daily",
do_complete_data = TRUE
|>
) ::select(ref_date, ibov = ret_closing_prices) dplyr
── Running yfR for 1 stocks | 2018-08-01 --> 2022-11-12 (1564 days) ──
ℹ Downloading data for benchmark ticker ^GSPC
ℹ (1/1) Fetching data for ^BVSP
! - not cached
✔ - cache saved successfully
✔ - got 1061 valid rows (2018-08-01 --> 2022-11-11)
✔ - got 96% of valid prices -- You got it !
ℹ Binding price data
ℹ Completing data points (do_complete_data = TRUE)
── Diagnostics ─────────────────────────────────────────────────────────────────
✔ Returned dataframe with 1061 rows -- Well done !
ℹ Using 864.4 kB at C:\Users\julio\AppData\Local\Temp\Rtmp4yKLF3/yf_cache for 7 cache files
ℹ Out of 1 requested tickers, you got 1 (100%)
<- market_returns |>
all_returns ::inner_join(portfolio_returns, "ref_date") |>
dplyr::drop_na()
tidyr
<- with(all_returns, cov(portfolio, ibov) / var(ibov))) (beta_geral
[1] 0.2333843
<- function(ativo) {
calcular_beta |>
da_train ::filter(ticker == ativo) |>
dplyr::inner_join(market_returns, "ref_date") |>
dplyr::drop_na() |>
tidyrwith(cov(ret_closing_prices, ibov) / var(ibov))
}
<- purrr::map_dbl(ativos, calcular_beta) |>
betas ::set_names(ativos)
purrr
sum(betas * pesos_finais)
[1] 0.2564028
beta_geral
[1] 0.2333843
<- lm(portfolio ~ ibov, data = all_returns) |>
capm_lm_tudo ::tidy() |>
broom::filter(term == "ibov") |>
dplyrwith(estimate)
<- purrr::map_dbl(ativos, \(ativo) {
capm_lm_individual <- da_train |>
da_model ::filter(ticker == ativo) |>
dplyr::inner_join(market_returns, "ref_date")
dplyrlm(ret_closing_prices ~ ibov, data = da_model) |>
::tidy() |>
broom::filter(term == "ibov") |>
dplyr::pull(estimate)
dplyr|>
}) ::set_names(ativos)
purrr
capm_lm_tudo
[1] 0.2333843
capm_lm_individual
HGRE11.SA BTLG11.SA HGRU11.SA VGIR11.SA MGFF11.SA
0.2635135 0.2264689 0.2475435 0.1971465 0.2871617