Bank-stock-api / app.py
marrtinagg's picture
Inicializar backend FastAPI con Dockerfile
35d2f50
# ============================================================
# 📈 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
# ============================================================
@app.get("/")
def home():
return {"message": "✅ API de predicciones BBVA y Santander operativa"}
# ============================================================
# 7️⃣ Endpoint principal
# ============================================================
@app.post("/predict/{banco}")
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
@app.post("/simulate/{banco}")
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'."}