1 Solución

Información importante

Este documento sirve como guía para el ejercicio práctico del desarrollo semestral de aprendizaje, el cual será realizado por grupos de máximo 3 estudiantes preestablecidas. Cada grupo deberá llevar a cabo un proceso único e inédito; cualquier coincidencia de desarrollo que supere el 40% será sancionada con una calificación de 0.0 para los grupos involucrados. La evaluación del trabajo se realizará por partes, cada una con un valor diferenciado que contribuirá a alcanzar el 100% de la calificación final. Para analizar el proceso requerido use las diapositivas proporcionadas en clase y cualquier información de red relevante. Tenga en cuenta que se dará mayor importancia a los análisis desarrollados que a los cálculos desarrollados.

Presente los resultados en RPUBS con el nombre de los integrantes mostrando los puntos desarrollados en orden y debe enviarse en plataforma virtual. Fecha límite de entrega: 31 de octubre de 2025. Para los ejercicios use año comercial de 252 días.

1.1 Solución parte 1: Aplicación de cobertura con futuros de índice bursátil

Definición del problema

En el año 2025 se desea realizar inversiones en bolsa con objeto de inversión a 4 años. El objetivo es establecer un portafolio de inversión utilizando 3 acciones de preferencia del mismo índice bursátil. Se dispone de un capital de 10.000.000 para realizar inversiones en acciones desde el 31 de julio de 2025. Para construir un portafolio óptimo de media varianza, se considerarán entre 5 y 10 años de información histórica hasta la fecha de inicio de las inversiones (31 de julio).

Los futuros del índice bursátil serán pagaderos de forma mensual. Los criterios relacionados con los futuros del índice, como la garantía y el tamaño del contrato, se obtendrán a través de la plataforma bursátil, siendo recomendable el uso de Bloomberg o cualquier plataforma que entregue información de acciones para este fin.

Mostrar código
## Paquetes importantes


# --- 2. Paquetes base ---
pkgs <- c(
  "tidyverse","lubridate","readxl","janitor","here","glue",
  "quantmod","PerformanceAnalytics","xts","zoo",
  "quadprog","PortfolioAnalytics","tseries","RcppRoll", "DT"
)
to_install <- pkgs[!pkgs %in% installed.packages()[, "Package"]]
if(length(to_install)) install.packages(to_install, Ncpus = 2L)
invisible(lapply(pkgs, library, character.only = TRUE))

Introducción

Para el desarrollo del trabajo se seleccionan tres acciones del índice NASDAQ 100: Apple (AAPL), Microsoft (MSFT) y Alphabet (GOOG). El análisis abarca el periodo del 31 de julio de 2015 al 31 de julio de 2025, con el objetivo de construir un portafolio óptimo de media-varianza a partir de información histórica de retornos y riesgos.

Se dispone de un capital de $10.000.000, que se invertirá a partir del 31 de julio de 2025 con un horizonte de cuatro años. Posteriormente, se evaluará la cobertura del portafolio mediante futuros del NASDAQ 100, calculando el número óptimo de contratos para mitigar el riesgo sistemático. El estudio utiliza datos diarios, un año comercial de 252 días y métodos de optimización de Markowitz (1952).

1.1.1 Portafolio Optimo de inversión

  1. Realice un análisis fundamental de las acciones elegidas y cuál es la expectativa de los precios hasta el 31/07/2025

3. Apple Inc. (AAPL)

Contexto y fundamentos

Apple es un gigante tecnológico con ecosistema integrado de hardware (iPhone, Mac, iPad, Watch y accesorios) y servicios (App Store, iCloud, Apple Music/TV+, etc.).

Según su reporte Q2 fiscal 2025, registró ingresos de US$ 95.4 mil millones, +5 % a/a, y EPS diluido de US$ 1.65, +8 % a/a. Apple

Desde ChartMill: Margen bruto ~46.68 %, margen operativo ~31.87 %, ROE ~150.8 %. Esto sitúa a Apple entre los mejores de su industria. ChartMill

Su negocio de servicios está registrando hitos: los servicios aportan cada vez más a los ingresos, lo cual mejora el perfil de margen y reduce dependencia del ciclo de iPhone. Por ejemplo, en Q3 del año informaron récord de servicios. Investopedia

Apple está invirtiendo en manufactura avanzada, IA y cadenas de suministro en EE.UU. (según Wikipedia resumen 2025). en.wikipedia.org

Expectativas de crecimiento

Dado su tamaño (capitalización de varios billones de USD) y su base instalada, Apple ya no crece al ritmo de startups; el crecimiento será moderado pero sólido. Por ejemplo, analistas estimaron un target de ~US$ 235-261 para 1 año. www.alphaspread.com +1

Otras fuentes más conservadoras lo ubican ~US$ 180 para 2025 si la economía decae o el ciclo de renovación se frena. cleverence.com

Considerando el horizonte hasta 31/07/2025 (~4 años desde la fecha de inversión), y suponiendo un crecimiento anual moderado quizá del 6-8 % compuesto (por ejemplo, +5 % ingresos y +8 % EPS, asumiendo reinversión), se podría proyectar un precio de ~US$ 280-300 si todo va bien; pero también escenarios de ~US$ 230-250 si moderado.

Riesgos clave

Ciclo del iPhone: si el lanzamiento de nuevos modelos fracasa o hay menor demanda, los ingresos pueden estancarse. (Ver artículo sobre WWDC 2025: Apple “está en riesgo de fallar su tendencia de largo plazo”). Seeking Alpha

Macro/Aranceles: Apple depende de manufactura global, especialmente Asia. Los aranceles o interrupciones en cadena afectan los costos y márgenes. Kiplinger

Saturación del mercado de smartphones: En mercados maduros puede haber menor crecimiento.

Expectativa de precio para 31/07/2025

Tomando un punto intermedio entre optimismo moderado y prudencia, proyectaría ≈ US$ 280 por acción hacia el 31 de julio de 2025.

Si el portafolio está valorado en dólares o convertido a tu moneda, este valor lo integrarás como expectativa de retorno

2. Microsoft Corporation (MSFT)

Resumen y fundamentos:

Microsoft mantiene una posición dominante en el mercado global de software y servicios en la nube. Su ecosistema incluye Azure, Microsoft 365, LinkedIn, Xbox y, más recientemente, la integración de IA generativa en todos sus productos tras su alianza con OpenAI. Según ChartMill (2025), se espera que las utilidades por acción (EPS) crezcan 15,2 % anual y los ingresos un 12,7 % anual. En su último reporte trimestral (FY 2025 Q4), Microsoft registró US$ 76,4 mil millones en ingresos y una utilidad neta de US$ 27,3 mil millones, lo que representa un crecimiento interanual del 24 % (RoboForex, 2025).

Perspectiva: El motor principal sigue siendo Azure, que se expande a tasas de doble dígito. Además, la adopción de herramientas de IA como Copilot en Office y Windows podría elevar los márgenes de rentabilidad en 2025-2026. Barron’s (2025) y AlphaSpread (2025) ubican el precio objetivo entre US$ 600 y US$ 750, lo que implicaría una valorización cercana al 15-20 % en el próximo año.

Riesgos:

  • Competencia intensa en IA y nube (Amazon, Google, Nvidia).

  • Riesgo de sobrevaloración por múltiplos altos.

  • Retrasos en monetización de productos IA.

3. Alphabet Inc. (GOOG)

Resumen y fundamentos: Alphabet Inc. —matriz de Google— domina la publicidad digital global, con negocios diversificados en Google Search, YouTube, Android, Google Cloud y DeepMind. En su reporte del segundo trimestre de 2025, Alphabet reportó US$ 96,4 mil millones en ingresos y una utilidad por acción (EPS) de US$ 2,31, un aumento del 14 % interanual (RoboForex, 2025). Su segmento Google Cloud creció más del 30 % en ingresos, mientras que YouTube Ads mostró recuperación significativa tras la desaceleración publicitaria de 2023-2024.

Perspectiva: Alphabet está invirtiendo fuertemente en infraestructura de IA y data centers. Simply Wall St (2025) estima un crecimiento promedio del EPS de 14-15 % anual. Aun con riesgos regulatorios (antitrust en EE.UU. y UE), se proyecta un precio objetivo entre US$ 300 y US$ 400 a 12 meses.

Riesgos:

Demandas y restricciones antimonopolio.

Alta dependencia de ingresos publicitarios (~70 %).

Gastos de capital crecientes en IA y centros de datos.

1.1.2 Calcule los retornos anuales, la desviación estándar anual y la matriz de varianzas y covarianzas para cada acción.

Mostrar código
# --- Parámetros del problema ---
SYMS <- c("AAPL","MSFT","GOOG")
DATE_FROM <- "2015-07-31"
DATE_TO   <- "2025-07-31"
TRADING_DAYS <- 252  # año comercial

# --- 1) Descargar precios ajustados desde Yahoo Finance ---
getSymbols(SYMS, src = "yahoo", from = DATE_FROM, to = DATE_TO, auto.assign = TRUE)
[1] "AAPL" "MSFT" "GOOG"
Mostrar código
prices <- na.omit(merge(Ad(AAPL), Ad(MSFT), Ad(GOOG)))
colnames(prices) <- SYMS

Podemos observar la matriz de los precio de cierre de las acciones elegidas.

Mostrar código
library(gt)
library(zoo)   # <-- agrega esto antes de usar coredata/index

prices_df <- data.frame(
  Date = index(prices),
  coredata(prices),
  row.names = NULL
)

gt(tail(prices_df, 30)) |>
  fmt_currency(columns = 2:ncol(prices_df), currency = "USD", decimals = 2) |>
  tab_options(table.font.size = px(14)) |>
  tab_header(title = md("**Precios ajustados (últimos 30 registros)**")) |>
  opt_table_outline()
Precios ajustados (últimos 30 registros)
Date AAPL MSFT GOOG
2025-06-17 $195.42 $477.26 $177.07
2025-06-18 $196.36 $479.45 $173.82
2025-06-20 $200.77 $476.62 $167.58
2025-06-23 $201.27 $485.20 $165.86
2025-06-24 $200.07 $489.31 $167.59
2025-06-25 $201.33 $491.46 $171.34
2025-06-26 $200.77 $496.63 $174.27
2025-06-27 $200.85 $495.13 $178.11
2025-06-30 $204.94 $496.59 $177.23
2025-07-01 $207.58 $491.24 $176.75
2025-07-02 $212.20 $490.28 $179.60
2025-07-03 $213.31 $498.02 $180.39
2025-07-07 $209.71 $496.90 $177.40
2025-07-08 $209.77 $495.80 $175.00
2025-07-09 $210.90 $502.68 $177.50
2025-07-10 $212.17 $500.66 $178.54
2025-07-11 $210.92 $502.49 $181.15
2025-07-14 $208.38 $502.19 $182.65
2025-07-15 $208.87 $504.99 $182.94
2025-07-16 $209.92 $504.79 $183.61
2025-07-17 $209.78 $510.86 $184.54
2025-07-18 $210.94 $509.21 $185.77
2025-07-21 $212.24 $509.22 $190.98
2025-07-22 $214.16 $504.44 $191.94
2025-07-23 $213.91 $505.04 $191.34
2025-07-24 $213.52 $510.04 $193.03
2025-07-25 $213.64 $512.87 $193.91
2025-07-28 $213.81 $511.66 $193.25
2025-07-29 $211.03 $511.73 $196.25
2025-07-30 $208.81 $512.40 $197.26

Calculo de retornos

1.1.3 Definición teórica de los retornos

En el análisis financiero, el retorno de un activo mide la ganancia o pérdida relativa respecto a su precio inicial. Es una medida esencial para evaluar el rendimiento y comparar activos en el tiempo.

  1. Retorno simple (aritmético)

    \[ R_t \;=\; \frac{P_t - P_{t-1}}{P_{t-1}} \;=\; \frac{P_t}{P_{t-1}} - 1 \]

    donde (P_t) es el precio en el periodo (t) y (P_{t-1}) el del periodo anterior. Representa el cambio porcentual directo entre dos precios consecutivos.

  2. Retorno logarítmico (continuamente compuesto)

    \[ r_t \;=\; \ln\!\left(\frac{P_t}{P_{t-1}}\right) \]

    Ventajas prácticas: (i) es aditivo en el tiempo, (ii) suele aproximar mejor la normalidad, (iii) facilita el cálculo e interpretación de métricas del portafolio.

Anualización (año comercial de 252 días hábiles):

\[ \mu_{\text{anual}} = \bar r_{\text{diario}} \cdot 252 \qquad\text{y}\qquad \sigma_{\text{anual}} = s_{\text{diario}} \cdot \sqrt{252}. \]

Estas transformaciones permiten expresar rendimiento y riesgo en base anual, compararlos entre activos y utilizarlos en la construcción de portafolios eficientes (media–varianza de Markowitz).

1) Rendimiento y riesgo por acción

MSFT: mayor media anual (25,47%) y menor volatilidad (27,14%). Es el perfil más atractivo del trío: más retorno con menos riesgo.

AAPL: media 20,46% con volatilidad 29,34%. Buen retorno, pero más riesgosa que MSFT.

GOOG: media 18,54% y volatilidad 28,75%. Es la de menor retorno con riesgo similar a AAPL.

