Taller 1
Jenyfer Martínez Solarte - 2401795
Jessica Paola
Mosquera Sánchez - 2508168
Karen Andrea Calvache Londoño -
2508272
Daniel Fernando Buitron Ruiz - 2502935
Este reporte documenta el flujo de trabajo para la obtención, limpieza y normalización de un dataset de 7 activos financieros del sector entretenimiento y tecnología para hacer una comparativa entre ellas: Disney (DIS), Warner Bros. Discovery (WBD), Paramount (PSKY), Fox (FOX), Netflix (NFLX), Sony (SONY) y Amazon (AMZN).
Se ha seleccionado la Ruta A, abarcando un periodo desde inicios de 2021 hasta 2026 para garantizar una ventana de análisis robusta.
Antes de proceder al análisis, definimos técnicamente cada variable para asegurar la reproducibilidad y el entendimiento del dataset final.
| Variable | Descripción | Unidad / Moneda | Tipo | Llave / Nivel | Fuente |
|---|---|---|---|---|---|
| date | Fecha de cierre de la sesión bursátil | YYYY-MM-DD | Date |
Sí (Compuesta) | Yahoo Finance |
| ticker | Símbolo identificador del activo | Texto | String |
Sí (Compuesta) | Yahoo Finance |
| price | Precio de cierre ajustado (\(P_t\)) | USD | Numeric |
Observación | Yahoo Finance |
| volume | Cantidad de acciones transaccionadas | Unidades | Numeric |
Observación | Yahoo Finance |
| retorno | Retorno logarítmico diario | Decimal | Numeric |
Derivada | Cálculo Propio |
| volatilidad | Riesgo medido por Desviación Estándar (21d) | Decimal | Numeric |
Derivada | Cálculo Propio |
library(quantmod)
library(tidyverse)
library(knitr)
Se ingresan los parámetros que emplearemos para el modelo, se pueden agregar y/o cambiar con facilidad las empresas, simplemente modificando los “tickers” que cotizan en la bolsa de valores, así como los períodos de tiempo a analizar. Tomamos una ventana de 5 años
tickers <- c('DIS', 'WBD', 'PSKY', 'FOX', 'NFLX', 'SONY', 'AMZN')
inicio <- "2021-01-01"
fin <- "2026-02-01"
Utilizamos la base de datos de yahoo finance para extraer el precio y volumen diario de las acciones en cuestión (tickers).
datos_raw <- map_df(tickers, function(x) {
df_temp <- getSymbols(x, src = "yahoo", from = inicio, to = fin, auto.assign = FALSE)
data.frame(
date = index(df_temp),
ticker = x,
price = as.numeric(Ad(df_temp)),
volume = as.numeric(Vo(df_temp))
)
})
Renombramos los tickers a nombres reales de las empresas.
Realizamos el cálculo de Retorno logarítmico: \[r_t = ln \left( \frac{P_t}{P_{t-1}} \right)\] Se utilizó la fórmula de diferencia de logaritmos para garantizar que la serie de retornos sea estacionaria y aditiva en el tiempo.
dataset_analitico <- datos_raw %>%
group_by(ticker) %>%
arrange(date) %>%
mutate(
# Recodificación de nombres para mejor legibilidad
ticker = recode(ticker,
"DIS" = "Disney",
"WBD" = "Warner Bros",
"PSKY" = "Paramount",
"FOX" = "Fox",
"NFLX" = "Netflix",
"SONY" = "Sony",
"AMZN" = "Amazon"),
# Cálculo de métricas financieras
retorno = (log(price) - log(lag(price))),
volatilidad = runSD(retorno, n = 21)
) %>%
ungroup()
Se definió una llave compuesta por ticker y date (Date). Eliminamos los precios nulos y los NAs por cálculo de retorno y volatilidad. Eliminamos los precios negativos o 0. Garantizamos la llave única.
Decisión: Se optó por eliminar estos registros iniciales (drop_na) para entregar un dataset donde cada fila contenga información completa de precio, retorno y riesgo, asegurando la calidad de las inferencias en el Taller 2.”
Tras la limpieza, el dataset cuenta con 0 valores nulos.
nas_detectados <- sum(is.na(dataset_analitico$volatilidad))
data_bolsa <- dataset_analitico %>%
drop_na(price, retorno, volatilidad) %>% # 1. Eliminar precios nulos y NAs por cálculo
filter(price > 0) %>% # 2. Solo precios reales
distinct(ticker, date, .keep_all = TRUE) # 3. Garantizar llave única
Check 1: Ver el tipo de dato de cada columna
print(str(data_bolsa))
## tibble [8,778 × 6] (S3: tbl_df/tbl/data.frame)
## $ date : Date[1:8778], format: "2021-02-03" "2021-02-03" ...
## $ ticker : chr [1:8778] "Disney" "Warner Bros" "Paramount" "Fox" ...
## $ price : num [1:8778] 172.4 40.8 46.3 27 53.9 ...
## $ volume : num [1:8778] 5508600 7654200 13200000 2419300 31723000 ...
## $ retorno : num [1:8778] -0.003 0.018 0.0352 -0.0136 -0.016 ...
## $ volatilidad: num [1:8778] 0.0194 0.0342 0.048 0.0388 0.0421 ...
## NULL
Se verificó mediante la función str() que la columna date fuera reconocida como objeto cronológico y price como numérico. Esto evita errores de cálculo en el Taller 2.
Check 2: Verificación de Llave Única
duplicados <- data_bolsa %>%
count(ticker, date) %>% # Cuenta cuántas veces aparece cada combinación
filter(n > 1) # Filtra las que aparezcan más de una vez
Resultado esperado: 0 filas
if(nrow(duplicados) == 0) {
print("Check superado: No hay duplicados en la llave (ticker + date)")
} else {
print(paste("ALERTA: Se encontraron llaves duplicadas. ","Existen ", nrow(duplicados)," filas con llave duplicada."))
}
## [1] "Check superado: No hay duplicados en la llave (ticker + date)"
Se realizó una prueba de integridad consistente en el conteo de frecuencia para la combinación ticker + date. Se confirmó que no existen registros duplicados, asegurando que cada precio corresponde a una única sesión de mercado por activo.
Check 3: ¿Hay NAs? Conteo de NAs por columna
nulos <- colSums(is.na(data_bolsa))[colSums(is.na(data_bolsa))>0]
mensaje <-paste("Sse encontraron ", nulos, " NAs.")
cat(mensaje, sep ="/n")
## Sse encontraron NAs.
Los NAs que se detectaron por los cálculos de retorno y volatilidad de cada activo se eliminaron satisfactoriamente.
Check 4: Consistencia de Moneda Verificamos los precios promedio para asegurar que están en la misma escala
data_bolsa %>%
group_by(ticker) %>%
summarise(
precio_medio = mean(price),
moneda_estimada = "USD (Verificado por Exchange: NYSE/NASDAQ)"
)
## # A tibble: 7 × 3
## ticker precio_medio moneda_estimada
## <chr> <dbl> <chr>
## 1 Amazon 165. USD (Verificado por Exchange: NYSE/NASDAQ)
## 2 Disney 116. USD (Verificado por Exchange: NYSE/NASDAQ)
## 3 Fox 36.2 USD (Verificado por Exchange: NYSE/NASDAQ)
## 4 Netflix 60.6 USD (Verificado por Exchange: NYSE/NASDAQ)
## 5 Paramount 20.5 USD (Verificado por Exchange: NYSE/NASDAQ)
## 6 Sony 20.0 USD (Verificado por Exchange: NYSE/NASDAQ)
## 7 Warner Bros 17.3 USD (Verificado por Exchange: NYSE/NASDAQ)
Moneda Única: Se verificó que todos los activos seleccionados (DIS, WBD, PSKY, FOX, NFLX, SONY, AMZN) corresponden a instrumentos listados en bolsas estadounidenses (NYSE y NASDAQ). Por lo tanto, los precios (price) están expresados uniformemente en Dólares Americanos (USD).
Unidades de Volumen: La variable volume se expresa en unidades de acciones transaccionadas por sesión.
Transformación: No se requirieron conversiones de tipo de cambio, asegurando que el cálculo del retorno sea puramente por variación de precio y no por riesgo cambiario.
Check 5: Rangos Lógicos ¿Hay precios negativos o volúmenes menores a cero?
errores_rango <- data_bolsa %>% filter(price <= 0 | volume < 0| retorno > 1 | retorno < -1)
if(nrow(errores_rango) == 0) {
print("Check superado: Todos los valores están dentro de rangos lógicos.")
} else {
print(paste("ALERTA: Existen ", nrow(errores_rango)," filas con valores negativos o precio cero."))
}
## [1] "Check superado: Todos los valores están dentro de rangos lógicos."
filas_iniciales <- nrow(dataset_analitico)
filas_perdidas_nas <- sum(is.na(dataset_analitico$volatilidad) | is.na(dataset_analitico$retorno)) # Cada ticker pierde 21 días. 21 * 7 tickers = 147 filas aprox.
filas_errores_logicos <- dataset_analitico %>%
filter(price <= 0 | volume < 0) %>%
nrow()
filas_duplicadas <- sum(duplicated(dataset_analitico[, c("ticker", "date")]))
filas_finales <- nrow(data_bolsa)
## [1] "=== BITÁCORA DE ELIMINACIÓN DE REGISTROS ==="
## Etapa Registros Estado
## 1 Datos Iniciales (Descargados) 8925 Bruto
## 2 NAs Eliminados (Cálculo Retorno/Vol) - 147 Limpieza Técnica
## 3 Errores Lógicos (Precios/Volumen <= 0) - 0 Limpieza Calidad
## 4 Duplicados Eliminados - 0 Limpieza Integridad
## 5 Dataset Final (Listo para análisis) 8778 OK
Resumen del Proceso de Calidad:
Parámetros y escalabilidad: para seleccionar y/o cambiar las empresas a analizar o los períodos, solo se modifican los parametros del modelo: la lista de tickers, fecha inicio y fin. El resto del código no se toca.
Extracción: Los datos de precio ajustado y volumen para las distintas acciones, a lo largo de la serie de tiempo señalada, fueron obtenidos mediante la API de Yahoo Finance.
Normalización: Se reemplazaron satisfactoriamente los códigos técnicos (tickers) por nombres comerciales de las empresas, con el fin de mejoranr la interpretación y visualización de los datos.
Cálculo de datos retorno y volatilidad: Se transformaron los precios absolutos en retornos para permitir la comparabilidad entre activos de distinta escala de precios.
Integridad: La llave primaria (ticker + date) es única para cada observación, eliminando el riesgo de duplicidad en los cálculos futuros.
Tipos de datos: Se verificó mediante la función str() que las columnas tuvieran los formatos adecuados de acuerdo al dato que almacenan. Esto evita errores de cálculo en el Taller 2.
Missing Controlado: Se eliminaron los registros iniciales que eran necesarios para calcular la volatilidad (ventana de 21 días de historia de cada activo). No se detectaron valores faltantes (NAs) en el resto de la serie.
Consistencia Monetaria: Todos los activos cotizan en Dólares (USD) en los mercados NYSE y NASDAQ., eliminando el riesgo de sesgo por tipo de cambio.
Rangos Lógicos: Se verificó que todos los precios sean positivos (\(P > 0\)). Los retornos máximos y mínimos se encuentran dentro de los límites históricos del mercado.
Formato Tidy: Como resultado se entrega una tabla en formato largo con 6 columnas claras. Es mucho más fácil filtrar por empresa (ticker) o por fecha (date) que tener una columna por cada compañía.
Trazabilidad: Con los parámetros es facilmente indentificar de dónde vino el dato, qué fechas se usaron y qué transformaciones se aplicaron.
Este es el aspecto de los datos que se utilizarán en el Taller 2. Una base de datos con 6 columnas (variables) definidas en el diccionario.
| date | ticker | price | volume | retorno | volatilidad |
|---|---|---|---|---|---|
| 2021-02-03 | Disney | 172.37077 | 5508600 | -0.0029995 | 0.0194466 |
| 2021-02-03 | Warner Bros | 40.81000 | 7654200 | 0.0180497 | 0.0342024 |
| 2021-02-03 | Paramount | 46.28687 | 13200000 | 0.0351875 | 0.0479991 |
| 2021-02-03 | Fox | 27.02439 | 2419300 | -0.0136291 | 0.0388152 |
| 2021-02-03 | Netflix | 53.94500 | 31723000 | -0.0160172 | 0.0421493 |
| 2021-02-03 | Sony | 21.65996 | 15131000 | 0.1147778 | 0.0294070 |
| 2021-02-03 | Amazon | 165.62650 | 141776000 | -0.0201635 | 0.0189057 |
| 2021-02-04 | Disney | 176.08334 | 9035400 | 0.0213096 | 0.0199962 |
| 2021-02-04 | Warner Bros | 41.11000 | 7120900 | 0.0073242 | 0.0340516 |
| 2021-02-04 | Paramount | 46.28687 | 18711000 | 0.0000000 | 0.0481229 |
Gráfico de la data
Para este gráfico, primero calculamos el retorno acumulado agrupando por ticker.
# 1. Calculamos el acumulado (Base 1 USD)
df_acumulado <- data_bolsa %>%
group_by(ticker) %>%
arrange(date) %>% # Ordenar cronológicamente para el cálculo
mutate(Retorno_Acumulado = cumprod(1 + retorno)) %>%
ungroup()
# 2. Gráfico comparativo único
ggplot(df_acumulado, aes(x = date, y = Retorno_Acumulado, color = ticker)) +
geom_line(size = 0.7) +
# Línea de referencia en 1 (punto de equilibrio-sin ganancias ni pérdidas)
geom_hline(yintercept = 1, linetype = "dashed", color = "black") +
# Ejes: Años y Formato Moneda
scale_x_date(date_labels = "%Y", date_breaks = "1 year") +
scale_y_continuous(labels = scales::dollar_format()) +
theme_minimal() +
labs(
title = "Crecimiento de 1 USD Invertido",
subtitle = "Comparativa de rendimiento acumulado entre activos",
x = "Año de Inversión",
y = "Valor de la Inversión (USD)",
color = "Empresa"
) +
theme(legend.position = "bottom")
Taller 2
Este código se puede editar para lograr visualizar el top N de empresas, reduciendo el ruido visual y enfocandosé en las N más volátiles.
topN <- 7
top_empresa <- data_bolsa %>%
group_by(ticker) %>%
summarise(sd_ret = sd(retorno), .groups = "drop") %>%
arrange(desc(sd_ret)) %>%
slice_head(n = topN) %>%
pull(ticker)
R_top <- data_bolsa %>% filter(ticker %in% top_empresa)
Tabla resumen por estrategia: tamaño muestral, tendencia central, volatilidad, asimetría/curtosis y percentiles clave (colas), ordenado por p05 (downside).
## # A tibble: 7 × 14
## ticker n mean med sd skew kurt p01 p05 p50
## <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Warner … 1254 -2.99e-4 -3.41e-4 0.0355 -0.454 13.3 -0.0936 -0.0503 -3.41e-4
## 2 Paramou… 1254 -1.10e-3 -6.96e-4 0.0357 -0.753 22.3 -0.0848 -0.0489 -6.96e-4
## 3 Netflix 1254 3.36e-4 4.10e-4 0.0277 -3.29 56.4 -0.0698 -0.0358 4.10e-4
## 4 Amazon 1254 2.77e-4 4.18e-4 0.0221 -0.0968 8.17 -0.0577 -0.0331 4.18e-4
## 5 Sony 1254 1.08e-4 -2.04e-4 0.0182 0.279 6.10 -0.0474 -0.0273 -2.04e-4
## 6 Disney 1254 -3.41e-4 -2.45e-4 0.0183 -0.294 10.8 -0.0472 -0.0271 -2.45e-4
## 7 Fox 1254 6.92e-4 6.96e-4 0.0168 -0.0725 6.69 -0.0460 -0.0254 6.96e-4
## # ℹ 4 more variables: p95 <dbl>, p99 <dbl>, min <dbl>, max <dbl>
Bloque para “ver” forma de retornos: dispersión, colas, outliers y qué tan lejos está la data de una normalidad ideal
Aquí se enfoca en extremos: qué tan frecuentes son los eventos raros y qué tan malos han sido (tail risk / event risk).
Outliers robustos por empresa usando MAD: MAD = Median Absolute Deviation (Desviación Absoluta Mediana). Se calcula como: MAD = mediana(|retorno - mediana(retorno)|). Es una medida de dispersión.
ROBUSTA: no se deja sesgar por outliers como sí pasa con la desviación estándar. Aquí se estima un “z-score robusto”: robust_z = (retorno - mediana)/MAD, y se marca como outlier los puntos con |robust_z| > 5. Luego, se resume cuántos outliers hay, su tasa, y dos referencias de cola: el peor retorno (min) y el percentil 5% (worst_5).
## # A tibble: 7 × 5
## ticker outliers out_rate worst_1 worst_5
## <chr> <int> <dbl> <dbl> <dbl>
## 1 Netflix 40 0.0319 -0.433 -0.0358
## 2 Paramount 34 0.0271 -0.333 -0.0489
## 3 Fox 27 0.0215 -0.101 -0.0254
## 4 Warner Bros 27 0.0215 -0.321 -0.0503
## 5 Disney 25 0.0199 -0.141 -0.0271
## 6 Amazon 24 0.0191 -0.151 -0.0331
## 7 Sony 17 0.0136 -0.0744 -0.0273
Peores 10 retornos por estrategia Se sacan los 10 días más malos por empresa para visualizar event risk.
Vol rolling, drawdowns: Ahora se mira el tiempo: volatilidad en ventanas móviles, crisis vs calma,y drawdowns (caídas máximas) como lectura clásica financiera.
Volatilidad rolling (12 meses): Se selecciona 3 empresas (fijas) para que el gráfico sea reproducible. Se calcula SD móvil de 12 meses para ver cambios de régimen y clustering.
Drawdowns (EDA financiero clásico): Este panel muestra el rendimiento acumulado , drawdowns y riesgo (las peores rachas de pérdidas).
Co-movimiento entre estrategias. Primera lectura de diversificación: quién se mueve con quién (linealmente). Si la correlación es alta (cercana a 1), no hay diversificación; si una cae, la otra probablemente también.
El Bootstrap simula miles de escenarios posibles para ver qué tan “seguro” es nuestro cálculo de retorno.(margeb)
Se arma un dashboard estático con 4 gráficos clave para llevar a slideso para una lectura rápida sin mil ventanas abiertas.