Análisis Dinámico de Acciones del Índice Financiero de la Bolsa de Valores de Caracas: 2023-2025
Autor/a
Javier Melendez
Fecha de publicación
12 de febrero de 2025
Resumen
Este proyecto analizará el comportamiento de las acciones del índice financiero de la Bolsa de Valores de Caracas (BVC) entre enero de 2023 y febrero de 2024. El objetivo es ofrecer una herramienta útil para inversionistas y entender mejor las dinámicas recientes del mercado de valores venezolano
Introducción
El mercado de valores venezolano ha experimentado fluctuaciones significativas en los últimos años, influenciadas por factores macroeconómicos y eventos globales. Este proyecto se enfoca en el análisis del comportamiento de las acciones del índice financiero de la BVC durante un período específico(2023-01 y 2025-02) excluyendo el impacto directo de la pandemia de COVID-19 y la recuperación posterior (2021), para centrarse en la dinámica más reciente del mercado
Plantiamiento del problema
¿Cuáles son las tendencias y patrones en el comportamiento de las acciones del índice financiero de la BVC durante el período comprendido entre el 2 de enero de 2023 y el 5 de febrero de 2025?
Objetivo general
Analizar el comportamiento de las acciones del índice financiero de la BVCaracas durante el período 2023-2024 y desarrollar un modelo ARIMA para predecir su valor futuro a corto plazo.
Objetivos específicos
1. Recopilar y procesar datos históricos del índice financiero de la BVCaracas y de las acciones que lo componen.
2. Identificar tendencias, patrones y relaciones entre las variables relevantes utilizando técnicas de análisis estadístico y econométrico.
Metodologia
Recoleccion de datos: Para obtener los datos
Las acciones analizadas son: BPV, BNC, BVCC, ABC.A, MVZ.A, MVZ.B.
1. Librerías Necesarias
Código
library(httr)library(jsonlite)library(lubridate)
Adjuntando el paquete: 'lubridate'
The following objects are masked from 'package:base':
date, intersect, setdiff, union
Código
library(dplyr)
Adjuntando el paquete: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
Código
library(purrr)
Adjuntando el paquete: 'purrr'
The following object is masked from 'package:jsonlite':
flatten
Código
library(DT)
Extraccion datos desnudos de la BVC
La siguiente función se encarga de realizar una solicitud POST a la Bolsa de Valores de Caracas y extraer los datos históricos correspondientes a cada acción. Se incluyen controles de error y mensajes informativo
Código
obtener_datos_desnudos <-function(simbolo) {tryCatch({# Configurar solicitud HTTP headers <-add_headers("User-Agent"="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36","Referer"="https://www.bolsadecaracas.com/historicos/" )# Realizar solicitud POST response <-POST("https://www.bolsadecaracas.com/wp-admin/admin-ajax.php", headers,body =list(action ="getHistoricoSimbolo", simbolo = simbolo),encode ="form",timeout(15) )# Verificar que la solicitud fue exitosaif (status_code(response) !=200) {message("Error en la respuesta para ", simbolo, ": ", status_code(response))return(NULL) }# Procesar JSON y devolver el contenido completo contenido <-content(response, "text", encoding ="UTF-8") datos_json <-fromJSON(contenido)return(datos_json) }, error =function(e) {message("Error procesando ", simbolo, ": ", e$message)return(NULL) })}
Definimos la lista de símbolos a analizar y ejecutamos la extracción de datos para cada uno, almacenando los resultados en una lista. Se introduce una pausa entre solicitudes para evitar saturar el servidor.
Código
# Lista de símbolossimbolos <-c("BPV", "BNC", "BVCC", "ABC.A", "MVZ.A", "MVZ.B")# Ejecución principal: recoger todos los datos sin limpiezadatos_finales <-list()for (simbolo in simbolos) {message("Procesando: ", simbolo) datos <-obtener_datos_desnudos(simbolo)if (!is.null(datos)) { datos_finales[[simbolo]] <- datosSys.sleep(1.5) # Pausa para evitar saturar el servidor }}
Procesando: BPV
Procesando: BNC
Procesando: BVCC
Procesando: ABC.A
Procesando: MVZ.A
Procesando: MVZ.B
Optimización y Limpieza del DataFrame ####En esta sección se combinan los datos extraídos, se ajustan los nombres de las columnas y se realiza la limpieza de datos:
Se convierte la columna de fechas al formato Date (usando lubridate::as_date). Se agrega la columna ACCION para identificar cada símbolo. Se limpian las columnas numéricas, sustituyendo comas por puntos para su correcta conversión a valores numéricos.
Código
datos_totales <-map_df(names(datos_finales), function(simbolo) {# Extraer el componente relevante df <- datos_finales[[simbolo]]$cur_hist_mov_emisoraif (is.null(df)) {return(data.frame(ACCION = simbolo)) } df <-as.data.frame(df)# Renombrar columnas según el formato requeridocolnames(df) <-c("FECHA", "PRECIO_APERT", "PRECIO_CIE", "VAR_ABS", "VAR_REL", "PRECIO_MAX", "PRECIO_MIN", "N_OPERACIONES", "TITULOS_NEGOCIADOS", "MONTO_EFECTIVO")[1:ncol(df)]# Convertir la columna FECHA al formato Date (formato original: 'dd-mm-yy') df$FECHA <-as_date(df$FECHA, format ='%d-%m-%y')# Agregar la columna ACCION df$ACCION <- simboloreturn(df)}) %>%# Limpiar columnas numéricas: reemplazar puntos y comas para que sean interpretadas como númerosmutate(across(c(PRECIO_APERT, PRECIO_CIE, VAR_ABS, VAR_REL, PRECIO_MAX, PRECIO_MIN, N_OPERACIONES, TITULOS_NEGOCIADOS, MONTO_EFECTIVO),~as.numeric(gsub(",", ".", gsub("\\.", "", .))) ))# Verificar la estructura final del DataFramestr(datos_totales)
Para realizar un análisis más enfocado, se filtran los datos para el período comprendido entre enero 2023 y febrero 2025.
Código
# Definir el rango de fechasinicio <-as.Date("2023-01-01")fin <-as.Date("2025-02-11") # Incluye febrero 2025# Filtrar el DataFrame para el rango deseadodatos_filtrados <- datos_totales %>%filter(FECHA >= inicio & FECHA <= fin)# Visualizar las primeras filas del conjunto filtradohead(datos_filtrados)
Acontinuacion se presente dos tipos de visualizaciones:
Gráficas independientes por acción: Cada acción se analiza de forma individual para apreciar su evolución en el tiempo.
Gráfica combinada: Se muestra la evolución del precio de cierre para todas las acciones en un mismo gráfico (usando facets), lo que permite compararlas manteniendo una escala adecuada para cada una y asegurando que el precio se lea bien.
Donde la data frame datos_filtrados que contiene, entre otras columnas, las variables:
FECHA (de tipo Date)
PRECIO_CIE (precio de cierre)
ACCION (símbolo de la acción)
1. Gráficas Independientes por Acción
Código
# Cargar librerías necesariaslibrary(plotly)
Cargando paquete requerido: ggplot2
Adjuntando el paquete: 'plotly'
The following object is masked from 'package:ggplot2':
last_plot
The following object is masked from 'package:httr':
config
The following object is masked from 'package:stats':
filter
The following object is masked from 'package:graphics':
layout
Código
library(dplyr)library(scales)
Adjuntando el paquete: 'scales'
The following object is masked from 'package:purrr':
discard
Código
# Lista única de accionesacciones <-unique(datos_filtrados$ACCION)# Crear una lista para almacenar la información de cada trazatrazas <-list()for (accion in acciones) { datos_accion <- datos_filtrados %>%filter(ACCION == accion)# Definir el texto a mostrar en el hover (tooltip) hover_text <-paste("Fecha:", datos_accion$FECHA,"<br>Precio Cierre:", dollar(datos_accion$PRECIO_CIE, prefix ="bs"),"<br>N_OPERACIONES:", datos_accion$N_OPERACIONES,"<br>TITULOS_NEGOCIADOS:", datos_accion$TITULOS_NEGOCIADOS,"<br>MONTO_EFECTIVO:", datos_accion$MONTO_EFECTIVO) trazas[[accion]] <-list(x = datos_accion$FECHA,y = datos_accion$PRECIO_CIE,type ='scatter',mode ='lines',name = accion,line =list(width =1.5),text = hover_text,hoverinfo ="text"# Se indica que se usará el texto personalizado en el hover )}# Definir la visibilidad inicial: solo se muestra la primera acciónvisibilidad_inicial <-sapply(acciones, function(x) x == acciones[1])# Crear los botones del menú superiorbotones <-lapply(seq_along(acciones), function(i) {# Vector de visibilidad: solo la traza i es visible vis <-rep(FALSE, length(acciones)) vis[i] <-TRUElist(method ="update",args =list(list(visible = vis), # Actualiza la visibilidad de las trazaslist(title =paste("Evolución del Precio de Cierre de", acciones[i])) ),label = acciones[i] )})# Construir el gráficofig <-plot_ly()for (i inseq_along(acciones)) { accion <- acciones[i] fig <- fig %>%add_trace(x = trazas[[accion]]$x,y = trazas[[accion]]$y,type = trazas[[accion]]$type,mode = trazas[[accion]]$mode,name = trazas[[accion]]$name,line = trazas[[accion]]$line,text = trazas[[accion]]$text,hoverinfo = trazas[[accion]]$hoverinfo,visible = visibilidad_inicial[i] )}# Configurar el layout del gráfico (incluye menú de actualización y rangos)fig <- fig %>%layout(title =paste("Evolución del Precio de Cierre de", acciones[1]),updatemenus =list(list(type ="buttons",direction ="right",x =0.5, # Ajusta la posición horizontal según convengay =1.15, # Ajusta la posición vertical (por encima del gráfico)showactive =TRUE,buttons = botones ) ),xaxis =list(title ="Fecha",rangeselector =list(buttons =list(list(count =7, label ="1 Semana", step ="day", stepmode ="backward"),list(count =1, label ="1 Mes", step ="month", stepmode ="backward"),list(count =6, label ="6 Meses", step ="month", stepmode ="backward"),list(count =1, label ="1 Año", step ="year", stepmode ="backward"),list(step ="all", label ="Todo") ) ),rangeslider =list(visible =TRUE) ),yaxis =list(title ="Precio de Cierre (Bs)"))# Mostrar el gráficofig
####. Gráfica Combinada: En este gráfico se agrupa la evolución de todas las acciones. Se utiliza facet_wrap para crear paneles independientes para cada acción, permitiendo que la escala y se ajuste de manera individual para que el precio se lea de forma óptima.
Código
# Cargar librerías necesariaslibrary(plotly)library(dplyr)library(scales)# Supongamos que 'datos_filtrados' es tu data frame con las columnas mencionadas.# Crear el gráfico compartidofig <-plot_ly()for (accion inunique(datos_filtrados$ACCION)) { datos_accion <- datos_filtrados %>%filter(ACCION == accion)# Crear el texto para el tooltip hover_text <-paste("Fecha:", datos_accion$FECHA,"<br>Precio Cierre:", dollar(datos_accion$PRECIO_CIE, prefix ="$"),"<br>N_OPERACIONES:", datos_accion$N_OPERACIONES,"<br>TITULOS_NEGOCIADOS:", datos_accion$TITULOS_NEGOCIADOS,"<br>MONTO_EFECTIVO:", datos_accion$MONTO_EFECTIVO) fig <- fig %>%add_trace(x = datos_accion$FECHA,y = datos_accion$PRECIO_CIE,type ='scatter',mode ='lines',name = accion,line =list(width =2),text = hover_text,hoverinfo ="text" )}# Configurar el layout del gráficofig <- fig %>%layout(title ="Evolución del Precio de Cierre - Acciones del Índice Financiero de Caracas",xaxis =list(title ="Fecha",rangeselector =list(buttons =list(list(count =7, label ="1 Semana", step ="day", stepmode ="backward"),list(count =1, label ="1 Mes", step ="month", stepmode ="backward"),list(count =6, label ="6 Meses", step ="month", stepmode ="backward"),list(count =1, label ="1 Año", step ="year", stepmode ="backward"),list(step ="all", label ="Todo") ) ),rangeslider =list(visible =TRUE) ),yaxis =list(title ="Precio de Cierre (Bs)"),legend =list(orientation ='h', # Leyenda horizontalx =0.5,xanchor ='center',y =1.15 ))# Mostrar el gráficofig
Analis de volumen de accion por Año
Código
library(dplyr)library(lubridate)# Agregar la columna 'Año' a partir de 'FECHA'datos_filtrados <- datos_filtrados %>%mutate(Año =year(FECHA))# Resumen anual: total negociado por cada acción en cada añovolumen_anual <- datos_filtrados %>%group_by(ACCION, Año) %>%summarise(total_vol =sum(TITULOS_NEGOCIADOS, na.rm =TRUE)) %>%ungroup()
`summarise()` has grouped output by 'ACCION'. You can override using the
`.groups` argument.
Código
accion_favorita_anual <- volumen_anual %>%group_by(Año) %>%filter(total_vol ==max(total_vol)) %>%# Selecciona la acción con el mayor volumen en cada añoarrange(Año) %>%ungroup()# Mostrar la acción favorita por añoprint(accion_favorita_anual)
library(plotly)fig_bar <-plot_ly(volumen_anual, x =~Año, y =~total_vol, color =~ACCION, type ='bar') %>%layout(title ="Comparativa del Volumen Negociado por Año y Acción",xaxis =list(title ="Año"),yaxis =list(title ="Total de Títulos Negociados"),barmode ='group',legend =list(title =list(text ="<b>Acción</b>")))fig_bar
Cálculo de Indicadores de Desempeño Con el paquete PerformanceAnalytics podemos calcular métricas importantes. Para cada acción creamos un objeto xts a partir del rendimiento diario y luego calculamos:
Rendimiento Anualizado: El promedio geométrico anualizado.
Volatilidad Anualizada: La desviación estándar anualizada.
Máximo Drawdown: La mayor caída desde un pico hasta un mínimo durante el período.
Código
library(PerformanceAnalytics)
Cargando paquete requerido: xts
Cargando paquete requerido: zoo
Adjuntando el paquete: 'zoo'
The following objects are masked from 'package:base':
as.Date, as.Date.numeric
######################### Warning from 'xts' package ##########################
# #
# The dplyr lag() function breaks how base R's lag() function is supposed to #
# work, which breaks lag(my_xts). Calls to lag(my_xts) that you type or #
# source() into this session won't work correctly. #
# #
# Use stats::lag() to make sure you're not using dplyr::lag(), or you can add #
# conflictRules('dplyr', exclude = 'lag') to your .Rprofile to stop #
# dplyr from breaking base R's lag() function. #
# #
# Code in packages is not affected. It's protected by R's namespace mechanism #
# Set `options(xts.warn_dplyr_breaks_lag = FALSE)` to suppress this warning. #
# #
###############################################################################
Adjuntando el paquete: 'xts'
The following objects are masked from 'package:dplyr':
first, last
Adjuntando el paquete: 'PerformanceAnalytics'
The following object is masked from 'package:graphics':
legend
Código
library(xts)library(dplyr)# Asegurar que FECHA es tipo Datedatos_filtrados <- datos_filtrados %>%mutate(FECHA =as.Date(FECHA))# Calcular rendimientos diarios si no están en el datasetdatos_filtrados <- datos_filtrados %>%group_by(ACCION) %>%arrange(FECHA) %>%mutate(rend_diario = (PRECIO_CIE /lag(PRECIO_CIE)) -1) %>%ungroup()# Función para calcular métricas financierascalcular_metricas <-function(df) { serie_xts <-xts(df$rend_diario, order.by = df$FECHA)# Si la serie está vacía o tiene solo un dato, evitar erroresif (nrow(serie_xts) <2) {return(data.frame(annualized_return =NA,annualized_volatility =NA,max_drawdown =NA )) }data.frame(annualized_return =as.numeric(Return.annualized(na.omit(serie_xts), scale =252)),annualized_volatility =as.numeric(StdDev.annualized(na.omit(serie_xts), scale =252)),max_drawdown =as.numeric(maxDrawdown(na.omit(serie_xts))) )}# Aplicar la función para cada acción usando group_modify()performance_metrics <- datos_filtrados %>%group_by(ACCION) %>%group_modify(~calcular_metricas(.)) %>%ungroup()# Mostrar resultadosprint(performance_metrics)