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: 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 | Variación porcentual diaria | 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)
tickers <- c('DIS', 'WBD', 'PSKY', 'FOX', 'NFLX', 'SONY', 'AMZN')
inicio <- "2021-01-01"
fin <- "2026-02-01"
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 Cálculo de Retorno Simple: \(R_t = \frac{P_t - P_{t-1}}{P_{t-1}}\)
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 = (price / lag(price)) - 1,
volatilidad = runSD(retorno, n = 21)
) %>%
ungroup()
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.0182 0.0358 -0.0135 -0.0159 ...
## $ volatilidad: num [1:8778] 0.0196 0.0346 0.0483 0.0394 0.0443 ...
## 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 definió una llave compuesta por ticker y date (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("ALERTA: En la columna ", names(nulos), "se encontraron ", nulos, " NAs.")
cat(mensaje, sep ="/n")
## ALERTA: En la columna se encontraron NAs.
Los NAs se detectaron en la primera fila de cada activo debido al cálculo del retorno
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:
Normalización: Se reemplazaron satisfactoriamente los códigos técnicos por nombres comerciales, mejorando la visualización.
Missing Controlado: Se eliminaron r nas_detectados registros correspondientes al inicio de la serie (ventana de 21 días para volatilidad). No se detectaron NAs en el cuerpo de la serie.
Unidades: Se confirmó que el 100% de los activos cotizan en Dólares (USD) en los mercados NYSE y NASDAQ.
Integridad: La llave primaria (ticker + date) es única para cada observación, eliminando el riesgo de duplicidad en los cálculos futuros.
Este es el aspecto de los datos que se utilizarán en el Taller 2.
| date | ticker | price | volume | retorno | volatilidad |
|---|---|---|---|---|---|
| 2021-02-03 | Disney | 172.37077 | 5508600 | -0.0029951 | 0.0195962 |
| 2021-02-03 | Warner Bros | 40.81000 | 7654200 | 0.0182136 | 0.0345545 |
| 2021-02-03 | Paramount | 46.28688 | 13200000 | 0.0358140 | 0.0483199 |
| 2021-02-03 | Fox | 27.02439 | 2419300 | -0.0135365 | 0.0394116 |
| 2021-02-03 | Netflix | 53.94500 | 31723000 | -0.0158896 | 0.0442794 |
| 2021-02-03 | Sony | 21.65997 | 15131000 | 0.1216244 | 0.0306843 |
| 2021-02-03 | Amazon | 165.62650 | 141776000 | -0.0199616 | 0.0190394 |
| 2021-02-04 | Disney | 176.08334 | 9035400 | 0.0215383 | 0.0201457 |
| 2021-02-04 | Warner Bros | 41.11000 | 7120900 | 0.0073511 | 0.0344161 |
| 2021-02-04 | Paramount | 46.28688 | 18711000 | 0.0000000 | 0.0484569 |
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 3.27e-4 -3.41e-4 0.0353 0.161 12.3 -0.0894 -0.0491 -3.41e-4
## 2 Paramou… 1254 -4.70e-4 -6.96e-4 0.0355 0.321 21.2 -0.0813 -0.0477 -6.96e-4
## 3 Netflix 1254 7.10e-4 4.10e-4 0.0268 -1.76 32.5 -0.0674 -0.0351 4.10e-4
## 4 Amazon 1254 5.21e-4 4.18e-4 0.0221 0.140 8.18 -0.0561 -0.0325 4.18e-4
## 5 Sony 1254 2.74e-4 -2.04e-4 0.0183 0.419 6.46 -0.0463 -0.0269 -2.04e-4
## 6 Disney 1254 -1.73e-4 -2.45e-4 0.0183 -0.0296 10.6 -0.0461 -0.0268 -2.45e-4
## 7 Fox 1254 8.33e-4 6.96e-4 0.0168 0.0695 6.61 -0.0450 -0.0251 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 42 0.0335 -0.351 -0.0351
## 2 Paramount 35 0.0279 -0.284 -0.0477
## 3 Fox 26 0.0207 -0.0962 -0.0251
## 4 Disney 25 0.0199 -0.132 -0.0268
## 5 Amazon 24 0.0191 -0.140 -0.0325
## 6 Warner Bros 24 0.0191 -0.274 -0.0491
## 7 Sony 19 0.0152 -0.0717 -0.0269
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.