| 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. |
| """) |
|
|
| |
| |
| |
| @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): |
| |
| 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() |
|
|
| |
| 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) |
|
|
| |
| |
| |
| 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) |
|
|
| |
| tickers = [company_options[c] for c in selected_companies] |
|
|
| |
| |
| |
| 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() |
|
|
| |
| 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) |
|
|
| |
| |
| |
| 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%}") |
|
|
| |
| |
| |
| 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) |
|
|
| |
| 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") |
|
|
| |
| |
| |
| tab_names = ["Portfolio"] + selected_companies |
| tabs = st.tabs(tab_names) |
|
|
| |
| portfolio_vaR_colors = ("tomato", "seagreen", "dodgerblue") |
|
|
| |
| with tabs[0]: |
| st.markdown("## Gráficos del Portafolio") |
| |
| 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%}<br>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) |
|
|
| |
| 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}<br>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) |
|
|
| |
| category_colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", |
| "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", |
| "#bcbd22", "#17becf"] |
|
|
| |
| for i, company in enumerate(selected_companies): |
| ticker = company_options[company] |
| |
| company_metrics = df_individual[df_individual["Ticker"] == ticker].iloc[0] |
| |
| 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}") |
| |
| 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%}<br>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) |
|
|
| |
| 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}<br>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") |
|
|
| |
| |
| |
| 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}") |
|
|