jtassos2025's picture
Initial Commit
41615f4
"""
model_utils.py β€” LSTM model utilities for inference with MC Dropout
Provides:
- load_model_and_scalers()
- prepare_input_sequence()
- predict_with_mc_dropout()
"""
import os
import numpy as np
import torch
import joblib
import pandas as pd
from retrain import LSTMModel, enable_mc_dropout, DEVICE
# ---------------------------------------------------------------------
# πŸ“₯ LOAD MODEL + SCALERS
# ---------------------------------------------------------------------
def load_model_and_scalers(ticker_name, horizon_name):
"""
Load trained LSTM model and associated scalers for a given ticker/horizon.
"""
base_dir = os.path.dirname(os.path.dirname(__file__))
model_dir = os.path.join(base_dir, "models", ticker_name)
model_path = os.path.join(model_dir, f"{ticker_name}_{horizon_name}_model.pth")
xscaler_path = os.path.join(model_dir, f"{ticker_name}_{horizon_name}_scaler.pkl")
yscaler_path = os.path.join(model_dir, f"{ticker_name}_{horizon_name}_y_scaler.pkl")
if not all(os.path.exists(p) for p in [model_path, xscaler_path, yscaler_path]):
raise FileNotFoundError(f"❌ Missing model/scaler files for {ticker_name} ({horizon_name})")
# Load scalers
x_scaler = joblib.load(xscaler_path)
y_scaler = joblib.load(yscaler_path)
# Determine input size dynamically
sample_input_size = len(x_scaler.mean_)
model = LSTMModel(input_size=sample_input_size,
hidden_size=256, num_layers=3, dropout=0.2).to(DEVICE)
model.load_state_dict(torch.load(model_path, map_location=DEVICE, weights_only=True))
model.eval()
return model, x_scaler, y_scaler
# ---------------------------------------------------------------------
# πŸ“Š PREPARE INPUT SEQUENCE
# ---------------------------------------------------------------------
def prepare_input_sequence(df, x_scaler, seq_len=90):
"""
Prepares the most recent sequence for inference.
Args:
df: DataFrame containing at least columns ["Open","High","Low","Close","Volume"]
x_scaler: fitted StandardScaler
seq_len: number of timesteps expected by model
Returns:
Tensor of shape [1, seq_len, features]
"""
df = df.dropna(subset=["Open","High","Low","Close","Volume"])
feats = df[["Open","High","Low","Close","Volume"]].values[-seq_len:]
X_scaled = x_scaler.transform(feats)
X_tensor = torch.tensor(X_scaled, dtype=torch.float32).unsqueeze(0).to(DEVICE)
return X_tensor
# ---------------------------------------------------------------------
# 🎲 MONTE CARLO DROPOUT INFERENCE
# ---------------------------------------------------------------------
def predict_with_mc_dropout(model, X_tensor, y_scaler, n_samples=100):
"""
Perform MC Dropout predictions to estimate uncertainty.
Returns:
{
"predicted_price": float,
"lower_bound": float,
"upper_bound": float,
"confidence_percent": float,
"all_preds": np.ndarray
}
"""
model = enable_mc_dropout(model)
preds = []
with torch.no_grad():
for _ in range(n_samples):
preds.append(model(X_tensor).item())
preds = np.array(preds)
mean_pred = preds.mean()
std_pred = preds.std()
# Inverse scale to get price prediction
mean_price = y_scaler.inverse_transform([[mean_pred]])[0][0]
std_price = y_scaler.inverse_transform([[std_pred]])[0][0] - y_scaler.inverse_transform([[0]])[0][0]
lower = mean_price - 2 * std_price
upper = mean_price + 2 * std_price
confidence_range = (upper - lower) / mean_price
confidence_percent = max(0.0, 100 * (1 - confidence_range))
return {
"predicted_price": float(mean_price),
"lower_bound": float(lower),
"upper_bound": float(upper),
"confidence_percent": round(confidence_percent, 2),
"all_preds": preds
}