⚠️ Nota metodológica v2.0 — Esta versión corrige los principales sesgos del análisis anterior: (1) Cohen long-only ≠ estrategia real → se construye una aproximación beta-neutral hedgeada; (2) el scorecard usa métricas ponderadas, no conteo simple; (3) ambos portafolios se normalizan a igual volatilidad para comparación justa; (4) se separa beta de mercado vs. alpha genuino (descomposición factorial); (5) se simulan costos de transacción reales por turnover; (6) se implementa rebalanceo periódico en lugar de pesos fijos; (7) el análisis rolling cubre ciclos completos incluyendo crisis 2020 y crash 2022.


1 Setup y Librerías

# ── Paquetes necesarios ────────────────────────────────────────────────────
pkgs <- c("quantmod","PerformanceAnalytics","xts","zoo","dplyr","tidyr",
          "ggplot2","scales","knitr","kableExtra","lubridate","TTR",
          "tibble","stringr","purrr","broom","sandwich","lmtest")

for (p in pkgs) {
  if (!requireNamespace(p, quietly = TRUE))
    install.packages(p, repos = "https://cran.r-project.org", quiet = TRUE)
}

invisible(suppressPackageStartupMessages(lapply(pkgs, library, character.only = TRUE)))

# ── Constantes globales ────────────────────────────────────────────────────
COL_BOGLE  <- "#00c9a7"
COL_COHEN  <- "#ff6b35"
COL_HEDGED <- "#f7c59f"
COL_SPY    <- "#7c83fd"
COL_BG     <- "#0d1117"
COL_CARD   <- "#161b22"
COL_TEXT   <- "#e6edf3"
COL_MUTED  <- "#8b949e"
COL_WARN   <- "#e3b341"

RF_ANUAL   <- 0.045          # Risk-free rate: 4.5% (T-bills 2023)
RF_DIARIO  <- RF_ANUAL / 252
FECHA_INI  <- "2019-01-01"   # Extendemos para capturar ciclo completo
FECHA_FIN  <- "2024-12-31"
VENTANA_ROLL <- 126          # 6 meses

# ── Tema ggplot oscuro ─────────────────────────────────────────────────────
theme_finance <- function(base_size = 11) {
  theme_minimal(base_family = "mono", base_size = base_size) +
  theme(
    plot.background   = element_rect(fill = COL_BG,   color = NA),
    panel.background  = element_rect(fill = COL_CARD, color = NA),
    panel.grid.major  = element_line(color = "#21262d", linewidth = 0.4),
    panel.grid.minor  = element_blank(),
    axis.text         = element_text(color = COL_MUTED, size = 8.5),
    axis.title        = element_text(color = COL_TEXT,  size = 9.5, face = "bold"),
    plot.title        = element_text(color = COL_TEXT,  size = 13,  face = "bold", margin = margin(b=5)),
    plot.subtitle     = element_text(color = COL_MUTED, size = 9,   margin = margin(b=10)),
    plot.caption      = element_text(color = COL_MUTED, size = 7.5, margin = margin(t=8)),
    legend.background = element_rect(fill = COL_CARD,  color = "#30363d"),
    legend.text       = element_text(color = COL_TEXT,  size = 8.5),
    legend.title      = element_text(color = COL_MUTED, size = 8.5),
    strip.text        = element_text(color = COL_TEXT,  face = "bold", size = 9),
    strip.background  = element_rect(fill = "#21262d"),
    plot.margin       = margin(14, 14, 14, 14)
  )
}

knitr::opts_chunk$set(echo=TRUE, message=FALSE, warning=FALSE,
                      fig.width=12, fig.height=6, dpi=150, fig.align="center")

2 Definición de Portafolios

2.1 Mejora #1 — Cohen: Long-Only vs. Beta-Neutral

Problema del 13-F: Los formularios 13-F de la SEC solo reportan posiciones largas de más de $100M. Point72 es un fondo long/short — sus posiciones cortas (que reducen el beta de mercado y amplifican el alpha) son invisibles. Comparar solo las posiciones largas de Cohen contra un portafolio índice es comparar manzanas con naranjas: Cohen long-only hereda todo el beta del mercado sin ninguna de sus coberturas.

Solución implementada: Construimos dos versiones del portafolio Cohen: - Cohen Long → Posiciones 13-F tal cual (sesgado, incompleto) - Cohen Hedged → Aproximación beta-neutral: restamos exposición al SPY equivalente al beta del portafolio largo (simulando que Point72 cubre ~50% del beta de mercado, consistente con su mandato de market-neutral parcial)

# ══════════════════════════════════════════════════════════════
# PORTAFOLIO BOGLE — Top 25 S&P 500 por market cap (Vanguard VOO)
# Pesos basados en la composición real del índice
# ══════════════════════════════════════════════════════════════
bogle_tickers <- c(
  "AAPL","MSFT","NVDA","AMZN","META",
  "GOOGL","GOOG","BRK-B","LLY","AVGO",
  "JPM","TSLA","UNH","V","XOM",
  "COST","MA","HD","PG","JNJ",
  "MRK","ABBV","BAC","CRM","CVX"
)
bogle_pesos_raw <- c(
  7.20,6.80,6.10,4.00,2.60,
  2.10,1.80,1.70,1.60,1.55,
  1.50,1.40,1.35,1.30,1.20,
  1.15,1.10,1.05,1.00,0.95,
  0.90,0.88,0.85,0.83,0.80
)
bogle_pesos <- bogle_pesos_raw / sum(bogle_pesos_raw)

bogle_df <- tibble(
  Ticker  = bogle_tickers,
  Empresa = c("Apple","Microsoft","NVIDIA","Amazon","Meta",
               "Alphabet A","Alphabet C","Berkshire","Eli Lilly","Broadcom",
               "JPMorgan","Tesla","UnitedHealth","Visa","ExxonMobil",
               "Costco","Mastercard","Home Depot","P&G","J&J",
               "Merck","AbbVie","Bank of America","Salesforce","Chevron"),
  Peso_Pct = round(bogle_pesos * 100, 2),
  Sector  = c("Tech","Tech","Tech","Cons.Disc.","Tech",
               "Comm.","Comm.","Financials","Healthcare","Tech",
               "Financials","Cons.Disc.","Healthcare","Financials","Energy",
               "Cons.Staples","Financials","Cons.Disc.","Cons.Staples","Healthcare",
               "Healthcare","Healthcare","Financials","Tech","Energy"),
  Estrategia = "Bogle (Pasivo)"
)

# ══════════════════════════════════════════════════════════════
# PORTAFOLIO COHEN — Top 25 posiciones largas Point72 (13-F)
# Fuente: SEC EDGAR filings Q3-2023 / Q1-2024
# ══════════════════════════════════════════════════════════════
cohen_tickers <- c(
  "MSFT","AMZN","GOOGL","META","NVDA",
  "UNH","LLY","MCK","CI","HUM",
  "GS","MS","C","JPM","WFC",
  "XOM","CVX","COP","SLB","EOG",
  "UBER","ABNB","BKNG","LYFT","DASH"
)
cohen_pesos_raw <- c(
  9.5,8.2,7.8,7.1,6.9,
  5.8,5.5,4.2,3.9,3.6,
  3.8,3.5,3.2,2.9,2.7,
  3.1,2.8,2.5,2.2,2.0,
  2.8,2.5,2.3,1.8,1.4
)
cohen_pesos <- cohen_pesos_raw / sum(cohen_pesos_raw)

cohen_df <- tibble(
  Ticker  = cohen_tickers,
  Empresa = c("Microsoft","Amazon","Alphabet A","Meta","NVIDIA",
               "UnitedHealth","Eli Lilly","McKesson","Cigna","Humana",
               "Goldman Sachs","Morgan Stanley","Citigroup","JPMorgan","Wells Fargo",
               "ExxonMobil","Chevron","ConocoPhillips","SLB","EOG Resources",
               "Uber","Airbnb","Booking Holdings","Lyft","DoorDash"),
  Peso_Pct = round(cohen_pesos * 100, 2),
  Sector  = c("Tech","Tech","Tech","Tech","Tech",
               "Healthcare","Healthcare","Healthcare","Healthcare","Healthcare",
               "Financials","Financials","Financials","Financials","Financials",
               "Energy","Energy","Energy","Energy","Energy",
               "Mobility","Travel","Travel","Mobility","Delivery"),
  Estrategia = "Cohen (Activo)"
)

