Taller 1

Jenyfer Martínez Solarte - 2401795
Jessica Paola Mosquera Sánchez - 2508168
Karen Andrea Calvache Londoño - 2508272
Daniel Fernando Buitron Ruiz - 2502935


1. Introducción

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.

2. Diccionario de Datos (Metadata)

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 (Compuesta) Yahoo Finance
ticker Símbolo identificador del activo Texto String (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

3. Implementación del Flujo de Datos

1. CARGA DE LIBRERÍAS

library(quantmod)
library(tidyverse)
library(knitr)

2. PARÁMETROS

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"

3. DESCARGA AUTOMATIZADA (Trazabilidad)

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))
  )
})

4. TRANSFORMACIÓN: CAMBIO DE NOMBRES Y CÁLCULOS

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()

5. PROCESO DE LIMPIEZA DE CALIDAD (Auditoría de NAs y Errores)

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

5.1.—– CHECKS DE CALIDAD ——

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."

— 5.2 SECCIÓN: CONTABILIDAD DE CALIDAD —

  1. Punto de partida (Raw)
filas_iniciales <- nrow(dataset_analitico)
  1. Conteo de NAs técnicos (Los 21 días por activo + el primer lag)
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.
  1. Conteo de Errores Lógicos (Precios <= 0 o Volumen < 0)
filas_errores_logicos <- dataset_analitico %>% 
  filter(price <= 0 | volume < 0) %>% 
  nrow()
  1. Conteo de Duplicados
filas_duplicadas <- sum(duplicated(dataset_analitico[, c("ticker", "date")]))
  1. Dataset Final
filas_finales <- nrow(data_bolsa)

5.3 — TABLA DE AUDITORÍA —

## [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

5.4 Gráfico rápido de consistencia

6. BITÁCORA DE TRAZABILIDAD

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.

4. Vista previa del DATASET final

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áficos

Gráfico de la data

1. Gráfico de Retornos Diarios (Facetas)

2. Retorno Acumulado (Crecimiento de 1 USD)

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)

KPIs Básicos y Cola

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>

Distribución de Colas

Bloque para “ver” forma de retornos: dispersión, colas, outliers y qué tan lejos está la data de una normalidad ideal

Ridgelines (densidades apiladas): Ordeno estrategias por volatilidad y comparo densidades apiladas. Útil para ver asimetría y colas (especialmente a la izquierda).

Raincloud (halfeye + box + puntos): “Todo en uno”, forma (halfeye), resumen robusto (boxplot) y nube de puntos. Muy útil para comparar mediana, dispersión y outliers sin asumir normalidad.

ECDF (cola izquierda enfocada): CDF empírica para leer probabilidades directas: P(Retorno ≤ x) probabilidad de que un retorno sea inferior a un valor de referencia o mínimo X. Útil para comparar downside risk entre empresas.

Q-Q plot vs Normal: Comparo cuantiles empíricos vs cuantiles normales (en z-scores). Si se despega en colas, queda evidente la no-normalidad (colas gordas/asimetría).

Outliers y Eventos Extremos

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.

Dinámica temporal

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).

Dependencia: Correlación

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.

Bootstrap para KPIs + Incertidumbre

El Bootstrap simula miles de escenarios posibles para ver qué tan “seguro” es nuestro cálculo de retorno.(margeb)

Error bars: media con IC: Comparar retorno promedio con incertidumbre (IC 95%).

Error bars: p05 con IC: Mismo enfoque, pero ahora para riesgo de cola (percentil 5%).

Panel Final

Se arma un dashboard estático con 4 gráficos clave para llevar a slideso para una lectura rápida sin mil ventanas abiertas.