File size: 5,258 Bytes
d61656f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80bf9cf
 
d61656f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d1d3783
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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
    }