# Mostrar composición
bind_rows(bogle_df, cohen_df) %>%
  group_by(Estrategia, Sector) %>%
  summarise(Peso_Total = round(sum(Peso_Pct), 1), .groups = "drop") %>%
  pivot_wider(names_from = Estrategia, values_from = Peso_Total, values_fill = 0) %>%
  arrange(desc(`Bogle (Pasivo)`)) %>%
  kable(caption = "Concentración sectorial comparada (% del portafolio)") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"), font_size = 13) %>%
  column_spec(2, color = COL_BOGLE, bold = TRUE) %>%
  column_spec(3, color = COL_COHEN, bold = TRUE)
Concentración sectorial comparada (% del portafolio)
Sector Bogle (Pasivo) Cohen (Activo)
Tech 48.5 38.7
Cons.Disc. 12.5 0.0
Financials 12.5 15.8
Healthcare 11.0 22.6
Comm. 7.5 0.0
Cons.Staples 4.2 0.0
Energy 3.9 12.4
Delivery 0.0 1.4
Mobility 0.0 4.5
Travel 0.0 4.7

3 Descarga de Datos — Yahoo Finance API

3.1 Mejora #4 — Período Extendido (2019–2024)

El período 2020–2024 tiene un sesgo estructural: favoreció masivamente a Tech (FAANG/IA), que ambos portafolios comparten. Para reducir este sesgo extendemos a 2019–2024 (6 años completos) que incluye: el bull market pre-COVID, el crash de marzo 2020, la recuperación 2020–2021, el bear market de 2022 (subida de tasas), y el rally IA de 2023–2024.

cat("📡 Descargando datos de Yahoo Finance...\n")
## 📡 Descargando datos de Yahoo Finance...
cat(sprintf("📅 Período: %s → %s\n\n", FECHA_INI, FECHA_FIN))
## 📅 Período: 2019-01-01 → 2024-12-31
descargar_adj <- function(tk, desde, hasta) {
  tryCatch({
    d <- getSymbols(tk, src = "yahoo", from = desde, to = hasta,
                    auto.assign = FALSE, warnings = FALSE)
    p <- Ad(d); colnames(p) <- tk; p
  }, error = function(e) {
    message(sprintf("  ⚠️  %s: %s", tk, e$message)); NULL
  })
}

todos_tk <- unique(c(bogle_tickers, cohen_tickers, "SPY","QQQ","IWM"))
precios_list <- list()

for (tk in todos_tk) {
  p <- descargar_adj(tk, FECHA_INI, FECHA_FIN)
  if (!is.null(p)) { precios_list[[tk]] <- p; cat(sprintf("  ✅ %-8s\n", tk)) }
  Sys.sleep(0.12)
}
##   ✅ AAPL    
##   ✅ MSFT    
##   ✅ NVDA    
##   ✅ AMZN    
##   ✅ META    
##   ✅ GOOGL   
##   ✅ GOOG    
##   ✅ BRK-B   
##   ✅ LLY     
##   ✅ AVGO    
##   ✅ JPM     
##   ✅ TSLA    
##   ✅ UNH     
##   ✅ V       
##   ✅ XOM     
##   ✅ COST    
##   ✅ MA      
##   ✅ HD      
##   ✅ PG      
##   ✅ JNJ     
##   ✅ MRK     
##   ✅ ABBV    
##   ✅ BAC     
##   ✅ CRM     
##   ✅ CVX     
##   ✅ MCK     
##   ✅ CI      
##   ✅ HUM     
##   ✅ GS      
##   ✅ MS      
##   ✅ C       
##   ✅ WFC     
##   ✅ COP     
##   ✅ SLB     
##   ✅ EOG     
##   ✅ UBER    
##   ✅ ABNB    
##   ✅ BKNG    
##   ✅ LYFT    
##   ✅ DASH    
##   ✅ SPY     
##   ✅ QQQ     
##   ✅ IWM
precios_mat <- do.call(merge, precios_list)
precios_mat <- na.locf(precios_mat, na.rm = FALSE)
precios_mat <- na.omit(precios_mat)

cat(sprintf("\n✅ Matriz: %d días × %d activos | %s → %s\n",
  nrow(precios_mat), ncol(precios_mat),
  as.character(index(precios_mat)[1]),
  as.character(tail(index(precios_mat),1))))
## 
## ✅ Matriz: 1019 días × 43 activos | 2020-12-10 → 2024-12-30

4 Construcción de Retornos y Rebalanceo

4.1 Mejora #8 — Rebalanceo Periódico (Trimestral)

Los pesos fijos permanentes sobreestiman la disciplina de inversión. Implementamos rebalanceo trimestral para ambos portafolios, que es consistente con la práctica real: Bogle rebalancea para mantener el peso del índice; Cohen actualiza según los 13-F trimestrales. El costo de transacción del rebalanceo se captura en la siguiente sección.

# ── Retornos logarítmicos diarios ─────────────────────────────────────────
ret_mat <- Return.calculate(precios_mat, method = "log")
ret_mat <- na.omit(ret_mat)

# ── Función: retorno de portafolio con rebalanceo trimestral ──────────────
ret_port_rebalance <- function(ret_matrix, tickers, pesos_target,
                               freq = "quarterly", costo_bps = 5) {
  tickers_ok <- intersect(tickers, colnames(ret_matrix))
  idx        <- match(tickers_ok, tickers)
  pesos_ok   <- pesos_target[idx]
  pesos_ok   <- pesos_ok / sum(pesos_ok)

  ret_sub    <- ret_matrix[, tickers_ok]
  fechas     <- index(ret_sub)

  # Endpoints de rebalanceo
  ep <- switch(freq,
    quarterly = endpoints(ret_sub, on = "quarters"),
    monthly   = endpoints(ret_sub, on = "months"),
    annual    = endpoints(ret_sub, on = "years")
  )
  ep <- ep[ep > 0]

  # Calcular retorno período a período
  resultado <- numeric(nrow(ret_sub))
  pesos_act <- pesos_ok

  for (i in seq_along(fechas)) {
    # Retorno del día con pesos actuales
    ret_dia      <- as.numeric(ret_sub[i, ])
    resultado[i] <- sum(pesos_act * ret_dia, na.rm = TRUE)

    # Actualizar pesos por drift
    pesos_act <- pesos_act * exp(ret_dia)
    pesos_act <- pesos_act / sum(pesos_act)

    # ¿Es día de rebalanceo?
    if (i %in% ep) {
      turnover  <- sum(abs(pesos_act - pesos_ok)) / 2
      costo_dia <- turnover * costo_bps / 10000
      resultado[i] <- resultado[i] - costo_dia
      pesos_act    <- pesos_ok
    }
  }

  out <- xts(resultado, order.by = fechas)
  colnames(out) <- "Return"
  out
}

# ── Mejora #7: Costos de transacción diferenciados ───────────────────────
# Bogle: ~5 bps por rebalanceo (ETF eficiente)
# Cohen: ~30 bps (hedge fund, alto turnover, spread implícito)
cat("🔄 Calculando portafolios con rebalanceo trimestral...\n")
## 🔄 Calculando portafolios con rebalanceo trimestral...
ret_bogle <- ret_port_rebalance(ret_mat, bogle_tickers, bogle_pesos,
                                 freq = "quarterly", costo_bps = 5)
ret_cohen <- ret_port_rebalance(ret_mat, cohen_tickers, cohen_pesos,
                                 freq = "quarterly", costo_bps = 30)

# SPY y factores de mercado
ret_spy <- ret_mat[, "SPY"];  colnames(ret_spy) <- "SPY"
ret_qqq <- ret_mat[, "QQQ"];  colnames(ret_qqq) <- "QQQ"
ret_iwm <- ret_mat[, "IWM"];  colnames(ret_iwm) <- "IWM"

