|
|
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)) |
|
|
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%}") |
|
|
|
|
|
|
|
|
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) |
|
|
|