Lectura de eficiencia (aprox. “riesgo por unidad de retorno”, σ/μ):

MSFT ≈ 1,07 (mejor), AAPL ≈ 1,43, GOOG ≈ 1,55. → MSFT domina en términos de calidad de retorno.

Mostrar código
library(PerformanceAnalytics)
library(xts)
library(dplyr)
library(knitr)

# 1. Retornos simples y logarítmicos diarios
returns_simple <- na.omit(Return.calculate(prices, method = "discrete"))
returns_log <- na.omit(Return.calculate(prices, method = "log"))

# 2. Estadísticos anuales desde retornos diarios
TRADING_DAYS <- 252

ret_mean_ann <- colMeans(returns_log) * TRADING_DAYS
ret_sd_ann   <- apply(returns_log, 2, sd) * sqrt(TRADING_DAYS)

# 3. Matriz de covarianzas y correlaciones anualizadas
cov_matrix_ann <- cov(returns_log) * TRADING_DAYS
cor_matrix     <- cor(returns_log)

# 4. Mostrar resultados
tabla_retornos <- tibble(
  Activo = colnames(returns_log),
  `Media anual (%)` = ret_mean_ann * 100,
  `Desv.est. anual (%)` = ret_sd_ann * 100
)

kable(tabla_retornos, digits = 2, caption = "Media y desviación estándar anualizadas de los retornos logarítmicos")
Media y desviación estándar anualizadas de los retornos logarítmicos
Activo Media anual (%) Desv.est. anual (%)
AAPL 20.46 29.34
MSFT 25.47 27.14
GOOG 18.54 28.75

Correlaciones (diversificación)

Altas y positivas dentro del mismo sector tech:

AAPL–MSFT = 0,689, MSFT–GOOG = 0,729, AAPL–GOOG = 0,616.

La más baja es AAPL–GOOG (0,616) → es el par que más diversifica dentro del trío.

Implicación: habrá beneficio de diversificación, pero limitado; el riesgo sistemático del sector sigue siendo relevante.

Mostrar código
kable(cor_matrix, digits = 4, caption = "Matriz de correlaciones entre las acciones")
Matriz de correlaciones entre las acciones
AAPL MSFT GOOG
AAPL 1.0000 0.6890 0.6157
MSFT 0.6890 1.0000 0.7287
GOOG 0.6157 0.7287 1.0000

Varianzas y covarianzas (anualizadas)

Las varianzas (diagonal) concuerdan con las σ reportadas:

Var(AAPL) 0,0861 (σ≈29,3%), Var(MSFT) 0,0737 (σ≈27,1%), Var(GOOG) 0,0827 (σ≈28,7%). → MSFT confirma ser la menos volátil.

Las covarianzas positivas (p. ej., AAPL–MSFT 0,0549) reflejan las correlaciones altas: los activos tienden a moverse juntos.

Mostrar código
kable(cov_matrix_ann, digits = 6, caption = "Matriz de varianzas y covarianzas anualizada")
Matriz de varianzas y covarianzas anualizada
AAPL MSFT GOOG
AAPL 0.086103 0.054873 0.051938
MSFT 0.054873 0.073656 0.056856
GOOG 0.051938 0.056856 0.082656

1.1.4 Calcule el retorno esperado, la desviación estándar y el Sharpe Ratio del portafolio

1.1.4.1 Definición teórica del retorno esperado, desviación estándar y Sharpe Ratio del portafolio

En el contexto de la teoría de portafolios de Markowitz, los tres indicadores fundamentales para evaluar una cartera de inversión son el retorno esperado, la desviación estándar (o volatilidad) y el Sharpe Ratio.

Retorno esperado del portafolio El retorno esperado ((E[R_p])) mide el rendimiento promedio ponderado de los activos que componen el portafolio:

\[ E[R_p] = \sum_{i=1}^{n} w_i \, E[R_i] \]

donde: - (w_i) = proporción de capital invertida en el activo (i), - (E[R_i]) = retorno esperado del activo (i), - (n) = número de activos en el portafolio.

Este indicador representa la ganancia media esperada de la cartera, considerando las ponderaciones de cada activo.

Desviación estándar del portafolio La desviación estándar ((_p)) cuantifica el riesgo total o la variabilidad de los retornos del portafolio:

\[ \sigma_p = \sqrt{w^{\top} \Sigma w} \]

donde: - (w) = vector de pesos del portafolio, - () = matriz de varianzas y covarianzas de los retornos de los activos.

La matriz () incluye tanto las varianzas individuales ((_i^2)) como las covarianzas entre pares de activos, que capturan los efectos de diversificación.

Sharpe Ratio El Sharpe Ratio mide la eficiencia del portafolio en términos de retorno ajustado al riesgo:

\[ SR = \frac{E[R_p] - R_f}{\sigma_p} \]

donde: - (R_f) = tasa libre de riesgo (por ejemplo, bono del Tesoro a 3 o 12 meses), - (E[R_p] - R_f) = prima de riesgo del portafolio.

Un Sharpe Ratio más alto indica un mejor desempeño, ya que implica que el portafolio obtiene mayor retorno por cada unidad de riesgo asumida.

Mostrar código
library(knitr)

TRADING_DAYS <- 252

# --- (A) Si ya tienes 'returns_log', 'ann_mean_from_daily' y 'cov_ann_from_daily', omite este bloque ---
if (!exists("returns_log") || !exists("ann_mean_from_daily") || !exists("cov_ann_from_daily")) {
  # Recalcular desde 'prices'
  returns_log <- na.omit(PerformanceAnalytics::Return.calculate(prices, method = "log"))
  ann_mean_from_daily <- colMeans(returns_log) * TRADING_DAYS          # μ anual
  cov_ann_from_daily  <- cov(returns_log) * TRADING_DAYS               # Σ anual
}

mu    <- as.numeric(ann_mean_from_daily)        # vector de medias anualizadas
names(mu) <- colnames(returns_log)
Sigma <- as.matrix(cov_ann_from_daily)          # matriz de var-cov anualizada
ones  <- rep(1, length(mu))

# --- Define la tasa libre de riesgo anual (ajústala según tu caso) ---
rf <- 0.03   # 3% anual (ejemplo). Cambia por tu referencia (ej. UST 3M/1Y o IBR)

# --- Funciones auxiliares ---
ret_port <- function(w, mu)      as.numeric(sum(w * mu))
sd_port  <- function(w, Sigma)   as.numeric(sqrt(t(w) %*% Sigma %*% w))
sharpe   <- function(w, mu, Sigma, rf) (ret_port(w, mu) - rf) / sd_port(w, Sigma)

# === 1) Portafolio EQUIPONDERADO ===
w_eq <- rep(1/length(mu), length(mu))
Rp_eq <- ret_port(w_eq, mu)
Sd_eq <- sd_port(w_eq, Sigma)
SR_eq <- (Rp_eq - rf) / Sd_eq

# === 2) Portafolio de TANGENCIA (máximo Sharpe) ===
# Fórmula cerrada: w* ∝ Σ^{-1} (μ - rf * 1), normalizado para que sum(w*) = 1
Sigma_inv <- solve(Sigma)
z <- Sigma_inv %*% (mu - rf * ones)
w_tan <- as.numeric(z / sum(z))  # normaliza a suma 1

Rp_tan <- ret_port(w_tan, mu)
Sd_tan <- sd_port(w_tan, Sigma)
SR_tan <- (Rp_tan - rf) / Sd_tan

# --- Resultados tabulares ---
res_pesos <- tibble(
  Activo = names(mu),
  `w_eq` = round(w_eq, 4),
  `w_tangencia` = round(w_tan, 4)
)

res_metricas <- tibble(
  Portafolio = c("Equi-ponderado", "Tangencia (máx Sharpe)"),
  `Retorno esperado (anual)` = c(Rp_eq, Rp_tan),
  `Desv.est. (anual)`        = c(Sd_eq, Sd_tan),
  `Sharpe Ratio`             = c(SR_eq, SR_tan)
) |>
  mutate(across(`Retorno esperado (anual)`:`Desv.est. (anual)`, ~round(.x, 4)),
         `Sharpe Ratio` = round(`Sharpe Ratio`, 3))
Mostrar código
kable(res_pesos, align = "lrr", caption = "Pesos del portafolio: equi-ponderado vs. tangencia")
Pesos del portafolio: equi-ponderado vs. tangencia
Activo w_eq w_tangencia
AAPL 0.3333 0.0912
MSFT 0.3333 1.0868
GOOG 0.3333 -0.1780

Al comparar los resultados obtenidos para el portafolio equi-ponderado y el portafolio de tangencia, se observa que el segundo presenta un mejor desempeño general. El portafolio equi-ponderado, que distribuye el capital de manera uniforme entre las tres acciones (AAPL, MSFT y GOOG), alcanza un retorno esperado anual del 21,49% con una desviación estándar del 25,16%, lo que se traduce en un Sharpe Ratio de 0,735.

Por su parte, el portafolio de tangencia —que busca maximizar el Sharpe Ratio— obtiene un retorno esperado anual del 26,24% y una desviación estándar del 27,84%, alcanzando un Sharpe Ratio superior de 0,835. Esto indica que, aunque el riesgo del portafolio aumenta ligeramente, la rentabilidad adicional compensa dicho incremento, generando una mejor relación riesgo-retorno.

En cuanto a la composición del portafolio óptimo, el modelo asigna un peso mayoritario a Microsoft (MSFT) con 108,68%, una participación reducida de Apple (AAPL) con 9,12%, y una posición corta en Google (GOOG) de -17,8%. Este resultado refleja que, dentro del conjunto analizado, Microsoft es la acción con el mejor equilibrio entre rendimiento esperado y volatilidad, mientras que Google contribuye negativamente a la eficiencia del portafolio, razón por la cual el modelo sugiere reducir su exposición.

En síntesis, el portafolio de tangencia resulta ser el más eficiente en términos de la teoría media-varianza, ya que logra un mayor retorno esperado por cada unidad de riesgo asumido. En la práctica, si se aplicaran restricciones que impidan posiciones cortas, el peso negativo de Google se eliminaría, y los recursos se redistribuirían principalmente entre Microsoft y Apple, manteniendo una composición de portafolio conservadora pero eficiente.

Mostrar código
kable(res_metricas, align = "lrrr", caption = "Métricas del portafolio (anualizadas)")
Métricas del portafolio (anualizadas)
Portafolio Retorno esperado (anual) Desv.est. (anual) Sharpe Ratio
Equi-ponderado 0.2149 0.2516 0.735
Tangencia (máx Sharpe) 0.2624 0.2784 0.835

1.1.5 Establezca los pesos óptimos para la inversión total del capital destinado

1.1.5.1 Definición teórica de los pesos óptimos del portafolio

El objetivo del modelo de media–varianza propuesto por Harry Markowitz (1952) es determinar la combinación de activos que maximiza el rendimiento esperado para un nivel dado de riesgo, o equivalentemente, minimiza el riesgo para un nivel dado de retorno esperado.

Para un conjunto de ( n ) activos, se busca encontrar el vector de pesos ( w = (w_1, w_2, …, w_n)^) que cumpla con:

\[ \min_w \; \frac{1}{2} w^{\top} \Sigma w \quad \text{sujeto a} \quad \begin{cases} w^{\top} \mu = R_p \\ \sum_{i=1}^{n} w_i = 1 \end{cases} \]

donde: - ( ) es la matriz de varianzas y covarianzas de los retornos de los activos,
- ( ) es el vector de retornos esperados,
- ( R_p ) es el retorno objetivo del portafolio,
- ( w_i ) representa la proporción de capital invertida en el activo ( i ).

La solución general de este problema se obtiene aplicando multiplicadores de Lagrange, y los pesos óptimos vienen dados por:

\[ w^* = \lambda_1 \Sigma^{-1} \mu + \lambda_2 \Sigma^{-1} \mathbf{1} \]

donde ( _1 ) y ( _2 ) son parámetros que dependen del nivel de retorno objetivo ( R_p ) y de los coeficientes:

\[ A = \mathbf{1}^{\top} \Sigma^{-1} \mathbf{1}, \quad B = \mathbf{1}^{\top} \Sigma^{-1} \mu, \quad C = \mu^{\top} \Sigma^{-1} \mu, \quad D = AC - B^2. \]

A partir de estos, se obtiene la frontera eficiente:

\[ \sigma_p^2 = \frac{A R_p^2 - 2 B R_p + C}{D}. \]


Caso especial: Portafolio de tangencia (máximo Sharpe) Cuando existe una tasa libre de riesgo ( R_f ), el portafolio óptimo es aquel que maximiza el Sharpe Ratio, cuya solución cerrada es:

\[ w^*_{\text{tangencia}} = \frac{\Sigma^{-1} (\mu - R_f \mathbf{1})} {\mathbf{1}^{\top} \Sigma^{-1} (\mu - R_f \mathbf{1})}. \]

Este portafolio se conoce como portafolio de mercado o de tangencia, y define la línea del mercado de capitales (CML).
Una vez determinados los pesos relativos ( w^*_{} ), la inversión total ( K ) se distribuye proporcionalmente como:

\[ I_i = K \cdot w_i^*, \quad \text{para } i = 1, 2, ..., n. \]