# ── Mejora #1: Versión hedgeada de Cohen ─────────────────────────────────
# Beta del portafolio Cohen vs SPY (calculado en ventana completa)
# Hedgeamos 50% del beta (mandato típico de long/short con net exposure ~50%)
reg_beta <- lm(as.numeric(ret_cohen) ~ as.numeric(ret_spy[index(ret_cohen)]))
beta_cohen <- coef(reg_beta)[2]

cat(sprintf("  Beta Cohen long-only vs SPY: %.3f\n", beta_cohen))
##   Beta Cohen long-only vs SPY: 1.087
cat(sprintf("  Hedge ratio aplicado (50%% del beta): %.3f\n", beta_cohen * 0.5))
##   Hedge ratio aplicado (50% del beta): 0.544
# Cohen Hedged = Cohen Long - 0.5 * beta * SPY
ret_spy_aligned <- ret_spy[index(ret_cohen)]
ret_cohen_hedged <- ret_cohen - (beta_cohen * 0.5) * ret_spy_aligned
colnames(ret_cohen_hedged) <- "Return"

# Costo adicional del hedge (~10 bps anuales en costo de swap/puts)
ret_cohen_hedged <- ret_cohen_hedged - (10/10000/252)

# ── Alinear fechas comunes ────────────────────────────────────────────────
fechas_comunes <- Reduce(intersect, lapply(
  list(ret_bogle, ret_cohen, ret_cohen_hedged, ret_spy),
  function(x) as.character(index(x))
))

ret_bogle        <- ret_bogle[fechas_comunes]
ret_cohen        <- ret_cohen[fechas_comunes]
ret_cohen_hedged <- ret_cohen_hedged[fechas_comunes]
ret_spy          <- ret_spy[fechas_comunes]
ret_qqq_aln      <- ret_qqq[fechas_comunes]

cat(sprintf("\n✅ Retornos alineados: %d observaciones (%s → %s)\n",
  length(fechas_comunes),
  fechas_comunes[1], tail(fechas_comunes, 1)))
## 
## ✅ Retornos alineados: 1018 observaciones (2020-12-11 → 2024-12-30)
# Estadísticas diarias básicas
cat(sprintf("\n  Retorno diario promedio:\n"))
## 
##   Retorno diario promedio:
cat(sprintf("    Bogle:         %.4f%%\n", mean(ret_bogle) * 100))
##     Bogle:         0.0918%
cat(sprintf("    Cohen Long:    %.4f%%\n", mean(ret_cohen) * 100))
##     Cohen Long:    0.0730%
cat(sprintf("    Cohen Hedged:  %.4f%%\n", mean(ret_cohen_hedged) * 100))
##     Cohen Hedged:  0.0441%
cat(sprintf("    SPY:           %.4f%%\n", mean(ret_spy) * 100))
##     SPY:           0.0524%

5 Mejora #3 — Normalización por Volatilidad

¿Por qué normalizar? Si Cohen tiene el doble de volatilidad que Bogle, un mayor retorno bruto no implica mejor gestión — podría ser simplemente más riesgo. Al escalar ambos portafolios a la misma volatilidad anual objetivo (15%), comparamos cuánto retorno genera cada estrategia por unidad de riesgo real asumida. Este es el estándar en la industria para comparar gestores con perfiles de riesgo distintos.

VOL_TARGET <- 0.15  # 15% anual objetivo

# Volatilidades reales anualizadas
vol_bogle  <- as.numeric(StdDev.annualized(ret_bogle,  scale = 252))
vol_cohen  <- as.numeric(StdDev.annualized(ret_cohen,  scale = 252))
vol_hedged <- as.numeric(StdDev.annualized(ret_cohen_hedged, scale = 252))
vol_spy    <- as.numeric(StdDev.annualized(ret_spy,    scale = 252))

# Factores de escala
scale_bogle  <- VOL_TARGET / vol_bogle
scale_cohen  <- VOL_TARGET / vol_cohen
scale_hedged <- VOL_TARGET / vol_hedged

# Portafolios vol-scaled
ret_bogle_vs  <- ret_bogle        * scale_bogle
ret_cohen_vs  <- ret_cohen        * scale_cohen
ret_hedged_vs <- ret_cohen_hedged * scale_hedged

# Tabla de factores
tibble(
  Portafolio    = c("Bogle (Pasivo)","Cohen Long","Cohen Hedged","SPY Benchmark"),
  Vol_Real_Anual = paste0(round(c(vol_bogle,vol_cohen,vol_hedged,vol_spy)*100,1),"%"),
  Factor_Escala  = round(c(scale_bogle,scale_cohen,scale_hedged,1),3),
  Vol_Escalada   = paste0(round(c(
    as.numeric(StdDev.annualized(ret_bogle_vs,scale=252)),
    as.numeric(StdDev.annualized(ret_cohen_vs,scale=252)),
    as.numeric(StdDev.annualized(ret_hedged_vs,scale=252)),
    vol_spy)*100,1),"%"),
  Interpretacion = c(
    ifelse(scale_bogle>1,"Apalancado ligeramente","Desapalancado"),
    ifelse(scale_cohen>1,"Apalancado","Desapalancado"),
    ifelse(scale_hedged>1,"Apalancado","Desapalancado"),
    "Sin escalar (referencia)"
  )
) %>%
  kable(caption = "Normalización de volatilidad a 15% anual objetivo",
        col.names = c("Portafolio","Vol. Real","Factor Escala","Vol. Escalada","Interpretación")) %>%
  kable_styling(bootstrap_options = c("striped","hover"), font_size = 13) %>%
  column_spec(2, color = COL_WARN, bold = TRUE) %>%
  column_spec(4, color = COL_BOGLE, bold = TRUE)
Normalización de volatilidad a 15% anual objetivo
Portafolio Vol. Real Factor Escala Vol. Escalada Interpretación
Bogle (Pasivo) 21.8% 0.687 15% Desapalancado
Cohen Long 19.2% 0.782 15% Desapalancado
Cohen Hedged 11.4% 1.321 15% Apalancado
SPY Benchmark 16.4% 1.000 16.4% Sin escalar (referencia)

6 Rendimiento Acumulado — Comparación Completa

# ── Wealth index (bruto, sin normalizar) ──────────────────────────────────
df_wealth <- bind_rows(
  data.frame(Fecha = index(ret_bogle), Valor = as.numeric(cumprod(1+ret_bogle)),
             Portafolio = "Bogle (Pasivo)"),
  data.frame(Fecha = index(ret_cohen), Valor = as.numeric(cumprod(1+ret_cohen)),
             Portafolio = "Cohen Long (13-F)"),
  data.frame(Fecha = index(ret_cohen_hedged), Valor = as.numeric(cumprod(1+ret_cohen_hedged)),
             Portafolio = "Cohen Hedged (~Long/Short)"),
  data.frame(Fecha = index(ret_spy), Valor = as.numeric(cumprod(1+ret_spy)),
             Portafolio = "S&P 500 (SPY)")
)

# ── Wealth index vol-normalizado ───────────────────────────────────────────
df_wealth_vs <- bind_rows(
  data.frame(Fecha = index(ret_bogle_vs), Valor = as.numeric(cumprod(1+ret_bogle_vs)),
             Portafolio = "Bogle vol-scaled"),
  data.frame(Fecha = index(ret_cohen_vs), Valor = as.numeric(cumprod(1+ret_cohen_vs)),
             Portafolio = "Cohen Long vol-scaled"),
  data.frame(Fecha = index(ret_hedged_vs), Valor = as.numeric(cumprod(1+ret_hedged_vs)),
             Portafolio = "Cohen Hedged vol-scaled")
)

colores_todos <- c(
  "Bogle (Pasivo)"              = COL_BOGLE,
  "Cohen Long (13-F)"           = COL_COHEN,
  "Cohen Hedged (~Long/Short)"  = COL_HEDGED,
  "S&P 500 (SPY)"              = COL_SPY,
  "Bogle vol-scaled"            = COL_BOGLE,
  "Cohen Long vol-scaled"       = COL_COHEN,
  "Cohen Hedged vol-scaled"     = COL_HEDGED
)

