Home IBOV Project
Post
Cancel

IBOV Project

Objetivo do trabalho

O trabalho busca desenvolver um dashboard que rode um modelo de machine learning capaz de prever o movimento do dia seguinte no indice ibovespa, baseado em dados históricos disponibilizados via API no yahoo finace, e para isso serão aplicadas técnicas estatisticas de regressão logística e regressão linear em series temporáis.

O resultado final esta disponível no dashboard que pode ser acessado em: Dashboard - URL

Aviso: Este estudo não é uma recomendação de compra ou venda, mas sim um estudo teorico de data science. O mercado financeiro é volátil e envolve riscos, por isso estude bem antes de realizar operações e converse com seu consultor.

Organização do trabalho

Premissas: Este trabalho foi realizado com a linguagem R, IDE Rstudio, e sistema operacional Linux Mint. Foram utilizados conhecimentos de data science e metodologias ágeis. Seguindo as boas práticas do mercado demos preferencia para bibliotecas do tidyverse.

Principais Etapas:

  • Definição do objetivo do trabalho;
  • Versionar trabalho no GitHub;
  • Utilizar a ferramenta Kanban para organizar projeto no formato de metodologia ágil;
  • Coleta dos dados;
  • Realizar analise exploratória;
  • Limpeza e tratamento dos dados;
  • Salvar modelo treinado em arquivo r para posteriormente aplicar no framework tidymodels;
  • Aplicar framework do tidymodels para desenvolver modelo;
  • Avaliar resultados do modelo e necessidade de tunar os hiperparametros;
  • Desenvolver um dashboard dinâmico com os pacotes flexdashboard, shiny e ploty;
  • Realizar deploy do modelo and uploud in the shinyapp.io

Versionar trabalho no GitHub

Foi criado um repositorio para ajudar no armazentamento de arquivos e versionamento de todo o projeto. Imagem do Repositório

Kanban para organizar projeto no formato de metodologia ágil

Esta ferramenta serve para realizar gestão à vista do andamento de cada atividade ao longo do projeto. Imagem do Kanban

Coleta dos dados

Para construir uma primeira versão do modelo foram usados os dados históricos do índice Ibovespa futuro, filtrando o período de jan.2021 até jul.2022, disponíbilizados pelo site investing.com: Imagem do Investing

Analise exploratória

Começamos carregando o arquivo csv extraido do site investing.com para dentro do rstudio para entender melhor o formato dos dados disponibilizados:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#Limpando Enviroment e Carregando principais Bibliotecas
rm(list = ls())
library(tidymodels)
library(readr)
library(janitor)
library(stringr)
library(lubridate)
library(ggplot2)
library(plotly)
library(DataExplorer)

#carregando Base extraida do site investing e adicionando uma coluna chamada meta
base <- readr::read_csv('Futuros Ibovespa - Dados Históricos.csv') %>% janitor::clean_names() %>% 
  dplyr::mutate(data = lubridate::dmy(data),
         meta = dplyr::if_else(var_percent > 0,1,0) %>% forcats::as_factor()) %>% 
  dplyr::arrange(data)
  
#Avaliando tamanho da base e tipo primitivo dos dados
dplyr::glimpse(base)  

Avaliando o dataframe notamos que as dimensões são 8 colunas e 361 linhas.

Trata-se de uma serie temporal com indexador de data mostrando as estatisticas do indice ibovespa ao longo de cada dia. Realizamos a tranformação da coluna data para um formato que utilizaremos, e também incluimos uma coluna chamada meta para entender o comportamento dos dados.

A ideia é que sempre que tivermos uma variação percentual positiva, coluna ultimo apresentar valor maior do que a coluna abertura, a meta tenha o valor um e caso contrario valor seja zero.