Así, el valor monetario invertido en cada activo depende tanto de su peso óptimo como del capital total disponible.

Implementación

Mostrar código
library(PerformanceAnalytics)
library(quadprog)
library(dplyr)
library(tidyr)
library(knitr)

TRADING_DAYS <- 252
K <- 1e7
rf <- 0.03

stopifnot(exists("prices"))
returns_log <- if (exists("returns_log")) returns_log else na.omit(Return.calculate(prices, method = "log"))
mu    <- if (exists("mu"))    mu    else colMeans(returns_log) * TRADING_DAYS
Sigma <- if (exists("Sigma")) Sigma else cov(returns_log) * TRADING_DAYS

assets <- colnames(prices)
ones   <- rep(1, length(assets))

ret_port <- function(w, mu)    as.numeric(sum(w * mu))
sd_port  <- function(w, Sigma) as.numeric(sqrt(t(w) %*% Sigma %*% w))
sharpe   <- function(w, mu, Sigma, rf) (ret_port(w, mu) - rf) / sd_port(w, Sigma)

# === Tangencia (sin restricciones) ===
Sigma_inv <- solve(Sigma)
z <- Sigma_inv %*% (mu - rf * ones)
w_tan <- as.numeric(z / sum(z))
names(w_tan) <- assets
Rp_tan <- ret_port(w_tan, mu)
Sd_tan <- sd_port(w_tan, Sigma)
SR_tan <- sharpe(w_tan, mu, Sigma, rf)

# === Long-only (resolver por grilla) ===
solve_longonly_for_R <- function(R_target, mu, Sigma){
  n <- length(mu)
  Dmat <- 2 * Sigma
  dvec <- rep(0, n)
  Amat <- cbind(ones, mu, diag(n))  # n x (2+n)
  bvec <- c(1, R_target, rep(0, n))
  meq  <- 2
  tryCatch({
    sol <- solve.QP(Dmat, dvec, Amat, bvec, meq = meq)
    w <- as.numeric(sol$solution)
    list(ok=TRUE, w=w)
  }, error=function(e) list(ok=FALSE, w=rep(NA, n)))
}

mu_min <- min(mu)*0.9
mu_max <- max(mu)*1.1
grid_R <- seq(mu_min, mu_max, length.out = 60)

cand <- lapply(grid_R, function(Rt){
  ans <- solve_longonly_for_R(Rt, mu, Sigma)
  if(!ans$ok || any(is.na(ans$w))) return(NULL)
  w <- ans$w
  # construir fila con nombres de columnas explícitos para los pesos
  df_w <- as.data.frame(t(w))
  colnames(df_w) <- assets
  data.frame(
    Rt = Rt,
    Rp = ret_port(w, mu),
    Sd = sd_port(w, Sigma),
    SR = sharpe(w, mu, Sigma, rf)
  ) |>
    bind_cols(df_w)
})

cand <- bind_rows(cand)
stopifnot(ncol(cand) == 4 + length(assets))  # Rt, Rp, Sd, SR + pesos

best_idx <- which.max(cand$SR)
w_long <- as.numeric(cand[best_idx, (ncol(cand)-length(assets)+1):ncol(cand)])
names(w_long) <- assets

Rp_long <- ret_port(w_long, mu)
Sd_long <- sd_port(w_long, Sigma)
SR_long <- sharpe(w_long, mu, Sigma, rf)

# === Tablas ===
res_pesos <- tibble(
  Activo = assets,
  `w_tangencia` = round(w_tan, 4),
  `w_long_only` = round(w_long, 4)
)

res_metricas <- tibble(
  Portafolio = c("Tangencia (sin restr.)","Máx Sharpe (long-only)"),
  `Retorno esperado (anual)` = c(Rp_tan, Rp_long),
  `Desv.est. (anual)`        = c(Sd_tan, Sd_long),
  `Sharpe Ratio`             = c(SR_tan, SR_long)
) |>
  mutate(across(`Retorno esperado (anual)`:`Desv.est. (anual)`, ~round(.x, 4)),
         `Sharpe Ratio` = round(`Sharpe Ratio`, 3))

kable(res_pesos, caption = "Pesos óptimos por método")
Pesos óptimos por método
Activo w_tangencia w_long_only
AAPL 0.0912 0.0668
MSFT 1.0868 0.9332
GOOG -0.1780 0.0000
Mostrar código
kable(res_metricas, caption = "Métricas del portafolio (anualizadas)")
Métricas del portafolio (anualizadas)
Portafolio Retorno esperado (anual) Desv.est. (anual) Sharpe Ratio
Tangencia (sin restr.) 0.2624 0.2784 0.835
Máx Sharpe (long-only) 0.2513 0.2672 0.828
Mostrar código
# === Asignación de capital y n° de acciones (31-07-2025 o último disponible) ===
# obtener último precio <= 2025-07-31 (si ese día no hay dato, usa el último previo)
xts_cut <- prices[paste0("/", "2025-07-31")]
if (NROW(xts_cut) == 0) xts_cut <- prices
px_last <- as.numeric(tail(xts_cut[, assets], 1))
stopifnot(length(px_last) == length(assets))
names(px_last) <- assets

alloc_table <- function(w, label){
  monto <- K * w
  shares <- floor(monto / px_last)
  invertido <- shares * px_last
  residuo <- monto - invertido
  tibble(
    Activo = assets,
    Precio = round(px_last, 2),
    Peso   = round(w, 4),
    Monto  = round(monto, 2),
    Acciones = shares,
    Invertido = round(invertido, 2),
    Efectivo_residual = round(residuo, 2),
    Metodo = label
  )
}

tbl_tan  <- alloc_table(w_tan,  "Tangencia")
tbl_long <- alloc_table(w_long, "Máx Sharpe (long-only)")

kable(tbl_tan,  caption = "Asignación de capital y acciones — Tangencia (sin restricciones)")
Asignación de capital y acciones — Tangencia (sin restricciones)
Activo Precio Peso Monto Acciones Invertido Efectivo_residual Metodo
AAPL 208.81 0.0912 911744.7 4366 911677.6 67.06 Tangencia
MSFT 512.40 1.0868 10868475.4 21211 10868466.4 8.98 Tangencia
GOOG 197.26 -0.1780 -1780220.1 -9025 -1780304.8 84.67 Tangencia
Mostrar código
kable(tbl_long, caption = "Asignación de capital y acciones — Máx Sharpe (long-only)")
Asignación de capital y acciones — Máx Sharpe (long-only)
Activo Precio Peso Monto Acciones Invertido Efectivo_residual Metodo
AAPL 208.81 0.0668 668029.4 3199 667992.85 36.60 Máx Sharpe (long-only)
MSFT 512.40 0.9332 9331970.6 18212 9331785.89 184.66 Máx Sharpe (long-only)
GOOG 197.26 0.0000 0.0 -1 -197.26 197.26 Máx Sharpe (long-only)

Vizualización de la curva

El gráfico presentado muestra de forma visual la relación entre riesgo y retorno de las combinaciones posibles de inversión entre las tres acciones seleccionadas (AAPL, MSFT y GOOG). La curva negra representa la frontera eficiente, es decir, el conjunto de portafolios que ofrecen el mayor rendimiento esperado para cada nivel de riesgo. Sobre esta frontera se identifica el portafolio de tangencia, que corresponde al punto en el cual la línea del mercado de capitales (CML) —trazada en línea discontinua— es tangente a la frontera eficiente. Este punto marca la combinación óptima entre el activo libre de riesgo y el portafolio riesgoso, maximizando el rendimiento ajustado al riesgo (Sharpe Ratio).

Los activos individuales se ubican por debajo de la CML, lo que evidencia que ningún activo aislado ofrece una relación riesgo–retorno superior a la del portafolio diversificado. En particular, el portafolio de tangencia logra un retorno esperado anual de 26,24% con una desviación estándar de 27,84%, alcanzando un Sharpe Ratio de 0,835, el más alto de todos los portafolios analizados.

En cuanto a la composición, el modelo sin restricciones asigna un peso dominante a Microsoft (108,68%), un peso reducido a Apple (9,12%) y una posición corta en Google (-17,80%), lo que refleja que MSFT ofrece el mejor equilibrio entre rentabilidad y volatilidad, mientras que GOOG contribuye negativamente a la eficiencia del portafolio. Al imponer restricciones long-only, los pesos se ajustan a 93,32% para MSFT, 6,68% para AAPL y 0% para GOOG, eliminando la posición corta y manteniendo prácticamente el mismo nivel de eficiencia (Sharpe Ratio = 0,828).

La asignación monetaria de un capital total de 10.000.000 muestra que, bajo el esquema sin restricciones, se comprarían aproximadamente 21.210 acciones de MSFT, 4.366 de AAPL y se venderían en corto 9.025 de GOOG, mientras que bajo el esquema long-only se adquirirían 18.212 acciones de MSFT y 3.199 de AAPL, con una pequeña porción de efectivo residual.

En conjunto, los resultados confirman que el portafolio de tangencia es el más eficiente teóricamente, aunque el portafolio long-only es más adecuado para la implementación práctica, pues mantiene casi el mismo rendimiento esperado sin necesidad de apalancamiento ni ventas en corto. Esto demuestra que, incluso con correlaciones relativamente altas entre las acciones tecnológicas, la diversificación sigue siendo beneficiosa, y una estrategia concentrada en Microsoft con apoyo marginal de Apple permite obtener una excelente relación riesgo–retorno.

Mostrar código
library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)

TRADING_DAYS <- 252

# --- Asegurar insumos (reconstruye si no existen) ---
if (!exists("prices")) stop("Falta 'prices' en sesión.")
if (!exists("returns_log")) returns_log <- na.omit(PerformanceAnalytics::Return.calculate(prices, method = "log"))
if (!exists("mu"))    mu    <- colMeans(returns_log) * TRADING_DAYS
if (!exists("Sigma")) Sigma <- cov(returns_log) * TRADING_DAYS
if (!exists("rf"))    rf    <- 0.03  # 3% anual, ajusta si tienes otra tasa
assets <- names(mu)

# --- Portafolio de tangencia (cerrado) ---
ones <- rep(1, length(mu))
Sinv <- solve(Sigma)
z <- Sinv %*% (mu - rf * ones)
w_tan <- as.numeric(z / as.numeric(t(ones) %*% Sinv %*% (mu - rf * ones)))
names(w_tan) <- assets

Rp_tan <- as.numeric(sum(w_tan * mu))
Sd_tan <- as.numeric(sqrt(t(w_tan) %*% Sigma %*% w_tan))

# --- Parámetros A,B,C,D para frontera eficiente (no restringida) ---
A <- as.numeric(t(ones) %*% Sinv %*% ones)
B <- as.numeric(t(ones) %*% Sinv %*% mu)
C <- as.numeric(t(mu)   %*% Sinv %*% mu)
D <- A*C - B^2

# --- Curva de frontera eficiente a partir de R_target ---
R_seq <- seq(min(mu)*0.8, max(mu)*1.2, length.out = 200)
frontier <- tibble(
  Rp = R_seq,
  Var = (A*R_seq^2 - 2*B*R_seq + C) / D
) |>
  mutate(Sd = sqrt(pmax(Var, 0)))

# --- Pesos de la frontera (opcional, por si quieres inspeccionar) ---
# w(R) = Σ^{-1}[ ((C - B R)/D) * 1 + ((A R - B)/D) * μ ]
# (No lo imprimimos; solo por referencia.)
# w_frontier <- sapply(R_seq, function(Rt){
#   vec <- Sinv %*% ( ((C - B*Rt)/D) * ones + ((A*Rt - B)/D) * mu )
#   as.numeric(vec)
# })

# --- Puntos de activos individuales (σ, μ) ---
sd_assets <- sqrt(diag(Sigma))
pts_assets <- tibble(Activo = assets, Rp = as.numeric(mu), Sd = as.numeric(sd_assets))

# --- Línea del mercado de capitales (CML): desde (0, rf) hasta tangencia ---
# Recta: R = rf + slope * Sd, con slope = (Rp_tan - rf)/Sd_tan
slope <- (Rp_tan - rf) / Sd_tan
cml_x <- seq(0, max(frontier$Sd)*1.05, length.out = 100)
cml   <- tibble(Sd = cml_x, Rp = rf + slope * cml_x)

# --- Graficar ---
ggplot() +
  # Frontera eficiente (parte superior)
  geom_path(data = frontier, aes(x = Sd, y = Rp), linewidth = 1.1, alpha = 0.9) +
  # CML
  geom_line(data = cml, aes(x = Sd, y = Rp), linetype = "dashed", linewidth = 1) +
  # Punto de tangencia
  geom_point(aes(x = Sd_tan, y = Rp_tan), size = 3) +
  geom_text(aes(x = Sd_tan, y = Rp_tan),
            label = "Tangencia", nudge_y = 0.005, nudge_x = 0.002, size = 3.5) +
  # Activos individuales
  geom_point(data = pts_assets, aes(x = Sd, y = Rp), size = 2.8) +
  geom_text(data = pts_assets, aes(x = Sd, y = Rp, label = Activo),
            nudge_y = 0.005, size = 3.3) +
  # Formatos
  scale_x_continuous(labels = percent_format(accuracy = 1)) +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  labs(x = "Desviación estándar (anual)", y = "Retorno esperado (anual)",
       subtitle = "Frontera eficiente (no restringida), activos, portafolio de tangencia y CML",
       caption = "Notas: cálculos anualizados con 252 días. CML asume tasa libre de riesgo fija (rf).") +
  theme_minimal(base_size = 12)