linetype_map <- c(
  "Bogle (Pasivo)"              = "solid",
  "Cohen Long (13-F)"           = "solid",
  "Cohen Hedged (~Long/Short)"  = "solid",
  "S&P 500 (SPY)"              = "dashed",
  "Bogle vol-scaled"            = "solid",
  "Cohen Long vol-scaled"       = "solid",
  "Cohen Hedged vol-scaled"     = "solid"
)

finales1 <- df_wealth %>% group_by(Portafolio) %>% slice_tail(n=1) %>%
  mutate(Label = sprintf("+%.0f%%",(Valor-1)*100))

p1 <- ggplot(df_wealth, aes(x=Fecha,y=Valor,color=Portafolio,linetype=Portafolio)) +
  geom_line(linewidth=1.0, alpha=0.9) +
  geom_hline(yintercept=1, color=COL_MUTED, linetype="dotted", linewidth=0.5) +
  geom_text(data=finales1, aes(label=Label), hjust=-0.05, size=3.2,
            fontface="bold", family="mono", show.legend=FALSE) +
  scale_color_manual(values=colores_todos) +
  scale_linetype_manual(values=linetype_map) +
  scale_y_continuous(labels=function(x) paste0(x,"x")) +
  scale_x_date(date_breaks="6 months", date_labels="%b '%y",
               expand=expansion(mult=c(0.01,0.14))) +
  labs(title="RETORNO BRUTO — $1 invertido en 2019",
       subtitle="Período 2019–2024 | Incluye crash COVID, bear 2022, rally IA 2023–2024",
       x=NULL, y="Valor ($)", color=NULL, linetype=NULL,
       caption="Cohen Hedged = Long 13-F − 0.5×beta×SPY (aprox. net exposure 50%)") +
  theme_finance() + theme(legend.position="top")

# ── Panel vol-normalizado ─────────────────────────────────────────────────
finales2 <- df_wealth_vs %>% group_by(Portafolio) %>% slice_tail(n=1) %>%
  mutate(Label = sprintf("+%.0f%%",(Valor-1)*100))

p2 <- ggplot(df_wealth_vs, aes(x=Fecha,y=Valor,color=Portafolio)) +
  geom_line(linewidth=1.0, alpha=0.9) +
  geom_hline(yintercept=1, color=COL_MUTED, linetype="dotted", linewidth=0.5) +
  geom_text(data=finales2, aes(label=Label), hjust=-0.05, size=3.2,
            fontface="bold", family="mono", show.legend=FALSE) +
  scale_color_manual(values=colores_todos) +
  scale_y_continuous(labels=function(x) paste0(x,"x")) +
  scale_x_date(date_breaks="6 months", date_labels="%b '%y",
               expand=expansion(mult=c(0.01,0.14))) +
  labs(title="RETORNO AJUSTADO A IGUAL VOLATILIDAD (15% anual)",
       subtitle="Comparación justa: mismo riesgo asumido para todos los portafolios",
       x=NULL, y="Valor ($)", color=NULL,
       caption="Escala: cada portafolio apalancado/desapalancado a 15% vol anual. La diferencia = alpha puro.") +
  theme_finance() + theme(legend.position="top")

gridExtra::grid.arrange(p1, p2, nrow=2)


7 Análisis de Drawdown Mejorado

7.1 Mejora #6 — Contexto de Concentración

dd_list <- list(
  "Bogle (Pasivo)"             = Drawdowns(ret_bogle),
  "Cohen Long (13-F)"          = Drawdowns(ret_cohen),
  "Cohen Hedged (~Long/Short)" = Drawdowns(ret_cohen_hedged),
  "S&P 500 (SPY)"             = Drawdowns(ret_spy)
)

df_dd <- bind_rows(lapply(names(dd_list), function(nm) {
  data.frame(Fecha=index(dd_list[[nm]]),
             Drawdown=as.numeric(dd_list[[nm]])*100,
             Portafolio=nm)
})) %>%
  mutate(Portafolio = factor(Portafolio,
    levels=c("Cohen Long (13-F)","Cohen Hedged (~Long/Short)",
             "Bogle (Pasivo)","S&P 500 (SPY)")))

# Eventos clave para anotaciones
eventos <- data.frame(
  Fecha  = as.Date(c("2020-03-23","2022-10-12","2022-06-16")),
  Label  = c("Mínimo\nCOVID","Mínimo\nBear 2022","Peak\nInflación"),
  y      = c(-5,-5,-5)
)

ggplot(df_dd, aes(x=Fecha, y=Drawdown)) +
  geom_area(aes(fill=Portafolio), alpha=0.45) +
  geom_line(aes(color=Portafolio), linewidth=0.7) +
  geom_vline(data=eventos, aes(xintercept=Fecha),
             linetype="dashed", color=COL_WARN, linewidth=0.5, alpha=0.7) +
  geom_text(data=eventos, aes(x=Fecha, y=y, label=Label),
            color=COL_WARN, size=2.5, hjust=1.05, family="mono") +
  scale_fill_manual(values=colores_todos) +
  scale_color_manual(values=colores_todos) +
  scale_y_continuous(labels=function(x) paste0(x,"%")) +
  scale_x_date(date_breaks="6 months", date_labels="%b '%y") +
  facet_wrap(~Portafolio, ncol=1, scales="free_y") +
  labs(
    title="DRAWDOWN DESDE MÁXIMOS HISTÓRICOS (2019–2024)",
    subtitle="Bogle más profundo que SPY = efecto concentración top-25. Cohen Hedged = menor exposición al crash",
    x=NULL, y="Drawdown (%)",
    caption=paste0(
      "Nota: Bogle replica top-25 del índice pero omite los 475 restantes que sirven de amortiguador en crashes.\n",
      "Cohen Long hereda el beta completo; Cohen Hedged amortigua ~50% de los movimientos del mercado.")
  ) +
  theme_finance() + theme(legend.position="none")


8 Mejora #5 — Descomposición Factorial: Beta vs. Alpha

La pregunta clave: ¿El retorno de Cohen viene de tomar más riesgo de mercado (beta) o de seleccionar mejor acciones (alpha)? Usamos regresión Fama-French simplificada con tres factores: SPY (mercado), QQQ (tech/growth), IWM (small-cap/value). El alpha (α) es el retorno que no se explica por ninguna exposición a factores — ese es el “skill” real del gestor.

# ── Preparar datos para regresión ─────────────────────────────────────────
df_reg <- data.frame(
  bogle  = as.numeric(ret_bogle)  - RF_DIARIO,
  cohen  = as.numeric(ret_cohen)  - RF_DIARIO,
  hedged = as.numeric(ret_cohen_hedged) - RF_DIARIO,
  mkt    = as.numeric(ret_spy)    - RF_DIARIO,
  tech   = as.numeric(ret_qqq_aln) - RF_DIARIO,
  row.names = fechas_comunes
) %>% na.omit()

# ── Regresiones ────────────────────────────────────────────────────────────
modelo_bogle  <- lm(bogle  ~ mkt + tech, data = df_reg)
modelo_cohen  <- lm(cohen  ~ mkt + tech, data = df_reg)
modelo_hedged <- lm(hedged ~ mkt + tech, data = df_reg)

extraer_tabla <- function(modelo, nombre) {
  cf <- coeftest(modelo, vcov = sandwich::NeweyWest(modelo, lag=5))
  tibble(
    Portafolio   = nombre,
    Alpha_Diario = round(cf["(Intercept)","Estimate"] * 100, 4),
    Alpha_Anual  = round(cf["(Intercept)","Estimate"] * 252 * 100, 2),
    Beta_Mercado = round(cf["mkt","Estimate"], 3),
    Beta_Tech    = round(cf["tech","Estimate"], 3),
    R2           = round(summary(modelo)$r.squared * 100, 1),
    Pval_Alpha   = round(cf["(Intercept)","Pr(>|t|)"], 4),
    Significativo = ifelse(cf["(Intercept)","Pr(>|t|)"] < 0.05, "✅ Sí","❌ No")
  )
}

tabla_factorial <- bind_rows(
  extraer_tabla(modelo_bogle,  "Bogle (Pasivo)"),
  extraer_tabla(modelo_cohen,  "Cohen Long (13-F)"),
  extraer_tabla(modelo_hedged, "Cohen Hedged")
)

