import yfinance as yf import pandas as pd import numpy as np from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error import datetime as dt import gradio as gr import plotly.express as px import plotly.graph_objects as go def download_price_data(ticker, start_date, end_date): df = yf.download(ticker, start=start_date, end=end_date, interval="1d", auto_adjust=False, progress=False) if df.columns.nlevels > 1: df.columns = df.columns.droplevel(1) # Flatten by dropping ticker level for single ticker compatibility if df.empty: raise ValueError(f"No se descargaron datos para {ticker} en ese rango de fechas.") df = df.reset_index()[["Date", "Close", "Volume"]].dropna() df["Date_ordinal"] = df["Date"].map(pd.Timestamp.toordinal) return df def fit_and_forecast_linear(df, test_size=30, horizon=30): if len(df) <= test_size + 5: raise ValueError("Muy pocos datos para el tamaño de prueba especificado.") X = df[["Date_ordinal"]].values y = df["Close"].values X_train, X_test = X[:-test_size], X[-test_size:] y_train, y_test = y[:-test_size], y[-test_size:] model = LinearRegression() model.fit(X_train, y_train) y_pred_test = model.predict(X_test) rmse = np.sqrt(mean_squared_error(y_test, y_pred_test)) mape = mean_absolute_percentage_error(y_test, y_pred_test) metrics = {"rmse": rmse, "mape": mape, "n_train": len(X_train), "n_test": len(X_test)} y_pred_all = model.predict(X).ravel() df_all = df.copy() df_all["Pred_Close_all"] = y_pred_all df_all["is_test"] = False df_all.loc[df_all.index[-test_size:], "is_test"] = True last_date = df["Date"].max() future_dates = [last_date + dt.timedelta(days=i) for i in range(1, horizon + 1)] future_dates_ordinal = np.array([d.toordinal() for d in future_dates]).reshape(-1, 1) future_pred = model.predict(future_dates_ordinal).ravel() future_df = pd.DataFrame({"Date": future_dates, "Predicted_Close": future_pred}) return metrics, df_all, future_df def add_sma(df, window_short=50, window_long=150): df = df.copy() df["SMA_short"] = df["Close"].rolling(window=window_short, min_periods=1).mean() df["SMA_long"] = df["Close"].rolling(window=window_long, min_periods=1).mean() return df def make_price_plot_with_sma(df_all, future_df, ticker, test_size, horizon, window_short=50, window_long=150): df_all_sma = add_sma(df_all, window_short=window_short, window_long=window_long) fig = go.Figure() fig.add_trace(go.Scatter(x=df_all_sma["Date"], y=df_all_sma["Close"], mode='lines', name='Histórico (Cierre)')) fig.add_trace(go.Scatter(x=df_all_sma["Date"], y=df_all_sma["Pred_Close_all"], mode='lines', name='Regresión (Ajuste)', line=dict(dash='dash'))) fig.add_trace(go.Scatter(x=df_all_sma["Date"], y=df_all_sma["SMA_short"], mode='lines', name=f'SMA {window_short} días', opacity=0.8)) fig.add_trace(go.Scatter(x=df_all_sma["Date"], y=df_all_sma["SMA_long"], mode='lines', name=f'SMA {window_long} días', opacity=0.8)) if test_size > 0 and test_size < len(df_all_sma): df_test = df_all_sma.tail(test_size) fig.add_trace(go.Scatter(x=df_test["Date"], y=df_test["Close"], mode='markers', name='Datos (Prueba)', marker=dict(symbol='circle'))) fig.add_trace(go.Scatter(x=df_test["Date"], y=df_test["Pred_Close_all"], mode='markers', name='Predicción (Prueba)', marker=dict(symbol='x'))) fig.add_trace(go.Scatter(x=future_df["Date"], y=future_df["Predicted_Close"], mode='lines', name='Predicción Futura', line=dict(dash='dot'))) fig.update_layout( title=f"{ticker}: Histórico, Regresión, SMAs y Predicción ({horizon} días)
SMA corta={window_short}, SMA larga={window_long}", xaxis_title="Fecha", yaxis_title="Precio de Cierre", hovermode="x unified", template="plotly_white" ) return fig def make_volume_plot(df_all, ticker): fig = px.bar(df_all, x="Date", y="Volume", title=f"{ticker}: Volumen Histórico") fig.update_layout(xaxis_title="Fecha", yaxis_title="Volumen", template="plotly_white") return fig def make_fundamentals_plot(ticker): t = yf.Ticker(ticker) fin = t.financials if fin is None or fin.empty: fig = go.Figure() fig.add_annotation(text=f"No hay datos financieros para {ticker}", x=0.5, y=0.5, showarrow=False) fig.update_layout(template="plotly_white") return fig fin = fin.T desired_cols = ["Total Revenue", "Gross Profit", "Net Income"] cols = [c for c in desired_cols if c in fin.columns] if not cols: fig = go.Figure() fig.add_annotation(text="No se encontraron columnas estándar de ingresos/beneficios.", x=0.5, y=0.5, showarrow=False) fig.update_layout(template="plotly_white") return fig fig = go.Figure() for col in cols: fig.add_trace(go.Scatter(x=fin.index, y=fin[col], mode='lines+markers', name=col)) fig.update_layout( title=f"{ticker}: Fundamentales Básicos (Estado de Resultados)", xaxis_title="Período (Año)", yaxis_title="Valor en Moneda de Reporte", hovermode="x unified", template="plotly_white" ) return fig def calculate_returns(df_all, future_df): if len(df_all) < 2: return 0.0, 0.0 historical_return = ((df_all["Close"].iloc[-1] - df_all["Close"].iloc[0]) / df_all["Close"].iloc[0]) * 100 projected_return = ((future_df["Predicted_Close"].iloc[-1] - df_all["Close"].iloc[-1]) / df_all["Close"].iloc[-1]) * 100 return historical_return, projected_return def stock_app(ticker, start_date, end_date, test_size, horizon, window_short, window_long): try: test_size = int(test_size) horizon = int(horizon) window_short = int(window_short) window_long = int(window_long) data = download_price_data(ticker, start_date, end_date) metrics, df_all, future_df = fit_and_forecast_linear(data, test_size=test_size, horizon=horizon) historical_return, projected_return = calculate_returns(df_all, future_df) fig_price = make_price_plot_with_sma(df_all, future_df, ticker, test_size, horizon, window_short, window_long) fig_volume = make_volume_plot(df_all, ticker) fig_fund = make_fundamentals_plot(ticker) metrics_text = (f"Símbolo: {ticker}\nIntervalo: {start_date} → {end_date}\n" f"n_entrenamiento: {metrics['n_train']}, n_prueba: {metrics['n_test']}\n" f"RMSE en prueba: {metrics['rmse']:.3f}\nMAPE en prueba: {metrics['mape']:.2%}\n" f"Rentabilidad Histórica: {historical_return:.2f}%\n" f"Rentabilidad Proyectada (Horizonte {horizon} días): {projected_return:.2f}%") return fig_price, fig_volume, fig_fund, metrics_text except Exception as e: return None, None, None, f"Error: {e}" theme = gr.themes.Soft( primary_hue="indigo", secondary_hue="blue", neutral_hue="gray", font=[gr.themes.GoogleFont("Inter"), gr.themes.GoogleFont("JetBrains Mono"), "system-ui"], ) with gr.Blocks(theme=theme) as demo: gr.Markdown("# Aplicación de Predicción y Análisis de Acciones\nIngresa un símbolo y rango de fechas para ajustar una regresión lineal, calcular métricas y ver predicciones con medias móviles. Solo para fines educativos—no es consejo financiero.") with gr.Row(): with gr.Column(scale=1): with gr.Accordion("Parámetros Principales", open=True): ticker = gr.Textbox(label="Símbolo (Ticker)", value="AAPL") start_date = gr.Textbox(label="Fecha Inicio (AAAA-MM-DD)", value="2018-01-01") end_date = gr.Textbox(label="Fecha Fin (AAAA-MM-DD)", value="2024-01-01") with gr.Accordion("Parámetros Avanzados", open=False): test_size = gr.Slider(10, 200, value=30, step=1, label="Días para Prueba") horizon = gr.Slider(5, 120, value=30, step=1, label="Horizonte Futuro (Días)") window_short = gr.Slider(5, 200, value=50, step=1, label="SMA Corta (Días)") window_long = gr.Slider(5, 400, value=150, step=1, label="SMA Larga (Días)") submit_btn = gr.Button("Analizar") with gr.Column(scale=2): with gr.Tabs(): with gr.Tab("Predicción de Precios"): price_plot = gr.Plot() with gr.Tab("Volumen"): volume_plot = gr.Plot() with gr.Tab("Fundamentales"): fund_plot = gr.Plot() with gr.Tab("Métricas"): metrics_text = gr.Textbox(lines=6, show_label=False) submit_btn.click( fn=stock_app, inputs=[ticker, start_date, end_date, test_size, horizon, window_short, window_long], outputs=[price_plot, volume_plot, fund_plot, metrics_text] ) demo.launch()