from fastapi import FastAPI from pydantic import BaseModel import numpy as np import pandas as pd import tensorflow as tf from tensorflow.keras.models import load_model import json import os app = FastAPI( title="Forex LSTM Prediction API", description="Prediksi harga EUR/USD H+1 dengan LSTM menggunakan scaler harian", version="2.0" ) # ========================================================== # LOAD MODEL DAN KONFIGURASI # ========================================================== MODEL_PATH = "lstm_model.h5" PARAMS_PATH = "best_params.json" SCALER_FILE = "scaler_config.json" print("📥 Loading LSTM model...") model = load_model(MODEL_PATH, compile=False) print("📥 Loading best parameters...") with open(PARAMS_PATH, "r") as f: best_params = json.load(f) LOOKBACK = best_params.get("lookback", 7) FEATURE_ORDER = best_params.get("features", [ "mood_score", "t_pos", "t_neg", "c_pos", "c_neg", "norm_ema20", "norm_ema50", "norm_close" ]) # ========================================================== # LOAD SCALER CONFIG # ========================================================== def load_scaler_config(): if not os.path.exists(SCALER_FILE): print("⚠️ Scaler config not found, using default values.") return {"CLOSE_MIN": 1.05, "CLOSE_MAX": 1.15} with open(SCALER_FILE, "r") as f: return json.load(f) scaler_cfg = load_scaler_config() CLOSE_MIN = scaler_cfg["CLOSE_MIN"] CLOSE_MAX = scaler_cfg["CLOSE_MAX"] print(f"✅ Scaler range loaded: {CLOSE_MIN:.5f} - {CLOSE_MAX:.5f}") # ========================================================== # INPUT SCHEMA # ========================================================== class LSTMInput(BaseModel): data: list # ========================================================== # HELPER FUNCTIONS # ========================================================== def prepare_input(data): df = pd.DataFrame(data) missing_cols = [f for f in FEATURE_ORDER if f not in df.columns] if missing_cols: raise ValueError(f"Missing columns: {missing_cols}") X = df[FEATURE_ORDER].values[-LOOKBACK:] if X.shape[0] < LOOKBACK: raise ValueError(f"Need at least {LOOKBACK} timesteps, got {X.shape[0]}") X = np.expand_dims(X, axis=0) return X, df def inverse_scale(norm_value): """Denormalisasi nilai close dari [0,1] ke skala asli""" return (norm_value * (CLOSE_MAX - CLOSE_MIN)) + CLOSE_MIN # ========================================================== # ENDPOINT # ========================================================== @app.post("/predict") def predict_price(input_data: LSTMInput): try: X, df = prepare_input(input_data.data) pred_norm = model.predict(X)[0][0] pred_close = inverse_scale(pred_norm) last_date = pd.to_datetime(df["date"].iloc[-1]) next_date = (last_date + pd.Timedelta(days=1)).strftime("%Y-%m-%d") result = { "next_date": next_date, "predicted_norm_close": float(pred_norm), "predicted_close": float(pred_close), "scaler_used": {"min": CLOSE_MIN, "max": CLOSE_MAX}, "features_used": FEATURE_ORDER } return {"status": "ok", "result": result} except Exception as e: return {"status": "error", "message": str(e)} @app.get("/") def root(): return {"message": "Forex LSTM Prediction API is active!"}