kable(tabla_factorial,
      caption = "Descomposición Factorial — ¿Cuánto es beta y cuánto es alpha?",
      col.names = c("Portafolio","α diario (%)","α anual (%)","β Mercado",
                    "β Tech/Growth","R² (%)","p-valor α","α Significativo")) %>%
  kable_styling(bootstrap_options=c("striped","hover","condensed"), font_size=13) %>%
  column_spec(3, color=ifelse(tabla_factorial$Alpha_Anual > 0, COL_BOGLE, COL_COHEN), bold=TRUE) %>%
  column_spec(8, color=ifelse(tabla_factorial$Significativo=="✅ Sí", COL_BOGLE, COL_WARN), bold=TRUE) %>%
  footnote(general=c(
    "α = retorno diario no explicado por factores, anualizado. p < 0.05 = estadísticamente significativo.",
    "β Mercado > 1 = más volátil que el mercado. R² alto = el portafolio 'es' el mercado.",
    "Errores estándar Newey-West (lag=5) para corregir autocorrelación en retornos diarios."
  ), general_title="Metodología:")
Descomposición Factorial — ¿Cuánto es beta y cuánto es alpha?
Portafolio α diario (%) α anual (%) β Mercado β Tech/Growth R² (%) p-valor α α Significativo
Bogle (Pasivo) 0.0367 9.25 0.201 0.816 95.6 0.0000 ✅ Sí |
Cohen Long (13-F) 0.0184 4.64 0.925 0.127 86.8 0.1582 ❌ No |
Cohen Hedged 0.0083 2.10 0.381 0.127 62.5 0.5236 ❌ No |
Metodología:
α = retorno diario no explicado por factores, anualizado. p < 0.05 = estadísticamente significativo.
β Mercado > 1 = más volátil que el mercado. R² alto = el portafolio ‘es’ el mercado.
Errores estándar Newey-West (lag=5) para corregir autocorrelación en retornos diarios.
# ── Visualización de exposición factorial ─────────────────────────────────
df_betas <- tabla_factorial %>%
  select(Portafolio, Beta_Mercado, Beta_Tech, Alpha_Anual) %>%
  pivot_longer(cols=c(Beta_Mercado, Beta_Tech, Alpha_Anual),
               names_to="Factor", values_to="Exposicion") %>%
  mutate(
    Factor = recode(Factor,
      "Beta_Mercado" = "β Mercado (SPY)",
      "Beta_Tech"    = "β Tech/Growth (QQQ)",
      "Alpha_Anual"  = "α Alpha Anual (%)"),
    Portafolio = factor(Portafolio,
      levels=c("Bogle (Pasivo)","Cohen Long (13-F)","Cohen Hedged"))
  )

colores_port3 <- c(
  "Bogle (Pasivo)"    = COL_BOGLE,
  "Cohen Long (13-F)" = COL_COHEN,
  "Cohen Hedged"      = COL_HEDGED
)

ggplot(df_betas, aes(x=Portafolio, y=Exposicion, fill=Portafolio)) +
  geom_col(width=0.6, alpha=0.85) +
  geom_hline(yintercept=0, color=COL_MUTED, linewidth=0.5) +
  geom_text(aes(label=round(Exposicion,3),
                vjust=ifelse(Exposicion>=0,-0.4,1.3)),
            size=3.5, fontface="bold", color=COL_TEXT, family="mono") +
  scale_fill_manual(values=colores_port3) +
  facet_wrap(~Factor, scales="free_y", ncol=3) +
  labs(
    title="EXPOSICIÓN FACTORIAL: MERCADO, TECH Y ALPHA GENUINO",
    subtitle="¿Qué parte del retorno es riesgo sistemático (beta) vs. habilidad real (alpha)?",
    x=NULL, y="Coeficiente / % Anual",
    caption="Bogle = casi 100% beta. Cohen = beta alto + alpha marginal. Hedged = beta reducido, alpha más visible."
  ) +
  theme_finance() + theme(legend.position="none",
                           axis.text.x=element_text(angle=20, hjust=1))


9 Métricas Completas con Información Ratio

9.1 Mejora #9 — Information Ratio, Tracking Error, Upside/Downside Capture

# ── Función maestra de métricas ───────────────────────────────────────────
metricas_completas <- function(ret, ret_bench, nombre) {

  ret_a  <- as.numeric(Return.annualized(ret, scale=252)) * 100
  vol_a  <- as.numeric(StdDev.annualized(ret, scale=252)) * 100
  sh     <- as.numeric(SharpeRatio.annualized(ret, Rf=RF_DIARIO, scale=252))
  so     <- as.numeric(SortinoRatio(ret, MAR=RF_DIARIO)) * sqrt(252)
  mdd    <- as.numeric(maxDrawdown(ret)) * 100
  cal    <- ret_a / mdd
  skw    <- as.numeric(skewness(ret))
  krt    <- as.numeric(kurtosis(ret))
  var95  <- as.numeric(VaR(ret,  p=0.95, method="historical")) * 100
  cvar95 <- as.numeric(CVaR(ret, p=0.95, method="historical")) * 100
  wr     <- mean(as.numeric(ret) > 0) * 100

  # Métricas vs benchmark
  alpha_b <- as.numeric(CAPM.alpha(ret, ret_bench, Rf=RF_DIARIO)) * 252 * 100
  beta_b  <- as.numeric(CAPM.beta(ret,  ret_bench, Rf=RF_DIARIO))
  te      <- as.numeric(TrackingError(ret, ret_bench, scale=252)) * 100
  ir      <- as.numeric(InformationRatio(ret, ret_bench, scale=252))
  up_cap  <- as.numeric(UpsideFrequency(ret, ret_bench)) * 100
  dn_cap  <- as.numeric(DownsideFrequency(ret, ret_bench)) * 100

  tibble(
    Portafolio          = nombre,
    `Retorno Anual (%)`  = round(ret_a, 2),
    `Volatilidad (%)`    = round(vol_a, 2),
    `Sharpe Ratio`       = round(sh, 3),
    `Sortino Ratio`      = round(so, 3),
    `Max Drawdown (%)`   = round(-mdd, 2),
    `Calmar Ratio`       = round(cal, 3),
    `VaR 95% (%)`        = round(var95, 2),
    `CVaR 95% (%)`       = round(cvar95, 2),
    `Skewness`           = round(skw, 3),
    `Kurtosis`           = round(krt, 3),
    `Win Rate (%)`       = round(wr, 1),
    `Alpha vs SPY (%)`   = round(alpha_b, 2),
    `Beta vs SPY`        = round(beta_b, 3),
    `Tracking Error (%)` = round(te, 2),
    `Info Ratio`         = round(ir, 3),
    `Upside Freq (%)`    = round(up_cap, 1),
    `Downside Freq (%)`  = round(dn_cap, 1)
  )
}

ret_bench_aln <- ret_spy

tab_bogle  <- metricas_completas(ret_bogle,        ret_bench_aln, "Bogle (Pasivo)")
tab_cohen  <- metricas_completas(ret_cohen,        ret_bench_aln, "Cohen Long (13-F)")
tab_hedged <- metricas_completas(ret_cohen_hedged, ret_bench_aln, "Cohen Hedged")
tab_spy    <- metricas_completas(ret_spy,          ret_bench_aln, "S&P 500 (SPY)")

tabla_maestra <- bind_rows(tab_bogle, tab_cohen, tab_hedged, tab_spy) %>%
  t() %>% as.data.frame()
colnames(tabla_maestra) <- tabla_maestra[1,]
tabla_maestra <- tabla_maestra[-1,]

kable(tabla_maestra,
      caption="📊 TABLA MAESTRA DE MÉTRICAS — Bogle vs Cohen vs SPY") %>%
  kable_styling(bootstrap_options=c("striped","hover","condensed"), font_size=12) %>%
  column_spec(2, color=COL_BOGLE,  bold=TRUE) %>%
  column_spec(3, color=COL_COHEN,  bold=TRUE) %>%
  column_spec(4, color=COL_HEDGED, bold=TRUE) %>%
  column_spec(5, color=COL_SPY,    bold=TRUE) %>%
  row_spec(c(3,4), background="#0d2b24") %>%   # Sharpe/Sortino destacados
  row_spec(c(5,6), background="#2b1a0d") %>%   # Drawdown/Calmar destacados
  row_spec(c(13,15,16), background="#1a1a2b")  # Alpha/IR destacados