1
2
3
4
5
6
7
8
9
10
Rows: 361
    Columns: 8
    $ data        <date> 2021-01-04, 2021-01-05, 2021-01-06, 2021-01-07, 2021-01-08, 2021-01-11, 2021-01-12, 2…
    $ ultimo      <dbl> 118.859, 119.393, 119.180, 122.684, 125.127, 123.120, 124.336, 121.959, 123.488, 120.3…
    $ abertura    <dbl> 120.320, 119.000, 119.195, 119.405, 123.050, 124.600, 123.805, 123.985, 122.775, 122.6…
    $ maxima      <dbl> 120.575, 119.955, 121.075, 123.450, 125.475, 124.910, 124.715, 124.385, 124.040, 122.7…
    $ minima      <dbl> 118.140, 116.770, 118.900, 119.235, 122.370, 122.465, 123.240, 121.015, 122.340, 120.0…
    $ vol         <chr> "162,34K", "179,66K", "182,04K", "176,66K", "190,05K", "164,15K", "113,43K", "237,23K"…
    $ var_percent <chr> "-0,31%", "0,45%", "-0,18%", "2,94%", "1,99%", "-1,60%", "0,99%", "-1,91%", "1,25%", "…
    $ meta        <fct> 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1,…```

Insigth: Avaliando a integridade dos dados para entender se por ventura temos algum valor ausente no dataset.

Design: Para construção desse gráfico utilizamos a biblioteca DataExplorer que possui gráficos especializados na analise exploratória e facilitam a analise.

1
2
#Avaliando tipos de dados e verificando dados faltantes
DataExplorer::plot_intro(base)

plot of chunk unnamed-chunk-6

Como não encontramos dados ausentes seguimos para a próxima etapa que seria entender o comportamento dos dados ao longo do tempo. Insights: Analisando o gráfico notamos que o comportamento é variar dentro de um range de 100 a 140 pontos. Em alguns momentos essa oscilação é mais extrema do que em outros; Em alguns momentos existe certa tendencia mais evidente, e em outros notamos um comportamento aleatório.

Design: Para construção desse gráfico utilizamos os pacotes ggplot e ploty buscando uma solução elegante que armonize a qualidade visual quanto interatividade.

1
2
3
4
5
6
7
8
#Analisando gráfico da seríe temporal
ggplotly(
ggplot(base,aes(x = data,y = ultimo))+
  geom_line()+
  geom_point(color='blue',size=1)+
  ggtitle('Gráfico Cotação Diária')+
  scale_x_date(date_breaks = "1 month", date_labels = "%b %d")
)
1
2
## PhantomJS not found. You can install it with webshot::install_phantomjs(). If it is installed, please make sure the phantomjs executable can be found via the PATH variable.
## PhantomJS not found. You can install it with webshot::install_phantomjs(). If it is installed, please make sure the phantomjs executable can be found via the PATH variable.
1
## Error in path.expand(path): invalid 'path' argument
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#Dividindo entre treino e teste
split_base <- initial_split(base,prop = .8)
train_base <- training(split_base)
test_base <- testing(split_base)


# Criando modelo ----------------------------------------------------------
lr_model <- logistic_reg() %>% 
  set_mode('classification') %>% 
  set_engine('glm')


# Criando Recipe ----------------------------------------------------------
lr_recipe <- recipe(meta ~ .,data = train_base) %>% 
  step_rm(var_percent,vol) %>% prep()


# Criando Workflow --------------------------------------------------------
wkf_model <- workflow() %>% 
  add_model(lr_model) %>% 
  add_recipe(lr_recipe)


# Treinando Modelo --------------------------------------------------------
lr_result <- last_fit(wkf_model,split = split_base)


# Avaliando Resultado -----------------------------------------------------
#Accuracy and roc_auc
lr_result %>% collect_metrics()
1
2
3
4
5
## # A tibble: 2 × 4
##   .metric  .estimator .estimate .config     
##   <chr>    <chr>          <dbl> <chr>       
## 1 accuracy binary         0.822 Preprocesso…
## 2 roc_auc  binary         0.915 Preprocesso…
1
2
#Matriz de Confusão
lr_result %>% unnest(.predictions) %>% conf_mat(truth = meta, estimate = .pred_class) %>% autoplot(type='heatmap')

plot of chunk unnamed-chunk-8

1
2
3
# Salvando modelo Final ---------------------------------------------------
final_lr_result <- fit(wkf_model,base)
saveRDS(object = final_lr_result,file = 'win_model.rds')

Carregar Bibliotecas necessárias

1
2
3
knitr::opts_chunk$set(echo = TRUE)
library(flexdashboard)
library(shiny)
1
2
## 
## Attaching package: 'shiny'
1
2
3
## The following object is masked from 'package:infer':
## 
##     observe
1
2
3
4
5
6
7
8
9
10
library(tidymodels)
library(readr)
library(janitor)
library(stringr)
library(lubridate)
library(ggplot2)
library(plotly)
library(patchwork)
library(knitr)
library(kableExtra)
1
2
## 
## Attaching package: 'kableExtra'
1
2
3
## The following object is masked from 'package:dplyr':
## 
##     group_rows
1
library(quantmod)
1
## Loading required package: xts
1
## Loading required package: zoo
1
2
## 
## Attaching package: 'zoo'
1
2
3
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
1
2
## 
## Attaching package: 'xts'
1
2
3
## The following objects are masked from 'package:dplyr':
## 
##     first, last
1
## Loading required package: TTR
1
2
## 
## Attaching package: 'TTR'
1
2
3
## The following object is masked from 'package:dials':
## 
##     momentum
1
2
3
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo

Carrega Modelo Salvo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Filtros {.sidebar}
--------------------------------------------------

#Carrega Modelo
modelo <- read_rds('win_model.rds')

#Carrega Base
base <- getSymbols(Symbols = '^BVSP')
base <- BVSP %>% data.frame() %>% rownames_to_column() %>% clean_names() %>%
  rename("data"='rowname',"ultimo"='bvsp_close',"abertura"='bvsp_open', "maxima"='bvsp_high',"minima"='bvsp_low', 'vol'='bvsp_volume') %>%
  mutate(data = as_date(data),
         vol=as.character(vol),
         var_percent= as.character(paste0(round(((ultimo/lag(ultimo))-1)*100,2),'%')),
         meta = if_else(var_percent > 0,1,0) %>% as.factor(),
         abertura= format(abertura,big.mark='.',decimal.mark=',') %>% as.numeric(),
         maxima= format(maxima,big.mark='.',decimal.mark=',') %>% as.numeric(),
         minima= format(minima,big.mark='.',decimal.mark=',') %>% as.numeric(),
         ultimo= format(ultimo,big.mark='.',decimal.mark=',') %>% as.numeric(),
         greenRed=ifelse(abertura-ultimo>0,"Red","Green")) %>%
  select(-bvsp_adjusted) %>% filter(!is.na(ultimo))

shiny::dateRangeInput(inputId = 'periodo',label = 'Período',start = max(base$data)-60,end = max(base$data),language = 'pt')

#Botão para Download da série
downloadHandler(
    filename = function() { 
      paste("dataset-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      readr::write_csv(base %>% select(-c(var_percent,meta,greenRed)) %>% 
                         filter(data>= input$periodo[1] & data <= input$periodo[2]), file)
    })

#Filtra último dia do banco de dados
novo_dado <- base %>% filter(data == max(data))

#Aplica o modelo para obter a probabilidade de movimento do dia seguinte
resultado_valor <- round(if_else(predict(object = modelo, new_data = novo_dado)==0,
        predict(object = modelo, new_data = novo_dado,type = 'prob') %>% pull(1),
        predict(object = modelo, new_data = novo_dado,type = 'prob') %>% pull(2))*100, 2)

resultado_label=if_else(predict(object = modelo, new_data = novo_dado)==0,'Baixa','Alta')
1
2
3
## Error: <text>:1:9: unexpected '{'
## 1: Filtros {
##             ^

Row {data-height=300}

Probabilidade e direção do Movimento

1
2
3
4
5
#Informa dentro do dashboard
renderGauge({
gauge(value = resultado_valor,label = resultado_label, min = 0, max = 100, symbol = '%', gaugeSectors(
  success = c(80, 100), warning = c(40, 79), danger = c(0, 39)))
})

Concentração Dos Dados

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
renderPlot({
p1 <- ggplot(base %>% filter(data>= input$periodo[1] & data <= input$periodo[2]))+
  geom_density(aes(x =ultimo),fill='blue',alpha=.25)+
  geom_vline(xintercept = novo_dado %>% pull(ultimo) ,color='orange')+
  ylab('')+xlab('')+
  theme(axis.text.y = element_blank(),
        axis.ticks.y = element_blank())

p2 <- ggplot(base %>% filter(data>= input$periodo[1] & data <= input$periodo[2]))+
  geom_boxplot(aes(x =ultimo,y = 1),fill='blue',alpha=.25)+
  geom_vline(xintercept = novo_dado %>% pull(ultimo) ,color='orange')+
  ylab('')+xlab('')+
  theme(axis.text.y = element_blank(),
        axis.ticks.y = element_blank())

p1 / p2
})

Row {data-height=700}

Comportamento Histórico

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
renderPlotly({
  ggplot(data = base %>% filter(data>= input$periodo[1] & data <= input$periodo[2]))+
    geom_segment(aes(x = data,
                     xend=data,
                     y =abertura,
                     yend =ultimo,
                     colour=greenRed),
                 size=3)+
    geom_segment(aes(x = data,
                     xend=data,
                     y =maxima,
                     yend =minima,
                     colour=greenRed))+
    scale_color_manual(values=c("Forest Green","Red"))+
    theme(legend.position ="none",
          axis.title.y = element_blank(),
          axis.title.x=element_blank(),
          axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
          plot.title= element_text(hjust=0.5))
  })

Previsão com Regressão Linear

1
2
3
4
5
renderPlotly({
  ggplot(data = base %>% filter(data>= input$periodo[1] & data <= input$periodo[2]))+
  geom_smooth(aes(x =data, y = ultimo))+
  ylab('')+xlab('')
})
This post is licensed under CC BY 4.0 by the author.