Spaces:
Sleeping
Sleeping
| # ============================================================ | |
| # 📈 app.py — Backend FastAPI para predicciones BBVA y SANTANDER | |
| # ============================================================ | |
| from fastapi import FastAPI | |
| from pydantic import BaseModel | |
| from fastapi.middleware.cors import CORSMiddleware | |
| import torch | |
| from torch import nn | |
| import joblib | |
| import numpy as np | |
| import pandas as pd | |
| # ============================================================ | |
| # 1️⃣ Configuración base de FastAPI | |
| # ============================================================ | |
| app = FastAPI(title="Predicciones BBVA y SANTANDER API") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ============================================================ | |
| # 2️⃣ Definir ambos modelos (LSTM y GRU) | |
| # ============================================================ | |
| class LSTMModel(nn.Module): | |
| def __init__(self, n_features, hidden_size=64): | |
| super().__init__() | |
| self.lstm = nn.LSTM(input_size=n_features, hidden_size=hidden_size, batch_first=True) | |
| self.fc = nn.Linear(hidden_size, 1) | |
| def forward(self, x): | |
| out, _ = self.lstm(x) | |
| out = out[:, -1, :] | |
| out = self.fc(out) | |
| return out.squeeze() | |
| class GRUModel(nn.Module): | |
| def __init__(self, n_features, hidden_size=64, num_layers=1): | |
| super().__init__() | |
| self.gru = nn.GRU( | |
| input_size=n_features, | |
| hidden_size=hidden_size, | |
| num_layers=num_layers, | |
| batch_first=True | |
| ) | |
| self.fc = nn.Linear(hidden_size, 1) | |
| def forward(self, x): | |
| out, _ = self.gru(x) | |
| out = out[:, -1, :] | |
| out = self.fc(out) | |
| return out.squeeze() | |
| # ============================================================ | |
| # 3️⃣ Cargar ambos modelos y scalers | |
| # ============================================================ | |
| device = torch.device("cpu") | |
| # --- BBVA --- | |
| bbva_model = LSTMModel(n_features=5, hidden_size=64) | |
| bbva_model.load_state_dict(torch.load("models_bbva/bbva_lstm_model.pth", map_location=device)) | |
| bbva_model.to(device) | |
| bbva_model.eval() | |
| bbva_scaler_features = joblib.load("models_bbva/scaler_features_full.pkl") | |
| bbva_scaler_target = joblib.load("models_bbva/scaler_target.pkl") | |
| # --- SANTANDER --- | |
| santander_model = GRUModel(n_features=5, hidden_size=64) | |
| santander_model.load_state_dict(torch.load("models_santander/santander_gru_model.pth", map_location=device)) | |
| santander_model.to(device) | |
| santander_model.eval() | |
| santander_scaler_features = joblib.load("models_santander/santander_scaler_features_full.pkl") | |
| santander_scaler_target = joblib.load("models_santander/santander_scaler_target.pkl") | |
| # ============================================================ | |
| # 4️⃣ Modelo de entrada | |
| # ============================================================ | |
| class PredictRequest(BaseModel): | |
| n_future: int = 3 | |
| # ============================================================ | |
| # 5️⃣ Función genérica para predicción | |
| # ============================================================ | |
| def generar_prediccion(df, model, scaler_features, scaler_target, features, cols_to_scale, window_size, n_future): | |
| # Escalar | |
| df_scaled = df.copy() | |
| df_scaled[cols_to_scale] = scaler_features.transform(df[cols_to_scale]) | |
| # Última ventana | |
| last_window = df_scaled[features].iloc[-window_size:].values | |
| preds_scaled = [] | |
| window = last_window.copy() | |
| for _ in range(n_future): | |
| X_input = torch.tensor(window, dtype=torch.float32).unsqueeze(0) | |
| with torch.no_grad(): | |
| pred_scaled = model(X_input).cpu().numpy().flatten()[0] | |
| preds_scaled.append(pred_scaled) | |
| next_day = window[-1].copy() | |
| window = np.vstack([window[1:], next_day]) | |
| # Desescalar | |
| preds_real = scaler_target.inverse_transform(np.array(preds_scaled).reshape(-1, 1)).flatten().tolist() | |
| # Fechas futuras | |
| last_date = df.index[-1] | |
| future_dates = pd.date_range(last_date + pd.Timedelta(days=1), periods=n_future, freq="B") | |
| fechas = [str(d.date()) for d in future_dates] | |
| return fechas, preds_real | |
| # ============================================================ | |
| # 6️⃣ Endpoint raíz | |
| # ============================================================ | |
| def home(): | |
| return {"message": "✅ API de predicciones BBVA y Santander operativa"} | |
| # ============================================================ | |
| # 7️⃣ Endpoint principal | |
| # ============================================================ | |
| def predict(banco: str, req: PredictRequest): | |
| banco = banco.lower() | |
| n_future = req.n_future | |
| # === BBVA === | |
| if banco == "bbva": | |
| path = "data/bbva_pred.csv" | |
| df = pd.read_csv(path) | |
| df["Date"] = pd.to_datetime(df["Date"]) | |
| df = df.set_index("Date") | |
| df = df.loc["2021-01-01":"2025-10-31"] | |
| cols_to_scale = [ | |
| 'Open','High','Low','Close','Adj Close', | |
| 'ma_5','close_lag1','close_lag2','close_lag3', | |
| 'Volume','ibex_momentum_5d' | |
| ] | |
| features = ['Open','High','Low','return_1d','return_3d'] | |
| fechas, preds = generar_prediccion(df, bbva_model, bbva_scaler_features, bbva_scaler_target, features, cols_to_scale, 3, n_future) | |
| return {"banco": "BBVA", "fechas": fechas, "predicciones": preds} | |
| # === SANTANDER === | |
| elif banco == "santander": | |
| path = "data/santander_pred.csv" | |
| df = pd.read_csv(path) | |
| df["Date"] = pd.to_datetime(df["Date"]) | |
| df = df.set_index("Date") | |
| df = df.loc["2021-01-01":"2025-10-31"] | |
| cols_to_scale = [ | |
| 'Open','High','Low','Close','Adj Close', | |
| 'ma_5','close_lag1','close_lag2','close_lag3', | |
| 'Volume','ibex_momentum_5d' | |
| ] | |
| features = ['Open','High','Low','close_lag1','close_lag2'] | |
| fechas, preds = generar_prediccion(df, santander_model, santander_scaler_features, santander_scaler_target, features, cols_to_scale, 3, n_future) | |
| return {"banco": "Santander", "fechas": fechas, "predicciones": preds} | |
| # === Otro banco no válido === | |
| else: | |
| return {"error": "Banco no reconocido. Usa 'bbva' o 'santander'."} | |
| # ============================================================ | |
| # 8️⃣ Nuevo endpoint de simulación — Variar "Open" | |
| # ============================================================ | |
| class SimulateRequest(BaseModel): | |
| factor_open: float = 1.0 # por defecto, sin cambio | |
| n_future: int = 3 # número de días de predicción | |
| def simulate(banco: str, req: SimulateRequest): | |
| banco = banco.lower() | |
| n_future = req.n_future | |
| factor_open = req.factor_open | |
| # === BBVA === | |
| if banco == "bbva": | |
| path = "data/bbva_pred.csv" | |
| df = pd.read_csv(path) | |
| df["Date"] = pd.to_datetime(df["Date"]) | |
| df = df.set_index("Date") | |
| df = df.loc["2021-01-01":"2025-10-31"] | |
| cols_to_scale = [ | |
| 'Open','High','Low','Close','Adj Close', | |
| 'ma_5','close_lag1','close_lag2','close_lag3', | |
| 'Volume','ibex_momentum_5d' | |
| ] | |
| features = ['Open','High','Low','return_1d','return_3d'] | |
| # Escalar los datos | |
| df_scaled = df.copy() | |
| df_scaled[cols_to_scale] = bbva_scaler_features.transform(df[cols_to_scale]) | |
| # Tomar la última ventana y modificar solo "Open" | |
| last_window = df_scaled[features].iloc[-3:].values | |
| modified_window = last_window.copy() | |
| modified_window[:, 0] *= factor_open # multiplicar solo el "Open" | |
| preds_scaled = [] | |
| window = modified_window.copy() | |
| for _ in range(n_future): | |
| X_input = torch.tensor(window, dtype=torch.float32).unsqueeze(0) | |
| with torch.no_grad(): | |
| pred_scaled = bbva_model(X_input).cpu().numpy().flatten()[0] | |
| preds_scaled.append(pred_scaled) | |
| next_day = window[-1].copy() | |
| window = np.vstack([window[1:], next_day]) | |
| preds_real = bbva_scaler_target.inverse_transform(np.array(preds_scaled).reshape(-1, 1)).flatten().tolist() | |
| last_date = df.index[-1] | |
| future_dates = pd.date_range(last_date + pd.Timedelta(days=1), periods=n_future, freq="B") | |
| fechas = [str(d.date()) for d in future_dates] | |
| return { | |
| "banco": "BBVA", | |
| "factor_open": factor_open, | |
| "fechas": fechas, | |
| "predicciones": preds_real | |
| } | |
| # === SANTANDER === | |
| elif banco == "santander": | |
| path = "data/santander_pred.csv" | |
| df = pd.read_csv(path) | |
| df["Date"] = pd.to_datetime(df["Date"]) | |
| df = df.set_index("Date") | |
| df = df.loc["2021-01-01":"2025-10-31"] | |
| cols_to_scale = [ | |
| 'Open','High','Low','Close','Adj Close', | |
| 'ma_5','close_lag1','close_lag2','close_lag3', | |
| 'Volume','ibex_momentum_5d' | |
| ] | |
| features = ['Open','High','Low','close_lag1','close_lag2'] | |
| df_scaled = df.copy() | |
| df_scaled[cols_to_scale] = santander_scaler_features.transform(df[cols_to_scale]) | |
| last_window = df_scaled[features].iloc[-3:].values | |
| modified_window = last_window.copy() | |
| modified_window[:, 0] *= factor_open | |
| preds_scaled = [] | |
| window = modified_window.copy() | |
| for _ in range(n_future): | |
| X_input = torch.tensor(window, dtype=torch.float32).unsqueeze(0) | |
| with torch.no_grad(): | |
| pred_scaled = santander_model(X_input).cpu().numpy().flatten()[0] | |
| preds_scaled.append(pred_scaled) | |
| next_day = window[-1].copy() | |
| window = np.vstack([window[1:], next_day]) | |
| preds_real = santander_scaler_target.inverse_transform(np.array(preds_scaled).reshape(-1, 1)).flatten().tolist() | |
| last_date = df.index[-1] | |
| future_dates = pd.date_range(last_date + pd.Timedelta(days=1), periods=n_future, freq="B") | |
| fechas = [str(d.date()) for d in future_dates] | |
| return { | |
| "banco": "Santander", | |
| "factor_open": factor_open, | |
| "fechas": fechas, | |
| "predicciones": preds_real | |
| } | |
| # === Error === | |
| else: | |
| return {"error": "Banco no reconocido. Usa 'bbva' o 'santander'."} | |