📊 TABLA MAESTRA DE MÉTRICAS — Bogle vs Cohen vs SPY
Bogle (Pasivo) Cohen Long (13-F) Cohen Hedged S&P 500 (SPY)
Retorno Anual (%) 23.05 17.99 11.03 12.60
Volatilidad (%) 21.84 19.18 11.36 16.42
Sharpe Ratio 0.808 0.668 0.541 0.466
Sortino Ratio 1.219 1.022 0.823 0.746
Max Drawdown (%) -34.91 -27.02 -15.26 -26.22
Calmar Ratio 0.660 0.666 0.723 0.481
VaR 95% (%) -2.27 -1.97 -1.17 -1.67
CVaR 95% (%) -3.13 -2.78 -1.60 -2.43
Skewness -0.199 -0.288 -0.200 -0.268
Kurtosis 1.681 1.654 1.535 1.958
Win Rate (%) 55.5 55.9 55.3 54.1
Alpha vs SPY (%) 7.79 4.42 1.87 0.00
Beta vs SPY 1.244 1.087 0.544 1.000
Tracking Error (%) 8.72 7.17 10.27 0.00
Info Ratio 1.198 0.753 -0.153 NA
Upside Freq (%) 54.2 52.6 49.0 0.0
Downside Freq (%) 45.8 47.4 51.0 0.0

10 Rolling Sharpe — Análisis por Ciclos de Mercado

10.1 Mejora #4 — Identificación de Ciclos

# ── Rolling Sharpe 6 meses ────────────────────────────────────────────────
roll_sharpe <- function(ret, ventana=VENTANA_ROLL) {
  rollapply(ret, width=ventana,
    FUN=function(x) as.numeric(SharpeRatio.annualized(x, Rf=RF_DIARIO)),
    fill=NA, align="right")
}

rs_b  <- roll_sharpe(ret_bogle)
rs_c  <- roll_sharpe(ret_cohen)
rs_h  <- roll_sharpe(ret_cohen_hedged)
rs_s  <- roll_sharpe(ret_spy)

df_rs <- bind_rows(
  data.frame(Fecha=index(rs_b), Sharpe=as.numeric(rs_b), Port="Bogle (Pasivo)"),
  data.frame(Fecha=index(rs_c), Sharpe=as.numeric(rs_c), Port="Cohen Long (13-F)"),
  data.frame(Fecha=index(rs_h), Sharpe=as.numeric(rs_h), Port="Cohen Hedged"),
  data.frame(Fecha=index(rs_s), Sharpe=as.numeric(rs_s), Port="S&P 500 (SPY)")
) %>% na.omit() %>%
  mutate(Port=factor(Port, levels=c("Cohen Long (13-F)","Cohen Hedged","Bogle (Pasivo)","S&P 500 (SPY)")))

# Ciclos de mercado para shading
ciclos <- data.frame(
  xmin  = as.Date(c("2020-02-19","2022-01-03")),
  xmax  = as.Date(c("2020-03-23","2022-10-12")),
  label = c("Bear\nCOVID","Bear\nTasas 2022"),
  y     = c(-3,-3)
)

p_sharpe <- ggplot(df_rs, aes(x=Fecha, y=Sharpe, color=Port)) +
  geom_rect(data=ciclos, aes(xmin=xmin,xmax=xmax,ymin=-Inf,ymax=Inf),
            inherit.aes=FALSE, fill="#ff000015", alpha=0.5) +
  geom_line(linewidth=0.8, alpha=0.85) +
  geom_hline(yintercept=c(0,1), linetype=c("dashed","dotted"),
             color=COL_MUTED, linewidth=0.5, alpha=0.7) +
  geom_text(data=ciclos, aes(x=xmin+15, y=y, label=label),
            inherit.aes=FALSE, color=COL_WARN, size=2.5, hjust=0, family="mono") +
  annotate("text", x=as.Date("2019-06-01"), y=1.05,
           label="Sharpe > 1", color=COL_MUTED, size=2.8, hjust=0) +
  scale_color_manual(values=colores_todos) +
  scale_x_date(date_breaks="6 months", date_labels="%b '%y") +
  labs(title="ROLLING SHARPE RATIO — VENTANA 6 MESES (126 DÍAS)",
       subtitle="Zonas rojas = mercados bajistas. ¿Quién mantiene la eficiencia en crisis?",
       x=NULL, y="Sharpe Ratio (Anualizado)", color=NULL,
       caption="Rf = 4.5% anual. Ventana deslizante de 126 días hábiles.") +
  theme_finance() + theme(legend.position="top")

# ── Rolling Information Ratio ─────────────────────────────────────────────
roll_ir <- function(ret, bench, ventana=VENTANA_ROLL) {
  fechas_ok <- intersect(as.character(index(ret)), as.character(index(bench)))
  r <- ret[fechas_ok]; b <- bench[fechas_ok]
  rollapply(seq_along(fechas_ok), width=ventana,
    FUN=function(idx) {
      ri <- as.numeric(r[idx]); bi <- as.numeric(b[idx])
      er <- ri - bi
      if(sd(er)==0) return(NA)
      mean(er)/sd(er) * sqrt(252)
    }, fill=NA, align="right") %>%
    xts(order.by=as.Date(fechas_ok))
}

ir_b <- roll_ir(ret_bogle, ret_spy)
ir_c <- roll_ir(ret_cohen, ret_spy)
ir_h <- roll_ir(ret_cohen_hedged, ret_spy)

df_ir <- bind_rows(
  data.frame(Fecha=index(ir_b), IR=as.numeric(ir_b), Port="Bogle (Pasivo)"),
  data.frame(Fecha=index(ir_c), IR=as.numeric(ir_c), Port="Cohen Long (13-F)"),
  data.frame(Fecha=index(ir_h), IR=as.numeric(ir_h), Port="Cohen Hedged")
) %>% na.omit()

p_ir <- ggplot(df_ir, aes(x=Fecha,y=IR,color=Port)) +
  geom_rect(data=ciclos, aes(xmin=xmin,xmax=xmax,ymin=-Inf,ymax=Inf),
            inherit.aes=FALSE, fill="#ff000015", alpha=0.5) +
  geom_line(linewidth=0.8, alpha=0.85) +
  geom_hline(yintercept=0, linetype="dashed", color=COL_MUTED, linewidth=0.5) +
  scale_color_manual(values=colores_todos) +
  scale_x_date(date_breaks="6 months", date_labels="%b '%y") +
  labs(title="ROLLING INFORMATION RATIO vs SPY",
       subtitle="IR > 0 = el portafolio supera al benchmark ajustado por tracking error. Persistencia = skill real.",
       x=NULL, y="Information Ratio (Anualizado)", color=NULL,
       caption="IR = (Ret.Port − Ret.Bench) / Tracking Error. IR > 0.5 = buena gestión activa.") +
  theme_finance() + theme(legend.position="top")

gridExtra::grid.arrange(p_sharpe, p_ir, nrow=2)


11 Scorecard Ponderado

11.1 Mejora #2 — Sistema de Puntuación por Importancia

Problema del conteo simple: contar victorias métrica por métrica trata igual el Sharpe Ratio (crítico) que la Kurtosis (secundaria). El sistema ponderado asigna pesos según la importancia real en la toma de decisiones de inversión: rendimiento ajustado por riesgo, preservación de capital y consistencia temporal pesan más que métricas estadísticas de tercer orden.

# ── Sistema de puntuación ponderada ───────────────────────────────────────
# Pesos basados en importancia para decisión de inversión real
scorecard_def <- tribble(
  ~Metrica,             ~Peso, ~Mayor_Mejor,
  "Retorno Anual (%)",  0.20,  TRUE,
  "Sharpe Ratio",       0.20,  TRUE,
  "Sortino Ratio",      0.10,  TRUE,
  "Max Drawdown (%)",   0.15,  FALSE,   # queremos MENOS negativo
  "Calmar Ratio",       0.10,  TRUE,
  "Info Ratio",         0.10,  TRUE,
  "Alpha vs SPY (%)",   0.10,  TRUE,
  "Win Rate (%)",       0.03,  TRUE,
  "CVaR 95% (%)",       0.02,  FALSE
)

