from fastapi import FastAPI from pydantic import BaseModel import yfinance as yf import ta import pandas as pd import numpy as np from tensorflow.keras.models import load_model import joblib import warnings import os warnings.filterwarnings('ignore') app = FastAPI() print("🚀 Servidor FastAPI encendido. Esperando peticiones de n8n...") # ========================================================= # 🚨 TRUCO ANTI-CRASH: Arrancamos con la RAM vacía # ========================================================= MODELOS = {} # IMPORTANTE: Revisa que estos nombres coincidan EXACTAMENTE con los que subiste CONFIG = { "ORO": {"ticker": "GC=F", "modelo": "cerebro_oro.keras", "scaler": "scaler_oro.pkl"}, "NVDA": {"ticker": "NVDA", "modelo": "cerebro_nvidia.keras", "scaler": "scaler_nvda.pkl"}, "WTI": {"ticker": "CL=F", "modelo": "cerebro_wti.keras", "scaler": "scaler_wti.pkl"}, "NASDAQ": {"ticker": "^NDX", "modelo": "cerebro_nasdaq.keras", "scaler": "scaler_nasdaq.pkl"} } # 🚨 NUEVO: El servidor ahora espera recibir el símbolo y el sentimiento class PredictionRequest(BaseModel): symbol: str sentimiento: float = 0.0 # Si n8n no manda nada, asume Neutral (0.0) @app.post("/predecir") def predecir_mercado(req: PredictionRequest): activo_solicitado = req.symbol.upper() traductor = { "GC=F": "ORO", "XAUUSD=X": "ORO", "ORO": "ORO", "NVDA": "NVDA", "CL=F": "WTI", "WTI": "WTI", "USOIL": "WTI", "^IXIC": "NASDAQ", "^NDX": "NASDAQ", "NQ=F": "NASDAQ", "NASDAQ": "NASDAQ" } if activo_solicitado not in traductor: return {"error": f"Activo no soportado. Usa uno de estos: {list(CONFIG.keys())}"} id_activo = traductor[activo_solicitado] # ========================================================= # 🚨 LAZY LOADING: Solo carga la IA a la RAM si n8n la pide # ========================================================= if id_activo not in MODELOS: print(f"🧠 Despertando IA de {id_activo} por primera vez...") archivo_mod = CONFIG[id_activo]["modelo"] archivo_scl = CONFIG[id_activo]["scaler"] if not os.path.exists(archivo_mod) or not os.path.exists(archivo_scl): return {"error": f"¡Falta el archivo {archivo_mod} o {archivo_scl} en Hugging Face!"} try: MODELOS[id_activo] = { "ticker": CONFIG[id_activo]["ticker"], "modelo": load_model(archivo_mod), "escalador": joblib.load(archivo_scl) } except Exception as e: return {"error": f"Error al cargar la IA de {id_activo}: {str(e)}"} ticker = MODELOS[id_activo]["ticker"] modelo = MODELOS[id_activo]["modelo"] escalador = MODELOS[id_activo]["escalador"] # 🎯 Obtener Precio Vivo try: df_minuto = yf.download(ticker, period="1d", interval="1m", progress=False) if isinstance(df_minuto.columns, pd.MultiIndex): df_minuto.columns = df_minuto.columns.droplevel(1) precio_actual = float(df_minuto['Close'].dropna().iloc[-1]) except: precio_actual = 0.0 # 🧠 Obtener Datos para IA df = yf.download(ticker, period="150d", interval="1d", progress=False, auto_adjust=True) if isinstance(df.columns, pd.MultiIndex): df.columns = df.columns.droplevel(1) for col in ['Open', 'High', 'Low', 'Close', 'Volume']: df[col] = df[col].astype(float) if precio_actual == 0.0: precio_actual = float(df['Close'].dropna().iloc[-1]) # Calcular Indicadores Técnicos df['SMA_20'] = ta.trend.sma_indicator(df['Close'], window=20) df['SMA_50'] = ta.trend.sma_indicator(df['Close'], window=50) df['RSI_14'] = ta.momentum.rsi(df['Close'], window=14) df['MACD'] = ta.trend.macd(df['Close']) df['MACD_Signal'] = ta.trend.macd_signal(df['Close']) df['ATR_14'] = ta.volatility.average_true_range(df['High'], df['Low'], df['Close'], window=14) # ========================================================= # 🚨 INYECTAMOS EL SENTIMIENTO RECIBIDO DESDE N8N # ========================================================= df['Sentimiento'] = req.sentimiento df.dropna(inplace=True) # 🚨 Matriz final con las 12 VARIABLES 🚨 features = ['Open', 'High', 'Low', 'Close', 'Volume', 'SMA_20', 'SMA_50', 'RSI_14', 'MACD', 'MACD_Signal', 'ATR_14', 'Sentimiento'] X_live = df.tail(60)[features].values.astype(np.float32) X_scaled = escalador.transform(X_live) X_reshaped = np.reshape(X_scaled, (1, 60, len(features))) # Predicción prediccion = float(modelo.predict(X_reshaped, verbose=0)[0][0]) decision = "LONG (COMPRAR)" if prediccion > 0.5 else "SHORT (VENDER/ESPERAR)" confianza = round(prediccion * 100, 2) if prediccion > 0.5 else round((1 - prediccion) * 100, 2) return { "symbol": req.symbol, "ticker_ia": ticker, "precio_actual": precio_actual, "decision_ia": decision, "confianza": f"{confianza}%", "probabilidad_matematica": prediccion, "sentimiento_aplicado": req.sentimiento # Te lo devuelvo para que confirmes que llegó bien }