VAR / app.py
VicMata's picture
Update app.py
821c8ab verified
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%}<extra></extra>"
))
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)