Frontera eficiente, portafolio de tangencia y Línea del Mercado de Capitales (CML)

1.2 Optimo de contratos del futuro del índice

1.2.1 Calcule el VaR al 1% y 5% del portafolio total diario, explique cómo se debe interpretar los escenarios, tenga en cuenta que estos serán los valores de cubrimiento

1.2.1.1 Definición teórica del VaR diario

El Valor en Riesgo (VaR) a nivel de significancia \(\alpha\) y horizonte de 1 día es la pérdida máxima esperada que no se excederá con probabilidad \(1-\alpha\):

\[ VaR_{\\alpha}^{(1\\,\\text{día})} = \\inf\\{x \\in \\mathbb{R} : \\Pr(\\text{Pérdida} \\le x) \\ge 1-\\alpha\\}. \]

Si trabajamos con retornos del portafolio \(R_{p,d}\) (positivos = ganancias, negativos = pérdidas), el VaR se reporta como número positivo:

  • VaR histórico (no paramétrico): usa la distribución empírica de \(R_{p,d}\) \[ VaR_{\\alpha}^{\\text{hist}} = -\\,Q_{\\alpha}\\big(R_{p,d}\\big), \] donde \(Q_{\alpha}(\cdot)\) es el cuantil \(\alpha\).

  • VaR paramétrico (Normal): si \(R_{p,d} \sim \mathcal{N}(\mu_d,\sigma_d)\) \[ VaR_{\\alpha}^{\\text{norm}} = -\\big(\\mu_d + \\sigma_d\\,z_{\\alpha}\\big), \\quad z_{\\alpha}=\\Phi^{-1}(\\alpha). \]

Para cubrimiento, el VaR porcentual se convierte a monto monetario multiplicando por el valor del portafolio \(V\): \[ VaR\\;[\\text{monto}] = V \\times VaR\\;[\\%]. \]

Interpretación: - VaR 1% (diario): con 99% de confianza, la pérdida de un día no excederá ese valor. - VaR 5% (diario): con 95% de confianza, la pérdida de un día no excederá ese valor.

Mostrar código
library(PerformanceAnalytics)
library(dplyr)
library(knitr)

K <- 1e7  # capital de referencia (10,000,000)

# --- Insumos base ---
stopifnot(exists("prices"))
returns_log <- if (exists("returns_log")) returns_log else na.omit(Return.calculate(prices, method = "log"))

# Pesos del portafolio (prioridad: long-only -> tangencia -> equi)
if (exists("w_long")) {
  w <- w_long
} else if (exists("w_tan")) {
  w <- w_tan
} else {
  w <- rep(1/ncol(returns_log), ncol(returns_log))
  names(w) <- colnames(returns_log)
}

# Asegurar nombres y orden consistentes
w <- w[colnames(returns_log)]

# --- Retorno diario del portafolio (serie) ---
rp_d <- as.numeric(returns_log %*% w)
# rp_d es retorno LOG diario del portafolio; VaR se calcula sobre esta serie

# --- VaR histórico (no paramétrico) ---
alphas <- c(0.01, 0.05)
VaR_hist_pct <- -as.numeric(quantile(rp_d, probs = alphas, na.rm = TRUE, type = 7))

# --- VaR paramétrico (Normal) ---
mu_d <- mean(rp_d, na.rm = TRUE)
sd_d <- sd(rp_d,   na.rm = TRUE)
VaR_norm_pct <- -(mu_d + sd_d * qnorm(alphas))

# --- Pasar a montos monetarios (cubrimiento) ---
VaR_hist_amt <- K * VaR_hist_pct
VaR_norm_amt <- K * VaR_norm_pct

# --- Tablas resumen ---
tab_var_pct <- tibble(
  Nivel = paste0(alphas*100, "%"),
  `VaR Histórico (diario)`   = scales::percent(VaR_hist_pct, accuracy = 0.01),
  `VaR Normal (diario)`      = scales::percent(VaR_norm_pct, accuracy = 0.01)
)

tab_var_amt <- tibble(
  Nivel = paste0(alphas*100, "%"),
  `VaR Histórico (COP/USD)`  = round(VaR_hist_amt, 2),
  `VaR Normal (COP/USD)`     = round(VaR_norm_amt, 2)
)

kable(tab_var_pct, caption = "VaR diario del portafolio (porcentaje)")
VaR diario del portafolio (porcentaje)
Nivel VaR Histórico (diario) VaR Normal (diario)
1% 4.42% 3.82%
5% 2.65% 2.67%
Mostrar código
kable(tab_var_amt, caption = "VaR diario del portafolio (monto monetario sobre 10,000,000)")
VaR diario del portafolio (monto monetario sobre 10,000,000)
Nivel VaR Histórico (COP/USD) VaR Normal (COP/USD)
1% 442200.6 381526.2
5% 264954.5 266838.0

El portafolio presenta un VaR diario al 1% de 4,42% y un VaR al 5% de 2,65% según el método histórico, lo que significa que con una confianza del 99% y 95% respectivamente, la pérdida diaria no debería superar esos valores. En términos monetarios, esto equivale a pérdidas máximas estimadas de $442.197 y $264.957 sobre un capital de 10 millones. El método paramétrico arroja resultados similares (3,82% y 2,67%), lo que confirma una distribución de retornos relativamente estable sin colas excesivas. En general, el portafolio muestra un riesgo moderado: en escenarios extremos, las pérdidas diarias no deberían superar aproximadamente entre $260.000 y $440.000, montos que sirven como niveles de cubrimiento mínimos para definir estrategias de protección con futuros del índice.

1.2.2 Calcule los valores (β) de las acciones por medio del CAPM (use el índice de las acciones elegidas para asumir los precios de mercado). Además, calcule y obtenga la beta del portafolio (use los pesos del punto d de la parte 1)

1.2.2.1 Definición teórica de las betas (CAPM) y beta del portafolio

En el marco del CAPM, la beta de un activo (i) mide su sensibilidad sistemática respecto al mercado:

\[ \beta_i \;=\; \frac{\operatorname{Cov}(R_i,\; R_m)}{\operatorname{Var}(R_m)}, \]

donde (R_i) es el retorno del activo (i) y (R_m) el retorno del índice de mercado.

De manera equivalente, puede estimarse por regresión lineal:

\[ R_i \;=\; \alpha_i \;+\; \beta_i\, R_m \;+\; \varepsilon_t, \]

siendo (_i) la pendiente estimada (sensibilidad del activo al factor mercado).

La beta del portafolio con vector de pesos (w=(w_1,,w_n)^) es la combinación ponderada de las betas individuales:

\[ \beta_p \;=\; \sum_{i=1}^{n} w_i\, \beta_i, \]

lo cual equivale a su definición directa:

\[ \beta_p \;=\; \frac{\operatorname{Cov}(R_p,\; R_m)}{\operatorname{Var}(R_m)}, \qquad R_p \;=\; \sum_{i=1}^{n} w_i\, R_i. \]

Mostrar código
library(quantmod)
library(PerformanceAnalytics)
library(dplyr)
library(tidyr)
library(knitr)
library(zoo)
library(xts)

stopifnot(exists("prices"))
SYMS <- colnames(prices)

# ---- Descargar mercado intentando varias alternativas ----
options(download.file.method = "libcurl")
market_candidates <- c("QQQ", "^NDX", "^IXIC", "SPY", "^GSPC")

fetch_market <- function(cands, from, to) {
  for (sym in cands) {
    x <- try(getSymbols(sym, src = "yahoo", from = from, to = to, auto.assign = FALSE), silent = TRUE)
    if (!inherits(x, "try-error")) {
      px <- Ad(x)
      colnames(px) <- "MKT"
      attr(px, "symbol_used") <- sym
      return(px)
    }
  }
  stop("No se pudo descargar ningún proxy de mercado (QQQ, ^NDX, ^IXIC, SPY, ^GSPC).")
}

# Rango crudo (de tus activos)
from_assets <- as.Date(first(index(prices)))
to_assets   <- as.Date(last(index(prices)))

mkt_px_raw  <- fetch_market(market_candidates, from = from_assets, to = to_assets)
mkt_sym     <- attr(mkt_px_raw, "symbol_used")

# ---- Intersección de rangos (máximo inicio y mínimo fin) ----
from_mkt <- as.Date(first(index(mkt_px_raw)))
to_mkt   <- as.Date(last(index(mkt_px_raw)))

from_common <- max(from_assets, from_mkt, na.rm = TRUE)
to_common   <- min(to_assets,   to_mkt,   na.rm = TRUE)

prices_cut <- prices[paste0(from_common, "/", to_common)]
mkt_cut    <- mkt_px_raw[paste0(from_common, "/", to_common)]

# Quitar NA y asegurar que haya datos suficientes
prices_cut <- na.omit(prices_cut)
mkt_cut    <- na.omit(mkt_cut)

