| 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" |
| ) |
|
|
| |
| |
| |
| 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" |
| ]) |
|
|
| |
| |
| |
| 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}") |
|
|
| |
| |
| |
| class LSTMInput(BaseModel): |
| data: list |
|
|
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| @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!"} |
|
|