import streamlit as st import yfinance as yf import numpy as np import pandas as pd from scipy.stats import norm import datetime import requests import os import plotly.graph_objects as go def obtener_tickers_desde_nombres(empresas): api_key = os.getenv("GEMINI_API_KEY") if not api_key: st.error("La variable de entorno 'GEMINI_API_KEY' no está definida.") return [] url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={api_key}" prompt = ( "Devuélveme únicamente una lista separada por comas con los tickers bursátiles reales de las siguientes empresas: " f"{empresas}. No expliques nada más, solo dame los tickers exactos, sin nombres ni texto adicional." ) headers = {"Content-Type": "application/json"} data = {"contents": [{"role": "user", "parts": [{"text": prompt}]}]} response = requests.post(url, headers=headers, json=data) if response.status_code != 200: st.error(f"Error {response.status_code} al consultar Gemini: {response.text}") return [] try: result = response.json() raw_text = result["candidates"][0]["content"]["parts"][0]["text"] tickers = [t.strip().upper() for t in raw_text.split(",") if t.strip()] return tickers except Exception: st.error("Error al procesar la respuesta de Gemini.") return [] st.title("Calculadora de VaR y CVaR con Gemini y Yahoo Finance") empresa_input = st.text_input("Escribe los nombres de las empresas separadas por coma (ej. Apple, Google, Meta):") fecha_inicio = st.date_input( "Selecciona la fecha de inicio para los históricos:", value=datetime.date(datetime.datetime.today().year, 1, 2), min_value=datetime.date(2000, 1, 1), max_value=datetime.date.today() ) confidence_percent = st.slider( "Nivel de confianza (%) [valores recomendados: 95% o 99%]", min_value=90, max_value=99, value=95, step=1 ) confidence_level = confidence_percent / 100 if st.button("Identificar Tickers") and empresa_input: tickers_detectados = obtener_tickers_desde_nombres(empresa_input) if len(tickers_detectados) >= 2: st.session_state["tickers"] = tickers_detectados base = int(100 / len(tickers_detectados)) pesos = [base] * (len(tickers_detectados) - 1) pesos.append(100 - sum(pesos)) # Ajuste final for i, ticker in enumerate(tickers_detectados): st.session_state[f"weight_{ticker}"] = pesos[i] else: st.warning("Se requieren al menos dos tickers válidos.") if "tickers" in st.session_state: tickers = st.session_state["tickers"] st.success(f"Tickers detectados: {', '.join(tickers)}") st.subheader("Asignar pesos a cada activo (múltiplos de 5%)") cols = st.columns(len(tickers)) total_weight = 0 weight_inputs = [] for i, ticker in enumerate(tickers): with cols[i]: key = f"weight_{ticker}" if key not in st.session_state: st.session_state[key] = float(round(100 / len(tickers), 0)) weight = st.number_input( f"{ticker} (%)", min_value=0.0, max_value=100.0, value=float(st.session_state[key]), key=key, step=5.0, format="%.0f" ) weight_inputs.append(weight) total_weight += weight st.markdown(f"**Suma actual:** {total_weight:.0f}%") if abs(total_weight - 100.0) > 0.01: st.warning("⚠️ La suma de los pesos debe ser exactamente 100% para continuar.") else: if st.button("Calcular VaR y CVaR"): weights = np.array(weight_inputs) / 100 start_date = fecha_inicio.strftime("%Y-%m-%d") end_date = datetime.datetime.today().strftime("%Y-%m-%d") data = yf.download(tickers, start=start_date, end=end_date)["Close"] if data.empty or data.isnull().all().all(): st.error("No se encontraron datos históricos para la fecha seleccionada.") else: data = data.dropna() returns = data.pct_change().dropna() portfolio_returns = returns.dot(weights) if portfolio_returns.empty: st.error("No se pudieron calcular retornos del portafolio.") else: tail_prob = 1 - confidence_level historical_VaR = np.percentile(portfolio_returns, tail_prob * 100) mean_ret = portfolio_returns.mean() std_ret = portfolio_returns.std() z_score = norm.ppf(tail_prob) parametric_VaR = mean_ret + z_score * std_ret simulated_returns = np.random.normal(mean_ret, std_ret, 10000) mc_VaR = np.percentile(simulated_returns, tail_prob * 100) historical_CVaR = portfolio_returns[portfolio_returns <= historical_VaR].mean() st.subheader("Resultados del Portafolio:") st.markdown(f"**Historical VaR:** {historical_VaR:.4%}") st.markdown(f"**Parametric VaR:** {parametric_VaR:.4%}") st.markdown(f"**Monte Carlo VaR:** {mc_VaR:.4%}") st.markdown(f"**Historical CVaR:** {historical_CVaR:.4%}") # 📈 Gráfico Plotly con leyenda clara y líneas completas hist_values = np.histogram(portfolio_returns.values, bins=50) max_y = max(hist_values[0]) + 1 fig = go.Figure() fig.add_trace(go.Histogram( x=portfolio_returns, nbinsx=50, marker_color='rgba(200,200,200,0.6)', name="Retornos del portafolio", hovertemplate="%{x:.2%}" )) for val, color, label in zip( [historical_VaR, parametric_VaR, mc_VaR], ["red", "blue", "green"], ["Historical VaR", "Parametric VaR", "Monte Carlo VaR"] ): fig.add_trace(go.Scatter( x=[val, val], y=[0, max_y], mode="lines", line=dict(color=color, dash="dash", width=2), name=label, hoverinfo="skip", showlegend=True )) fig.update_layout( title="Distribución de Retornos del Portafolio con líneas VaR", xaxis_title="Retorno diario", yaxis_title="Frecuencia", legend=dict( orientation="h", yanchor="top", y=-0.25, xanchor="center", x=0.5 ), plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', font=dict(color='white', size=14), margin=dict(t=80, l=40, r=40, b=100), height=500 ) st.plotly_chart(fig, use_container_width=True)