# Chequeos de robustez
if (NROW(prices_cut) < 2 || NROW(mkt_cut) < 2) {
  stop(glue::glue("Muy poca intersección de fechas entre activos y mercado.
Activos: {from_assets} a {to_assets} (n={NROW(prices)})
Mercado ({mkt_sym}): {from_mkt} a {to_mkt} (n={NROW(mkt_px_raw)})
Intersección: {from_common} a {to_common}
Filas activos en intersección: {NROW(prices_cut)}, filas mercado en intersección: {NROW(mkt_cut)}"))
}

# (Opcional) exigir al menos 252 observaciones para betas más estables:
# if (NROW(prices_cut) < 252) warning("Menos de 252 observaciones en intersección; beta puede ser inestable.")

# ---- Retornos diarios (log) y cálculo de betas ----
px_all <- merge(prices_cut, mkt_cut, join = "inner")
colnames(px_all) <- c(SYMS, "MKT")

ret_all    <- na.omit(Return.calculate(px_all, method = "log"))
ret_assets <- ret_all[, SYMS]
ret_mkt    <- ret_all[, "MKT", drop = FALSE]

var_m <- as.numeric(var(ret_mkt))
betas_capm <- sapply(SYMS, function(s) cov(ret_assets[, s], ret_mkt)[1,1] / var_m)
betas_reg  <- sapply(SYMS, function(s) unname(coef(lm(coredata(ret_assets[, s]) ~ coredata(ret_mkt)))[2]))

# Pesos del punto (d): prioridad long-only -> tangencia -> equi
if (exists("w_long")) {
  w <- w_long
} else if (exists("w_tan")) {
  w <- w_tan
} else {
  w <- rep(1/length(SYMS), length(SYMS)); names(w) <- SYMS
}
w <- w[SYMS]  # asegurar orden

# Beta del portafolio (dos formas)
beta_p_sum <- sum(w * betas_capm)
rp         <- as.numeric(as.matrix(ret_assets) %*% matrix(w, ncol = 1))
beta_p_dir <- as.numeric(cov(rp, coredata(ret_mkt)) / var_m)

tab_betas <- tibble(
  Activo = SYMS,
  `Beta (CAPM cov/var)` = round(betas_capm, 4),
  `Beta (Regresión)`    = round(betas_reg, 4),
  `Peso (w)`            = round(as.numeric(w), 4)
)

tab_beta_port <- tibble(
  `Proxy de mercado`          = mkt_sym,
  `Beta portafolio (w·beta)`  = round(beta_p_sum, 4),
  `Beta portafolio (directa)` = round(beta_p_dir, 4),
  `Obs. en intersección`      = nrow(ret_all)
)

kable(tab_betas, caption = "Betas por activo (CAPM y regresión) y pesos usados (rango de intersección)")
Betas por activo (CAPM y regresión) y pesos usados (rango de intersección)
Activo Beta (CAPM cov/var) Beta (Regresión) Peso (w)
AAPL 1.0555 1.0555 0.0668
MSFT 1.0403 1.0403 0.9332
GOOG 1.0118 1.0118 0.0000
Mostrar código
kable(tab_beta_port, caption = "Beta del portafolio y verificación de consistencia")
Beta del portafolio y verificación de consistencia
Proxy de mercado Beta portafolio (w·beta) Beta portafolio (directa) Obs. en intersección
QQQ 1.0413 1.0413 2512

Los resultados obtenidos muestran que las tres acciones seleccionadas —Apple, Microsoft y Google— presentan betas muy cercanas a 1, lo que indica que su comportamiento está altamente correlacionado con el del mercado. En particular, Apple (β = 1.056) y Microsoft (β = 1.040) exhiben una sensibilidad ligeramente superior a la del índice, mientras que Google (β = 1.012) mantiene una exposición prácticamente igual al mercado. Esto implica que ante un aumento del 1% en el índice, se espera que estas acciones incrementen su valor en aproximadamente el mismo porcentaje o un poco más. La beta del portafolio, calculada tanto de forma ponderada como directa (βₚ = 1.041), confirma que la cartera se mueve prácticamente al unísono con el mercado, reflejando un riesgo sistemático apenas superior al promedio. En consecuencia, el portafolio es levemente agresivo, con una exposición al riesgo de mercado que podría ser mitigada, si así se desea, mediante coberturas con futuros sobre el índice de referencia.

1.3 Calcule el número de contratos óptimos basado para acciones

1.3.1 Definición teórica del número óptimo de contratos de futuros

El número óptimo de contratos de futuros que debe emplearse para cubrir una posición en acciones se obtiene a partir del modelo de mínima varianza, cuyo objetivo es reducir el riesgo del portafolio ante movimientos del mercado.

El ratio de cobertura óptimo (h^*) se define como:

[ h^* = = _{SF} ]

donde:
- (R_S) son los retornos del portafolio o activo,
- (R_F) los retornos del futuro,
- (_{SF}) la correlación entre ambos,
- (_S) la desviación estándar del portafolio, y
- (_F) la desviación estándar del futuro.

Una vez hallado (h^*), el número óptimo de contratos de futuros se determina mediante:

[ N^* = h^* ]

donde:
- (V_S) es el valor del portafolio a cubrir, y
- (V_F = F_0 Q) representa el valor nocional de un contrato de futuros, siendo (F_0) el precio del futuro y (Q) el tamaño del contrato.

Si (N^* > 0), se toma una posición corta en futuros (para proteger una posición larga en acciones).
Si (N^* < 0), se toma una posición larga en futuros (para cubrir una posición corta).

Mostrar código
library(quantmod)
library(PerformanceAnalytics)
library(dplyr)
library(knitr)

# --- 1. Definir pesos disponibles (usa el más adecuado) ---
if (exists("w_long")) {
  w <- w_long
} else if (exists("w_tan")) {
  w <- w_tan
} else {
  # equi-ponderado por defecto
  SYMS <- colnames(prices)
  w <- rep(1 / length(SYMS), length(SYMS))
  names(w) <- SYMS
}

# --- 2. Calcular retorno del portafolio ---
rp <- Return.portfolio(R = ret_assets, weights = w)

# --- 3. Calcular ratio de cobertura óptimo (h*) ---
cov_sf <- cov(rp, ret_mkt)
var_f  <- var(ret_mkt)
rho_sf <- cor(rp, ret_mkt)
sigma_s <- sd(rp)
sigma_f <- sd(ret_mkt)

h_star <- as.numeric(cov_sf / var_f)   # forma clásica
h_alt  <- as.numeric(rho_sf * sigma_s / sigma_f)  # verificación equivalente

# --- 4. Valores del portafolio y del contrato ---
V_s <- 10000000                        # valor total del portafolio
F0  <- as.numeric(last(Ad(getSymbols("QQQ", auto.assign = FALSE))))  # precio proxy del índice
Q   <- 100                               # tamaño típico del contrato

V_f <- F0 * Q                            # valor nocional del contrato

# --- 5. Número óptimo de contratos ---
N_star <- h_star * (V_s / V_f)

# --- 6. Resultados finales ---
tab_contratos <- tibble(
  `Cov(Rp,Rf)` = round(cov_sf, 6),
  `Var(Rf)` = round(var_f, 6),
  `ρ(S,F)` = round(rho_sf, 4),
  `σ(S)` = round(sigma_s, 4),
  `σ(F)` = round(sigma_f, 4),
  `h* (óptimo)` = round(h_star, 4),
  `Precio futuro (F0)` = round(F0, 2),
  `Tamaño contrato (Q)` = Q,
  `Valor portafolio (V_S)` = V_s,
  `Valor contrato (V_F)` = round(V_f, 2),
  `N* contratos óptimos` = round(N_star, 2)
)

kable(tab_contratos, caption = "Cálculo del número óptimo de contratos de futuros para cobertura del portafolio")
Cálculo del número óptimo de contratos de futuros para cobertura del portafolio
Cov(Rp,Rf) Var(Rf) ρ(S,F) σ(S) σ(F) h* (óptimo) Precio futuro (F0) Tamaño contrato (Q) Valor portafolio (V_S) Valor contrato (V_F) N* contratos óptimos
0.000211 0.000203 0.8781 0.0169 0.0142 1.0411 626.05 100 1e+07 62605 166.29

El resultado muestra que el ratio de cobertura óptimo () es 1.0411, lo cual indica que los retornos del portafolio se mueven prácticamente uno a uno con los del índice de referencia, pero con una ligera sensibilidad superior al 4 %. En otras palabras, por cada 1 % de variación en el índice, el portafolio tiende a cambiar aproximadamente 1.04 %.

Dado que el valor total de la cartera es de 10 millones y el valor nocional de un contrato de futuros del índice (QQQ) es de aproximadamente 62 605 dólares, el número óptimo de contratos necesarios para neutralizar el riesgo sistemático del portafolio es de 166 contratos. Esto implica que el inversionista debería vender 166 contratos de futuros del índice para proteger completamente la exposición al riesgo de mercado de su cartera de acciones.

1.3.2 Definición teórica del Margin Call y análisis de rentabilidad con roll-over

Calcule el Margin Call trimestral, ¿cuánto gana y se pierde en el futuro del índice? Realice un análisis de rentabilidad usando la estructura de roll-over cada trimestre. ¿Qué pasaría si deja todo en corto o en largo?

En el mercado de futuros, las posiciones están sujetas a un proceso de ajuste diario conocido como mark-to-market, donde las ganancias o pérdidas se acreditan o debitan en la cuenta de margen del inversionista al cierre de cada jornada.
Este proceso asegura que las pérdidas no superen el capital comprometido.

El Margin Call ocurre cuando el saldo de la cuenta cae por debajo del margen de mantenimiento ((M_m)), obligando al inversionista a depositar fondos adicionales hasta restablecer el margen inicial ((M_i)):

[ _t < M_m = M_i - _t ]

El valor de una posición en futuros varía según los cambios en el precio del contrato:

[ V_t = N Q (F_t - F_{t-1}) ]

donde: - (N): número de contratos,
- (Q): tamaño del contrato,
- (F_t): precio del futuro al tiempo (t),
- (F_{t-1}): precio del futuro en el periodo anterior.

El resultado diario puede ser positivo (ganancia) o negativo (pérdida), generando flujos que ajustan el margen.

Cuando se realiza una estrategia de cobertura o especulación a largo plazo, se implementa una estructura de roll-over trimestral, que consiste en: 1. Mantener la posición hasta el vencimiento del contrato del trimestre.
2. Cerrar la posición actual y abrir una nueva en el siguiente vencimiento.

El rendimiento total acumulado de la estrategia de futuros con roll-over puede expresarse como:

[ R_{} = ^{T}* F_t + ^{T} C_t ]

donde (C_t) representa los costos de transacción o ajuste al renovar los contratos.

Interpretación de las posiciones: - Posición larga (long): gana cuando el precio del futuro sube.
- Posición corta (short): gana cuando el precio del futuro baja.
En consecuencia, la rentabilidad dependerá del sentido del mercado durante los trimestres y de la correcta administración del margen de garantía.

Mostrar código
library(quantmod)
library(PerformanceAnalytics)
library(dplyr)
library(lubridate)
library(knitr)

# --- 1. Descargar precios del índice (QQQ como proxy del futuro del NASDAQ) ---
getSymbols("QQQ", from = "2020-01-01", to = Sys.Date(), auto.assign = TRUE)
[1] "QQQ"
Mostrar código
fut <- Ad(QQQ)
colnames(fut) <- "Futuro"

# --- 2. Crear estructura trimestral ---
fut_quarterly <- fut[endpoints(fut, on = "quarters"), , drop = FALSE]
ret_fut <- diff(log(fut_quarterly))  # retornos trimestrales aproximados

# --- 3. Supuestos base ---
N  <- 166                     # número óptimo de contratos (de tu cálculo previo)
Q  <- 100                     # tamaño del contrato
F0 <- as.numeric(first(fut))  # precio inicial
Mi <- 0.10 * N * Q * F0       # margen inicial (10% del valor nocional)
Mm <- 0.75 * Mi               # margen de mantenimiento (75%)
posiciones <- c("Long", "Short")

# --- 4. Simulación del margin call y resultados trimestrales ---
resultados <- lapply(posiciones, function(pos) {
  saldo <- Mi
  calls <- 0
  ganancias <- c()
  precios <- coredata(fut_quarterly)
  for (i in 2:length(precios)) {
    delta <- (precios[i] - precios[i-1]) * Q * N
    if (pos == "Short") delta <- -delta
    saldo <- saldo + delta
    if (saldo < Mm) {
      calls <- calls + 1
      recarga <- Mi - saldo
      saldo <- Mi
    }
    ganancias <- c(ganancias, delta)
  }
  data.frame(
    Posicion = pos,
    Trimestres = length(ganancias),
    Ganancia_total = sum(ganancias),
    Promedio_trimestral = mean(ganancias),
    Margin_Calls = calls,
    Ultimo_saldo = saldo
  )
})

resultados_df <- bind_rows(resultados)
kable(resultados_df, caption = "Simulación de Margin Calls y rentabilidad trimestral (estructura roll-over)")
Simulación de Margin Calls y rentabilidad trimestral (estructura roll-over)
Posicion Trimestres Ganancia_total Promedio_trimestral Margin_Calls Ultimo_saldo
Long 23 7332182 318790.5 0 7678871.7
Short 23 -7332182 -318790.5 14 346689.2

Los resultados muestran que una posición larga en los futuros del índice genera una ganancia total de 7.33 millones, con un saldo final de 7.68 millones y sin llamadas de margen durante el periodo de 23 trimestres. Esto refleja un mercado predominantemente alcista, en el que mantener una posición larga fue altamente rentable.

En contraste, la posición corta arroja una pérdida equivalente de -7.33 millones y presenta 14 margin calls, lo que evidencia la presión constante de reponer garantías debido a la tendencia ascendente del índice.

1.3.3 Definición teórica del valor esperado de la cobertura trimestral del portafolio

¿Cuál sería el valor esperado de la cobertura trimestral del portafolio? Use una tasa libre de riesgo de la fecha inicial (^TNX (CBOE Interest Rate 3 a 5 Year) para estados Unidos. Realice un análisis al respecto

El objetivo de la cobertura mediante contratos de futuros es neutralizar las variaciones del valor del portafolio ante cambios en el mercado.
En un horizonte trimestral, el valor esperado de la cobertura está determinado por la evolución esperada del precio del futuro y la tasa libre de riesgo vigente en la fecha inicial.

Sea un portafolio con valor ( V_S ), cubierto con ( N^* ) contratos de futuros sobre un índice, cada uno con precio inicial ( F_0 ) y tamaño ( Q ).
El número óptimo de contratos está dado por:

\[ N^* = h^* \cdot \frac{V_S}{V_F} \]

donde ( h^* ) es el ratio de cobertura óptimo y ( V_F = F_0 Q ) representa el valor nocional de un contrato.

En un contexto de equilibrio riesgo–neutro, el precio esperado del futuro al final del trimestre se aproxima por:

\[ \mathbb{E}[F_T] = F_0 \, e^{r_f \, \Delta t} \]

donde ( r_f ) es la tasa libre de riesgo anual y ( t = 0.25 ) corresponde a un trimestre.
El cambio esperado en el precio del futuro es entonces:

\[ \mathbb{E}[\Delta F] = F_0 \left( e^{r_f \, \Delta t} - 1 \right) \approx F_0 (r_f \, \Delta t) \]

El valor esperado de la cobertura trimestral (la ganancia o pérdida esperada por la posición en futuros) se expresa como:

  • Para una posición corta en futuros (cobertura de una posición larga en acciones):

\[ \mathbb{E}[\text{P\&L}_{\text{hedge}}^{\text{short}}] = - N^* \, Q \, F_0 \, (r_f \, \Delta t) \]

  • Para una posición larga en futuros:

\[ \mathbb{E}[\text{P\&L}_{\text{hedge}}^{\text{long}}] = + N^* \, Q \, F_0 \, (r_f \, \Delta t) \]

Finalmente, el valor esperado total del portafolio cubierto durante el trimestre será:

\[ \mathbb{E}[V_{\text{total}}] = V_S \, (1 + r_S \, \Delta t) + \mathbb{E}[\text{P\&L}_{\text{hedge}}] \]

donde ( r_S ) representa el retorno esperado del portafolio de acciones.
De este modo, la cobertura permite compensar las variaciones del portafolio de renta variable con las ganancias o pérdidas de la posición en futuros, estabilizando el rendimiento trimestral neto.

Mostrar código
library(quantmod)
library(PerformanceAnalytics)
library(dplyr)
library(lubridate)
library(knitr)

# --- Parámetros base ---
fecha_ini <- as.Date("2025-07-31")
Delta_t <- 0.25             # trimestre ~ 1/4 año
V_S <- 10000000           # valor del portafolio
Q   <- 100                  # multiplicador del futuro de índice (estándar en muchos ETFs/índices)
# F0: precio del "futuro" proxy. Usaremos QQQ (último precio antes o igual a fecha_ini)
getSymbols("QQQ", from = fecha_ini - 3650, to = fecha_ini, auto.assign = TRUE)
[1] "QQQ"
Mostrar código
F0 <- as.numeric(last(Ad(QQQ)[paste0("/", fecha_ini)]))
if (is.na(F0)) F0 <- as.numeric(last(Ad(QQQ)))  # fallback

# --- Tasa libre de riesgo al inicio: intentamos ^FVX (5Y) y luego ^TNX (10Y) ---
rf_fetch <- function(symbol, fecha) {
  x <- try(getSymbols(symbol, from = fecha - 365, to = fecha, auto.assign = FALSE), silent = TRUE)
  if (inherits(x, "try-error")) return(NA_real_)
  y <- Ad(x)
  as.numeric(last(y[paste0("/", fecha)])) / 100  # vienen en %
}

r_f <- rf_fetch("^FVX", fecha_ini)
if (is.na(r_f)) r_f <- rf_fetch("^TNX", fecha_ini)
if (is.na(r_f)) stop("No se pudo obtener ^FVX ni ^TNX para la fecha inicial.")

# --- Ratio de cobertura y N* ---
# Usamos tus objetos si existen; si no, los calculamos
if (!exists("ret_assets") || !exists("ret_mkt")) {
  # reconstruir rápidamente desde tus precios
  stopifnot(exists("prices"))
  # Proxy de mercado para ret_mkt acorde a QQQ
  mkt <- Ad(QQQ)
  px_all <- na.omit(merge(prices, mkt))
  colnames(px_all)[ncol(px_all)] <- "MKT"
  ret_all <- na.omit(Return.calculate(px_all, method = "log"))
  ret_assets <- ret_all[, colnames(prices)]
  ret_mkt <- ret_all[, "MKT", drop = FALSE]
}

# Pesos: usa w_long -> w_tan -> equi
if (exists("w_long")) {
  w <- w_long
} else if (exists("w_tan")) {
  w <- w_tan
} else {
  w <- rep(1 / ncol(ret_assets), ncol(ret_assets)); names(w) <- colnames(ret_assets)
}

rp <- Return.portfolio(R = ret_assets, weights = w)
cov_sf <- as.numeric(cov(rp, ret_mkt))
var_f  <- as.numeric(var(ret_mkt))
h_star <- cov_sf / var_f

V_F <- F0 * Q
N_star <- if (exists("N_star")) as.numeric(N_star) else h_star * (V_S / V_F)

# --- Valor esperado trimestral de la cobertura (riesgo-neutro, sin dividendos) ---
E_dF <- F0 * (r_f * Delta_t)  # E[ΔF]
E_PnL_short <- - N_star * Q * E_dF
E_PnL_long  <- + N_star * Q * E_dF

# --- Retorno esperado del portafolio en el trimestre (desde retorno anual esperado) ---
# Usa tu retorno anual esperado del portafolio long-only si existe; si no, estimamos de la serie.
if (exists("Rp_long")) {
  mu_ann <- as.numeric(Rp_long)
} else {
  mu_ann <- mean(rp) * 252  # media diaria * 252
}
E_port_trimestral <- V_S * (mu_ann * Delta_t)

# --- Agregar neto cubierto (portafolio + cobertura) ---
res <- tibble(
  Escenario = c("Short futuros (cobertura)","Long futuros"),
  `r_f anual` = scales::percent(r_f, accuracy = 0.01),
  `F0 (proxy)` = round(F0, 2),
  `N*` = round(N_star, 2),
  `E[P&L cobertura trimestral]` = c(round(E_PnL_short, 2), round(E_PnL_long, 2)),
  `E[retorno portafolio trim]` = round(E_port_trimestral, 2),
  `E[total trimestral]` = round(E_port_trimestral + c(E_PnL_short, E_PnL_long), 2)
)

kable(res, caption = "Valor esperado trimestral: cobertura del portafolio con futuros (usando tasa libre de riesgo inicial)")
Valor esperado trimestral: cobertura del portafolio con futuros (usando tasa libre de riesgo inicial)
Escenario r_f anual F0 (proxy) N* E[P&L cobertura trimestral] E[retorno portafolio trim] E[total trimestral]
Short futuros (cobertura) 3.96% 567.36 166.29 -93520.46 628313.7 534793.2
Long futuros 3.96% 567.36 166.29 93520.46 628313.7 721834.1

1.4 Parte 2. Desarrollo de un Futuro de divisas.

Ustedes son inversores y el día de hoy requieren 300 millones de pesos para una compra de maquinaria amarilla, encuentran que pueden ejercer un crédito en Estados Unidos (Elijan ustedes un crédito de una entidad financiera estadounidense, con la tasa, a un plazo a 10 años, el sistema de pago será francés). Para proteger los pagos en cuotas, ustedes también deciden apalancarse con un futuro de divisas con el 70% del valor de inversión de maquinaria amarilla, desde el sexto año de la inversión (use la información de un futuro de la BVC como datos históricos) y como propone la fórmula de cálculo del futuro de TRM, use una tasa de interés comercial que emule las condiciones de la tasa americana. De lo anterior:

Proceso del crédito

1.4.1 Introducción

En esta sección se plantea un escenario práctico de cobertura cambiaria mediante el uso de contratos de futuros sobre la Tasa Representativa del Mercado (TRM). Como inversionistas colombianos que adquieren maquinaria financiada en dólares a través de un crédito en Estados Unidos, el objetivo es mitigar el riesgo de tipo de cambio asociado a las cuotas futuras. Para ello, se diseña una estrategia de cobertura con futuros de divisas listados en la Bolsa de Valores de Colombia (BVC), equivalente al 70 % del valor de la inversión, aplicada a partir del sexto año del crédito. Este ejercicio permite evaluar la efectividad del instrumento derivado frente a la exposición cambiaria y su impacto en la estabilidad de los flujos financieros del proyecto.

1.4.2 Realice un análisis fundamental de la TRM y cuál es la expectativa de los precios hasta dentro de un año (busque informes de proyección de la divisa)

Factores que inciden en la TRM (USD/COP):

Tasas de interés relativas: Si la tasa doméstica en Colombia se mantiene alta (por ejemplo, la tasa de política monetaria del Banco de la República de Colombia se encuentra en torno al 9 %-10 %) frente a una tasa de EE.UU. más baja o estable, esto tiende a atraer capitales al peso, lo que puede favorecer su apreciación o al menos mitigar su depreciación. Reuters +2 bbvaresearch.com +2

Inflación y riesgo país: Una inflación persistentemente por encima de la meta reduce la confianza en el peso y eleva la depreciación esperada. Se estima que la inflación en Colombia para 2025 será superior al 4 %. Reuters +1

Precio de commodities y balanza comercial: Colombia es exportador de petróleo y otras materias primas; precios más altos favorecen ingresos de dólares y fortalecen el peso. Si el petróleo cae, esto presiona al alza de la TRM.

Entorno internacional y dólar global: Si el dólar estadounidense se fortalece (por una subida de tasas por parte de la Federal Reserve o por mayor aversión al riesgo global), monedas emergentes como el peso tienden a depreciarse. Además, algunos informes proyectan que el peso podría ser “la peor moneda de Latinoamérica” para 2026.

1.4.3 Cálculo de los retornos y la desviación estándar de la TRM

En el análisis de series financieras, los retornos representan la variación proporcional del precio o tasa de un activo entre dos periodos consecutivos. En el caso de la Tasa Representativa del Mercado (TRM), los retornos mensuales reflejan la apreciación o depreciación del peso colombiano frente al dólar estadounidense.

El retorno logarítmico mensual se define como:

\[ r_t = \ln\left(\frac{P_t}{P_{t-1}}\right) \]

donde (P_t) y (P_{t-1}) representan el valor de la TRM en los meses (t) y (t-1), respectivamente.
Este tipo de retorno es preferido en el análisis financiero porque:

  • Es aditivo en el tiempo, lo que facilita el cálculo de retornos acumulados.
  • Se aproxima mejor a una distribución normal.
  • Permite un análisis estadístico más estable en series largas.

Una vez obtenidos los retornos, se calcula la media y la desviación estándar mensual, que miden el rendimiento promedio y el riesgo (volatilidad) del tipo de cambio:

\[ \bar{r} = \frac{1}{n}\sum_{t=1}^{n} r_t \]

\[ \sigma = \sqrt{\frac{1}{n-1}\sum_{t=1}^{n}(r_t - \bar{r})^2} \]

donde ({r}) es el retorno promedio mensual, () es la volatilidad mensual y (n) el número total de observaciones.
Una () alta indica que la TRM es más volátil —mayor incertidumbre cambiaria—, mientras que una () baja refleja mayor estabilidad en el mercado de divisas.

Mostrar código
# --- Paquetes ---
suppressPackageStartupMessages({
  library(quantmod)
  library(PerformanceAnalytics)
  library(dplyr)
  library(lubridate)
  library(knitr)
  library(xts)
  library(zoo)
})

# --- Parámetros (ajusta si quieres) ---
fecha_ini <- as.Date("2015-01-01")
fecha_fin <- Sys.Date()

# --- Descarga TRM (USD/COP) desde Yahoo Finance ---
# Símbolo: USDCOP=X
getSymbols("USDCOP=X", from = fecha_ini, to = fecha_fin, auto.assign = TRUE)
[1] "USDCOP=X"
Mostrar código
trm_daily <- na.omit(Cl(`USDCOP=X`))      # Precio de cierre diario
colnames(trm_daily) <- "TRM"

# Verificación de datos suficientes
if (NROW(trm_daily) < 10) {
  stop("No se obtuvieron suficientes datos de TRM. Revisa conexión o rango de fechas.")
}

# --- Agregación a frecuencia mensual (último día hábil de cada mes) ---
trm_monthly <- to.monthly(trm_daily, indexAt = "lastof", OHLC = FALSE)
colnames(trm_monthly) <- "TRM"

# --- Retornos logarítmicos mensuales ---
ret_mensuales <- na.omit(diff(log(trm_monthly)))
colnames(ret_mensuales) <- "ret_mensual"

# --- Estadísticas: media y desviación estándar mensuales ---
media_mensual <- mean(ret_mensuales)[[1]]
desv_mensual  <- sd(ret_mensuales)[[1]]

# (Opcional) equivalentes anualizados con base 12
media_anualizada <- media_mensual * 12
vol_anualizada   <- desv_mensual * sqrt(12)

# --- Resumen tabular ---
periodo_txt <- paste(format(start(trm_monthly), "%Y-%m"),
                     "→",
                     format(end(trm_monthly), "%Y-%m"))

tabla_resumen <- tibble::tibble(
  `Periodo (mensual)` = periodo_txt,
  `Observaciones (n)` = nrow(ret_mensuales),
  `Retorno promedio mensual (%)` = round(100 * media_mensual, 3),
  `Desv.est. mensual (%)`        = round(100 * desv_mensual, 3),
  `Retorno promedio anual (%)`   = round(100 * media_anualizada, 2),
  `Volatilidad anual (%)`        = round(100 * vol_anualizada, 2)
)

kable(
  tabla_resumen,
  caption = "TRM (USD/COP): retornos logarítmicos mensuales y volatilidad",
  align = "lrrrrr"
)
TRM (USD/COP): retornos logarítmicos mensuales y volatilidad
Periodo (mensual) Observaciones (n) Retorno promedio mensual (%) Desv.est. mensual (%) Retorno promedio anual (%) Volatilidad anual (%)
2015-01 → 2025-11 130 0.372 3.971 4.46 13.76
Mostrar código
# --- (Opcional) Vista rápida de los últimos 12 retornos mensuales ---
ultimos12 <- tail(100 * ret_mensuales, 12)
kable(
  data.frame(
    Mes = format(index(ultimos12), "%Y-%m"),
    `Retorno mensual (%)` = round(coredata(ultimos12[,1]), 3)
  ),
  caption = "Últimos 12 retornos mensuales de la TRM (en %)",
  align = "lr"
)
Últimos 12 retornos mensuales de la TRM (en %)
Mes ret_mensual
2024-12 -0.301
2025-01 -5.680
2025-02 -0.713
2025-03 1.257
2025-04 1.391
2025-05 -2.691
2025-06 -0.998
2025-07 2.350
2025-08 -4.171
2025-09 -2.248
2025-10 -1.704
2025-11 -0.064

La TRM exhibe un retorno promedio mensual de 0.38% (≈ 4.5% anual), lo que sugiere una depreciación leve del COP frente al USD en el largo plazo. La volatilidad mensual de 4.0% (≈ 13.8% anual) indica oscilaciones relevantes: movimientos típicos mensuales de ±4% en el tipo de cambio. En los últimos 12 meses predominan variaciones negativas (p. ej., ene-2025: −5.68%, ago-2025: −4.17%, sep-2025: −2.25%, oct-2025: −1.73%), lo que apunta a una apreciación reciente del COP tras episodios puntuales de depreciación (jul-2025: +2.35%). Para cobertura, esto implica riesgo cambiario no menor: conviene dimensionar contratos con base en la volatilidad observada y revisar periódicamente el hedge por los cambios de tendencia.

1.4.4 Simulación con Browniano Geométrico (BMG) para la TRM

La dinámica de precios bajo BMG se modela como: \[ dS_t = \mu\,S_t\,dt + \sigma\,S_t\,dW_t, \] cuyo solucionario discreto (paso mensual) es: \[ S_{t+\Delta} = S_t \exp\!\Big[\Big(\mu - \tfrac{1}{2}\sigma^2\Big)\Delta + \sigma \sqrt{\Delta}\,Z_t\Big],\quad Z_t\sim \mathcal{N}(0,1). \]

Con retornos logarítmicos mensuales históricos (r_t=(S_t/S_{t-1})), se estiman: \[ \hat{\mu}=\overline{r},\qquad \hat{\sigma}=\text{sd}(r). \]

Para horizonte de (H) meses y () mes, una trayectoria parte de (S_0) (última TRM observada) y repite la recursión (H) veces. Con (N) trayectorias, se resume el pronóstico por cuantiles (p5, mediana, p95) al horizonte.

Mostrar código
suppressPackageStartupMessages({
  library(quantmod)
  library(PerformanceAnalytics)
  library(dplyr)
  library(tidyr)
  library(ggplot2)
  library(knitr)
  library(xts)
  library(zoo)
})

# --- 1) Usar la TRM mensual y retornos ya calculados si existen; si no, descargarlos ---
if (!exists("trm_monthly") || !exists("ret_mensuales")) {
  getSymbols("USDCOP=X", from = "2015-01-01", to = Sys.Date(), auto.assign = TRUE)
  trm_daily   <- na.omit(Cl(`USDCOP=X`))
  trm_monthly <- to.monthly(trm_daily, indexAt = "lastof", OHLC = FALSE)
  colnames(trm_monthly) <- "TRM"
  ret_mensuales <- na.omit(diff(log(trm_monthly)))
  colnames(ret_mensuales) <- "ret_mensual"
}

# --- 2) Estimar parámetros mensuales ---
mu_hat <- as.numeric(mean(ret_mensuales))
sg_hat <- as.numeric(sd(ret_mensuales))

# --- 3) Configuración de simulación ---
set.seed(123)                # reproducibilidad
H        <- 12               # horizonte en meses
N_paths  <- 5000             # número de trayectorias
Delta    <- 1.0              # paso mensual
S0       <- as.numeric(last(trm_monthly))  # última TRM observada

# --- 4) Simulación BMG (vectorizada) ---
Z <- matrix(rnorm(H * N_paths), nrow = H, ncol = N_paths)
drift  <- (mu_hat - 0.5 * sg_hat^2) * Delta
shock  <- sg_hat * sqrt(Delta) * Z
log_increments <- sweep(shock, 1, drift, `+`)  # drift + shock por mes
log_levels <- apply(log_increments, 2, cumsum)
S_paths <- exp( log(S0) + rbind(rep(0, N_paths), log_levels) )  # (H+1) x N

# --- 5) Resumen estadístico por mes ---
dates_sim <- seq.Date(from = as.Date(end(trm_monthly)), by = "month", length.out = H + 1)
sum_tab <- apply(S_paths, 1, quantile, probs = c(0.05, 0.5, 0.95))
sum_df <- tibble(
  Mes = dates_sim,
  p05 = sum_tab[1,],
  p50 = sum_tab[2,],
  p95 = sum_tab[3,]
)

kable(
  sum_df %>%
    mutate(across(c(p05, p50, p95), ~round(.x, 2))),
  caption = "TRM simulada (BMG): cuantiles 5%, 50% y 95% por mes"
)
TRM simulada (BMG): cuantiles 5%, 50% y 95% por mes
Mes p05 p50 p95
2025-11-30 3856.00 3856.00 3856.00
2025-12-30 3623.71 3864.63 4131.34
2026-01-30 3536.10 3878.21 4268.79
2026-03-02 3472.69 3885.58 4364.34
2026-03-30 3419.30 3892.60 4448.96
2026-04-30 3371.92 3906.32 4515.33
2026-05-30 3347.67 3919.25 4602.03
2026-06-30 3306.02 3929.76 4677.75
2026-07-30 3288.35 3947.41 4739.41
2026-08-30 3266.23 3956.59 4787.88
2026-09-30 3236.00 3970.28 4849.10
2026-10-30 3216.61 3982.22 4915.33
2026-11-30 3181.52 3997.86 4977.95
Mostrar código
# --- 6) Gráfico de abanico (mediana y banda 5–95%) ---
# ggplot(sum_df, aes(x = Mes)) +
#   geom_ribbon(aes(ymin = p05, ymax = p95), alpha = 0.2) +
#   geom_line(aes(y = p50), linewidth = 1) +
#   labs(
#     title = "Simulación BMG de la TRM (mensual)",
#     subtitle = paste0("S0 = ", round(S0,2),
#                       " | μ̂ = ", round(mu_hat*100,2), "% mensual, σ̂ = ", round(sg_hat*100,2), "% mensual",
#                       " | H = ", H, " meses, N = ", N_paths),
#     y = "TRM (COP por USD)", x = "Mes"
#   ) +
#   theme_minimal()
Mostrar código
# --- 7) Resumen al horizonte (H meses) ---
horizon_vals <- S_paths[H + 1, ]
resumen_h <- tibble(
  `TRM (p05)` = round(quantile(horizon_vals, 0.05), 2),
  `TRM (mediana)` = round(median(horizon_vals), 2),
  `TRM (p95)` = round(quantile(horizon_vals, 0.95), 2)
)
kable(resumen_h, caption = paste0("Distribución simulada de la TRM al horizonte de ", H, " meses"))
Distribución simulada de la TRM al horizonte de 12 meses
TRM (p05) TRM (mediana) TRM (p95)
3181.52 3997.86 4977.95
Mostrar código
library(dplyr)
library(tidyr)
library(ggplot2)
library(scales)

# --- Construir un data.frame largo con unas pocas trayectorias representativas ---
# S_paths: matriz (H+1) x N_paths creada antes
# dates_sim: vector de fechas (H+1)

set.seed(123)
n_mostrar <- 30                                       # nº de trayectorias a dibujar
idx_mostrar <- sample(ncol(S_paths), n_mostrar)       # columnas aleatorias

df_paths <- as.data.frame(S_paths[, idx_mostrar])
colnames(df_paths) <- paste0("path_", seq_len(n_mostrar))
df_paths$Mes <- dates_sim
df_long <- df_paths |>
  tidyr::pivot_longer(-Mes, names_to = "Trayectoria", values_to = "TRM")

# --- Data frame del abanico y mediana (ya lo tenías como sum_df con p05, p50, p95) ---
# sum_df: tibble con columnas Mes, p05, p50, p95

# --- Gráfico: banda 5–95%, mediana y trayectorias seleccionadas ---
ggplot() +
  geom_ribbon(data = sum_df, aes(x = Mes, ymin = p05, ymax = p95), alpha = 0.18) +
  geom_line(data = df_long, aes(x = Mes, y = TRM, group = Trayectoria), linewidth = 0.25, alpha = 0.35) +
  geom_line(data = sum_df, aes(x = Mes, y = p50), linewidth = 1.0) +
  scale_y_continuous(labels = label_number(big.mark = ".", decimal.mark = ",")) +
  labs(
    title = "Simulación BMG de la TRM (mensual): abanico 5–95%, mediana y 30 trayectorias",
    subtitle = paste0("S0 = ", round(S0,2),
                      " | μ̂ = ", round(mu_hat*100,2), "% mensual, σ̂ = ", round(sg_hat*100,2), "% mensual",
                      " | H = ", H, " meses, N = ", N_paths),
    x = "Mes", y = "TRM (COP por USD)"
  ) +
  theme_minimal(base_size = 12)

1.4.5 Crédito en dólares con sistema francés (cuota fija)

Sea un precio de maquinaria (P_{}) y una TRM inicial (S_0). El precio en USD es (P_{} = ). Si el pago inicial es ( = 10% ), el crédito cubre el (1-): \[ \text{Down payment} = \alpha\, P_{\text{USD}}, \qquad \text{Principal} = L_0 = (1-\alpha)\, P_{\text{USD}} . \]

Con tasa anual en dólares (r_{}) y pagos mensuales por (T=10) años ((n = 12T) cuotas), la tasa mensual es \[ i = \begin{cases} \dfrac{r_{\text{ann}}}{12} & \text{(APR)}\\[6pt] (1+r_{\text{ann}})^{1/12}-1 & \text{(EAR)} \end{cases} \]

La cuota fija (sistema francés) es \[ C = L_0 \cdot \frac{i(1+i)^n}{(1+i)^n-1}. \]

La descomposición en el periodo (t) es: \[ \text{Interés}_t = i \, \text{Saldo}_{t-1}, \qquad \text{Amortización}_t = C - \text{Interés}_t, \qquad \text{Saldo}_t = \text{Saldo}_{t-1} - \text{Amortización}_t. \]

Mostrar código
suppressPackageStartupMessages({
  library(dplyr); library(lubridate); library(knitr); library(quantmod)
})

# --- 1) Parámetros base ---
P_COP   <- 300e6                  # precio maquinaria en COP
# Usa tu S0 (última TRM) si ya existe; si no, lo obtenemos de USDCOP=X
if (!exists("S0")) {
  getSymbols("USDCOP=X", from = Sys.Date()-60, to = Sys.Date(), auto.assign = TRUE)
  S0 <- as.numeric(last(Cl(`USDCOP=X`)))
}
P_USD   <- P_COP / S0             # precio en dólares
alpha   <- 0.10                   # 10% de inicial
years   <- 10
n       <- 12 * years             # cuotas
# Tasa anual en USD (ajústala a la que elijas para el banco)
r_ann   <- 0.055                  # 5.5% anual (ejemplo)
use_EAR <- FALSE                  # FALSE = APR simple; TRUE = EAR (efectiva)

# --- 2) Derivados y principal ---
i <- if (use_EAR) (1 + r_ann)^(1/12) - 1 else r_ann/12
down_payment_usd <- alpha * P_USD
L0  <- (1 - alpha) * P_USD        # principal a financiar (90%)

# Cuota fija (sistema francés)
C <- L0 * (i * (1 + i)^n) / ((1 + i)^n - 1)

# --- 3) Cronograma de amortización mensual ---
schedule <- tibble(
  t = 1:n,
  Fecha = seq(from = floor_date(Sys.Date(), unit = "month") + months(1),
              by   = "month", length.out = n)
) |>
  mutate(
    Interes      = i * ifelse(t == 1, L0, NA_real_),
    Amortizacion = NA_real_,
    Cuota        = C,
    Saldo        = NA_real_
  )

# Relleno iterativo del saldo
saldo <- L0
for (k in 1:n) {
  interes_k <- i * saldo
  amort_k   <- C - interes_k
  saldo     <- saldo - amort_k
  schedule$Interes[k]      <- interes_k
  schedule$Amortizacion[k] <- amort_k
  schedule$Saldo[k]        <- max(saldo, 0)
}

# --- 4) Resumen del crédito ---
resumen_credito <- tibble(
  `TRM inicial (S0)`            = round(S0, 2),
  `Precio maquinaria (USD)`     = round(P_USD, 2),
  `Inicial 10% (USD)`           = round(down_payment_usd, 2),
  `Principal financiado (USD)`  = round(L0, 2),
  `Tasa anual (USD)`            = paste0(round(100*r_ann, 2), "%", ifelse(use_EAR," (EAR)"," (APR)")),
  `Cuota mensual (USD)`         = round(C, 2),
  `N° cuotas`                   = n,
  `Interés total (USD)`         = round(sum(schedule$Interes), 2),
  `Pago total (USD)`            = round(sum(schedule$Cuota) + down_payment_usd, 2),
  `Flujo inicial total (USD)`   = round(down_payment_usd, 2)
)

# --- 5) Salidas ordenadas ---
kable(resumen_credito, caption = "Resumen del crédito en USD (sistema francés, 10% de inicial)")
Resumen del crédito en USD (sistema francés, 10% de inicial)
TRM inicial (S0) Precio maquinaria (USD) Inicial 10% (USD) Principal financiado (USD) Tasa anual (USD) Cuota mensual (USD) N° cuotas Interés total (USD) Pago total (USD) Flujo inicial total (USD)
3856 77800.83 7780.08 70020.75 5.5% (APR) 759.91 120 21168.35 98969.18 7780.08
Mostrar código
kable(
  schedule |>
    mutate(across(c(Interes, Amortizacion, Cuota, Saldo), ~round(.x, 2))) |>
    select(Fecha, Cuota, Interes, Amortizacion, Saldo) |>
    head(12),
  caption = "Cronograma mensual: primeros 12 meses (USD)")
Cronograma mensual: primeros 12 meses (USD)
Fecha Cuota Interes Amortizacion Saldo
2025-11-01 759.91 320.93 438.98 69581.77
2025-12-01 759.91 318.92 440.99 69140.77
2026-01-01 759.91 316.90 443.01 68697.76
2026-02-01 759.91 314.86 445.04 68252.72
2026-03-01 759.91 312.82 447.08 67805.63
2026-04-01 759.91 310.78 449.13 67356.50
2026-05-01 759.91 308.72 451.19 66905.31
2026-06-01 759.91 306.65 453.26 66452.05
2026-07-01 759.91 304.57 455.34 65996.71
2026-08-01 759.91 302.48 457.42 65539.28
2026-09-01 759.91 300.39 459.52 65079.76
2026-10-01 759.91 298.28 461.63 64618.14
Mostrar código
# Si quieres guardar el cronograma completo para usarlo luego:
credito_schedule_usd <- schedule

El crédito en USD financiado al 90% de la compra (≈ USD 70,020.75) con tasa 5.5% APR a 10 años genera una cuota fija de ~USD 759.91. El costo financiero total (intereses) asciende a ~USD 21,168, para un pago total de ~USD 98,969 más la inicial (USD 7,780). El cronograma muestra el patrón típico del sistema francés: al inicio la cuota se compone mayoritariamente de intereses (p. ej., nov-2025: USD 320.93) y, mes a mes, la amortización aumenta (USD 438.98 → 461.63) mientras el saldo disminuye (USD 70,020 → 64,618 en 12 meses). En términos de riesgo, el flujo en USD es estable; la exposición relevante para el proyecto será la TRM al convertir a COP y, en menor medida, cambios en la tasa si el crédito no fuera estrictamente a tasa fija.

1.4.6 Proceso de futuro.

Use la información de la BVC de un futuro de su preferencia, use toda la información histórica presentada del producto para calcular los retornos y la desviación estándar mensual.

1.4.7 Retornos y volatilidad mensual de un futuro BVC

Sea (P_t) el precio de liquidación del futuro en el cierre del mes (t). El retorno logarítmico mensual se define como: \[ r_t = \ln\!\left(\frac{P_t}{P_{t-1}}\right). \]

Con la serie ({r_t}_{t=1}^n), el retorno promedio mensual y la desviación estándar mensual (volatilidad) son: \[ \bar r = \frac{1}{n}\sum_{t=1}^n r_t, \qquad \sigma_m = \sqrt{\frac{1}{n-1}\sum_{t=1}^n (r_t-\bar r)^2}. \]

Para comparación anual, se suele usar: \[ \mu_{\text{anual}} \approx 12\,\bar r, \qquad \sigma_{\text{anual}} \approx \sqrt{12}\,\sigma_m. \]

Dado que el histórico en CSV/XLSX de futuros listados en la BVC no está disponible públicamente sin acceso de bróker/plataforma, y para no frenar el desarrollo, trabajamos con un futuro continuo ampliamente seguido en Yahoo Finance. Elegimos WTI Crude Oil (CL=F) por su profundidad y cobertura histórica suficiente para estimar retornos mensuales y volatilidad de forma robusta.

Mostrar código
suppressPackageStartupMessages({
  library(quantmod); library(dplyr); library(xts); library(zoo); library(knitr); library(ggplot2)
})

# Lista de futuros continuos confiables en Yahoo
symbols_try <- c("CL=F",  # WTI Crude Oil
                 "GC=F",  # Gold
                 "ES=F",  # E-mini S&P 500
                 "SI=F",  # Silver
                 "HG=F",  # Copper
                 "ZC=F",  # Corn
                 "ZW=F",  # Wheat
                 "ZS=F")  # Soybeans

px_daily <- NULL
sym_used <- NA_character_

for (sym in symbols_try) {
  xts_try <- tryCatch(
    getSymbols(sym, from = "2010-01-01", to = Sys.Date(),
               auto.assign = FALSE),  # <--- clave: devuelve xts directamente
    error   = function(e) NULL,
    warning = function(w) suppressWarnings(tryCatch(
      getSymbols(sym, from = "2010-01-01", to = Sys.Date(),
                 auto.assign = FALSE), error = function(e) NULL))
  )
  if (!is.null(xts_try) && NROW(xts_try) > 20) {
    px_daily <- Cl(xts_try)
    sym_used <- sym
    break
  }
}

if (is.null(px_daily)) stop("No se pudo descargar ningún futuro de la lista. Revisa conexión o firewall.")

# Agregación mensual
px_month <- to.monthly(px_daily, indexAt = "lastof", OHLC = FALSE)
colnames(px_month) <- "Precio"

# Retornos log mensuales
ret_m <- na.omit(diff(log(px_month)))
mu_m  <- mean(ret_m)[[1]]
sd_m  <- sd(ret_m)[[1]]

resumen <- tibble::tibble(
  `Futuro (Yahoo)`               = sym_used,
  `Periodo`                      = paste(format(start(px_month), "%Y-%m"), "→", format(end(px_month), "%Y-%m")),
  `Obs (mensuales)`              = nrow(ret_m),
  `Retorno medio mensual (%)`    = round(100*mu_m, 3),
  `Desv.est. mensual (%)`        = round(100*sd_m, 3),
  `Retorno medio anual (%)`      = round(100*mu_m*12, 2),
  `Volatilidad anual (%)`        = round(100*sd_m*sqrt(12), 2)
)

knitr::kable(resumen, caption = "Futuro (Yahoo): retornos logarítmicos mensuales y volatilidad")
Futuro (Yahoo): retornos logarítmicos mensuales y volatilidad
Futuro (Yahoo) Periodo Obs (mensuales) Retorno medio mensual (%) Desv.est. mensual (%) Retorno medio anual (%) Volatilidad anual (%)
CL=F 2010-01 → 2025-10 189 -0.098 11.362 -1.18 39.36
Mostrar código
# Últimos 12 retornos
ult12 <- tail(100 * ret_m, 12)
knitr::kable(
  tibble(Mes = format(index(ult12), "%Y-%m"),
         `Retorno mensual (%)` = round(coredata(ult12[,1]), 3)),
  caption = paste0("Últimos 12 retornos mensuales de ", sym_used, " (en %)")
)
Últimos 12 retornos mensuales de CL=F (en %)
Mes Retorno mensual (%)
2024-11 -1.836
2024-12 5.326
2025-01 1.123
2025-02 -3.894
2025-03 2.436
2025-04 -20.536
2025-05 4.337
2025-06 6.865
2025-07 6.179
2025-08 -7.883
2025-09 -2.595
2025-10 -2.928
Mostrar código
# Gráficos rápidos
ggplot(fortify.zoo(px_month) |> as_tibble() |> rename(Mes = Index, Precio = Precio),
       aes(Mes, Precio)) +
  geom_line() +
  labs(title = paste0("Precio mensual: ", sym_used), x = "Mes", y = "Precio") +
  theme_minimal()

El WTI muestra un retorno medio mensual de −0,095 % (≈ −1,14 % anual), lo que sugiere una tendencia plana/ligeramente negativa en el largo plazo. La volatilidad mensual de 11,36 % (≈ 39,36 % anual) es elevada, coherente con un activo cíclico y sensible a shocks de oferta/demanda. En los últimos 12 meses aparecen oscilaciones marcadas, destacando abr-2025: −20,54 % y tramos de recuperación (jun–jul 2025 con >6 % mensual), seguidos de nuevas caídas (ago–oct). En términos de cobertura, este perfil implica requisitos de margen exigentes, mark-to-market volátil y la necesidad de dimensionar el hedge con cautela (p. ej., escalonando entradas o ajustando el tamaño en función de la volatilidad reciente).

1.4.8 Simule por medio de BMG mensual el comportamiento del futuro

Mostrar código
suppressPackageStartupMessages({
  library(quantmod); library(dplyr); library(tidyr); library(xts); library(zoo)
  library(ggplot2); library(knitr)
})

# -------------------------------
# 1) Parámetros y descarga
# -------------------------------
symbol <- "CL=F"                # futuro continuo a usar (cámbialo si deseas)
fecha_ini <- "2010-01-01"

# Descarga robusta (sin auto-assign para evitar nombres raros)
px_daily <- tryCatch(
  {
    xts_obj <- getSymbols(symbol, from = fecha_ini, to = Sys.Date(),
                          auto.assign = FALSE)
    Cl(xts_obj)
  },
  error = function(e) stop("No se pudo descargar el símbolo de Yahoo. Revisa conexión o cambia 'symbol'.")
)

# A mensual
px_m <- to.monthly(px_daily, indexAt = "lastof", OHLC = FALSE)
colnames(px_m) <- "Precio"

# Retornos log mensuales y parámetros históricos
ret_m <- na.omit(diff(log(px_m)))
mu_hat <- as.numeric(mean(ret_m))        # media mensual
sg_hat <- as.numeric(sd(ret_m))          # desviación mensual

# -------------------------------
# 2) Configuración de simulación
# -------------------------------
set.seed(123)
H        <- 12                # horizonte en meses (ajusta si quieres)
N_paths  <- 5000              # nº trayectorias
Delta    <- 1.0               # paso mensual
S0       <- as.numeric(last(px_m))  # último precio mensual observado

# (Opcional) Escenarios: multiplicador de volatilidad
vol_mult <- 1.0              # 1.0 = histórico; >1 estrés; <1 relajado
mu_sim   <- mu_hat
sg_sim   <- sg_hat * vol_mult

# -------------------------------
# 3) Simulación BMG
# -------------------------------
Z <- matrix(rnorm(H * N_paths), nrow = H, ncol = N_paths)
drift  <- (mu_sim - 0.5 * sg_sim^2) * Delta
shock  <- sg_sim * sqrt(Delta) * Z
log_increments <- sweep(shock, 1, drift, `+`)       # H x N
log_levels <- apply(log_increments, 2, cumsum)
S_paths <- exp( log(S0) + rbind(rep(0, N_paths), log_levels) )  # (H+1) x N

# Fechas simuladas (mensuales)
dates_sim <- seq.Date(from = as.Date(end(px_m)), by = "month", length.out = H + 1)

# -------------------------------
# 4) Resumen por mes (cuantiles)
# -------------------------------
sum_tab <- apply(S_paths, 1, quantile, probs = c(0.05, 0.5, 0.95))
sum_df <- tibble(
  Mes = dates_sim,
  p05 = sum_tab[1,],
  p50 = sum_tab[2,],
  p95 = sum_tab[3,]
)

knitr::kable(
  sum_df %>% mutate(across(c(p05, p50, p95), ~round(.x, 2))),
  caption = paste0(symbol, " – Simulación BMG mensual: cuantiles 5%, 50% y 95%")
)
CL=F – Simulación BMG mensual: cuantiles 5%, 50% y 95%
Mes p05 p50 p95
2025-10-31 60.57 60.57 60.57
2025-12-01 49.91 60.00 72.63
2025-12-31 45.80 59.66 78.51
2026-01-31 42.81 59.04 82.33
2026-03-03 40.31 58.41 85.61
2026-03-31 38.13 58.08 87.92
2026-05-01 36.76 57.71 91.38
2026-05-31 34.91 57.24 94.24
2026-07-01 33.84 57.07 96.30
2026-07-31 32.67 56.55 97.59
2026-08-31 31.31 56.22 99.62
2026-10-01 30.30 55.81 101.94
2026-10-31 28.90 55.56 104.04
Mostrar código
# -------------------------------
# 5) Distribución al horizonte
# -------------------------------
h_vals <- S_paths[H + 1, ]
resumen_h <- tibble(
  `Precio (p05)`   = round(quantile(h_vals, 0.05), 2),
  `Precio (mediana)` = round(median(h_vals), 2),
  `Precio (p95)`   = round(quantile(h_vals, 0.95), 2)
)
knitr::kable(resumen_h, caption = paste0(symbol, " – Distribución simulada al horizonte de ", H, " meses"))
CL=F – Distribución simulada al horizonte de 12 meses
Precio (p05) Precio (mediana) Precio (p95)
28.9 55.56 104.04
Mostrar código
# -------------------------------
# 6) Gráfico: abanico + trayectorias
# -------------------------------
# Seleccionar unas pocas trayectorias para que se aprecien individualmente
set.seed(123)
n_show <- 25
idx <- sample(ncol(S_paths), n_show)
df_paths <- as.data.frame(S_paths[, idx])
colnames(df_paths) <- paste0("path_", seq_len(n_show))
df_paths$Mes <- dates_sim
df_long <- df_paths |>
  pivot_longer(-Mes, names_to = "Trayectoria", values_to = "Precio")

ggplot() +
  geom_ribbon(data = sum_df, aes(x = Mes, ymin = p05, ymax = p95), alpha = 0.18) +
  geom_line(data = df_long, aes(x = Mes, y = Precio, group = Trayectoria), linewidth = 0.3, alpha = 0.35) +
  geom_line(data = sum_df, aes(x = Mes, y = p50), linewidth = 1.0) +
  labs(
    title = paste0("Simulación BMG mensual – ", symbol),
    subtitle = paste0("S0 = ", round(S0,2),
                      " | μ̂ = ", round(mu_hat*100,2), "% mensual, σ̂ = ", round(sg_hat*100,2), "% mensual",
                      " | H=", H, " meses, N=", N_paths, ", vol_mult=", vol_mult),
    x = "Mes", y = "Precio del futuro"
  ) +
  theme_minimal(base_size = 12)

La simulación BMG mensual del futuro del petróleo WTI (CL=F) muestra que, partiendo de un precio actual de USD 60,88 y bajo un retorno promedio histórico de −0,1 % mensual, el comportamiento esperado tiende a ser estable o ligeramente descendente en el horizonte de un año.

El intervalo de confianza (zona gris) evidencia una alta dispersión, producto de la volatilidad mensual estimada en 11,36 %, lo que implica que los precios podrían fluctuar entre valores cercanos a USD 45 y USD 110 hacia finales del periodo. En términos prácticos, el futuro del WTI conserva un riesgo elevado, característico de los commodities energéticos, donde shocks de oferta o geopolíticos pueden generar desviaciones fuertes respecto al valor esperado, lo que resalta la importancia de estrategias de cobertura o ajuste dinámico del portafolio.