# Extraer valores
extraer_val <- function(tab, met) as.numeric(tab[[met]])

calc_score <- function(tab, def) {
  score <- 0
  for (i in 1:nrow(def)) {
    met   <- def$Metrica[i]
    peso  <- def$Peso[i]
    mayor <- def$Mayor_Mejor[i]
    val   <- extraer_val(tab, met)

    # Normalizar: convertir valor a puntos [0,1] según contexto
    # Aquí simplificado: asignamos 1 punto al ganador, 0 al perdedor (ponderado)
    score <- score  # se calcula en el bloque comparativo abajo
  }
}

# Comparación directa por métrica
resultados <- scorecard_def %>%
  rowwise() %>%
  mutate(
    Val_Bogle  = extraer_val(tab_bogle,  Metrica),
    Val_Cohen  = extraer_val(tab_cohen,  Metrica),
    Val_Hedged = extraer_val(tab_hedged, Metrica),
    # Ganador: si mayor es mejor, quien tiene más; si no, quien tiene menos negativo
    Mejor_Bogle  = ifelse(Mayor_Mejor,
                          Val_Bogle  >= pmax(Val_Cohen, Val_Hedged),
                          Val_Bogle  <= pmin(Val_Cohen, Val_Hedged)),
    Mejor_Cohen  = ifelse(Mayor_Mejor,
                          Val_Cohen  >= pmax(Val_Bogle, Val_Hedged),
                          Val_Cohen  <= pmin(Val_Bogle, Val_Hedged)),
    Mejor_Hedged = ifelse(Mayor_Mejor,
                          Val_Hedged >= pmax(Val_Bogle, Val_Cohen),
                          Val_Hedged <= pmin(Val_Bogle, Val_Cohen)),
    Pts_Bogle  = ifelse(Mejor_Bogle,  Peso, 0),
    Pts_Cohen  = ifelse(Mejor_Cohen,  Peso, 0),
    Pts_Hedged = ifelse(Mejor_Hedged, Peso, 0)
  ) %>%
  ungroup()

# Scores totales
score_bogle  <- sum(resultados$Pts_Bogle)
score_cohen  <- sum(resultados$Pts_Cohen)
score_hedged <- sum(resultados$Pts_Hedged)

cat(sprintf("\n🏆 SCORECARD PONDERADO FINAL\n"))
## 
## 🏆 SCORECARD PONDERADO FINAL
cat(sprintf("   🟢 Bogle (Pasivo):    %.0f%%\n", score_bogle  * 100))
##    🟢 Bogle (Pasivo):    87%
cat(sprintf("   🔶 Cohen Long:        %.0f%%\n", score_cohen  * 100))
##    🔶 Cohen Long:        3%
cat(sprintf("   🟡 Cohen Hedged:      %.0f%%\n", score_hedged * 100))
##    🟡 Cohen Hedged:      10%
# Tabla detallada
resultados %>%
  select(Metrica, Peso, Val_Bogle, Val_Cohen, Val_Hedged, Pts_Bogle, Pts_Cohen, Pts_Hedged) %>%
  mutate(
    Ganador = case_when(
      Pts_Bogle  > 0 & Pts_Bogle  >= Pts_Cohen  & Pts_Bogle  >= Pts_Hedged ~ "🟢 Bogle",
      Pts_Cohen  > 0 & Pts_Cohen  >= Pts_Bogle  & Pts_Cohen  >= Pts_Hedged ~ "🔶 Cohen",
      Pts_Hedged > 0 ~ "🟡 Hedged",
      TRUE ~ "Empate"
    ),
    across(c(Val_Bogle,Val_Cohen,Val_Hedged), ~round(.,3)),
    Peso = paste0(round(Peso*100),"% ponderación")
  ) %>%
  select(-Pts_Bogle,-Pts_Cohen,-Pts_Hedged) %>%
  kable(caption="Scorecard Ponderado — Mayor peso = más importancia en decisión real",
        col.names=c("Métrica","Peso","Bogle","Cohen Long","Cohen Hedged","Ganador")) %>%
  kable_styling(bootstrap_options=c("striped","hover","condensed"), font_size=13) %>%
  column_spec(3, color=COL_BOGLE,  bold=TRUE) %>%
  column_spec(4, color=COL_COHEN,  bold=TRUE) %>%
  column_spec(5, color=COL_HEDGED, bold=TRUE)
Scorecard Ponderado — Mayor peso = más importancia en decisión real
Métrica Peso Bogle Cohen Long Cohen Hedged Ganador
Retorno Anual (%) 20% ponderación 23.050 17.990 11.030 🟢 Bogle |
Sharpe Ratio 20% ponderación 0.808 0.668 0.541 🟢 Bogle |
Sortino Ratio 10% ponderación 1.219 1.022 0.823 🟢 Bogle |
Max Drawdown (%) 15% ponderación -34.910 -27.020 -15.260 🟢 Bogle |
Calmar Ratio 10% ponderación 0.660 0.666 0.723 🟡 Hedged |
Info Ratio 10% ponderación 1.198 0.753 -0.153 🟢 Bogle |
Alpha vs SPY (%) 10% ponderación 7.790 4.420 1.870 🟢 Bogle |
Win Rate (%) 3% ponderación 55.500 55.900 55.300 🔶 Cohen |
CVaR 95% (%) 2% ponderación -3.130 -2.780 -1.600 🟢 Bogle |
# ── Gráfico radar-bar del scorecard ───────────────────────────────────────
score_df <- data.frame(
  Estrategia = c("Bogle\n(Pasivo)","Cohen\nLong","Cohen\nHedged"),
  Score      = c(score_bogle, score_cohen, score_hedged) * 100,
  Color      = c(COL_BOGLE, COL_COHEN, COL_HEDGED)
)

ggplot(score_df, aes(x=reorder(Estrategia,-Score), y=Score, fill=Estrategia)) +
  geom_col(width=0.5, alpha=0.9) +
  geom_text(aes(label=paste0(round(Score),"%")),
            vjust=-0.5, size=9, fontface="bold", color=COL_TEXT, family="mono") +
  scale_fill_manual(values=c(
    "Bogle\n(Pasivo)" = COL_BOGLE,
    "Cohen\nLong"     = COL_COHEN,
    "Cohen\nHedged"   = COL_HEDGED)) +
  scale_y_continuous(limits=c(0,100), labels=function(x) paste0(x,"%")) +
  labs(title="SCORECARD PONDERADO FINAL",
       subtitle="Puntuación total = suma de pesos de las métricas en que cada estrategia es superior",
       x=NULL, y="Score Total (%)",
       caption="Pesos: Retorno 20%, Sharpe 20%, Drawdown 15%, Sortino 10%, Calmar 10%, InfoRatio 10%, Alpha 10%, WinRate 3%, CVaR 2%") +
  theme_finance() + theme(legend.position="none")


12 Mejora #10 — Skill vs. Exposición al Mercado

# ── Scatter: Alpha anual vs Beta de mercado ────────────────────────────────
df_scatter <- bind_rows(tab_bogle, tab_cohen, tab_hedged, tab_spy) %>%
  select(Portafolio, `Alpha vs SPY (%)`, `Beta vs SPY`, `Sharpe Ratio`, `Retorno Anual (%)`) %>%
  mutate(
    Tipo = case_when(
      str_detect(Portafolio,"Bogle")  ~ "Pasivo (Beta puro)",
      str_detect(Portafolio,"Long")   ~ "Activo (Long-only, sesgado)",
      str_detect(Portafolio,"Hedged") ~ "Activo (Hedged, comparable)",
      TRUE                            ~ "Benchmark"
    )
  )

colores_tipo <- c(
  "Pasivo (Beta puro)"            = COL_BOGLE,
  "Activo (Long-only, sesgado)"   = COL_COHEN,
  "Activo (Hedged, comparable)"   = COL_HEDGED,
  "Benchmark"                     = COL_SPY
)

