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
17 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, influenciado por factores macroeconómicos y eventos globales. Este análisis se centra en el comportamiento de las acciones del índice financiero de la BVC, focalizándose en el período posterior a 2021, para capturar la dinámica más reciente sin el efecto residual de la pandemia.
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 14 de febrero de 2025?
Objetivo general
Analizar el comportamiento de las acciones del índice financiero de la BVCaracas durante el período 2023-2024
Objetivos específicos
1. Recopilar y procesar datos históricos del índice financiero de la BVC 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
1. Recolección de Datos
Se trabajará con los siguientes símbolos de acciones:
“BPV”, “BNC”, “BVCC”, “ABC.A”, “MVZ.A”, “MVZ.B”
1.1. Librerías Necesarias
En primer lugar, se cargan las librerías que permiten realizar solicitudes HTTP, manejar JSON, procesar fechas y manipular datos:
Código
library(httr) # Para realizar solicitudes HTTPlibrary(jsonlite) # Para procesar datos en formato JSONlibrary(lubridate) # Para el manejo de fechas
Attaching package: 'lubridate'
The following objects are masked from 'package:base':
date, intersect, setdiff, union
Código
library(dplyr) # Para la manipulación de datos
Attaching package: '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) # Para aplicar funciones a listas y data frames
Attaching package: 'purrr'
The following object is masked from 'package:jsonlite':
flatten
Código
library(DT) # Para visualización interactiva de tablas
1.2. Extracción de Datos “Desnudos” desde la BVC
La siguiente función se encarga de realizar una solicitud POST a la página de la BVC y extraer los datos históricos de cada acción. Se incluyen controles de error y mensajes informativos para detectar posibles problemas en la solicitud.
Código
obtener_datos_desnudos <-function(simbolo) {tryCatch({# Configurar solicitud HTT para obtener datos desde la API de la BVC 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) })}
1.3. Extracción de Datos para Cada Símbolo
Se define la lista de símbolos y se recorre uno a uno para extraer sus datos. Se introduce una pausa entre solicitudes para no sobrecargar 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
2. Optimización y Limpieza del DataFrame
En esta sección se combinan los datos extraídos, se renombrar y limpian las columnas:
Conversión de fechas: Se transforma la columna FECHA al formato Date. Identificación de acción: Se añade una columna ACCION para identificar el símbolo correspondiente. Limpieza de datos numéricos: Se sustituyen comas por puntos para permitir la conversión correcta a tipo numérico.
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)
Se filtran los datos para conservar únicamente el período comprendido entre el 1 de enero de 2023 y el 14 de febrero de 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)
Se realizan dos tipos de visualizaciones para explorar la evolución de las acciones:
4.1. Gráficas Independientes por Acción
En este bloque se crea un gráfico interactivo con plotly para visualizar, de forma individual, la evolución del precio de cierre de cada acción. Se configura un menú que permite cambiar la traza visible y se personaliza el texto que aparece en el hover.
Código
# Cargar librerías necesariaslibrary(plotly)
Loading required package: ggplot2
Attaching package: '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)
Attaching package: '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
4.2 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
5. Análisis del Volumen Negociado
Se analiza el volumen de negociación (títulos negociados) por año y acción, determinando cuál es la acción con mayor volumen negociado cada año y se genera un gráfico comparativo.
5.1. Cálculo del Volumen Anual
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.
5.2. Identificación de la Acción con Mayor Volumen por Año
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
6. Cálculo de Indicadores de Desempeño
Se calculan métricas clave utilizando el paquete PerformanceAnalytics. En primer lugar, se calcula el rendimiento diario a partir del precio de cierre y, posteriormente, se obtiene el rendimiento acumulado. Finalmente, se calculan indicadores de desempeño como el rendimiento anualizado, la volatilidad anualizada y el máximo drawdown.
6.1. Cálculo del Rendimiento Diario y Acumulado
Código
library(dplyr)library(lubridate)# Aseguramos que los datos estén ordenados por acción y fechadatos_filtrados <- datos_filtrados %>%arrange(ACCION, FECHA)# Calcular el rendimiento diario usando el precio de cierre.# Rendimiento diario = (Precio actual / Precio anterior) - 1datos_filtrados <- datos_filtrados %>%group_by(ACCION) %>%arrange(FECHA) %>%mutate(rend_diario = PRECIO_CIE /lag(PRECIO_CIE) -1) %>%ungroup()# Calcular el rendimiento acumulado (rendimiento total a lo largo del tiempo)datos_filtrados <- datos_filtrados %>%group_by(ACCION) %>%arrange(FECHA) %>%mutate(rend_acumulado =cumprod(1+coalesce(rend_diario, 0)) -1) %>%ungroup()
6.2 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)
Loading required package: xts
Loading required package: zoo
Attaching package: '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. #
# #
###############################################################################
Attaching package: 'xts'
The following objects are masked from 'package:dplyr':
first, last
Attaching package: '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)
6.3. Gráfico Interactivo del Rendimiento Acumulado
Se genera un gráfico interactivo para visualizar la evolución del rendimiento acumulado de cada acción.
Código
# Cargar librerías necesariaslibrary(plotly)# Crear el gráfico interactivo del rendimiento acumuladofig_rendimiento_acumulado <-plot_ly()# Obtener la lista de accionesacciones <-unique(datos_filtrados$ACCION)# Agregar trazas para cada acciónfor (accion in acciones) { datos_accion <- datos_filtrados %>%filter(ACCION == accion) fig_rendimiento_acumulado <- fig_rendimiento_acumulado %>%add_trace(x = datos_accion$FECHA,y = datos_accion$rend_acumulado,type ='scatter',mode ='lines',name = accion,line =list(width =2) )}# Configurar el layout del gráficofig_rendimiento_acumulado <- fig_rendimiento_acumulado %>%layout(title ="Evolución del Rendimiento Acumulado de las Acciones",xaxis =list(title ="Fecha"),yaxis =list(title ="Rendimiento Acumulado"),legend =list(title =list(text ="<b>Acción</b>")))# Mostrar el gráficofig_rendimiento_acumulado
En el gráfico se ilustra la rentabilidad anualizada, la volatilidad anualizada y el máximo drawdown de seis acciones: ABC.A, BNC, BPV, BVCC, MVZ.A y MVZ.B. Observamos que, aunque BNC presenta una rentabilidad anualizada notablemente alta del 20.25%, también exhibe una volatilidad anualizada extremadamente elevada (379.66%) y un máximo drawdown del 97.84%, indicando un perfil de riesgo considerable. En contraste, MVZ.B muestra una rentabilidad anualizada más modesta del 3.17%, acompañada de una volatilidad anualizada del 0.75% y un máximo drawdown del 32.20%, sugiriendo una inversión potencialmente más estable. Este análisis destaca la importancia de evaluar conjuntamente la rentabilidad y las métricas de riesgo al considerar decisiones de inversión
Guardar datos
Código
# Guardar el data frame 'datos_totales' como CSVwrite.csv(datos_totales, file ="datos_totales.csv", row.names =FALSE)# Guardar el data frame 'datos_filtrados' como CSVwrite.csv(datos_filtrados, file ="datos_filtrados.csv", row.names =FALSE)# Guardar el resumen anual del volumen negociadowrite.csv(volumen_anual, file ="volumen_anual.csv", row.names =FALSE)# Guardar la acción favorita por añowrite.csv(accion_favorita_anual, file ="accion_favorita_anual.csv", row.names =FALSE)# Guardar las métricas de desempeño financieraswrite.csv(performance_metrics, file ="performance_metrics.csv", row.names =FALSE)
Guardar Graficas
Código
# Exportar el gráfico interactivo del precio de cierre por acciónhtmlwidgets::saveWidget(fig, "grafico_precio_cierre.html")#htmlwidgets::saveWidget(fig, "grafico_precio_cierre_combinado.html")# Exportar el gráfico comparativo del volumen negociadohtmlwidgets::saveWidget(fig_bar, "grafico_volumen_negociado.html")# Exportar el gráfico interactivo del rendimiento acumuladohtmlwidgets::saveWidget(fig_rendimiento_acumulado, "grafico_rendimiento_acumulado.html")