import streamlit as st import yfinance as yf import numpy as np import pandas as pd import datetime from scipy.stats import norm import plotly.graph_objects as go st.set_page_config(page_title="VaR & CVaR Calculator", layout="wide") st.title("📉 Portfolio VaR & CVaR Calculator") st.markdown(""" Esta app permite calcular el **Valor en Riesgo (VaR)** y el **CVaR** para un portafolio de empresas, usando tres métodos: histórico, paramétrico y simulación Monte Carlo. 👉 Solo selecciona las empresas, rango de fechas, nivel de confianza y el número de simulaciones para el método Monte Carlo. """) # ------------------------------------------------- # Funciones Auxiliares # ------------------------------------------------- @st.cache_data def fetch_data(tickers, start_date, end_date): data = yf.download(tickers, start=start_date, end=end_date)["Close"] return data.dropna() def compute_weights(returns): # Pesos inversamente proporcionales a la volatilidad volatilidad = returns.std() inv_vol = 1 / volatilidad weights = inv_vol / inv_vol.sum() return weights def calculate_metrics(returns, confidence_level, n_simulations=10000): tail_prob = 1 - confidence_level portfolio_returns = returns.dot(compute_weights(returns)) mean_ret = portfolio_returns.mean() std_ret = portfolio_returns.std() # Cálculos para el portafolio historical_VaR = np.percentile(portfolio_returns, tail_prob * 100) z_score = norm.ppf(tail_prob) parametric_VaR = mean_ret + z_score * std_ret simulated_returns = np.random.normal(mean_ret, std_ret, n_simulations) mc_VaR = np.percentile(simulated_returns, tail_prob * 100) historical_CVaR = portfolio_returns[portfolio_returns <= historical_VaR].mean() portfolio_metrics = { "mean_return": mean_ret, "std_return": std_ret, "historical_VaR": historical_VaR, "parametric_VaR": parametric_VaR, "mc_VaR": mc_VaR, "historical_CVaR": historical_CVaR, "portfolio_returns": portfolio_returns } return portfolio_metrics def calculate_individual_metrics(returns, tail_prob, z_score, n_simulations=10000): stats = [] for ticker in returns.columns: r = returns[ticker] m = r.mean() s = r.std() hvar = np.percentile(r, tail_prob * 100) pvar = m + z_score * s sim_r = np.random.normal(m, s, n_simulations) mcvar = np.percentile(sim_r, tail_prob * 100) hcvar = r[r <= hvar].mean() stats.append({ "Ticker": ticker, "Retorno Promedio": m, "Volatilidad": s, "VaR Histórico": hvar, "VaR Paramétrico": pvar, "VaR Monte Carlo": mcvar, "CVaR Histórico": hcvar }) return pd.DataFrame(stats) # ------------------------------------------------- # Configuración de la App (Sidebar) # ------------------------------------------------- company_options = { "Apple (AAPL)": "AAPL", "Tesla (TSLA)": "TSLA", "Microsoft (MSFT)": "MSFT", "Amazon (AMZN)": "AMZN", "Google (GOOGL)": "GOOGL", "Meta (META)": "META", "Netflix (NFLX)": "NFLX", "NVIDIA (NVDA)": "NVDA", "JPMorgan Chase (JPM)": "JPM", "UnitedHealth (UNH)": "UNH", "Coca-Cola (KO)": "KO", "PepsiCo (PEP)": "PEP", "Berkshire Hathaway (BRK-B)": "BRK-B", "Procter & Gamble (PG)": "PG", "Johnson & Johnson (JNJ)": "JNJ", "Walmart (WMT)": "WMT", "Visa (V)": "V", "Mastercard (MA)": "MA", "Grupo Bimbo (BIMBOA.MX)": "BIMBOA.MX", "Cemex (CEMEXCPO.MX)": "CEMEXCPO.MX", "América Móvil (AMXL.MX)": "AMXL.MX", "Grupo México (GMEXICOB.MX)": "GMEXICOB.MX", "Banorte (GFNORTEO.MX)": "GFNORTEO.MX", "FEMSA (FEMSAUBD.MX)": "FEMSAUBD.MX", "Televisa (TLEVISACPO.MX)": "TLEVISACPO.MX", "Alsea (ALSEA.MX)": "ALSEA.MX", "Liverpool (LIVEPOLC-1.MX)": "LIVEPOLC-1.MX", "Walmart de México (WALMEX.MX)": "WALMEX.MX" } with st.sidebar: st.header("🔧 Configuración del Análisis") selected_companies = st.multiselect("Selecciona las empresas del portafolio", list(company_options.keys())) start_date = st.date_input("Fecha de inicio", datetime.date(2022, 1, 1)) end_date = st.date_input("Fecha de fin", datetime.date.today()) confidence_level = st.select_slider("Nivel de confianza", options=[0.90, 0.95, 0.99], value=0.95) n_simulations = st.number_input("Número de simulaciones Monte Carlo", min_value=1000, max_value=100000, value=10000, step=1000) # Obtener tickers a partir de la selección tickers = [company_options[c] for c in selected_companies] # ------------------------------------------------- # Ejecución y Cálculos # ------------------------------------------------- if st.button("📊 Calcular VaR y CVaR"): if not tickers: st.warning("⚠️ Debes seleccionar al menos una empresa.") elif start_date >= end_date: st.warning("⚠️ La fecha de inicio debe ser anterior a la fecha de fin.") else: with st.spinner("Obteniendo datos y calculando..."): try: data = fetch_data(tickers, start_date, end_date) returns = data.pct_change().dropna() if returns.empty: st.error("❌ No se encontraron datos válidos para el rango y empresas seleccionadas.") st.stop() # Cálculo de métricas del portafolio y de cada empresa weights = compute_weights(returns) portfolio_metrics = calculate_metrics(returns, confidence_level, n_simulations) tail_prob = 1 - confidence_level z_score = norm.ppf(tail_prob) df_individual = calculate_individual_metrics(returns, tail_prob, z_score, n_simulations) # ------------------------------------------------- # Resultados del Portafolio # ------------------------------------------------- st.subheader("📌 Resultados del Portafolio") st.markdown(f"**Nivel de confianza:** {int(confidence_level * 100)}%") st.markdown("**Pesos calculados automáticamente (inversamente proporcionales a la volatilidad):**") weights_df = pd.DataFrame({ "Ticker": tickers, "Peso": weights.round(4).values }) st.dataframe(weights_df) col1, col2 = st.columns(2) with col1: st.metric("Retorno promedio diario", f"{portfolio_metrics['mean_return']:.5f}") st.metric("Volatilidad diaria", f"{portfolio_metrics['std_return']:.5f}") st.metric("VaR Histórico", f"{portfolio_metrics['historical_VaR']:.2%}") with col2: st.metric("VaR Paramétrico", f"{portfolio_metrics['parametric_VaR']:.2%}") st.metric("VaR Monte Carlo", f"{portfolio_metrics['mc_VaR']:.2%}") st.metric("CVaR Histórico", f"{portfolio_metrics['historical_CVaR']:.2%}") # ------------------------------------------------- # Resultados Individuales por Empresa # ------------------------------------------------- st.subheader("📊 Resultados Individuales por Empresa") df_individual_display = df_individual.style.format({ "Retorno Promedio": "{:.5f}", "Volatilidad": "{:.5f}", "VaR Histórico": "{:.2%}", "VaR Paramétrico": "{:.2%}", "VaR Monte Carlo": "{:.2%}", "CVaR Histórico": "{:.2%}" }) st.dataframe(df_individual_display) # Botones para descargar resultados csv_weights = weights_df.to_csv(index=False).encode('utf-8') st.download_button("Descargar Pesos", csv_weights, "pesos_portafolio.csv", "text/csv") csv_individual = df_individual.to_csv(index=False).encode('utf-8') st.download_button("Descargar Resultados Individuales", csv_individual, "resultados_individuales.csv", "text/csv") # ------------------------------------------------- # Creación de pestañas: una para el portafolio y otras para cada empresa # ------------------------------------------------- tab_names = ["Portfolio"] + selected_companies tabs = st.tabs(tab_names) # --- Colores para el portafolio --- portfolio_vaR_colors = ("tomato", "seagreen", "dodgerblue") # ----- Tab: Portafolio ----- with tabs[0]: st.markdown("## Gráficos del Portafolio") # Histograma de Rendimientos del Portafolio con líneas de VaR fig_port_hist = go.Figure() fig_port_hist.add_trace(go.Histogram( x=portfolio_metrics["portfolio_returns"], nbinsx=50, name="Rendimientos", marker=dict(color='rgba(70, 130, 180, 0.6)'), hovertemplate='Rendimiento: %{x:.2%}
Frecuencia: %{y}' )) for value, label, color in zip( [portfolio_metrics["historical_VaR"], portfolio_metrics["parametric_VaR"], portfolio_metrics["mc_VaR"]], ["VaR Histórico", "VaR Paramétrico", "VaR Monte Carlo"], portfolio_vaR_colors ): fig_port_hist.add_trace(go.Scatter( x=[value, value], y=[0, portfolio_metrics["portfolio_returns"].count() * 0.1], mode="lines", name=f"{label}: {value:.2%}", line=dict(dash="dash", color=color, width=2) )) fig_port_hist.update_layout( title="Distribución de Rendimientos del Portafolio", xaxis_title="Rendimiento Diario", yaxis_title="Frecuencia", template="plotly_white", hovermode="x unified", bargap=0.05, height=450 ) st.plotly_chart(fig_port_hist, use_container_width=True) # Serie Temporal de Rendimientos del Portafolio fig_port_line = go.Figure() fig_port_line.add_trace(go.Scatter( x=portfolio_metrics["portfolio_returns"].index, y=portfolio_metrics["portfolio_returns"], name="Rendimientos", mode="lines", line=dict(color="orange"), hovertemplate="Fecha: %{x}
Retorno: %{y:.2%}" )) for value, label, color in zip( [portfolio_metrics["historical_VaR"], portfolio_metrics["parametric_VaR"], portfolio_metrics["mc_VaR"]], ["VaR Histórico", "VaR Paramétrico", "VaR Monte Carlo"], portfolio_vaR_colors ): fig_port_line.add_trace(go.Scatter( x=[portfolio_metrics["portfolio_returns"].index[0], portfolio_metrics["portfolio_returns"].index[-1]], y=[value, value], mode="lines", name=f"{label}: {value:.2%}", line=dict(dash="dash", color=color, width=2) )) fig_port_line.update_layout( title="Serie Temporal de Rendimientos del Portafolio", xaxis_title="Fecha", yaxis_title="Rendimiento Diario", template="plotly_white", hovermode="x unified", height=450 ) st.plotly_chart(fig_port_line, use_container_width=True) # --- Paleta de colores para empresas (d3 Category10) --- category_colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"] # ----- Tabs: Gráficos Individuales de cada Empresa ----- for i, company in enumerate(selected_companies): ticker = company_options[company] # Extraer los valores del VaR para la empresa desde df_individual company_metrics = df_individual[df_individual["Ticker"] == ticker].iloc[0] # Asignar colores distintos para la empresa usando la paleta comp_hist_color = category_colors[i % len(category_colors)] comp_param_color = category_colors[(i+1) % len(category_colors)] comp_mc_color = category_colors[(i+2) % len(category_colors)] with tabs[i+1]: st.markdown(f"### {company}") # Primero, el histograma de rendimientos (gráfica de barras) fig_hist = go.Figure() fig_hist.add_trace(go.Histogram( x=returns[ticker], nbinsx=50, name="Rendimientos", marker=dict(color=comp_hist_color, opacity=0.6), hovertemplate='Rendimiento: %{x:.2%}
Frecuencia: %{y}' )) for value, label, color in zip( [company_metrics["VaR Histórico"], company_metrics["VaR Paramétrico"], company_metrics["VaR Monte Carlo"]], ["VaR Histórico", "VaR Paramétrico", "VaR Monte Carlo"], [comp_hist_color, comp_param_color, comp_mc_color] ): fig_hist.add_trace(go.Scatter( x=[value, value], y=[0, returns[ticker].count() * 0.1], mode="lines", name=f"{label}: {value:.2%}", line=dict(dash="dash", color=color, width=2) )) fig_hist.update_layout( title=f"{company}: Distribución de Rendimientos", xaxis_title="Rendimiento", yaxis_title="Frecuencia", template="plotly_white", bargap=0.05 ) st.plotly_chart(fig_hist, use_container_width=True) # Luego, la serie de precios (gráfica de líneas) fig_price = go.Figure() fig_price.add_trace(go.Scatter( x=data.index, y=data[ticker], mode="lines", name="Precio de Cierre", hovertemplate="Fecha: %{x}
Precio: %{y:.2f}" )) fig_price.update_layout( title=f"{company}: Serie de Precios", xaxis_title="Fecha", yaxis_title="Precio (USD)", template="plotly_white" ) st.plotly_chart(fig_price, use_container_width=True) st.success("✅ Cálculos completados exitosamente") # ------------------------------------------------- # Explicación de los Métodos de Cálculo # ------------------------------------------------- with st.expander("ℹ️ Más información sobre los métodos de cálculo"): st.markdown(""" **VaR Histórico:** Calcula el percentil de los rendimientos históricos. **VaR Paramétrico:** Utiliza la media, la desviación estándar y la distribución normal (z-score) para estimar el VaR. **VaR Monte Carlo:** Simula rendimientos usando una distribución normal basada en la media y la desviación estándar del portafolio. **CVaR Histórico:** Es el promedio de los rendimientos que están por debajo del VaR histórico. """) except Exception as e: st.error(f"❌ Ocurrió un error durante el cálculo: {e}")