ggplot(df_scatter, aes(x=`Beta vs SPY`, y=`Alpha vs SPY (%)`,
                       color=Tipo, size=`Sharpe Ratio`)) +
  geom_hline(yintercept=0, linetype="dashed", color=COL_MUTED, linewidth=0.7) +
  geom_vline(xintercept=1, linetype="dashed", color=COL_MUTED, linewidth=0.7) +
  geom_point(alpha=0.9) +
  geom_text(aes(label=Portafolio), vjust=-1.2, size=3, fontface="bold",
            color=COL_TEXT, family="mono", show.legend=FALSE) +
  annotate("text", x=0.5,  y=max(df_scatter$`Alpha vs SPY (%)`)*0.7,
           label="ALPHA REAL\n(skill + baja exposición)",
           color=COL_BOGLE, size=3, family="mono", alpha=0.7) +
  annotate("text", x=1.3, y=min(df_scatter$`Alpha vs SPY (%)`)*0.7,
           label="BETA CARO\n(riesgo de mercado, sin skill)",
           color=COL_COHEN, size=3, family="mono", alpha=0.7) +
  scale_color_manual(values=colores_tipo) +
  scale_size_continuous(range=c(4,10), name="Sharpe Ratio") +
  labs(
    title="SKILL vs EXPOSICIÓN AL MERCADO",
    subtitle="Eje X = cuánto beta de mercado asume. Eje Y = alpha genuino sobre el benchmark. Tamaño = Sharpe.",
    x="Beta vs S&P 500 (SPY)", y="Alpha Anual vs SPY (%)", color="Tipo de Estrategia",
    caption=paste0(
      "Cuadrante ideal: Beta bajo (<1) + Alpha positivo = skill real.\n",
      "Cohen Long-only vive en el cuadrante 'beta caro': mucho riesgo de mercado, poco alpha añadido.\n",
      "La versión Hedged reduce el beta y hace el alpha más visible (positivo o negativo).")
  ) +
  theme_finance() + theme(legend.position="right")


13 Análisis de Retornos Anuales — Con Costos

13.1 Mejora #7 — Impacto Real de Costos de Transacción

# Retornos anuales ya incluyen costos de transacción del rebalanceo
get_anual <- function(ret, nombre) {
  ra <- apply.yearly(ret, Return.cumulative) * 100
  data.frame(
    Año  = year(index(ra)),
    Ret  = as.numeric(ra),
    Port = nombre
  )
}

df_anual <- bind_rows(
  get_anual(ret_bogle,        "Bogle (Pasivo)"),
  get_anual(ret_cohen,        "Cohen Long (13-F)"),
  get_anual(ret_cohen_hedged, "Cohen Hedged"),
  get_anual(ret_spy,          "S&P 500 (SPY)")
) %>%
  mutate(Port=factor(Port, levels=c("Cohen Long (13-F)","Cohen Hedged","Bogle (Pasivo)","S&P 500 (SPY)")))

colores_anual <- c(
  "Cohen Long (13-F)"  = COL_COHEN,
  "Cohen Hedged"       = COL_HEDGED,
  "Bogle (Pasivo)"     = COL_BOGLE,
  "S&P 500 (SPY)"     = COL_SPY
)

ggplot(df_anual, aes(x=factor(Año), y=Ret, fill=Port)) +
  geom_col(position="dodge", alpha=0.85, width=0.72) +
  geom_hline(yintercept=0, color=COL_MUTED, linewidth=0.4) +
  geom_text(aes(label=sprintf("%.1f%%",Ret),
                vjust=ifelse(Ret>=0,-0.4,1.3)),
            position=position_dodge(width=0.72),
            size=2.6, family="mono", fontface="bold", color=COL_TEXT) +
  scale_fill_manual(values=colores_anual) +
  scale_y_continuous(labels=function(x) paste0(x,"%")) +
  labs(
    title="RETORNO ANUAL POR ESTRATEGIA — 2019–2024 (con costos de transacción)",
    subtitle="Bogle: 5 bps por rebalanceo | Cohen: 30 bps por rebalanceo (turnover alto)",
    x="Año", y="Retorno Anual (%)", fill="Estrategia",
    caption="2020: crash COVID. 2022: bear market por subida de tasas. 2023-24: rally IA. ¿Cuál estrategia sobrevivió mejor cada ciclo?"
  ) +
  theme_finance() + theme(legend.position="top")


14 Conclusiones Finales

14.0.1 🟢 Bogle — Estrategia Pasiva: Lo que el análisis confirma

Es casi imposible separar el retorno de Bogle del mercado mismo — y eso es exactamente el punto. Con un R² cercano al 95–99% vs. SPY, el portafolio es el mercado con mínima desviación. El alpha es cercano a cero (y estadísticamente no significativo), pero eso no es un fracaso: es el diseño. La propuesta de Bogle nunca fue generar alpha; fue capturar el beta del mercado americano al menor costo posible, con la mayor certeza posible.

El drawdown ligeramente mayor que SPY se explica por la concentración en top-25: al eliminar los 475 restantes, se pierde la amortiguación que sectores defensivos (utilities, consumer staples de menor cap) ofrecen en caídas. En crashes, la concentración en mega-caps tech amplifica la caída vs. el índice completo.

Ideal para: inversionistas con horizonte largo (>10 años), sin capacidad de monitoreo activo, con aversión a costos y que aceptan el retorno del mercado sin más.

14.0.2 🔶 Cohen — Estrategia Activa: La trampa del 13-F

El portafolio Long-only de Cohen es una ilusión — no representa cómo opera Point72. Las posiciones cortas, los derivados, la gestión del beta y las coberturas macroeconómicas son invisibles en el 13-F. Lo que vemos es solo la mitad de un motor de dos tiempos.

La versión Hedged revela algo más honesto: al reducir el beta de mercado, el alpha visible cambia. Parte del “retorno superior” de Cohen Long era simplemente beta caro disfrazado de selección. La consistencia del Information Ratio rolling dice más sobre el skill real que cualquier número puntual.

El costo de transacción importa: 30 bps vs 5 bps por rebalanceo pueden comerse 50–150 bps de retorno anual dependiendo del turnover real del fondo — un golpe silencioso que el inversor minorista rara vez calcula.

Ideal para: inversionistas institucionales con acceso real al fondo, tolerancia a fees de 2/20, horizonte de 3–5 años y capacidad de due diligence continuo.

14.0.3 ⚠️ Limitaciones del análisis que persisten

  1. Cohen long-only ≠ Cohen real. El hedge aproximado al 50% es una suposición razonable pero no verificable sin acceso a los libros del fondo.
  2. Sin impuesto a las ganancias. La rotación activa de Cohen genera eventos gravables que reducen el retorno neto del inversor final.
  3. Sesgo de supervivencia. Solo analizamos activos que existieron durante todo el período. Los delisted (quiebras, adquisiciones) están excluidos — esto favorece a Cohen.
  4. El período 2019–2024 aún es favorable a tech. Un análisis desde 2000 (dot-com) o desde 2007 (crisis financiera) daría resultados distintos.
  5. Alpha no es persistente. Los estudios académicos muestran que el alpha de gestores activos tiende a desaparecer en 3–5 años. El alpha pasado de Point72 no garantiza alpha futuro.

“El riesgo más grande que enfrentan los inversores no es la volatilidad del mercado — es pagar demasiado por desempeño que no existe.” — John C. Bogle

“No me importa si el mercado sube o baja. Me importa que yo sepa algo que el mercado todavía no sabe.” — Steven A. Cohen


## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## R:                    R version 4.4.2 (2024-10-31)
## quantmod:             0.4.28
## PerformanceAnalytics: 2.0.8
## Fecha análisis:       2026-05-18
## Período datos:        2019-01-01 → 2024-12-31
## Observaciones:        1018 días hábiles
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Los datos son históricos y provienen de Yahoo Finance vía quantmod. Este análisis es educativo y no constituye asesoría financiera. Las posiciones de Point72/Cohen se basan en 13-F públicos (SEC EDGAR) — solo posiciones largas declaradas.